6.9 KiB
date, author, type, tags, commits
| date | author | type | tags | commits | ||||
|---|---|---|---|---|---|---|---|---|
| 2026-05-03 | tegwick (Claude Sonnet 4.6 pair) | adhoc |
|
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_inWeb/Controller/TypeRegistries.hs(4×) andWeb/Controller/Api/V2/Registries.hs(3×) — crashes on page loadtextField #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 ~10–60 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 1–2 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: 20–30 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 (
label→display_label,type→entry_type, etc.). Requires a migration but permanently removes the fragility. - Avoid
textField/orderByAscwith underscore fields; use raw inputs and order by a non-conflicting field (#name). This is the current workaround.