Skip to content

feat: modern-layout capability (design-system guides + layout/home schema + deterministic home redirect) + judge belongsTo false-park fix - #213

Merged
agjs merged 21 commits into
mainfrom
feat/modern-layout-capability
Jul 29, 2026
Merged

feat: modern-layout capability (design-system guides + layout/home schema + deterministic home redirect) + judge belongsTo false-park fix#213
agjs merged 21 commits into
mainfrom
feat/modern-layout-capability

Conversation

@agjs

@agjs agjs commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What

Two harness capabilities from the autonomous Spec-1 + Spec-2 effort, both panel-gated and proven by a full-green live build.

1. Modern-layout capability (Spec 1)

  • Design-system convention guides (conventions.ts): design-tokens / theming / responsive / accessibility (16 jsx-a11y rules) / components-ui — front-loaded like the existing guides.
  • layout/home plan schema (plan-types.ts): broad LAYOUT_ARCHETYPES vocabulary; IMPLEMENTED_LAYOUT_ARCHETYPES = {app-sidebar, settings} gates validation (unimplemented archetypes are rejected, never silently fallen-back). home marks the post-login landing (≤1 per plan).
  • Deterministic home-redirect wiring (wireHomeRedirectForPlanapplyHomeRedirect): the harness rewrites DEFAULT_REDIRECT_TO to the home slice's route, plan-level (resume-safe), after the pristine baselines + infra fail-closed. Sidebar grouping (primary vs demoted Settings) is guide-driven.
  • Panel-gated through r9 PASS.

2. Judge belongsTo false-PARK fix

The completeness judge was fed only a feature's own dirs, so it couldn't see the shared Drizzle schema and false-rejected belongsTo FKs ('schema not shown') — parking a fully-correct feature. readResourceCode now feeds the judge the shared schema (via sliceSchemaForJudge: windowed around the feature's own table, budget-reserved so feature code isn't starved). Panel-gated r1/r2/r3 = 3× PASS.

Proof

Live Todoist-like build (Project + Task, Task belongsTo Project, home): reached 2/2 verified, final acceptance GREEN (full validate + build + size checks). Validated end-to-end over live HTTP+DB (register → create projects → create tasks with/without due dates → list); lands on Today, settings demoted. bun run validate green (3228 tests).

Note

Signed/admin merge is yours to land (my signed merge is classifier-blocked).

agjs and others added 21 commits July 29, 2026 08:48
…idValue)

Live deep-chain build (Organization→Project→Task) parked Task: fast gate green but
e2e acceptance timed out with the task-form never hiding — i.e. the create was
failing, not a transient hiccup as the build's own note guessed.

Root cause: validValue (acceptance-spec.ts) had no case for date/datetime/timestamp
types, so a dueDate field got the generic '${name}-${seed}' sample ('dueDate-2').
That passes the UI Zod (z.string) and API TypeBox (t.String), but the service inserts
it into a timestamptz column and Postgres throws 'invalid input syntax for type
timestamp with time zone' -> create 500s -> onSuccess/closeForm never fires -> form
stays visible -> 10s waitFor(hidden) times out -> false park.

Confirmed at the boundary against the live DB:
  'dueDate-3'::timestamptz  -> ERROR: invalid input syntax
  '2024-06-15'::timestamptz -> 2024-06-15 00:00:00+00 (accepted)

Fix: validValue returns '2024-06-15' for date/datetime/timestamp types. Date-only so it
stays a substring of whatever timestamp representation the row cell renders back (the
shows assertion is toContainText). Boolean fields were already fine (checkbox path).

Regression test added. typecheck/lint/format clean, 3193/3206 suite green.

Follow-ups (filed): empty-optional-date '' also 500s a timestamptz insert (generated
form should coerce '' -> undefined for optional date/number); e2e seed resilience to
transient socket hang-ups / vitest worker timeouts under load.
…panel r1 finding)

- Exercise date, datetime AND timestamp (production branch handles all three).
- Replace the weak !isNaN(new Date()) check, which accepts normalized-impossible dates
  (new Date('2024-02-31') rolls to 2024-03-02), with a strict UTC round-trip that rejects
  them. Self-checks the checker rejects '2024-02-31' and accepts '2024-06-15'.
…lue (panel r2 finding)

A date-typed field whose NAME matches the email heuristic (emailVerifiedAt, lastEmailSentAt)
was getting user@example.com because the email name-check ran before the date-type branch —
an email string 500s a timestamp-column insert, the same false-park class. Reordered so
explicit type checks (email/number/date) run first, then name-based heuristics for string
fields. Test covers emailVerifiedAt(timestamp) → real date, not an email.
…esFor (panel r3 finding)

3 reviewers converged: negativesFor still used f.type === 'email' || /email/i.test(name), so a
date/number field named like an email (emailVerifiedAt) got a 'not-an-email' negative. That value
reaches a timestamp/number column → 500 (not the expected 400/422) → false park — the same class
validValue was just fixed for. Extracted a shared isEmailField(field) helper (explicit type wins
over name; number/date types are never email) and used it in BOTH validValue and negativesFor so
they can never disagree. Test asserts the negatives array: emailVerifiedAt(timestamp) gets NO
not-an-email negative, a real email field DOES (positive control).
…ype-precedence, panel r4)

isEmailField also guards number types (emailCount type=number → number sample + negative-number
negative, never email). Added that case to the regression suite: numeric valid value (no '@'),
no not-an-email negative, and the '-1' number negative present.
…sponsive/a11y/components-ui)

Spec 1A of the modern-layout capability. The harness guided 12 topics but nothing on styling —
so the model built UI blind to BoringStack's production-ready design system (CSS-var tokens,
ShadCN/Radix primitives in components/ui, data-theme theming, mobile-first responsive, and the
14 jsx-a11y rules the gate runs as ERRORS) and only discovered a11y violations at the gate.

Adds 5 front-loaded convention topics that CODIFY what the scaffold already ships (lean on it,
reinvent nothing):
- design-tokens: never hardcode colors; use CSS-var Tailwind tokens by role.
- theming: data-theme-driven; never dark: variants.
- responsive: mobile-first breakpoints + the Sheet mobile-drawer nav pattern.
- accessibility: satisfy jsx-a11y proactively (aria-label/aria-hidden/sr-only, no interactive
  div, semantic landmarks); mapped to the jsx-a11y rules so it also PUSHes reactively on a trip.
- components-ui: prefer @/components/ui Radix primitives, cn()+cva composition, asChild.

Synced the pull_conventions tool enum + description. Tests lock each guide's load-bearing
content + the a11y rule mapping. typecheck/lint/format clean, 3195 green.

Also adds the design spec: docs/superpowers/specs/2026-07-29-modern-layout-capability-design.md
Adds the structural half of the modern-layout capability so an app's PRIMARY UI can be the
landing (not a generic /dashboard) and settings can be demoted — without touching the proven
route/nav/e2e machinery (role only drives sidebar grouping + the post-login redirect).

- Plan schema (plan-types): IUiIntent.layout (LayoutArchetype: app-sidebar|app-topnav|settings|
  focused|public, default app-sidebar) + home?:boolean. Enum is broad on purpose (anti-lock-in);
  v1 implements app-sidebar + settings, the rest fall back to app-sidebar + guidance.
- Validation (plan-store): layout must be a known archetype; at most ONE home slice. Both fields
  optional → every existing plan/test unaffected.
- Wiring (refine-prompt): per-slice layout guidance — app-sidebar features go to the primary nav
  group at the top of AppSidebar; settings features go to a demoted Settings group; the home
  feature repoints DEFAULT_REDIRECT_TO to its own route.
- Scope (build.scopeFor): the home feature — and ONLY it — gets LoginPage.constants in scope so it
  can set the landing (AUTH_REDIRECT_FILE); threaded from the plan's ui.home at the one call site.

Tests: schema round-trip, invalid-layout + >1-home rejection, exactly-one-home accept, scopeFor
home-only redirect scope, refine-prompt home-redirect + settings-group wiring. typecheck/lint/
format clean, 3202 green.
… test coverage (panel r1)

Round-1 panel findings on the layout capability:

- (major, false-green) Home landing was prompt-only → a model could leave DEFAULT_REDIRECT_TO at
  /dashboard and still pass every gate. FIXED at the root: the harness now writes the redirect
  DETERMINISTICALLY (wireHomeRedirect/applyHomeRedirect in wire-resource; called from build.implement
  for the ui.home slice) — not model-prompted. Dropped the scopeFor(isHome)/AUTH_REDIRECT_FILE +
  prompt-edit path entirely; the refine-prompt now tells the model NOT to touch the redirect.
- (minor, dual source) validLayouts was a hand-copy of the LayoutArchetype union → replaced with a
  single-source LAYOUT_ARCHETYPES tuple that drives BOTH the type and the runtime validation list.
- Tests added/strengthened: wireHomeRedirect (repoint/idempotent/throw-on-missing-anchor);
  refine-prompt default-layout (no layout → app-sidebar, no home note) + updated home test for the
  deterministic note; plan-store non-boolean-home reject, zero-home accept, omitted-fields
  backward-compat; convention-index diff-visible guard that the 5 new topics are in both the
  registry and the pull enum.
- (finding on text-destructive-foreground) verified FALSE — --destructive-foreground +
  --color-destructive-foreground exist in the scaffold tokens; guide is correct.

Grouping stays guide-driven (cosmetic: a mis-grouped nav link still works); only the FUNCTIONAL
landing is now deterministic + gate-safe. typecheck/lint/format clean, 3209 green.
… .some→.includes, breaking typecheck)

The prior round's diff-visible enum guard used .some((t) => t === topic); eslint --fix rewrote it
to .includes(topic), which fails tsc (string vs the ConventionTopic union) — and bun test doesn't
typecheck, so it slipped past the local test run but the panel's validate (typecheck) caught it.
Use new Set<string>(...).has(topic): cast-free, typechecks, and eslint won't rewrite it. bun run
validate fully green.
…wiring test + JSDoc merge (panel r3 advisories)

Panel r3 PASSED; this closes its advisory findings:
- (false-green hole) applyHomeRedirect silently returned when LoginPage.constants was absent — a
  home:true plan could go green with the landing never changed. Now THROWS (the scaffold always
  ships the file; a missing one is a real failure, not a skip).
- (missing test) build.implement's redirect wiring is now covered: applyHomeRedirect is an
  injectable dep; a test asserts it fires for the home slice (route /task) and NOT for a non-home
  slice. Plus a direct applyHomeRedirect fs-wrapper test (rewrite + throw-on-absent).
- (dead-code) merged the two consecutive JSDoc blocks on LAYOUT_ARCHETYPES.
- (text-destructive-foreground) re-confirmed FALSE: --destructive-foreground exists in the scaffold.
Sidebar grouping remains guide-driven (cosmetic; a mis-grouped link still works). bun run validate green.
…ome + regex anchor (panel r4)

Round-4 panel findings (real false-green + completeness holes):

- (false-green, resume) Redirect wiring lived in implement(), which a RESUMED build skips for an
  already-passing home feature → landing could stay /dashboard while final acceptance is green.
  Moved to a PLAN-LEVEL step in runBoringstackBuild (homeRouteForPlan → applyHomeRedirect) that
  runs once per build regardless of resume. Removed it from implement() (+ the injectable dep).
- (false-green, comment) wireHomeRedirect's regex was unanchored → could rewrite a commented-out
  DEFAULT_REDIRECT_TO while the live one stays /dashboard. Anchored to line-start (^…/m).
- (capability inert) The PLANNER never emitted layout/home (schema said 'match EXACTLY, no extra
  keys'; example had none) → applyHomeRedirect never ran on planner-generated plans. Documented
  layout/home in PLANNER_SYSTEM (schema + a rules line), set home:true + layout on PLANNER_EXAMPLE,
  and exported PLANNER_SYSTEM. Test locks the contract surfaces them + the example demonstrates one
  app-sidebar home.
- Tests: homeRouteForPlan (route for the home slice / null when none); applyHomeRedirect fs wrapper
  (rewrite + throw-on-absent); refine-prompt deferred-archetype fallback (app-topnav → app-sidebar
  note). Added the 2 missing jsx-a11y rule names (anchor-is-valid, img-redundant-alt) to the
  accessibility mapping. Merged the duplicate LAYOUT_ARCHETYPES JSDoc.
- (kept) at-most-one home, NOT exactly-one: zero-home is the correct backward-compatible fallback
  (every existing plan has zero); the app-intent case is handled by teaching the planner above.

bun run validate fully green.
…or case (panel r5)

Panel r5 PASSED; closes its advisory test-gaps (both guard false-greens the code fixes but nothing
asserted the guard):
- Extracted wireHomeRedirectForPlan(cwd, plan, apply=applyHomeRedirect) — runBoringstackBuild calls
  it; a test asserts it FIRES applyHomeRedirect for a home plan (route /task) and skips a home-less
  one. Deleting the call would now fail this test instead of silently restoring the resume false-green.
- wireHomeRedirect test for the commented-out-line case: a leading // export const DEFAULT_REDIRECT_TO
  is left untouched while the LIVE export is repointed (the reason the regex is line-anchored).
- Dropped the stale '14 jsx-a11y rules' count from the design spec (now 16; use no count).
bun run validate green.
…es the redirect + spec-doc sync (panel r6)

Panel r6 agreement-2: the prior 'wiring fires' test exercised wireHomeRedirectForPlan in isolation
— it did NOT prove runBoringstackBuild calls it (my comment over-claimed). Deleting the plan-level
call would still pass. Fixed for real with an OBSERVABLE outcome test: a home plan + a real
LoginPage.constants in a temp clone → after runBoringstackBuild, the file's DEFAULT_REDIRECT_TO is
the home route (/task). Deleting the runBoringstackBuild call leaves it /dashboard → the test fails.

Also synced the design spec to the FINAL implementation (deterministic applyHomeRedirect at the plan
level, LoginPage.constants NOT in feature scope, planner emits layout/home) and removed the last
stale '14' jsx-a11y count. Documented the accepted limitations honestly: sidebar grouping is
guide-only/cosmetic (not gate-verified); a no-home plan leaves the redirect untouched (fresh
scaffolds default to /dashboard); the redirect regex assumes a plain constants file.

bun run validate green.
…archetypes + resume test (panel r7)

Panel r7 PASSED; this closes its findings (two real correctness issues + coverage/doc):
- (baseline invariant) Moved wireHomeRedirectForPlan to AFTER the pristine-baseline capture + infra
  fail-closed, so the redirect write is never swept into the 'pristine' baseline and an
  infra-aborted build never mutates the clone.
- (public → authenticated) Plan validation now gates on IMPLEMENTED_LAYOUT_ARCHETYPES = {app-sidebar,
  settings}; a roadmap-only archetype (app-topnav/focused/public) is REJECTED, not silently wrapped
  in ProtectedRoute+AppShell (which would make a 'public' feature authenticated). LayoutArchetype
  keeps the broad vocabulary; planner offers only the implemented set. Simplified layoutGuidance
  (dead fallback removed) + planner schema.
- (resume coverage) Added an observable RESUME test: home feature pre-marked passing (implement
  skipped) → runBoringstackBuild still rewrites DEFAULT_REDIRECT_TO to /task. This distinguishes the
  plan-level wiring from implement-gated (moving it back to implement would fail this). Plus the
  fresh-plan outcome test from r6.
- Fixed the over-claiming helper-test comment; rejected roadmap archetypes get a plan-store test.
- Docs: LAYOUT_ARCHETYPES JSDoc + spec Part B corrected (reject not fall-back); token list makes
  destructive-foreground/success-foreground explicit (they exist — killing the recurring false find).
- Defended (accepted limitation, documented): no-home leaves the redirect untouched (fresh scaffolds
  default to /dashboard); sidebar grouping is guide-only/cosmetic.

bun run validate fully green.
…t no-mutation test (panel r8)

Panel r8 PASSED; closes its real finding + adds the missing test:
- (gate-relaxed) The redirect still ran before host.captureMetaBaseline() (I'd moved it after the
  COMMAND baseline in r7 but not the META baseline). Moved wireHomeRedirectForPlan to AFTER both
  pristine captures (command gate + captureMetaBaseline) and after the infra fail-closed, so the
  mutation is never swept into either baseline.
- (test) Added: an infra-aborted build with a home slice leaves LoginPage.constants UNMUTATED
  (proves the wiring is after the infra return + both captures; moving it back fails this).
Defended as accepted limitations (documented; harness never produces these inputs): the line-anchored
regex doesn't skip a /* block comment */ containing a fake export (the scaffold's LoginPage.constants
is a plain constants file); refinePrompt's app-sidebar default for non-settings only matters if an
UNVALIDATED slice reaches the exported API — the harness always calls it post-plan-validation, which
rejects unimplemented archetypes.

bun run validate fully green.
…panel r9 advisory)

Adds an instrumented-host test that observes LoginPage.constants AT
captureMetaBaseline time — it must still read /dashboard then, and only
be repointed to /task afterwards. This locks the meta-baseline ordering
that the infra-abort test could not (an infra abort never reaches
captureMetaBaseline). Also corrects that test's comment to stop
over-claiming it covers the meta-baseline edge.
…(kills a belongsTo false-park)

The live todos build (Project + Task, Task belongsTo Project) reached fast-gate GREEN,
migrated the DB (task table + task_project_id_fkey present and working end-to-end), yet
the completeness JUDGE false-REJECTED it:

  judge rejected "Task": Database schema (Drizzle) defining the task table and the
  foreign key to project is not shown, so the required 'belongs to a project'
  relationship cannot be fully verified.

readResourceCode (which builds the judge's evidence) gathered ONLY the feature dirs
(apps/api/src/api/<f>/**, apps/ui/src/features/<f>/**). The table + FK live in the SHARED
app.schema.ts, outside those globs — so a belongsTo feature is judged on evidence that
structurally cannot contain the FK, and the reject-by-default rubric parks a correct,
fully-working feature the model cannot fix (nothing to fix). A false-PARK is the twin of a
false-green: the gate verdict diverges from observable reality (the FK exists in the DB).

Fix: readResourceCode now emits the shared Drizzle schema FIRST and counts it against the
budget up front, so the FK evidence always reaches the judge and is never truncated away.
Best-effort: no schema file (non-boringstack / not yet generated) → judge sees feature code
alone, exactly as before. +2 tests (schema included; schema survives truncation of a huge
feature). Full validate green (3223).
…dvisory)

The schema block is priority evidence (emitted first), but must still respect the ~96000-char
cap — a many-entity app accretes one table per feature into app.schema.ts, so an unchecked push
could return unbounded evidence to the judge. Now truncate the schema to the cap (head preserved,
where its own table + FK sit) rather than push it whole. +1 test (oversized schema stays bounded).
… + reserve budget (panel r2)

r2 correctly flagged that the r1 naive head-cap was worse than the disease: (1) tables accrete in
build order so the feature under review sits near the END — head-first truncation dropped the exact
belongsTo FK the change exists to surface; (2) letting the schema take the whole budget starved the
feature implementation the judge must also evaluate (a fresh 'implementation not shown' false-PARK).

New sliceSchemaForJudge (pure, tested): schema ≤ budget → whole; else a window CENTERED on this
feature's `export const <camel> =` table (its columns + FK), head fallback only if the export can't
be located. readResourceCode reserves at most HALF the budget for the schema, so feature code is
never starved. Honest JSDoc. Tests: oversized schema with the feature's FK at the TAIL still surfaces
the FK AND feature code AND stays bounded; +3 direct sliceSchemaForJudge unit tests. Validate green (3227).
… advisory) + dual-pressure test

r3's one real improvement: on a table-export miss in an OVERSIZED schema, sliceSchemaForJudge now
returns "" (caller omits the schema) instead of an arbitrary head slice — under the accretion model
a head slice cannot contain this feature's table anyway, so it only burned budget better spent on
feature code. Adds a dual-pressure test (oversized schema windowed to its tail FK + a real-sized
feature file, both surviving, still bounded). JSDoc made honest.

Accepted limitation (documented, NOT fixed — requires an unreal input): a schema >96k chars AND a
single feature file >~48k chars simultaneously can still drop that one file. Greenfield builds never
produce either (schemas are a few KB; files far smaller). Three consecutive panel PASSes; stopping
here rather than grind agreement-1 edges on inputs the harness cannot generate. Validate green (3228).
@agjs
agjs merged commit b48e39e into main Jul 29, 2026
8 checks passed
@agjs
agjs deleted the feat/modern-layout-capability branch July 29, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant