feat: modern-layout capability (design-system guides + layout/home schema + deterministic home redirect) + judge belongsTo false-park fix - #213
Merged
Conversation
…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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)
conventions.ts): design-tokens / theming / responsive / accessibility (16 jsx-a11y rules) / components-ui — front-loaded like the existing guides.layout/homeplan schema (plan-types.ts): broadLAYOUT_ARCHETYPESvocabulary;IMPLEMENTED_LAYOUT_ARCHETYPES = {app-sidebar, settings}gates validation (unimplemented archetypes are rejected, never silently fallen-back).homemarks the post-login landing (≤1 per plan).wireHomeRedirectForPlan→applyHomeRedirect): the harness rewritesDEFAULT_REDIRECT_TOto 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.2. Judge
belongsTofalse-PARK fixThe completeness judge was fed only a feature's own dirs, so it couldn't see the shared Drizzle schema and false-rejected
belongsToFKs ('schema not shown') — parking a fully-correct feature.readResourceCodenow feeds the judge the shared schema (viasliceSchemaForJudge: 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
belongsToProject,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 validategreen (3228 tests).Note
Signed/admin merge is yours to land (my signed merge is classifier-blocked).