generated from coulomb/repo-seed
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
140 lines
6.9 KiB
Markdown
140 lines
6.9 KiB
Markdown
---
|
||
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 ~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.
|