Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8a1ef6f
fix(acceptance): emit a real calendar date for date-typed fields (val…
agjs Jul 29, 2026
dab29cf
test(acceptance): cover timestamp type + strict calendar-date check (…
agjs Jul 29, 2026
53e62e5
fix(acceptance): explicit field TYPE beats name heuristics in validVa…
agjs Jul 29, 2026
f37b9cd
fix(acceptance): apply email type-precedence symmetrically to negativ…
agjs Jul 29, 2026
4eab460
test(acceptance): cover number field named like email (isEmailField t…
agjs Jul 29, 2026
be07036
feat(conventions): front-load design-system guides (tokens/theming/re…
agjs Jul 29, 2026
4a6d1ca
feat(layout): plan layout-archetype + home landing capability (Spec 1B)
agjs Jul 29, 2026
e09692b
fix(layout): deterministic home redirect + single-source archetypes +…
agjs Jul 29, 2026
9b0117b
fix(test): Set-membership check in convention-index (eslint autofixed…
agjs Jul 29, 2026
d07d03c
harden(layout): applyHomeRedirect throws on missing file + implement-…
agjs Jul 29, 2026
81d587d
fix(layout): resume-safe plan-level redirect + planner emits layout/h…
agjs Jul 29, 2026
5e90525
test(layout): guard the plan-level redirect firing + the comment-anch…
agjs Jul 29, 2026
3a363e7
test(layout): observable outcome test proving runBoringstackBuild wir…
agjs Jul 29, 2026
7fa0bea
fix(layout): baseline-safe redirect placement + reject unimplemented …
agjs Jul 29, 2026
79a55db
fix(layout): wire redirect after BOTH pristine baselines + infra-abor…
agjs Jul 29, 2026
8d0972a
test(layout): lock the redirect-after-meta-baseline ordering (closes …
agjs Jul 29, 2026
c2d83b2
fix(judge): show the shared Drizzle schema to the completeness judge …
agjs Jul 29, 2026
b32c92c
fix(judge): bound the shared-schema block by maxChars too (panel r1 a…
agjs Jul 29, 2026
8d2d804
fix(judge): window an oversized schema around the feature's own table…
agjs Jul 29, 2026
1c29774
fix(judge): omit an oversized schema on a table-export miss (panel r3…
agjs Jul 29, 2026
f869557
Merge branch 'main' into feat/modern-layout-capability
agjs Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Modern Layout Capability — Design Spec (Spec 1)

**Status:** approved-by-delegation (user AFK, full decision delegation; decision rule = "best for tsforge + BoringStack"). Panel-gate + live-build validation substitute for user review.

**Goal:** Give the tsforge harness a first-class, general understanding of modern web-app **layouts** and **design-system usage**, so builds produce responsive, accessible, themeable UIs that *lean on* BoringStack's existing primitives, and so an app's **primary UI can live outside the generic "dashboard"** with settings demoted to a secondary area.

**Principle:** Layout + design-system knowledge is a **harness** capability, not a BoringStack change (BoringStack stays minimal). The harness already ships this style of value as front-loaded convention guides + build-time wiring + conditional acceptance; we extend those seams. **Design the model broad** (don't lock into too few layout options); **implement narrow** (ship what the todos app needs, with seams for the rest).

---

## Global Constraints (verbatim, bind every task)

- **Never relax the gate.** No downgrading rules/severity. Fixes make the model satisfy the gate.
- **Core stays stack-agnostic.** All BoringStack-specific logic lives under `packages/core/src/loop/boringstack/`; generic seams only in the core loop.
- **No `as`/`!` casts, no eslint-disable, cc ≤ 20, shared AST walkers.** Run full `bun run validate` before "done".
- **Lean on what exists — do not reinvent.** Compose BoringStack's CSS-variable design tokens + `components/ui/` (ShadCN/Radix) + `data-theme` theming. Author no new CSS framework, no new component library.
- **Responsive + accessible + themeable are defaults, not options**, in every layout the harness emits.
- **Every harness change is panel-gated** (4-model panel, reviewers ok ≥ 2) **and live-build validated** (solo build on a quiet box).

---

## Reality this builds on (from two read-only audits)

**Routing (audit 1):** BoringStack routes are already flat/top-level (`/task`, not `/dashboard/task`). The shell is `AppShell` (sidebar + header) at `apps/ui/src/components/core/AppShell/`. Post-login redirect is `DEFAULT_REDIRECT_TO = "/dashboard"` (`apps/ui/src/features/auth/components/LoginPage/LoginPage.constants.ts`). The harness hardcodes every feature → `ProtectedRoute → AppShell → sidebar entry → nav-testid → sidebar-nav e2e` (`wire-resource.ts` `wireUiRouteFile` ~L133-187 `path:"/${camel}"`; `build.ts` `scopeFor` ~L161-179; `refine-prompt.ts` ~L269; `acceptance/testid-contract.ts` ~L130; `acceptance/e2e-generator.ts` nav test ~L681-686).

**Design system (audit 2):** Tokens in `apps/ui/src/assets/css/tailwind.css` (`:root` + `:root[data-theme="dark"]`, mapped via Tailwind `@theme`): colors `background/foreground/primary(+strong/low/ink/foreground)/secondary(+foreground)/muted(+foreground/strong)/accent(+cyan/pink)/destructive(+foreground)/success(+foreground)/border(+strong)/input/ring/card(+foreground)/panel(+strong)/popover(+foreground)` — note `destructive-foreground` and `success-foreground` DO exist (tailwind.css lines 34/86/132), `--radius*`, `--font-sans` (Inter) / `--font-mono`, `--animate-*`. Components in `apps/ui/src/components/ui/`: Button, Card, Dialog, DropdownMenu, Form, Input, Label, Popover, ScrollArea, Sheet, Skeleton, Sonner, Switch, Tabs (cva + `cn()` + tailwind-merge + Radix + lucide). Theme via `useTheme()` + `data-theme` attribute (NO `dark:` classes; `AGENT_CONTRACT.md` forbids them). Responsive = mobile-first Tailwind + Sheet drawer (`hidden md:flex` sidebar). A11y = `eslint-plugin-jsx-a11y` (16 rules) as ERRORS + Radix + semantic HTML + skip link. Harness `conventions.ts` has 12 topic guides but **nothing on styling/theming/responsive/a11y/composition**.

---

## Part A — Design-system convention guides (codify what exists)

New harness convention topics (front-loaded like the existing 12 in `packages/core/src/loop/conventions.ts`). Pure knowledge; leans 100% on the scaffold. This is the low-risk, high-value half and lands first.

New topics:
1. **`design-tokens`** — the exact token vocabulary + when each applies (primary = CTA, destructive = delete, muted-foreground = secondary text, border/border-strong = dividers, panel/card = containers, ring = focus). Rule: **never hardcode hex/rgb**; use tokens via bare Tailwind classes (`bg-primary`, `text-muted-foreground`). Opacity variants (`border-strong/40`).
2. **`theming`** — theming is `data-theme`-driven; **never use `dark:` variants**; tokens flip automatically. Test both themes by toggling `data-theme`.
3. **`responsive`** — mobile-first (no-prefix = mobile; `md:`/`lg:` = up); the Sheet mobile-drawer pattern for nav; responsive padding idioms (`px-4 lg:px-6`); container queries for intra-component layout.
4. **`accessibility`** — semantic landmarks (`nav`/`main`/`header`/`section`); `aria-label` for icon-only buttons; `aria-hidden` on decorative icons; `sr-only`; `aria-current="page"`; skip link; labels linked to inputs; never make a `div` interactive (use `Button`/links). Frame it as "satisfy the jsx-a11y rules proactively, don't discover them at the gate."
5. **`components-ui`** — prefer `@/components/ui/*` primitives; extend via `cva` variants; compose classes with `cn()` (not ternary/template strings); `asChild` slot pattern; `data-slot` scoping.

Wire these into the topic registry + surface at the same points as existing guides (build refine-prompt + interim `check`/RULE_DOCS).

## Part B — Layout-role capability (the structural change)

**Plan schema (broad).** Extend `IUiIntent` (`packages/core/src/loop/planning/plan-types.ts`):
```ts
layout?: "app-sidebar" | "app-topnav" | "settings" | "focused" | "public"; // default "app-sidebar"
home?: boolean; // this feature's route is the post-login landing (AT MOST one per plan; none → /dashboard)
```
`nav` (existing description) stays. The `LayoutArchetype` TYPE is intentionally broad (anti-lock-in, the roadmap); **v1 implements `app-sidebar` + `settings`**. Plan validation gates on `IMPLEMENTED_LAYOUT_ARCHETYPES` = {`app-sidebar`, `settings`} — a not-yet-built archetype (`app-topnav`/`public`/`focused`) is **rejected**, NOT silently accepted/fallen-back (critical for `public`, which implies unauthenticated but would be wrapped in `ProtectedRoute`). The vocabulary stays future-proof; a new archetype ships by adding wiring + extending the implemented set.

**Wiring (implement narrow):** In `packages/core/src/loop/boringstack/`:
- **Sidebar grouping** — the harness's sidebar wiring/guidance groups nav into a **primary app group** (`layout: app-sidebar`) and a demoted **Settings group** (`layout: settings`, plus the scaffold's existing account/profile/notification links). AppSidebar edit stays in feature scope; the refine-prompt tells the model which group to add to based on `layout`.
- **Home landing** — the harness writes `DEFAULT_REDIRECT_TO` DETERMINISTICALLY for the `home` slice's route (`wireHomeRedirectForPlan` → `applyHomeRedirect`, called ONCE at the plan level in `runBoringstackBuild` — resume-safe). It is NOT model-edited and `LoginPage.constants` is deliberately NOT in feature scope; the refine-prompt tells the model not to touch it. At most one home (validated); none → login keeps `/dashboard`.
- **Route/layout** — keep `ProtectedRoute → AppShell` for `app-sidebar` and `settings` (both are authenticated app areas; "settings" is a grouping + optional sub-nav, not a separate auth boundary in v1). `wireUiRouteFile` stays; only grouping + home differ. (Distinct SettingsLayout / public-unauth shells are **designed-for** via the enum but **deferred** — YAGNI until a build needs them.)

**Sidebar grouping is guide-only (cosmetic)** — a mis-grouped nav link still works, so it is not gate-verified (deferred). The home landing IS verified by an observable outcome test (runBoringstackBuild rewrites the live redirect). Accepted limitations: a no-home plan leaves the redirect untouched (fresh scaffolds default to `/dashboard`); the redirect regex assumes a plain constants file (line-anchored, not a full parser).

**Acceptance (unchanged contract, role-aware only where safe):** Every feature remains reachable + e2e-tested via its nav testid — `role` only changes *which sidebar group* the nav link lives in, not whether it exists. This deliberately preserves the proven acceptance machinery (no gate relaxation). Smart-view filtering (Spec 2) is verified as UI within the Task feature, not as new entities.

---

## What's implemented now vs designed-for-later

- **Now:** Parts A (all 5 guides) + B (`app-sidebar` + `settings` roles, home landing, sidebar grouping, broad schema).
- **Later (schema-valid, not implemented):** `app-topnav`, `public` (unauth features), `focused` as a distinct feature layout, a dedicated `SettingsLayout` shell. Each is its own future spec when a build needs it.
- **Non-goals:** any BoringStack fork; new CSS/components; M2M relationships; scheduled reminders (Spec 2 Phase 2).

## Testing & validation

- Unit tests for: plan-schema accepts/defaults `layout`/`home`; exactly-one-home validation; sidebar-group selection by role; home→`DEFAULT_REDIRECT_TO` wiring; guides present in the registry.
- **Panel-gate** the harness diff (4-model, ≥2 agree).
- **Live build**: Spec 2 (todos app) is the end-to-end proof — app lands on Today, settings demoted, UI uses tokens/primitives, a11y gate clean. Run solo on a quiet box.

## Architecture / files to change

- `packages/core/src/loop/conventions.ts` — register + author the 5 design-system topics (Part A).
- `packages/core/src/loop/planning/plan-types.ts` — `IUiIntent.layout` + `home` (Part B schema).
- `packages/core/src/loop/boringstack/wire-resource.ts` (`wireHomeRedirect`/`applyHomeRedirect`) + `build.ts` (`homeRouteForPlan`/`wireHomeRedirectForPlan`, called in `runBoringstackBuild`) — deterministic home redirect. `refine-prompt.ts` — sidebar-group GUIDANCE (cosmetic; not gate-verified) + a note telling the model the redirect is auto-wired. `propose-plan.ts` — planner emits `layout`/`home`. NO `LoginPage.constants` in feature scope.
- `packages/core/src/loop/boringstack/acceptance/testid-contract.ts` — keep nav contract; document role→group only.
- Plan validation (where `isEntitySpec`/plan is validated) — exactly-one-home + valid `layout`.
- Tests alongside each.
7 changes: 6 additions & 1 deletion packages/core/src/agent/agent.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,14 @@ export const PULL_CONVENTIONS_TOOL = {
"testing",
"api-service",
"i18n",
"design-tokens",
"theming",
"responsive",
"accessibility",
"components-ui",
],
description:
'which guide: component-anatomy (where a component lives + one-per-file), file-layout (no inline types/constants/helpers), jsx (no computation in markup), state (hooks, not component body), no-casts (type guards instead of `as`/`!`), routing (thin route files), forms, data-fetching (api-client, never raw fetch), lint-gotchas (await promises, no void-expr values, no stringified errors, no duplicate strings), testing (.test.ts vs .test.tsx, the vi.hoisted api-client mock, createApp/app.handle route tests, enforced test rules), api-service (mutating service methods record an audit event; throw ApiError), i18n (add a locale key only when you reference it via t("key") — never pre-declare, or it\'s a dead-key error).',
'which guide: component-anatomy (where a component lives + one-per-file), file-layout (no inline types/constants/helpers), jsx (no computation in markup), state (hooks, not component body), no-casts (type guards instead of `as`/`!`), routing (thin route files), forms, data-fetching (api-client, never raw fetch), lint-gotchas (await promises, no void-expr values, no stringified errors, no duplicate strings), testing (.test.ts vs .test.tsx, the vi.hoisted api-client mock, createApp/app.handle route tests, enforced test rules), api-service (mutating service methods record an audit event; throw ApiError), i18n (add a locale key only when you reference it via t("key") — never pre-declare, or it\'s a dead-key error), design-tokens (never hardcode colors; use CSS-variable Tailwind tokens by role), theming (data-theme-driven, never dark: variants), responsive (mobile-first breakpoints + Sheet mobile drawer), accessibility (satisfy jsx-a11y up front: aria-label/aria-hidden/sr-only, no interactive div, semantic landmarks), components-ui (use @/components/ui Radix primitives, cn() + cva, asChild).',
},
},
required: ["topic"],
Expand Down
Loading