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

140 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
date: 2026-05-03
author: tegwick (Claude Sonnet 4.6 pair)
type: adhoc
tags: [ui, navigation, bugfix, profiling]
commits: 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 (`label``display_label`,
`type``entry_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.