feat(frontend): the design system renders, islands boot, admin pages register, one row per id - #91
Conversation
…register, one row per id
Five slices, four agents, all from the same audit. Each found the problem was
worse than reported, and three found a shipped API that was broken on its first
line — none of which a read-only audit could see, because each only surfaces
when something calls it.
ui — the throw was protecting nothing
--------------------------------------
setSolidRuntime had zero call sites outside this package's own tests, and 24
components (not 23 — eight call solid() directly for signals, which patching
useUi() alone would have left broken) could not render server-side at all. The
demo says so in its own source: two auth pages hand-rolled the same field block
twice and had already drifted.
Then the deeper finding: registering the real solid-js and rendering
Ctx.Provider through the inert factory, the child STILL read the default. Solid's
context needs an owner and an inert tree is walked outside every owner — so
UiProvider has always been a no-op on the server, and the throw guarded nothing.
Rejected the obvious fix. Falling back to defaultUiContext() would render every
server-side <DateTime> in UTC and every <Money> in USD, silently, for every app —
the exact degradation this repo keeps closing. Instead useUi() off-DOM returns an
ambient context reading locale and zone off core's existing request ALS; no new
store, because i18n and time already project those. The probe is "is reactivity
possible here", not "am I a server": a DOM with no runtime still throws, because
that is the theme-toggle-does-nothing bug.
UiProvider now refuses on the server rather than silently dropping its props —
it is the one thing an inert runtime cannot honour.
render + cli — hydrate was a promise with no referent
------------------------------------------------------
IslandDirective, emitIslandAttributes, hydrateRuntime, RouteEntry.islands and
routeJsBytes all shipped, and NOTHING ever constructed or populated any of them.
render-static.ts:55 even carried a fix: telling authors to "move the
request-dependent part into an island" — naming a mechanism the framework could
not express.
island({ src, props }) is a factory over the route's existing `hydrate`; the
route says WHEN, the island says WHICH module. defineRoute gains no key, because
a second way to say "this route hydrates" is what axiom 1 forbids.
`src` is a string, not an import. There is no edge from the page to the island
for a bundler or the surface-boundary walk to follow, so a static page's graph
cannot grow the island's dependencies — structural, not policed. Props are gated:
declared, JSON-only, 4kb; <Modal {...post} /> fails and NAMES email and
passwordHash, every column the spread would have shipped.
Wired end to end, both paths from one table — x dev and serve.ts, deliberately,
because a seam that works in dev and not in the image is the same failure one
release later. Proof from the emitted artifact: one data-x-entry, the static page
next door emits no <script> at all and reports jsBytes 0.
And a live budget bug in each layer: routeJsBytes filtered by entry.islands,
which nothing populated, and measureDocumentJs weighed <script src> tags while an
island is reached by import() — so a page was charged for the hydration runtime
and never for the code it boots. Both now charge the chunk, and an oversized
island fails naming the island.
admin — adminRoutes() was dead on arrival
------------------------------------------
adminRouteConfig emitted defineRoute({ render: 'spa' }) with no policy, which
render's own assertModeShape refuses — so every call threw X_ROUTE_MODE_INVALID.
Published, advertised in README.md:61, no test file, no consumer anywhere. That
is why it went unnoticed.
pages: now registers a custom screen, and the guard cannot be omitted, by four
independent locks: refused at declaration when permissions is empty; the frame's
admin:read composed in front of the declared one; adminRouteConfig writes the
defineRoute call so the author never does; and the router is handed a wrapped
component, with AdminPageProps.ctx required so the raw one cannot even be called.
Nav is closed too — an unlinkable page was visible to everyone.
The escape hatch is the main event, not a corner case: of treasury's 70
ActiveAdmin files, 28 reach for Arbre or register_page and hold 55% of the LOC,
and 20 of 70 are bespoke ops screens rather than model CRUD.
Named the strength that was undocumented: there is no Arbre analogue and there
will not be one. Every renderer is ordinary TSX importing @ultimat3/ui, the
generated pieces are individually exported so a table can be lifted out of its
page — which ActiveAdmin cannot do — and an admin route is an ordinary route.
realtime — the local store was write-only
-------------------------------------------
Two live queries overlapping on one row held two copies of it, which the thesis
table has called a bug the framework should make unrepresentable since before it
shipped. Fixed by lifting the identity map out of the tier-3 local store so both
tiers share one, keyed (entity, id) — posts/7 and users/7 are two rows, and a
slug primary key makes that collision plausible rather than theoretical.
The larger bug the test exposed: useLive never read the local store AT ALL.
store.apply() wrote MemoryLocalStore and nothing anywhere read it, so a mutator's
optimistic local(tx, input) twin was invisible in every live query until the
server round-tripped a patch.
Membership and identity are separate — Ember's own split — because sharing one
Map<table, Map<id, Row>> means rolling back an optimistic insert deletes a row
the server had since sent to another window. A write merges columns rather than
replacing the row, since two queries may project different columns and a narrower
snapshot must not blank what a wider one renders.
SnapshotFrame carries `entity`, and PROTOCOL_VERSION is deliberately NOT bumped:
the field is additive and readable both directions, so bumping would refuse every
in-flight client on a rolling deploy in exchange for nothing. The package rule now
says the version guards incompatibility, not novelty.
Also
----
Two framework i18n keys were structurally unreachable: `ui.error` and `ui.theme`
are leaves under branches (`ui.error.code`, `ui.theme.light`), and a catalog is
authored nested where a name is a leaf or a branch, never both — so ThemeToggle's
aria-label rendered ⟦ui.theme⟧. Renamed to ui.error.title and ui.theme.label, and
the whole ui.* namespace now ships in en.json; it was absent entirely.
x g island and x g admin:page, both in FIXTURE_GENERATORS so the scaffold gate
compiles them. cmd-generate.ts had reached exactly 500 lines — passing with zero
headroom — and is split.
New codes (5): X_ADMIN_PAGE_UNGUARDED, X_ADMIN_PAGE_PATH_INVALID,
X_ISLAND_INVALID, X_ISLAND_PROPS_INVALID, X_ISLAND_NOT_HYDRATED. The CLI borrows
X_ISLAND_INVALID rather than twinning it.
Gate: bun run verify 14/17, 3 skipped. Reference-app gate: every pin holds.
328 error codes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (86)
Comment |
…sland is one line (#92) * feat(harness): .claude ships with x new, x docs answers offline, an island is one line Four slices. Two of them deleted a design I had specced, because checking beat assuming again. .claude/ ships with x new — 9 files, 18.9kb -------------------------------------------- All five audited repos built one independently: 24,249 lines of agent-facing markdown, written five times. x new shipped none, so a scaffolded app inherited nothing. /feature is 5,001 bytes against this repo's 18,727 — the hive coordination, the PR splitting and the npm release are framework-monorepo concerns, not an app's. Subagents are scoped by BOUNDARY, never by role, and the boundary is not "one per app directory" as the audit assumed: resourceFiles puts the whole vertical slice in one apps/web/app/<feature>/, so that cut would hand one agent everything. The real lines are the app's own import direction — data -> server -> web — plus `shape`, read-only, which decides whether to build at all before there is a boundary to hold. No new-model/new-route/new-service commands: every one is `x g <kind>` already, and a slash command wrapping a shipped command is a second path that drifts. A test asserts none exists. No size budget, no X_CLAUDE_BUDGET_* code, nothing gated. A ceiling on an app's own markdown is the framework policing the user's docs, and how big your .claude/ is, is your convention. Nothing reaches outside the project directory either — a test asserts every path starts with .claude/ and holds no `..` or `~`. The scoped-typecheck hook I had specced twice does NOT exist and never will: cmd-verify.ts:1-4 says narrowing the gate "would make 'green' mean whatever the caller chose". So the one hook is biome on the edited file, with the reason written down. Every `x …` a template names is asserted to exist, planned commands excluded — a planned command is not a fix. x docs — and the payload I specced was wrong --------------------------------------------- I asked for a generated docs.json per package. Verifying the premise inverted the conclusion: Bun runs TypeScript directly, so the published artifact IS the source and every doc is already inside the installed tarball. A docs.json would be a second copy of bytes the install already has, and the second copy is the one that drifts. So `x docs "<question>"` reads the installed source at query time. 66ms scan + 16ms search over 906 entries across 29 packages; 0.69s end to end against `x errors list`'s 2.2s, because it reads files instead of importing 27 packages. The doc unit was measured, not assumed. JSDoc above a public export covers 42.3% — job(), the flagship, has none, so ranking on JSDoc would have missed it. File header comments, which the conventions already mandate, cover 99.8%. That is the index. Two bugs its own tests caught: the tokenizer lowercased before splitting camelCase, so nextRetry never split — the one case it existed for; and a one-token-of-five match was returned in the same shape as a real answer. An agent cannot see that difference, and a confident irrelevant answer is worse than none because it stops the search. An entry now has to cover half the question. CLAUDE.md is added to all 29 files arrays: +333kb, +5.8%. It was required to exist by workspace-checks.ts and thrown away at publish. islands and admin pages, proven in the apps -------------------------------------------- PR #91 shipped both and admitted neither was exercised by real app code. examples/dummy's /pricing now carries a Talk to sales island. Measured: /pricing 1894 bytes (875-byte chunk + 1019-byte interaction runtime), / next door still 0. Everything on screen is server-rendered inside the wrapper — a real <form method="post"> — so the enquiry sends with scripting off; only three post-click strings cross as props. The demo's admin ops page moved to pages:, deleting a hand-written defineRoute, a hand-written policy line, a hand-written permission branch and a raw <a href>. That anchor was the one sidebar link no permission filtered: it rendered for anyone who could open the dashboard, pointing at a page they would be refused. an island is now the whole declaration --------------------------------------- island() could not be used in JSX at all — TS2786. solid's JSX.Element is a type ALIAS and therefore unaugmentable, and the framework's own island tests all call h(Modal, …), never JSX, which is why it was never exercised. IslandNode now extends Array<never>: ArrayElement is the union's only object-shaped member, so it satisfies the configured JSX.Element structurally without render importing solid-js. render-html's unwrap tests isIslandNode BEFORE Array.isArray, or the generic branch renders '' and drops the island silently. And the ceremony is down to one line. A route carrying an island derives hydrate: 'interaction' and its budget from the surface baseline (site 4kb, app 18kb) — the two facts the framework can work out are no longer two lines to forget. Explicit always wins. Per-island strategy was rejected: RouteDescriptor .hydrate is one field read by sw.js, the web manifest and x routes, and two islands with different timings would leave it with no true answer. props stays explicit. It is the only thing between <Modal {...post} /> and a passwordHash in the client bundle, and no default recovers that. RouteEntry.islands was `input.islands ?? []` and nothing has ever passed islands — so routeJsBytes' "what registration declared" half has read [] on every route in the framework's history. x g admin:page gains --at; it wrote to a hardcoded directory. Debt named, not hidden ----------------------- - cmd-generate.ts writes x.manifest.json after ANY generator run, creating one in an app that never had it, and prints a line report.written does not count. - A named import from @ultimat3/action pulls 36.1kb minified through the barrel into core; naming.ts's "pure string math without importing a byte of server code" is true of the function and false of the import. 40x the island. - SURFACE_SPECS.site.mayImportTypes is ['shared'] but checkSurfaceBoundary only classifies value edges, so a site/ page type-importing from api/ passes. Gate: bun run verify 14/17, 3 skipped. Reference-app gate: every pin holds; the typecheck pin drops 138 -> 137 because the TS2786 class is fixed. 328 codes, no new ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review — a decorative rate limit, a lost redirect, an ambient date Nineteen findings. Two premises were wrong and the agents said so; three fixes uncovered something bigger than the finding. the rate limit was not what the review said, and worse than it looks -------------------------------------------------------------------- The review called the demo's public contactSales an unthrottled open relay. It is not: pipeline.ts:235 runs rate-limit on every route by default, keyed contactSales|ip:<addr>, so it sat on the default bucket at 120 burst / 2 per second per IP. A page's read allowance on a mail endpoint is the wrong shape, but it is not a relay. Declaring rateLimit: { limit: 5, windowMs: 600_000 } fixed the published contract — openapi.json stops advertising "unlimited" on a public mail endpoint. But proving that surfaced a framework gap: an action's rateLimit is DECORATIVE. toRoute (packages/action/src/http.ts:85) sets meta.rateLimit = <actionName>, so the limiter looks up a bucket literally named contactSales; defineConfig has no http block, dev-roles.ts and serve.ts pass no rateLimit to defineHttpConfig, and nothing derives a bucket from the action's declared numbers. They reach the OpenAPI spec and stop. An author who writes limit: 5 gets 120. Named at the declaration and in the PR body rather than papered over; the fix is cross-package and belongs in its own slice. x docs lost the redirect on the one query most likely to be a typo ------------------------------------------------------------------- `x docs "X_DB_DRIFT"` ranked FIVE prose files above the redirect, on the words "db" and "drift" — contradicting the contract in the command's own file header. And a mistyped code got no redirect at all, so the query most likely to be wrong had no path to the command that would have suggested the right one. The code query now resolves before any scan, which also skips 81ms of work for a question that was never about documentation. The duplicate-heading finding was accepted on a different ground than the one cited: all 58 shipped READMEs have zero slug collisions today, but DocEntry.topic's own JSDoc promises ids are unique within a package and the code did not hold it. Code contradicting its own documented invariant is worth four lines. an ambient date in the scaffolded /planx ----------------------------------------- The template told the agent to run `date +%F` — the host shell's ambient zone, so two machines produce two plan dates for one instant. It is also the one rule this repo enforces hardest: no date formatted without an explicit IANA timeZone, anywhere. Replaced with a named-zone Bun one-liner, deliberately NOT added to the permission allowlist, because `bun -e` is arbitrary code execution and one prompt per plan is the right price. The scaffolded README claimed "nothing here reaches outside this directory" while the PostToolUse hook runs biome --write over any edited file. Both halves were wrong. The containment claim is load-bearing and stayed, corrected to "repository"; the hook is now disclosed in three places as the bundle's one component that WRITES, with a test that fails if the disclosure disappears. the rest -------- - The island test template put its header comment below the imports, so every scaffolded island test shipped in violation of the header rule. - X_ISLAND_NOT_HYDRATED has a THIRD cause the wiki did not name — islandWithoutCollector, which is the host's wiring rather than the author's page. - A receipt's idempotency key held only email + message, while the template reads plan, currency and locale — so the same question about team/USD and business/EUR deduped and the second visitor silently got nothing. - A test dereferenced JSON.parse output through `as`, so a shape change threw a TypeError instead of failing by name; now unknown + a schema parse, with a test proving the guard is not decoration. - node: imports carry their why-comments; measurements carry their date and their denominators. Gate: bun run verify 14/17, 3 skipped. Reference-app gate: every pin holds. 328 error codes, none new. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five slices, four agents, all from the same audit of five production codebases. Each found the problem was worse than reported, and three found a shipped API broken on its first line — none of which the read-only audit could see, because each only surfaces when something calls it.
ui— the throw was protecting nothingsetSolidRuntimehad zero call sites outside this package's own tests, and 24 components (not 23 — eight callsolid()directly, which patchinguseUi()alone would have left broken) could not render server-side. The demo says so in its own source: two auth pages hand-rolled the same field block twice and had already drifted.Then the deeper finding: registering the real
solid-jsand renderingCtx.Providerthrough the inert factory, the child still read the default. Solid's context needs an owner and an inert tree is walked outside every owner — soUiProviderhas always been a server-side no-op and the throw guarded nothing.The obvious fix was rejected. Falling back to
defaultUiContext()would render every server-side<DateTime>in UTC and every<Money>in USD, silently, for every app. InsteaduseUi()off-DOM returns an ambient context reading locale and zone off core's existing request ALS — no new store, becausei18nandtimealready project those. The probe is "is reactivity possible here", not "am I a server": a DOM with no runtime still throws, because that is the theme-toggle-does-nothing bug.render+cli—hydratewas a promise with no referentIslandDirective,emitIslandAttributes,hydrateRuntime,RouteEntry.islandsandrouteJsBytesall shipped, and nothing ever constructed or populated any of them.render-static.ts:55even carried afix:telling authors to "move the request-dependent part into an island" — naming a mechanism the framework could not express.island({ src, props })is a factory over the route's existinghydrate: the route says when, the island says which module.defineRoutegains no key, because a second way to say "this route hydrates" is what axiom 1 forbids.srcis a string, not an import. There is no edge from the page to the island for a bundler or the surface-boundary walk to follow, so a static page's graph cannot grow the island's dependencies — structural, not policed. Props are gated: declared, JSON-only, 4kb —<Modal {...post} />fails and namesemailandpasswordHash, every column the spread would have shipped.Wired end to end through both paths from one table —
x devandserve.ts— deliberately, because a seam that works in dev and not in the image is the same failure one release later. Proof from the emitted artifact: onedata-x-entry, and the static page next door emits no<script>at all withjsBytes: 0.And a live budget bug in each layer:
routeJsBytesfiltered byentry.islands, which nothing populated;measureDocumentJsweighed<script src>tags while an island is reached byimport(). A page was charged for the hydration runtime and never for the code it boots.admin—adminRoutes()was dead on arrivalIt emitted
defineRoute({ render: 'spa' })with nopolicy, which render's ownassertModeShaperefuses — so every call threwX_ROUTE_MODE_INVALID. Published, advertised inREADME.md:61, no test file, no consumer anywhere.pages:now registers a custom screen, and the guard cannot be omitted, by four independent locks: refused at declaration whenpermissionsis empty; the frame'sadmin:readcomposed in front of the declared one;adminRouteConfigwrites thedefineRoutecall so the author never does; and the router is handed a wrapped component, withAdminPageProps.ctxrequired so the raw one cannot even be called. Nav is closed too — an unlinkable page was visible to everyone.The escape hatch is the main event: of treasury's 70 ActiveAdmin files, 28 reach for Arbre or
register_pageand hold 55% of the LOC, and 20 of 70 are bespoke ops screens rather than model CRUD.Also names the strength that was undocumented: there is no Arbre analogue and there will not be one. Every renderer is ordinary TSX importing
@ultimat3/ui, the generated pieces are individually exported so a table can be lifted out of its page — which ActiveAdmin cannot do — and an admin route is an ordinaryroute.realtime— the local store was write-onlyTwo live queries overlapping on one row held two copies of it, which the thesis table has called "a bug the framework should make unrepresentable" since before it shipped. Fixed by lifting the identity map out of the tier-3 local store so both tiers share one, keyed
(entity, id)—posts/7andusers/7are two rows, and a slug primary key makes that collision plausible rather than theoretical.The larger bug the test exposed:
useLivenever read the local store at all.store.apply()wroteMemoryLocalStoreand nothing anywhere read it, so a mutator's optimisticlocal(tx, input)twin was invisible in every live query until the server round-tripped a patch.Membership and identity are separate — Ember's own split — because sharing one
Map<table, Map<id, Row>>means rolling back an optimistic insert deletes a row the server had since sent to another window. A write merges columns rather than replacing the row, since two queries may project different columns.SnapshotFramecarriesentity, andPROTOCOL_VERSIONis deliberately not bumped: the field is additive and readable in both directions, so bumping would refuse every in-flight client on a rolling deploy in exchange for nothing. The package rule now says the version guards incompatibility, not novelty.Also
Two framework i18n keys were structurally unreachable:
ui.errorandui.themeare leaves under branches (ui.error.code,ui.theme.light), and a catalog is authored nested where a name is a leaf or a branch, never both — so<ThemeToggle>'s aria-label rendered⟦ui.theme⟧. Renamed, and the wholeui.*namespace now ships inen.json; it was absent entirely.x g islandandx g admin:page, both inFIXTURE_GENERATORSso the scaffold gate compiles them.cmd-generate.tshad reached exactly 500 lines — passing with zero headroom — and is split.Gate
bun run verify— 14 of 17 passed, 3 skipped. Reference-app gate: every pin holds. 328 error codes. Every new test written failure-case-first and run red; theuitable was re-run with the fix reverted to prove 26 of 28 cases fail.Owed, and named rather than left silent: neither tracked app exercises an island yet, so the seam is not proven against real app code in the app gate.
x g island <name> --at apps/web/site/<route>writes the first half.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.