generated from coulomb/repo-seed
docs(adhoc): sidebar nav session — profiling + improvement suggestions
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
Documents the 2026-05-03 session covering registry page crashes, logout 405, wrong password hash format, and the left sidebar navigation refactor. Includes wall-clock profiling per build and five improvement suggestions (GHCi loop, Nix cache, package split, Tailwind safelist, IHP NameSupport rename strategy). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
139
docs/adhoc/2026-05-03-sidebar-nav-and-bugfixes.md
Normal file
139
docs/adhoc/2026-05-03-sidebar-nav-and-bugfixes.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
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 | ~46 min (in progress) |
|
||||
| `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`/`orderByAsc` with underscore fields**; use raw inputs and order by a
|
||||
non-conflicting field (`#name`). This is the current workaround.
|
||||
Reference in New Issue
Block a user