Skip to content

Fix(vinaya-web): site chrome polish — nav, switch label, badges, spacing, ring labels, hero - #704

Merged
daniboomerang merged 10 commits into
mainfrom
task/misc-hardening-v1/18
Jul 30, 2026
Merged

Fix(vinaya-web): site chrome polish — nav, switch label, badges, spacing, ring labels, hero#704
daniboomerang merged 10 commits into
mainfrom
task/misc-hardening-v1/18

Conversation

@daniboomerang

@daniboomerang daniboomerang commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Closes #702

For: Sonnet (a coding-agent CLI on a dev machine, interactive session)

Project: vinaya

Summary

Six presentational fixes on the public Vinaya site, all inside apps/vinaya/web: reordered the top nav so Start/CLI sit before Studio; gave the Portal↔Studio switch a visible STUDIO label (kept its aria-label and the retro-only size='sm' prop unchanged); wrapped state-machine-vocabulary cells in Badge on /state-machine, matching the sibling diagram's own treatment; gave /cli its own layout.tsx duplicating the /docs//start content-pane shape (measure, top spacing, background, independent scroll) rather than extracting a shared layout, since tasks #692/#696 are mid-edit on those two routes; raised harness ring/wedge label sizes on /the-harness for legibility; re-enabled the hero build animation on every page load by commenting out (not deleting) the once-only gate. Decisions beyond the brief's explicit table: after the Principal reviewed the first pass live and found the ring/wedge labels and the hero's VINAYA/HARNESS labels still too large, I stepped them down further (wedge/band labels text-3xltext-2xl, hero labels text-2xltext-xl) — verified against the same computed-geometry check described below, which showed the size reduction also shrank (but did not eliminate) the one wedge-label overflow finding on the Contracts ring.

Test plan

  • [agent] Build smoke: bun run build (turbo build --filter=@atta/vinaya-web) completes with exit 0, no error mentioning SwitchProps or size. Verified twice (before and after the post-review label-size follow-up).
  • [agent] Route smoke against the booted app: curl -s -o /dev/null -w '%{http_code}' localhost:3006/{,cli,state-machine,the-harness}200 for all four.
  • [agent] Nav order: curl -s localhost:3006/ | grep -o 'href="/[a-z-]*"' (deduped) → /, /the-harness, /state-machine, /start, /cli, /docs, /the-studio, /roadmap, in that document order.
  • [agent] Badges render server-side: curl -s localhost:3006/state-machine contains <span data-slot="badge" ...>vinaya/tier:1</span>-shaped markup (76 badge spans total on the page), and the response contains no 'use client' literal.
  • [principal] Desktop: topbar reads Home · The Harness · State Machine · Start · CLI · Docs · Studio · Roadmap, and the word STUDIO sits tight above the switch with no visible gap.
  • [principal] Phone width: open the hamburger sheet — the STUDIO label + switch sit correctly inside the fixed-height row, nothing clipped or overflowing.
  • [principal] /the-harness: drill into every ring; labels are comfortably readable and no label — tranche-archivist especially, on a left/right wedge — overflows or overlaps its wedge (see the one known exception below).
  • [principal] /cli beside /docs and /start at the same window size: same top spacing, same measure, same background, content pane scrolling the same way.
  • [principal] /state-machine: label cells read as badges, prose cells unchanged, no table spilling sideways at ~800px width.
  • [principal] Home page reloaded three times in a row (and once after a Portal↔Studio switch) — the hero build animation plays every time.

Premise

Premise:

  • apps/vinaya/web/src/app/(site)/layout.tsx contains: { label: 'Start', href: '/start' }
  • apps/vinaya/web/src/app/_components/ProductSwitchControl.tsx contains: Studio
  • apps/vinaya/web/src/app/(site)/state-machine/_components/StateMachineTables.tsx contains: Badge
  • apps/vinaya/web/src/app/(site)/the-harness/_components/DiagramCanvas.tsx contains: text-2xl font-bold uppercase
  • apps/vinaya/web/src/app/(site)/_components/hero-canvas/VinayaHeroEmblem.tsx contains: // if (seen) {
  • apps/vinaya/web/src/app/(site)/cli/layout.tsx contains: CliSidebar

Verification evidence

Scoped, @atta/vinaya-web:

  • bun run typecheck --filter=@atta/vinaya-webTasks: 14 successful, 14 total
  • bunx biome lint .Checked 1204 files ... Found 1 warning (the one accepted warning below, no errors)
  • bunx turbo test --filter=@atta/vinaya-webTest Files 15 passed (15) / Tests 90 passed (90)
  • bunx turbo build --filter=@atta/vinaya-webTasks: 15 successful, 15 total, all 27 routes including /cli, /state-machine, /the-harness compiled

Unscoped, full monorepo:

  • bunx turbo test (all packages) → Tasks: 53 successful, 53 total

Accepted lint warning (Part 6 / §10 fallback): VinayaHeroEmblem.tsx:89seen is assigned (via the localStorage read) but, with the once-only branch commented out, never read. noUnusedVariables is warn-level in this repo's biome.json (does not fail biome lint ., exit 0). No existing pattern in this codebase silences an intentionally-unused variable (checked for void x / biome-ignore / underscore-prefix precedent — none exists). Per the brief's own fallback: kept the read live (the mechanism that must survive), accepted the warning rather than inventing a new suppression convention.

Route/nav/badge/layout checks — see the Test Plan [agent] rows above; each ran against bunx next dev --turbopack --port 3006 from the worktree.

Diff vs merge-base:

$ git diff $(git merge-base HEAD origin/main) --stat
 apps/vinaya/specs/vinaya-spec.md                   |   8 +-
 apps/vinaya/web/CLAUDE.md                          |   4 +-
 .../_components/hero-canvas/VinayaHeroEmblem.tsx   |  28 +++---
 apps/vinaya/web/src/app/(site)/cli/layout.tsx      |  20 ++++
 apps/vinaya/web/src/app/(site)/cli/page.tsx        | 111 +++++++++------------
 apps/vinaya/web/src/app/(site)/layout.tsx          |   2 +-
 .../_components/StateMachineTables.tsx             |  44 ++++++--
 .../the-harness/_components/DiagramCanvas.tsx      |  15 +--
 .../src/app/_components/ProductSwitchControl.tsx   |  63 +++++++-----
 9 files changed, 172 insertions(+), 123 deletions(-)

Nine files — matches the brief's §3 surface map exactly. Zero files under packages/ — the blast-radius-ack: holds.

Scope

All six edits are presentational and confined to apps/vinaya/web; no shared package (packages/ui or otherwise) is touched, so no consumer beyond this app is affected. /docs and /start themselves are untouched (C1) — /cli's content-pane shape is a deliberate duplicate, not a shared extraction, because tasks #692 and #696 are open on those two routes right now. The switch label lives in the app-local ProductSwitchControl.tsx, not packages/ui/topbar (C2), keeping this a one-project change. Non-goals, all explicitly out of scope per the brief: /docs and /start internals, /cli's below-lg sidebar behavior, the-harness/_lib/geometry.ts (ring radii/wedge angles), the hero's storage mechanism itself (key/read/write all still live), and any nav copy/route/new page.

Known, documented, non-blocking finding: the Contracts ring's archivist-tranche-archivist wedge label overflows its wedge — verified by computing the real geometry (the actual d3-pie centroid position and the drilled ring's 330-viewBox-unit radial thickness, replicating wrapLabel/shortLabel/humanLabel and drillArcs from the real source), not assumed. It lands on the ring's left wedge, where only the wedge's radial thickness — not the wider arc width a top/bottom wedge gets — is available to a horizontal label. Fixing it needs the wedge's own geometry, which is out of scope by construction (§9 stop condition, not a blocker for this PR); recorded in vinaya-spec.md's Known limits.

Tier: 1

Token report

Phase Role Agent/Model Tokens in Tokens out Cost Date
misc-hardening-v1/18: develop Developer Sonnet 5 (Claude Code CLI) 2026-07-29

No /cost-equivalent tool is exposed to this session, so exact token/cost figures are not available to report here — noting the gap explicitly rather than fabricating a number, per this repo's own anti-pattern list.

Reference — the dispatched brief

Full brief (reference copy — the gates read the anchored fields above, never this block)

Task brief — misc-hardening-v1 18 — The site's own chrome is the least finished surface it ships

For: Sonnet (a coding-agent CLI on a dev machine, interactive session)
Reason: mid — six small, disjoint presentational edits in one app. No architecture to decide, no shared package, no data layer. What the task actually demands is discipline: holding three "read this file, do not edit it" boundaries while working directly next to those files, and one SVG-in-viewBox change where the naive fix is subtly wrong. A fast model reliably takes the refactor bait in Constraint C1; a high model buys nothing the reasoning needs — the Planner and Brief Author already did it.
Owner: Principal (Daniel Estevez)
Goal: Fix six presentational defects on the public Vinaya site — nav order, a labelled Portal↔Studio switch, state-machine labels as Badges, /cli's content pane brought to the /docs+/start shape, legible harness ring labels, and a hero animation that plays on every visit.

Tier: 1
Project: vinaya
Closes #702


You are the AEG Developer. Read aeg-root/roles/developer.md first, then .claude/skills/executor-protocol/SKILL.md. Those two govern how you execute this brief; this brief governs what you build.


1. Context — read before doing anything

What this is

apps/vinaya/web is the public site for Vinaya, an agentic engineering harness (vinaya.attalabs.dev). The (site) route group is the marketing/reference surface: Home, The Harness, State Machine, Studio, Start, CLI, Docs, Roadmap. A separate /studio subtree is the local-only dashboard and is not in scope.

The Principal walked the live site and collected eight polish defects in one sitting. The Planner's dig resolved those eight reports into six edit sites, because two pairs collapse: the two nav-order requests are one array, and the desktop and mobile "label the switch" requests are one component rendered through one slot. You are implementing six changes, not eight.

The Planner's rationale, carried forward

Boundary. Six presentational edits, all inside apps/vinaya/web. Explicitly out: packages/ui in its entirety; /docs and /start themselves; a shared layout factored out of the three sidebar routes; /cli's below-lg sidebar behaviour; the ring diagram's radii and wedge geometry; the hero's once-only storage mechanism itself; any nav copy, route target, or new page.

Blast radius. vinaya only — and that is a decision, not an observation. The switch label could be implemented in the shared TopBar instead of the app-local control. Routing it that way would put five consumers in the blast radius (Vinaya's site shell, Vinaya Studio's shell, Vāda's main layout, and two Herald surfaces), make this a three-product regression surface, and fire a C5 doc binding requiring .claude/skills/ui-components/SKILL.md in the same diff. The Issue carries an explicit blast-radius-ack: that is only valid while no file under packages/ui is edited. Keep it valid.

Traps are reproduced as hard constraints in §11. Read them before you write code, not after.

What is locked and must not be relitigated

  • The nav order is Principal-set, exactly as written in Part 1. Eight items have no arithmetic "middle"; the Principal chose position 4 for Start. Do not re-reason about it.
  • The hero's once-only logic is disabled, not deleted — Principal-set. One line to flip back.
  • /cli adopts the /docs+/start shape. /docs and /start are the reference; they do not move toward /cli.

Doc knowledge you need (surfaced here, not by reference)

Read during the Dig; the load-bearing facts, so you do not have to re-derive them:

  • apps/vinaya/specs/vinaya-spec.md carries a ## Pages route table with a per-route UI polish column — the standing record of each page's finish state — and a ## /the-harness page section describing the ring renderer's label treatment. Both go stale from this task. Status: is already present on the spec; you are not adding one.
  • This repo's specs and CLAUDE.md files hold architecture, never low-level UI detail. No pixel values, no Tailwind class names, no nav arrays, no switch-label copy in a spec. Record that /cli shares the content-pane shape of /docs and /start, and that harness ring labels are sized for legibility at rendered scale. Do not transcribe your diff.
  • apps/vinaya/web/CLAUDE.md is the de-facto detailed web-surface doc. It currently states, among much else, that /cli "is hidden below lg with no drawer because that sidebar is a same-page scroll-spy TOC rather than cross-route nav" — still true after this task — and describes the ProductSwitch split across a server gate and a client control. Update only what this task makes false.
  • .claude/rules/ui-patterns.md loads automatically on .tsx edits. Its rules bind here: @atta/ui components over raw HTML, semantic theme tokens only (no text-green-500, no hex, no bg-[#…]), no inline style={{}} unless provably impossible in Tailwind, lucide-react for icons, semantic font classes.
  • No skill-check hook covers apps/vinaya/web/**. .claude/skills/*/paths.txt binds packages/ui/**, packages/cms/**, apps/herald-ai/**, apps/vada-ai/** and similar — none match this surface. Nothing will force you to load ui-components or ui-theme-tokens. The conventions still apply; this brief is what carries them.

2. Technical dependencies — what must already exist

All satisfied at authoring time; verify in pre-flight.

  • Badge from @atta/ui/components — required by Part 3. Already used on this same page's sibling component (StateMachineDiagram.tsx renders status names in <Badge variant='outline' className='font-mono text-xs font-normal'>) and imported by (site)/cli/page.tsx. You are matching an existing in-page treatment, not introducing one.
  • Flex from @atta/ui/shared — required by Part 4's new layout file. Already imported by docs/layout.tsx, start/layout.tsx, and cli/page.tsx.
  • COMMANDS from @atta/vinaya-sources — required by Part 4; the new cli/layout.tsx must pass it to CliSidebar. Currently imported by cli/page.tsx.
  • Switch from @atta/ui/components with its retro-only size='sm' prop — Part 2 keeps this working. apps/vinaya/web resolves the retro library at build time; size is retro-only and fails at compile time on basic/animate/brutal. Do not remove or change it.
  • No task-level depends-on. No conflicts-with. Nothing must merge before this runs.

3. Technical surface map

Files you will modify or create

# Path Change
1 apps/vinaya/web/src/app/(site)/layout.tsx reorder the links array (Part 1)
2 apps/vinaya/web/src/app/_components/ProductSwitchControl.tsx add the visible STUDIO label + rewrite the stale doc comment (Part 2)
3 apps/vinaya/web/src/app/(site)/state-machine/_components/StateMachineTables.tsx label cells → Badge (Part 3)
4 apps/vinaya/web/src/app/(site)/cli/layout.tsx new file — the /docs+/start shell shape (Part 4)
5 apps/vinaya/web/src/app/(site)/cli/page.tsx drop the inlined shell it moves into the layout (Part 4)
6 apps/vinaya/web/src/app/(site)/the-harness/_components/DiagramCanvas.tsx ring label font sizes + line offsets (Part 5)
7 apps/vinaya/web/src/app/(site)/_components/hero-canvas/VinayaHeroEmblem.tsx comment out the if (seen) early-return (Part 6)
8 apps/vinaya/specs/vinaya-spec.md §7 doc update
9 apps/vinaya/web/CLAUDE.md §7 doc update

Explicitly out of surface — do NOT touch

Shared-symbol check

No symbol is deleted or renamed. SEEN_KEY stays defined and referenced (the write site keeps using it). ProductSwitchControl's export signature is unchanged. No importer anywhere is affected.

Premise:

  • apps/vinaya/web/src/app/(site)/layout.tsx contains: { label: 'Studio', href: '/the-studio' }
  • apps/vinaya/web/src/app/_components/ProductSwitchControl.tsx contains: The control carries NO visible text label
  • apps/vinaya/web/src/app/(site)/state-machine/_components/StateMachineTables.tsx absent: Badge
  • apps/vinaya/web/src/app/(site)/the-harness/_components/DiagramCanvas.tsx contains: fill-foreground font-mono text-xl font-medium
  • apps/vinaya/web/src/app/(site)/_components/hero-canvas/VinayaHeroEmblem.tsx contains: if (seen) {

4. Pre-flight checks

Step 0 (mandatory, first) — create the worktree

git worktree add .worktrees/task/misc-hardening-v1/18 -b task/misc-hardening-v1/18 origin/main && cd .worktrees/task/misc-hardening-v1/18 && bun install --frozen-lockfile --silent

Remaining pre-flight

  1. git status — clean.
  2. git log --oneline -3 — parent is origin/main.
  3. bun packages/aeg-core/bin/verify-dispatch.ts misc-hardening-v1 18 --premise <this-brief-file> — READY, and every premise assertion holds. A failed premise means a file moved since authoring: STOP.
  4. apps/vinaya/web/src/app/(site)/cli/layout.tsx does not exist (you are creating it).
  5. bun run dev:vinaya (or the app's dev script) boots and /, /cli, /state-machine, /the-harness all render before you change anything. You need the before-state to compare against.

5. Parts

Part 1 — Nav order

File: apps/vinaya/web/src/app/(site)/layout.tsx

Reorder the links array to exactly:

Home  ·  The Harness  ·  State Machine  ·  Start  ·  CLI  ·  Docs  ·  Studio  ·  Roadmap

Only the order changes. Every label, href and exact value stays byte-identical. Studio remains /the-studio.

The array carries a comment above State Machine explaining why it sits beside The Harness ("both are code-derived reference pages"). That reasoning survives the reorder — they stay adjacent — so keep the comment with the entry it describes.

Part 2 — A visible name for the Portal↔Studio switch

File: apps/vinaya/web/src/app/_components/ProductSwitchControl.tsx

Wrap the existing Switch so the word STUDIO renders directly above it in very small type, with no gap — the two must read as one control, not a label and a separate widget.

Requirements:

  • Use @atta/ui primitives and semantic tokens. Text from @atta/ui/shared is the house primitive for this; a bare <span> is a raw-HTML violation of ui-patterns.md RULE 1.
  • Very small, uppercase, muted — text-muted-foreground with the smallest step on the type scale. Match the eyebrow treatment already used elsewhere in this app (font-mono … uppercase tracking-…), do not invent a new one.
  • Tight vertical stacking: a flex column with no gap utility, and no margin between label and switch.
  • Keep size='sm' on the Switch and its entire explanatory comment block verbatim — it documents a real compile-time coupling to the retro library.
  • Keep the aria-label exactly as-is. It states the current surface and the destination; the new visible text does not replace it and must not be duplicated into it.
  • Rewrite the component's doc comment. Lines 15–17 currently assert "The control carries NO visible text label: the destination is announced by the accessible name alone … so the topbar's right cluster stays a row of icon-sized controls." That is now false. Replace it with why the label was added — the control's purpose was unreadable to a first-time visitor — and note that the accessible name is retained. Do not leave the old sentence in place.

This one change covers both the desktop right cluster and the mobile sheet row: (site)/layout.tsx passes <ProductSwitch current='portal' /> into TopBar's extraActions, and TopBar renders that same slot in both places. You do not edit TopBar.

The mobile sheet renders extraActions inside a fixed-height row (flex h-14 items-center). A stacked label-over-switch is taller than a bare switch. Verify it still sits correctly at a phone width — see the Test Plan.

Part 3 — State-machine labels become Badges

File: apps/vinaya/web/src/app/(site)/state-machine/_components/StateMachineTables.tsx

Add Badge to the existing @atta/ui/components import. Wrap the cell contents that are labels — an identifier drawn from the model's vocabulary — in a Badge. Leave prose cells as prose.

Wrap these (they are labels):

Table Cell
Source of truth input.fact
Labels label.id
Derived statuses row.status
Derived statuses each entry of row.concludedBy
Derivation rules rule.id
Derivation rules rule.status

Leave these alone (they are prose or data, not labels): input.readsFrom, input.meaning, label.category, label.carries, row.derivable's Yes/No, rule.chainStep, rule.when, rule.why.

Rules:

  • Match the sibling treatment already on this page — StateMachineDiagram.tsx uses <Badge variant='outline' className='font-mono text-xs font-normal'>. Use the same variant and class shape so the two components agree.
  • row.concludedBy currently renders as .join(', '). Replace the join with one Badge per rule id in a wrapping flex container. Keep the EMPTY_CELL () fallback for the empty case rendered as plain text, not a Badge — an em-dash is an absence marker, not a label.
  • Do not introduce a single hand-authored row, cell or literal. The file's own header comment states that every row maps over a model array and that a literal added later is a regression. That property is the reason the route exists. You are changing how a cell renders, never what it contains.
  • Do not add 'use client'. This is a server component holding @atta/aeg-core-sourced data; a client directive drags node:child_process into the browser bundle. Badge is a presentational component and works server-side.
  • The tables carry deliberate min-w-[720px] / min-w-[760px] values with a long comment explaining the 780px stickyHeader threshold. Do not change them. If badge padding makes a table overflow, that is a finding to report, not a licence to retune the width.

Part 4 — /cli's content pane matches /docs and /start

Create apps/vinaya/web/src/app/(site)/cli/layout.tsx in the shape docs/layout.tsx and start/layout.tsx share:

<Flex className='h-full min-h-0 w-full flex-col overflow-hidden lg:flex-row'>
  <CliSidebar commands={COMMANDS} />
  <main className='flex-1 min-h-0 overflow-y-auto px-6 pb-10 bg-background lg:px-12'>
    <div className='mx-auto max-w-4xl pt-10'>{children}</div>
  </main>
</Flex>

COMMANDS comes from @atta/vinaya-sources; CliSidebar from ./_components/CliSidebar. This is a synchronous server component — unlike /docs it needs no async data fetch, exactly as start/layout.tsx does not.

Then reduce cli/page.tsx to its content only: it returns the two <section> elements it already renders, wrapped in a container supplying their vertical rhythm (flex flex-col gap-10). Delete the Flex shell, the <main>, the inner mx-auto max-w-3xl … lg:max-w-4xl div, the CliSidebar render and its now-unused imports (Flex, CliSidebar, COMMANDS only if COMMANDS is genuinely no longer read in the page — it still is, by the .map, so keep it).

What this deliberately changes: measure becomes max-w-4xl at every breakpoint (was max-w-3xl below lg); top spacing becomes pt-10 at every breakpoint (was py-12 / lg:pt-4); bg-background is now set; the content pane scrolls at all widths rather than only at lg. All four are the point of the task.

Keep the existing scroll-mt-6 on each command block — the scroll-spy anchor offset still applies.

Do not add a mobile drawer or a lg:hidden bar. /cli's sidebar is a same-page scroll-spy TOC, not cross-route nav, and its below-lg behaviour is deliberately different from /docs and /start. Out of scope.

Part 5 — Legible harness ring labels

File: apps/vinaya/web/src/app/(site)/the-harness/_components/DiagramCanvas.tsx

The ring labels are SVG <text> sized in viewBox units, not CSS pixels. The canvas has viewBox='36 36 1468 1468' and scales to its container, so a Tailwind size class here is a viewBox-unit size that then scales. The evidence the current sizes are wrong is in the file itself: the drilled hub title was already scaled to text-6xl with a comment saying text-xl "read tiny inside a 380-radius circle" — the wedge labels sitting beside it at text-xl never got the same treatment.

Raise these, keeping the Tailwind scale (the file's own rule: no arbitrary px values):

Line What From To
~269 overview band labels (ring names) text-xl text-3xl
~290 drilled wedge labels (principal, planner, brief-author, …) text-xl text-3xl
~287 wedge multi-line y-offset — must scale with the font 26 39
~372 "← Back" control text-xl text-2xl
~392 overview hub "protected" text-xs text-lg

Leave text-6xl (hub title), text-2xl (hub legend) and text-5xl ("main") as they are — those were already scaled.

The binding constraint, derived — verify it, do not assume it. The drilled ring runs DRILL_R_IN 400DRILL_R_OUT 730, so a wedge is 330 viewBox units thick radially. A label is horizontal text at the wedge centroid, so for wedges sitting at the left and right of the ring, the available width is that 330 units — not the much larger arc width the top and bottom wedges get. The longest actor label is tranche-archivist (17 characters), and it renders on a left wedge. wrapLabel splits on spaces only, so a hyphenated label never wraps: all 17 characters are one line. At text-3xl a monospace character advances roughly 0.6em ≈ 18 units, so 17 characters ≈ 306 units — inside 330, with little to spare. text-4xl overflows it. If your visual check shows overflow at text-3xl, that ceiling — not the font scale — is the real finding: report it and stop, per §10.

Apply the same "does the longest label still fit" check at every ring level, not just actors. Drill into each ring from the overview and look.

Do not touch geometry.ts. Radii, band thicknesses and wedge angles are out of surface.

Part 6 — The hero animation plays on every visit

File: apps/vinaya/web/src/app/(site)/_components/hero-canvas/VinayaHeroEmblem.tsx

Comment out the if (seen) { … return } early-return block (currently ~lines 96–104) so the build timeline always runs.

Keep, working and uncommented:

  • const SEEN_KEY = 'vinaya-hero-seen'
  • the try { seen = window.localStorage.getItem(SEEN_KEY) === '1' } catch {} read
  • the at(6300, …) localStorage.setItem write
  • the cleanup return

Add a short comment above the commented block saying the gate is deliberately disabled so the animation plays on every load, and that restoring it means uncommenting this block — nothing else. The intent is a one-line flip back, not a removal.

Update the component's existing comment at ~line 85 ("Plays the build ONCE, ever…"), which will otherwise assert the opposite of what the code does.

Expect a lint warning that seen is assigned but never read once the branch is commented. Resolve it the way this codebase already does elsewhere rather than deleting the read — the read is part of what must stay live. If the only clean resolution is deleting the read, that contradicts the keep-the-mechanism instruction: take the safe half (keep the read), record the choice in the PR body, and continue.


6. Documentation-update list

  • apps/vinaya/specs/vinaya-spec.md — the ## Pages route table's UI polish column for /cli, /state-machine and /the-harness; and the ## /the-harness page section where it describes ring label treatment. Record the architectural facts only: /cli shares the content-pane shape of /docs and /start; harness ring labels are sized for legibility at rendered scale, bounded by the drilled wedge's radial thickness; state-machine label cells render as badges. No class names, no pixel values, no nav array, no label copy. The spec already carries a Status: field — do not add one.
  • apps/vinaya/web/CLAUDE.md — update where this task makes it false: the (site) nav order it enumerates; the ProductSwitch description ("no visible text label" is no longer true); /cli's layout description. Its statement that /cli hides its sidebar below lg with no drawer remains true — do not change it.

Write both so a reader with no access to this tracker can resolve them: state the durable fact, do not cite this Issue number or the tranche slug as the explanation, and do not use coined vocabulary (tranche, brief, forge, provenance) without a definition or a glossary link at first use on a page.

No new file is created for any finding, report or summary. Findings go in the PR body.


7. Verification before claiming done

  • bun run typecheck --filter=@atta/vinaya-web passes.
  • bun run lint --filter=@atta/vinaya-web passes.
  • bunx turbo test passes unscoped. (bun run check filters tests to cms + aeg-core and will not run everything.)
  • Production build passes — bun run build for the vinaya web app. This catches stricter resolution the dev server tolerates, and it is the gate that would catch a Switch prop regression.
  • bun packages/aeg-core/bin/verify-docs.ts --pr passes with PR_BODY set to the intended body.
  • Blast radius: no consumer re-verification is owed, because no shared package is touched. Prove it: git diff $(git merge-base HEAD origin/main) --stat shows zero files under packages/. If any appear, the Issue's blast-radius-ack: is void — stop and escalate.
  • git diff $(git merge-base HEAD origin/main) --stat matches the §3 surface map exactly — nine files, no more. Use the merge-base, not a bare git diff main: sibling tasks merging while you work make a bare diff drift.

8. Test Plan

Test Plan:

  • [agent] Build smoke: bun run build for @atta/vinaya-web completes with exit 0 and no type error mentioning SwitchProps or size.
  • [agent] Route smoke against the booted app: curl -s -o /dev/null -w '%{http_code}' localhost:3000/cli200, and the same for /state-machine, /the-harness, / — all 200.
  • [agent] Nav order: curl -s localhost:3000/ | grep -o 'href="/[a-z-]*"' on the topbar region shows /the-harness, /state-machine, /start, /cli, /docs, /the-studio, /roadmap in that document order.
  • [agent] Badges render server-side: curl -s localhost:3000/state-machine contains the rendered badge markup around a known label id (e.g. vinaya/tier:1) and the page still contains no 'use client'-only failure in the console.
  • [principal] Desktop: topbar reads Home · The Harness · State Machine · Start · CLI · Docs · Studio · Roadmap, and the word STUDIO sits tight above the switch with no visible gap.
  • [principal] Phone width: open the hamburger sheet — the STUDIO label + switch sit correctly inside the fixed-height row, nothing clipped or overflowing.
  • [principal] /the-harness: drill into every ring; labels are comfortably readable and no label — tranche-archivist especially, on a left/right wedge — overflows or overlaps its wedge.
  • [principal] /cli beside /docs and /start at the same window size: same top spacing, same measure, same background, content pane scrolling the same way.
  • [principal] /state-machine: label cells read as badges, prose cells unchanged, no table spilling sideways at ~800px width.
  • [principal] Home page reloaded three times in a row (and once after a Portal↔Studio switch) — the hero build animation plays every time.

9. Stop conditions

STOP and report rather than improvising if:

  • Any pre-flight check fails, or a --premise assertion fails (a file moved since authoring).
  • Part 2 cannot be done without editing packages/ui. This converts a one-project task into a three-product one and must be re-planned, not absorbed. Escalate severity:strategy. Do not "just add a small prop" to the shared TopBar.
  • Part 5's labels cannot be made legible by type size alone and would need ring radii or band thicknesses moved. Geometry is out of scope by construction — report and stop.
  • Part 4 cannot reach parity without editing /docs or /start. That collides with tasks 13 ([misc-hardening-v1] 13 — Two docs pages print their own title twice #692) and 16 ([misc-hardening-v1] 16 — Every stage of the loop says what it takes in and what it hands on, and draws it #696); the Principal decides the ordering, not you.
  • The nav reorder surfaces a route that is broken, unreachable or unlisted — a different finding; report it, do not fix it in passing.
  • Your own reading contradicts the Planner's boundary or breaks the sizing (this is no longer one PR) — escalate severity:strategy.
  • Two brief instructions turn out to contradict each other — do the safe half, flag it in the PR body, continue.
  • You are about to touch a file outside §3.

10. Constraints

C1. Do NOT factor a shared layout out of /docs, /start and /cli. This is the obvious engineering instinct on reading Part 4 and it is wrong here: tasks 13 (#692) and 16 (#696) are open right now on /docs and /start, and a shared extraction rewrites exactly the files they are editing — turning a clean no-conflict into a three-way merge. Duplicate the shape into a new cli/layout.tsx. The de-duplication is a legitimate later task once those merge; it is not this one.

C2. Do NOT put the switch label in packages/ui/topbar. The label goes in the app-local ProductSwitchControl, already rendered into extraActions on both desktop and mobile — one edit, both breakpoints, zero shared-package diff. This single decision determines whether the task is one project or three.

C3. Do NOT leave the stale "no visible text label" comment in ProductSwitchControl.tsx. A rationale comment asserting the opposite of what the file does is worse than none — the next agent reads it as a constraint and reverts your work.

C4. Do NOT change the wedge font size without scaling the line offset with it, and do NOT assume a hyphenated label wraps — wrapLabel splits on spaces only. Verify against the longest label at each ring level, on a left or right wedge where only the 330-unit radial thickness is available.

C5. Do NOT delete the hero's once-only logic. Key, read and write all stay live; only the acting branch is commented, with a restore note.

C6. Semantic theme tokens only. No text-green-500, no hex, no oklch/hsl, no bg-[#…]. No inline style={{}} unless provably impossible in Tailwind. @atta/ui components over raw HTML — no bare <span>/<div> where a primitive exists. lucide-react for any icon.

C7. Do NOT add a hand-authored row, cell or literal to StateMachineTables.tsx, and do NOT add 'use client' to it or to state-machine/page.tsx.

C8. Never write status anywhere — status is derived from the forge. Never add execution metadata to a tranche file. Never commit a new file for a finding, report or audit summary; that content goes in the PR body.

C9. Never git stash in a worktree — the stash stack is per-repo, and a bare pop can apply another task's work.

Autonomy: Do not stop to ask clarifying questions. For any ambiguity not covered by a Section 10 stop condition, choose the most reasonable option consistent with this brief, record the choice in the PR body, and continue. Halt only for the explicit Section 10 stop conditions — and when you halt, record the blocker in the PR body or an Issue comment rather than waiting interactively for input.


11. Deliverable

Open a PR with:

  • Title: Fix(vinaya-web): site chrome polish — nav, switch label, badges, spacing, ring labels, hero
  • Body: start from aeg-root/templates/pr-report-template.md for the report half, carrying Tier: 1, Project: vinaya, Closes #702, and the §8 Test Plan checkboxes in their anchored homes. Paste this brief below it inside a collapsed <details><summary>Brief</summary>…</details> block.
  • Report in the body: the merge-base --stat output; confirmation that zero files under packages/ changed; every choice you made under the autonomy clause; and any finding you hit a stop condition on.
  • Run bun packages/aeg-core/bin/verify-docs.ts --pr with PR_BODY set before opening, and fix any failure. Never open a PR that would be red on arrival.
  • Open it with bun packages/aeg-core/bin/open-pr.ts — raw gh pr create is blocked by the forge gate.

Then stop. The PR is not done when it opens. Code review, security review, and the Verification phase (executing the §8 Test Plan and posting evidence) are separate invocations. Address REQUEST CHANGES / FAIL findings in follow-up commits on the same branch.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vinaya Ready Ready Preview Jul 30, 2026 3:46pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
atta-ai Ignored Ignored Preview Jul 30, 2026 3:46pm
herald-ai Ignored Ignored Preview Jul 30, 2026 3:46pm
vada-ai Ignored Ignored Preview Jul 30, 2026 3:46pm

@daniboomerang

Copy link
Copy Markdown
Owner Author

VERDICT: PASS

FINDINGS (ordered by severity):
None.

CONFIG SCAN: not applicable — no agent/skill/hook/MCP config touched
SECRETS: none found

Scope confirmed via merge-base diff (main...pr704): 9 files, matches gh pr view --json files exactly — all presentational changes inside apps/vinaya/web: nav link reorder ((site)/layout.tsx), a visible "Studio" text label added beside the existing Portal↔Studio Switch (ProductSwitchControl.tsx — the server-side isVercelDeploy() && hasForgeConnection() gate lives in the untouched sibling ProductSwitch.tsx, aria-label unchanged, onCheckedChange still does a plain router.push with no user-controlled input), Badge-wrapping existing label cells on /state-machine (no new data source), a duplicated (not shared) layout.tsx for /cli reproducing an existing shape, font-size bumps on /the-harness's SVG ring labels, and re-enabling the hero build animation by commenting out a once-only localStorage gate (client-side cosmetic state only, no auth/session implication). No secrets, no BYOK/crypto, no auth/permission/route logic touched, no MCP/agent/hook config, no new dependencies, no injection surface, nothing sent to a client that wasn't already there.

Tokens: misc-hardening-v1/18: security — Security — claude-sonnet-5 — —/—/—

@daniboomerang

Copy link
Copy Markdown
Owner Author

VERDICT: APPROVE

BRIEF CONFORMANCE: Yes, all six parts of Issue #702 delivered exactly as specified. Verified each against the brief's own tables: (1) nav order Home · The Harness · State Machine · Start · CLI · Docs · Studio · Roadmap — byte-identical labels/hrefs, only order changed, the State Machine adjacency comment kept with its entry; (2) ProductSwitchControl.tsx gets a Text-based STUDIO label, size='sm' and its comment kept verbatim, aria-label unchanged and not duplicated, stale "NO visible text label" doc comment rewritten (C3); (3) StateMachineTables.tsx wraps exactly the six specified label cells in Badge (variant='outline' className='font-mono text-xs font-normal', matching the sibling diagram), concludedBy renders one Badge per id with EMPTY_CELL kept as plain text, all other cells left as prose, no hand-authored literal, no 'use client' added; (4) cli/layout.tsx created matching /docs+/start's shape verbatim, cli/page.tsx reduced to content-only, COMMANDS import correctly kept (still .map-consumed); (5) harness ring labels sized up on the Tailwind scale with the line-offset scaled proportionally; (6) hero's if (seen) block commented out (not deleted), SEEN_KEY/read/write all live, comments updated both at the disable site and the stale ~line-85 comment. C1/C2 held — no shared layout extracted, no packages/ui touched. The one label-size deviation from the brief's literal table (settled on text-2xl rather than text-3xl, and hero labels text-2xltext-xl) is disclosed in the Summary as a Principal-directed live-review adjustment, verified against the same geometry check — not silent scope drift.

SPEC CONFORMANCE: Clean. vinaya-spec.md's ## Pages UI-polish column and ## /the-harness page section, and web/CLAUDE.md's nav enumeration, ProductSwitch description, and /cli layout description, were all substantively corrected (confirmed via word-level diff, not just presence) — including the nav-order list in CLAUDE.md, which is easy to miss and was in fact updated with a citation. Both stay architectural, no pixel values or class names leaked in per the brief's own ban.

FINDINGS (ordered by severity):
None. Worth noting, not blocking: the PR's own Scope section and vinaya-spec.md's Known limits already disclose that the Contracts ring's archivist-tranche-archivist wedge label still overflows at the chosen size — correctly identified as the actual worst case across all rings (longer than the actors-ring example the brief worked through), correctly left unfixed since closing it needs the out-of-scope wedge geometry, and correctly escalated for Principal ratification (Status: PENDING) rather than silently claimed clean.

SCOPE: Clean — 9 files changed, matches the brief's §3 surface map and gh pr view --json files exactly; zero files under packages/, confirmed.

TESTS: Honest — route/nav/badge checks run curl against a booted app and assert real rendered markup, not mocks. Build/typecheck/lint/unscoped-test evidence all present and match the brief's §7 gates.

DOCS: Tier-appropriate and correct, verified at the sentence level.

Tokens: misc-hardening-v1/18: review — Reviewer — claude-sonnet-5 — —/—/—

@daniboomerang
daniboomerang merged commit 9cb4439 into main Jul 30, 2026
8 checks passed
@github-actions

Copy link
Copy Markdown

AEG provenance — task 18 (tranche misc-hardening-v1)

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.

[misc-hardening-v1] 18 — The site's own chrome is the least finished surface it ships

1 participant