Files
inter-hub/docs/adhoc/2026-05-03-sidebar-nav-and-bugfixes.md
tegwick c74fb8fddd
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
docs(adhoc): update layout-fix build time (10 min via .hi cache)
2026-05-03 01:07:02 +02:00

6.9 KiB
Raw Blame History

date, author, type, tags, commits
date author type tags commits
2026-05-03 tegwick (Claude Sonnet 4.6 pair) adhoc
ui
navigation
bugfix
profiling
6078c48, 08d662d, e8b0c7c

Session: Registry Bugfixes + Left Sidebar Navigation

What was done

Bug fixes (commit 6078c48)

Registry pages crashed on load (/WidgetTypeRegistry and the three sibling tabs). Root cause: IHP.NameSupport's Megaparsec-based runtime parser chokes on field names that end with a trailing underscore (e.g. "label_"). IHP generates label_ as the Haskell record field name for the label SQL column to avoid shadowing the HTML <label> element. The parser accepts label but then fails when it encounters the trailing _.

Affected call sites:

  • orderByAsc #label_ in Web/Controller/TypeRegistries.hs (4×) and Web/Controller/Api/V2/Registries.hs (3×) — crashes on page load
  • textField #label_ in 4 view files — would crash on New/Edit form render

Fix: orderByAsc #name everywhere (semantically equivalent for these registries); manual <input name="label_"> replacing textField #label_ in forms. fill @'["label_"], validateField #label_, and createRecord/updateRecord are NOT affected — they use explicit columnNames from generated types or HTTP param names, bypassing NameSupport.

Logout returned 500 (UnexpectedMethodException {allowedMethods = [DELETE], method = GET}). The nav <a href={DeleteSessionAction}> issues a GET, but IHP routes DeleteSessionAction as DELETE. IHP ships Network.Wai.Middleware.MethodOverridePost in its middleware stack. Fix: replace the <a> with a POST form carrying <input type="hidden" name="_method" value="DELETE">.

Seed migration had wrong password hash format. Application/Migration/1744416000-seed-admin-user.sql used a bcrypt hash ($2b$10$…). IHP's verifyPassword uses Crypto.PasswordStore.verifyPassword from pwstore-fast, which produces and consumes PBKDF2-SHA256 hashes in the format sha256|17|<b64salt>|<b64hash>. The formats are incompatible. Fixed the migration; added a "Password Hashing" section to deploy/railiance/RUNBOOK.md with a runghc snippet for generating correct hashes on haskelseed.

UI change: left sidebar navigation (commits 08d662d, e8b0c7c)

Moved all operational links out of a flat top nav (which was overflowing on any screen) into a grouped left sidebar (192 px wide). Top nav retains: logo (left) and About / Tutorial / Extend / Sign out (right).

Sidebar groups:

Group Links
(top, ungrouped) Hubs, Widgets
Governance Candidates, Requirements, Decisions, Deployments
Intelligence Agent Proposals, Agents, Routing, Collective, AI Gov, Learning
Platform Adapters, Propagations, Ops Review, Federation, Policies, Archive
Registry Registries, Extensions
API & Market API Consumers, API Dashboard, Hub Registry, Marketplace

Follow-up fix (e8b0c7c): flex-col and flex-1 were absent from the compiled prod.css. Tailwind's build only bundles classes that appear in templates at compile time; these were new to the codebase. Without flex-col, the body defaulted to flex-row, placing the top nav beside the sidebar instead of above it. Fixed by replacing layout-structural Tailwind classes with inline styles (style="display:flex;flex-direction:column") so the column layout is independent of the CSS bundle. Visual Tailwind classes (colors, spacing, hover states) remain as-is.


Profiling results

All times are wall-clock, measured between commit push and kubectl rollout completion.

Phase Bug-fix build Sidebar build Layout-fix build
Code edit ~25 min (diagnosis + 12 edits) ~8 min (1 file) ~5 min (1 file, 4 lines)
Commit + push to Gitea <1 min <1 min <1 min
nix build .#docker on haskelseed ~37 min ~46 min ~10 min (.hi cache reuse)
skopeo push to Gitea registry ~1 min ~1 min ~1 min
kubectl set image + rollout ~2 min ~2 min ~2 min
Total ~66 min ~58 min ~55 min

The Nix build dominates in every case. A 4-line change costs the same as a 50-line change.


Improvement suggestions

1. Dev-loop: GHCi hot-reload for template changes (highest impact)

The 46-minute build cycle is entirely driven by GHC recompiling the full app layer via Nix. In the devenv environment, ghcid reloads only changed modules in ~1060 seconds. The current production pipeline skips devenv entirely.

Proposal: Use the existing devenv + ghcid setup for all iterative work (including layout changes), test locally, then trigger a single nix build only when a feature is ready to ship. This reduces the effective iteration cycle from 46 min to 12 min for UI/template work.

2. Nix binary cache for the app layer

Each nix build on haskelseed rebuilds inter-hub-lib-0.1.0.drv from scratch because Nix detects a new input hash whenever any source file changes. A Nix binary cache (e.g. cachix) would allow haskelseed to upload build outputs and reuse them across machines, but would not help with incremental rebuilds on the same machine since the derivation hash changes per-commit.

Partial improvement: configure Nix to use a remote builder with a warm store so that unchanged dependencies (IHP framework, GHC) are never fetched twice.

3. Split the Haskell package into stable-lib + app layers

Currently inter-hub-lib contains everything: IHP framework wiring AND all controllers and views. A single-file change causes the entire ~199-module package to recompile.

Proposal: Extract a stable inter-hub-core package (models, types, helpers) and keep a thin inter-hub-app package (controllers, views, FrontController). Nix would then only rebuild inter-hub-app for UI/controller changes, leaving inter-hub-core cached. Estimated saving: 2030 min per UI-only change.

4. Tailwind CSS: safelist layout primitives

Layout-structural Tailwind classes (flex-col, flex-1, min-h-screen, grid-cols-*) are frequently needed but may be absent from the compiled CSS if they haven't been used before. Workaround (current): inline styles. Proper fix: add a safelist entry in tailwind.config.js for layout utilities, or maintain a CSS file with layout primitives that is always included regardless of template scanning.

5. IHP NameSupport — avoid trailing-underscore fields

IHP generates label_ (trailing underscore) for columns named label to avoid shadowing the HSX <label> element. The NameSupport runtime parser cannot handle the trailing underscore in query positions (orderByAsc, filterWhere, textField). This is an IHP v1.5 bug. Two mitigations until it is fixed upstream:

  • Rename columns at the schema level to avoid IHP-reserved words (labeldisplay_label, typeentry_type, etc.). Requires a migration but permanently removes the fragility.
  • Avoid textField/orderByAsc with underscore fields; use raw inputs and order by a non-conflicting field (#name). This is the current workaround.