feat: rewrite DESIGN.md against D66 and ship the rendered reference (#36) - #735
Conversation
) Ticket 0 of the redesign. Rewrites the spec against D66 and builds the page that outranks it (D42), so the direction can be approved by looking. Tokens are derived in OKLCH and measured, not picked: --bg #09090B, --primary #24B4E2 (ink on it 8.35:1, on canvas 8.24:1), --fg-4 raised to #5D5D60 so it clears the 3:1 non-text floor. Three findings the derivation forced: white on the new accent is 2.41:1, so --fg-on-primary is the canvas ink; --status-frozen sat 12 degrees from the accent, inside the 15 degree "same colour" band, so it is retired as a hue and rendered as a neutral chip plus the snowflake glyph; the surface ladder was too tight at the dark end and is widened. Overlay: the primitive is specified with a named library per platform. Web adopts @base-ui/react 1.7.0 Dialog; the hand-rolled app-overlay renders <dialog open> without showModal(), which is why it rebuilds the focus trap, the scroll lock and Escape by hand and needs a z-[9999] that breaks the repo's own ban. Mobile keeps TrueSheet: the library is not the defect, the wrapper is, because it stacks a ScrollView inside the native scrollable and defaults to fixed detents. Also corrects the icon spec to Tabler's native 24 grid (22 rendered soft), reserves the pill radius for interactive elements so a Badge stops collecting dead clicks, adds the listing spec, the ninth "at capacity" state, and an enumerated 25 entry banned-word set scoped per entry. design/handoff/ is deleted. Its README claimed to be the design source of truth and said "when in doubt, the files below win", which outranked the spec under D42 while encoding six schemes and the old direction. The eslint-rules re-derivation and the spacing codification follow grant 1, because D66 decision 12 sets the step values against the rendered page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
@pullfrog review |
There was a problem hiding this comment.
Important
The D36 drift workflow cannot observe pushes to main from its current branch, so the synchronization this PR promises is inert until the dispatcher exists on main.
Reviewed changes Reviewed the full design-authority replacement, rendered reference, legacy cleanup, reviewer guidance, and drift automation in this initial review.
- Design authority: Rewrites
DESIGN.mdaround the new near-black, space-blue token system, layout rules, component states, overlays, and accessibility guidance. - Rendered reference: Adds a self-contained
design/reference.htmlwith embedded fonts, four live visual controls, token samples, responsive shell examples, primitives, overlays, and listing states. - Legacy handoff removal: Deletes the 40-file vendored
design/handoff/**source and its assets. - Reviewer guidance: Aligns
.claude/agents/design-reviewer.mdwith the new design constraints and staged gates. - Drift automation: Adds
.github/workflows/redesign-drift.ymlto merge futuremainchanges intoredesign/main.
⚠️ The reference clips on phone-width viewports
At a 375px viewport, the document grows to 436px while the visible client is 360px, and the right edge of the mobile-shell sample is clipped unless the reader scrolls horizontally. That makes the rendered authority awkward to inspect on the same class of viewport it is demonstrating.
Technical details
# Keep the rendered reference inside narrow viewports
## Affected sites
- `design/reference.html:280` — `.shell412` has a fixed 412px width and cannot shrink within the page's padded wrapper.
- `design/reference.html:532-543` — the fixed-width sample is rendered directly in the wrapping shell layout.
## Required outcome
- The reference page must avoid document-level horizontal overflow on narrow phones while retaining a true 412px sample whenever enough width is available.
## Suggested approach
- Constrain the sample with `max-width: 100%` and let its inner content adapt below 412px.ℹ️ Deleted handoff paths remain allowlisted
The PR deletes design/handoff/**, but all 25 allowances for that tree remain in tools/dash-baseline.json. The current checker only rejects growth, so its passing result does not reveal these stale exemptions and the documented shrink-only ratchet remains artificially inflated.
Technical details
# Shrink the dash baseline with the deleted tree
## Affected sites
- `tools/dash-baseline.json:87-111` — 25 entries still allow violations in paths removed by this PR.
- `tools/check-dashes.mjs:149-157` — `--check-baseline` compares only current counts above the baseline and does not reject stale keys.
## Required outcome
- Remove baseline entries for deleted files so the committed baseline represents only extant debt.
## Suggested approach
- Regenerate with `node tools/check-dashes.mjs --write-baseline` after the handoff deletion, then commit the shrink.GPT Sol | 𝕏
There was a problem hiding this comment.
Important
The integration branch currently bypasses CI, its drift workflow cannot trigger from where it lands, and the approval reference misrenders candidate choices.
Reviewed changes across the D66 design-canon rewrite, rendered decision artifact, legacy handoff removal, reviewer guidance, and redesign-branch automation.
- Design canon — rewrites
DESIGN.mdaround the single space-blue scheme, derived tokens, component rules, accessibility, motion, overlays, and enforcement. - Rendered reference — adds the offline
design/reference.htmldecision page with mode, accent, serif, and spacing controls plus representative primitives and layouts. - Legacy retirement — removes the 40-file handoff prototype and updates the design-review agent for the new direction.
- Integration automation — adds a workflow intended to merge each
mainadvance intoredesign/main.
⚠️ The integration branch bypasses the repository gates
Every existing PR workflow still filters its base to main, so PRs targeting the new shared redesign/main branch do not run Build, Lint, Type Check, Unit Tests, Guards, architecture drift, or the other normal verification. PR 735's actual rollup confirms the gap: only GitGuardian and Vercel registered, which lets each R-ticket enter the long-lived integration branch without the checks that normally stop regressions.
Technical details
# Run the normal gates on redesign PRs
## Affected sites
- [`test.yml:3-6`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/.github/workflows/test.yml#L3-L6) — the build, lint, type-check, and unit-test workflow accepts only PRs targeting `main`.
- [`guards.yml:14-23`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/.github/workflows/guards.yml#L14-L23) — the deterministic repository gates also accept only PRs targeting `main`.
- `.github/workflows/{arch-map,dependency-review,mutation,perf,react-doctor,sonarcloud,visual}.yml` — each has the same `pull_request.branches: [main]` filter.
- PR 735 check rollup at `b9ef378` — no repository build, test, or guard jobs were created.
## Required outcome
- Every R-ticket PR targeting `redesign/main` receives the same applicable verification before it enters the shared integration branch.
- Treat branch protection as a separate decision; the workflows must first create checks for this base branch at all.⚠️ The approval page does not faithfully render two decisions
The page that outranks DESIGN.md lets dark accent rules override the light palette and keeps the tint pinned to candidate A, while spacing candidate B omits its documented 48px and 64px steps. Human grant 1 could therefore approve an option from a rendering that is neither contrast-correct nor complete.
Technical details
# Make every approval control render its declared candidate
## Affected sites
- [`design/reference.html:129-165`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/design/reference.html#L129-L165) — the later `[data-accent]` blocks override `[data-mode="light"]`. In a real browser, light + calm computed `--primary-soft: #43B3DB` at 2.31:1 on `#FAFAFA`; light + B computed `#0080A4` at 4.35:1. Those miss the page's stated 4.5:1 text floor, and calm also misses the 3:1 graphic floor.
- [`design/reference.html:93`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/design/reference.html#L93) — `--primary-dim` remains `rgba(36,180,226,0.14)` for every mode and accent even though each control updates `--primary-rgb`.
- [`design/reference.html:166-170`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/design/reference.html#L166-L170) and [`426-437`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/design/reference.html#L426-L437) — candidate B renders `4 8 12 16 20 24 32 40 56`, omitting 48 and 64 from the twelve-step candidate specified in [`DESIGN.md:69-72`](https://github.com/thomasluizon/orbit-ui-mobile/blob/b9ef378d05c9ce784cadddf7b58d54bed284e199/DESIGN.md#L69-L72).
## Required outcome
- Every selectable mode/accent combination uses the intended mode-specific candidate, derives its tint from that candidate, and clears the advertised contrast floors.
- Spacing B renders the complete candidate set exactly as `DESIGN.md` defines it.GPT Sol | 𝕏
From Thomas's first pass over design/reference.html.
Settled and now in the spec:
spacing scale A, 0 4 8 12 16 24 32 48 64 96, plus two padding roles
(card 24, row 16) because a card that hugs its text reads as cramped
however correct the gaps around it are;
no serif, so the direction ADR's ban on a display serif as a second
warmth source stands unamended and D66 decision 9 resolves to "cut";
XP and levels render as a row, not a ring.
New: a hover and transition canon, which the spec required as a STATE but
never specified mechanically. A clickable thing with no hover state is now
a defect. 180ms for a surface, 120ms for a control, one step per hover,
never transform (that is press), and the transition is declared on the base
rule rather than inside :hover, which is what makes a state arrive smoothly
and then snap away.
The accent is reopened rather than assumed. The reference page now renders
TEN hues against BOTH fill treatments on real components: violet, indigo,
azure, space blue, teal, emerald, gold, amber, orange, rose. Every one
clears all three floors. Amber and rose are flagged: they sit inside 15
degrees of the overdue and bad status hues, so accent and status would read
as one colour. The light-fill anchor lightness is hand-tuned per hue,
because a single lightness across the wheel turns yellow to mustard while
leaving violet correct.
Verified in a real browser: all 20 hue/fill pairs resolve to their derived
values, hover transitions ease in AND out, zero page errors, zero network
requests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three defects Thomas found by hovering the rendered page. 1. Hover spilled. A card lit up whenever the pointer was on a button inside it, so the pointer appeared to be in two places. A container now suppresses its own hover while an interactive descendant is hovered, and static cards lost their hover state entirely: a surface that lights up under the pointer but cannot be clicked is an invitation to a dead click. 2. Hover was invisible. Stepping --bg-card to --bg-elev measures 1.09:1, which on a near-black canvas reads as nothing. The mistake was borrowing from the elevation ladder, whose steps are sized for STACKING rather than for being seen against one particular resting surface. --bg-hover is now its own role at 1.31:1, and the spec sets a 1.25:1 floor for any hover step. The three demo cards also described their behaviour instead of showing it, which broke the spec's own show-do-not-label rule; they now contain live rows and buttons. 3. Hover was too fast. Durations move from 120/180 to 180/280, and the page carries a live speed control (fast, medium, slow) so the final value is set by feel rather than guessed again. Accent shortlist narrowed to three, presented as presets: emerald dark with white, rose dark with white, orange light with canvas ink. Rose moved from hue 15 to 350: at 15 it sat 10 degrees from the destructive red, inside the band where two hues read as one, so the primary CTA and the delete button looked alike. At 350 it clears by 35 and stays a pink-red. Verified in a real browser: all three presets resolve to their derived values, all three speeds apply, hover no longer spills, static cards are inert, zero page errors, zero network requests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… left (#36) Thomas's second pass. The page now carries a control only for what is still open, which is the accent hue and the light/dark mode. Settled and folded in: ONE fill treatment, a dark fill with white on it. The light fill carrying the canvas ink was rendered and rejected by looking, so orange moves from #FF8E59 to #C4530F; the slowest of the three rendered hover speeds, 240ms on a control and 380ms on a surface; spacing A, so the B alternative is deleted rather than left switchable. Removed from the page: the fill selector, the speed selector, the spacing selector, and the nine-hue explorer. Six candidate hues are deleted outright rather than left as dead tokens. Also fixes a contradiction all three design specialists found independently: the spec reopened the accent hue but still locked "deep space-blue" in four places, including a Working model step reading "Do not re-pick" and a scene-sentence test that every non-blue screen would fail by construction. Those four now name a rationed accent without naming its hue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Important
The new grant-state commits leave the design authorities and rendered decision controls disagreeing, so both automated reviews and the human approval pass can act on obsolete or contradictory rules.
Reviewed changes Reviewed the two commits after b9ef378 that incorporated the first grant decisions and expanded the rendered reference.
- Finalized grant decisions: Removed the provisional serif, selected spacing A, and kept XP as a row rather than a ring.
- Reworked the accent choice: Replaced the space-blue candidates with ten measured hues, two fill treatments, and an emerald/rose/orange shortlist.
- Added the hover canon: Introduced hover tokens, timing variants, transition rules, and rendered surface/control examples.
- Refined the reference layouts: Adjusted card/row padding, overlay sizing, shells, status examples, and control organization.
⚠️ The grant state is contradictory across authorities
DESIGN.md now settles the spacing scale and leaves hue/fill open, but its locked summary and the design-review agent still mandate space blue, canvas ink on every primary, and provisional spacing. An R-ticket review would therefore ignore a real off-scale value while rejecting valid shortlisted dark fills that use white text.
Technical details
# Align every authority with the current grant state
## Affected sites
- `DESIGN.md:2,22` — the locked summary still names a deep space-blue accent.
- `DESIGN.md:67-70` — spacing is now explicitly chosen and enforceable.
- `DESIGN.md:177-202` — hue and fill remain open, and dark fills intentionally use white foregrounds.
- `.claude/agents/design-reviewer.md:37` — still instructs reviewers not to flag off-scale spacing because the values are provisional.
- `.claude/agents/design-reviewer.md:39` — still requires space blue and canvas ink on primary, contradicting every dark-fill shortlist option.
- PR description — still describes the removed space-blue/serif/provisional-spacing state and its old manual controls.
## Required outcome
- `DESIGN.md`, the rendered reference, the PR description, and the design-review checklist must agree on which decisions are settled and which remain open.
- The design reviewer must enforce the chosen spacing scale without rejecting a valid grant-1 accent/fill candidate.⚠️ The rendered authority can represent impossible active states
The page can render rose with a light fill while still marking Emerald selected, and it lets the rejected spacing B override the settled scale. Its default medium timing computes 280ms/180ms while labels say 180ms/120ms, the row demo never applies the promised strong hairline, and --primary-rgb remains the light-fill byte even when --primary is dark; because this page outranks the prose, these are conflicting authority states rather than demo polish.
Technical details
# Keep the rendered canon synchronized with its active controls
## Affected sites
- `design/reference.html:95-106,158-173` — `--primary` switches between light and dark fills, but `--primary-rgb` always resolves to the light-fill RGB.
- `design/reference.html:137-178` — medium is `180ms` for controls and `280ms` for surfaces, while spacing B still activates the removed `20/40/56` values.
- `design/reference.html:198` — row hover changes only the background despite `DESIGN.md:598` and the rendered explanation requiring `--hairline-strong` too.
- `design/reference.html:339-377,746-766` — direct hue/fill changes do not clear or update the three preset buttons; a browser run left Emerald pressed after selecting rose plus light fill.
- `design/reference.html:408,466,474,479,489` — visible timing labels describe the fast preset while medium is selected and active.
## Required outcome
- Every rendered selection must have one synchronized pressed state and one corresponding semantic-token state.
- Settled alternatives must not remain able to override the canon that the page declares authoritative.
- Visible timing and hover descriptions must match the selected tokens and the behavior they demonstrate.
- `--primary-rgb` must describe the active `--primary` value for every hue, fill, and mode combination.GPT Sol | 𝕏
There was a problem hiding this comment.
Important
The newly settled dark-fill hover state fails the contrast floor for every remaining accent, and the hover-speed decision also slows press feedback beyond its fixed contract.
Reviewed changes Reviewed 2fd8b22, which settled the fill and hover-speed decisions and reduced the rendered choice to three accent hues.
- Settled the fill: Removed the light-fill treatment and standardized all candidates on dark fills with white foregrounds.
- Settled hover timing: Removed the speed selector and fixed controls at
240msand surfaces at380ms. - Reduced the accent choice: Removed the comparison grid and retained emerald, rose, and orange as the only interactive candidates.
- Synchronized token state: Updated candidate RGB values and removed the redundant preset/spacing control paths.
⚠️ Every primary button fails AA on hover
Each chosen fill starts only slightly above the 4.5:1 white-text floor, then --primary-hover mixes it 12% toward white and removes that margin. A real-browser measurement gives 3.65-3.70:1 on hover for all three candidates, so the rendered canon makes its primary CTA inaccessible in the state this commit just settled.
Technical details
# Preserve text contrast through the primary hover state
## Affected sites
- `design/reference.html:45-55` — the three dark fills are explicitly the lightest values at which white clears 4.5:1.
- `design/reference.html:94-95` — `--primary-hover` moves each threshold fill 12% toward its white foreground.
- `design/reference.html:174` — every primary pill uses that lighter value on hover.
- `DESIGN.md:189-197` — the settled treatment says every candidate clears all three floors and always carries white text.
## Browser evidence
- Emerald hover resolves to `rgb(58,150,103)`: white contrast `3.66:1`.
- Rose hover resolves to `rgb(201,100,151)`: white contrast `3.65:1`.
- Orange hover resolves to `rgb(205,105,57)`: white contrast `3.70:1`.
## Required outcome
- White text must remain at or above 4.5:1 in the primary button's default, hover, active, and disabled-visible states for every candidate and mode.
- The hover treatment must remain visibly distinct without invalidating the chosen foreground contract.⚠️ The hover-speed choice also replaces the press timing
The new 240ms control-hover token also drives the pill's scale transition, so the rendered press now takes the same 240ms as hover. That contradicts both the fixed 150ms press contract and the stated reason for choosing a slower hover than press.
Technical details
# Keep press response independent from hover timing
## Affected sites
- `design/reference.html:126` — `--dur-fast` is now 240ms for the selected control-hover speed.
- `design/reference.html:183` — the same token controls both background hover and press scale.
- `design/reference.html:421-423` — the authority now explicitly presents press as 240ms.
- `DESIGN.md:394` — press feedback is fixed at 150ms.
- `DESIGN.md:586-592` — hover is deliberately slower than press, with controls at 240ms.
## Required outcome
- Press scale must retain its 150ms response independently of the selected 240ms control-hover transition.
- The rendered labels, tokens, and transition declarations must describe the same timing model.GPT Sol | 𝕏
…date (#36) Two decisions Thomas delegated, plus the brand comparison he asked to wake up to. DECISION 1: --status-done is unbound from --primary. It was var(--primary), so the brand colour and the completed state were one byte. An adversarial pass found that this broke three rules already written in this document: derivation rule 6 requires every status to sit more than 15 degrees from the accent and this sat at zero; the accent note says a static element rendered in the accent is as misleading as an interactive one rendered neutral, and a done ring is static; and "fill exactly one action per view" was broken six times over by a six-habit list with four done. Done now renders as an --fg-1 disc with a filled check, and the accent is reserved for what is NEXT and what is interactive. The neutral status ranking is done --fg-1, frozen --fg-2, skip --fg-3, empty --fg-4, so three neutral statuses can share one column and stay apart. The first attempt used --fg-2 for done, which collided with frozen, the exact failure that retired the cyan. This also shrinks orange's squeeze: the highest-frequency accent-versus- overdue adjacency, a done ring beside an overdue chip, no longer exists. DECISION 2: the mark carries the accent on exactly one element, its body, and that is the only non-state use of the accent in the system. The rationing list is amended to name it rather than leaving the mark out of a list that closes with "that is the whole list". The reference page gains a brand section: three columns, one per candidate, each showing the store icon at 200, a thumbnail row, both mark treatments, the lockup, the mark down to a natively redrawn 16px favicon, Astra beside the logo, and a mini app in context. The mark is a tilted ellipse with one body on the path, which is deliberately not the ProgressRing shape, so a logo is never read as a completion percentage. Astra keeps its centre core: anything with a core is Astra, anything empty in the middle is Orbit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…36) A completeness critic and a design reviewer, both fresh, were pointed at the page. The reviewer's closing line is the reason this commit exists: the page made the accent decision HARDER, because it was over-using the accent it was supposed to be auditioning, so anyone judging "does this hue feel rationed" was judging a canvas that had already broken the ration. Blockers fixed: every PRO / BETA / ACCENT badge was painted in the accent. A static label is on none of the six sanctioned surfaces, and DESIGN.md names this as the rule most often broken. Badges are now neutral; the Astra glyph spent the accent on three elements. The "one element inside the logo mark" exception is scoped to the Orbit mark and was never granted to Astra. Astra is now neutral and separates from the mark by its core alone. Also fixed: a streak total took accent text, but a streak is a record of what is finished; --selection-bg was a hardcoded cyan left over from the retired frozen hue and never repointed when the hue changed, which is the literal "no hardcoded accent rgba" ban, so it now derives from --hue-rgb; frozen and overdue rings carried state by ring colour alone with the glyph relocated to body text, so the glyph is back on the ring; a routine list-screen action was full-bleed outside the Buttons allowlist and used the over-wordy label the spec's own example calls out. From the completeness critic: the page named nine states but rendered no error state and no Field at all, so Field, OTP and Switch are added with default, focus, error and disabled; the listing spec was missing its 8-to-20 tier; the num type role was never shown; two unexplained row heights became --row-h and --row-h-compact; and font-size:17px was written three times where --fs-md already existed. The store icon radius is now a named exception in the spec rather than three off-scale literals: a platform icon mask is set by iOS and Android, not by the UI radius scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nds (#36) The completeness critic's headline finding was that roughly 5 of the 19 primitives in the kit table had any representation at all. That matters for this decision specifically: the FAB, the active tab and the progress ring are three of the six sanctioned accent surfaces, and none of them was on the page, so the accent was being judged on a swatch rather than on the surfaces that carry it. Added: TabBar with the active tab, FAB, a true ProgressRing at 0, 62 and 100 percent, NavHeader, SectionTitle, SettingsGroup with the rule between rows and none after the last, StatTile, InfoCard in both tones, PlanCard with exactly one recommended, and the Satellite empty-state glyph. The ProgressRing being on the page also makes the mark's geometry argument visible rather than asserted: the ring is a true circle with a sweep over a track, which is why the logo is a tilted ellipse with a solid body and can never be read as a completion percentage. Verified across every hue and both modes: active-tab accent text clears 4.5 in all six combinations (4.52 to 4.58), and white on the FAB clears 4.5 in all six (4.52 to 4.57). Zero page errors, zero network requests, no literal outside the single :root block. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Important
The new brand comparison breaks its light-mode text floor, and the brand contract now differs between the rendered authority, prose, and design reviewer.
Reviewed changes Reviewed 5f3ae37, which unbound completion from the accent and added the rendered brand-mark comparison.
- Neutralized completion: Moved
--status-doneonto the foreground ramp and updated the habit-row, derivation, and accent-rationing rules so finished work never takes the accent. - Defined the identity mark: Added the Orbit/Astra shape distinction, mark asset sizes, 16px redraw, wordmark, lockup, and logo-body accent exception.
- Rendered the shortlist: Added side-by-side emerald, rose, and orange brand specimens across store, lockup, navigation, Astra, and in-context habit examples.
⚠️ The brand comparison breaks light-mode text contrast
The newly added brand columns hard-override --primary-soft to dark-canvas values, so switching the page to Light leaves every candidate's mini-tab accent text below 4.5:1. Since this page is the grant artifact and explicitly claims all candidates clear every floor, the light rendering is not a faithful basis for approval.
Technical details
# Preserve the light-mode text token in every brand column
## Affected sites
- `design/reference.html:143-150` — light mode maps `--primary-soft` to the candidate's `--hue-lmtext` token.
- `design/reference.html:300-302` — each new `.brandcol` overrides that mapping with the dark-canvas `*-text` token regardless of mode.
- `design/reference.html:485,539,593` — the 11px active mini-tab text consumes the overridden token directly on the light surface.
## Browser evidence
- Emerald rendered `#008B56` at 4.17:1 on `#FAFAFA`; its `#008451` light token measures 4.55:1.
- Rose rendered `#C2508C` at 4.17:1; its `#BB4986` light token measures 4.56:1.
- Orange rendered `#C85716` at 4.16:1; its `#C15109` light token measures 4.52:1.
## Required outcome
- The side-by-side candidate comparison must remain independent of the selected hue while resolving text roles through each candidate's mode-correct token.
- Every rendered text role must meet the documented 4.5:1 floor in both modes.⚠️ The new identity contract has no single authority
DESIGN.md fixes exact lockup geometry and a new accent allowlist, but the rendered page uses different dimensions and the design reviewer still requires accented done dots while omitting the logo-body exception. A future implementation can therefore conform to one authority and still be rejected by another.
Technical details
# Align the rendered and reviewable identity contract
## Affected sites
- `DESIGN.md:30,163,216` — the mark's body is the only non-state accent use, and completion is explicitly neutral.
- `.claude/agents/design-reviewer.md:19` — the review checklist still includes done dots in the accent allowlist and does not allow the logo body.
- `DESIGN.md:36` — the horizontal lockup is fixed at a 28px mark, 12px gap, and 22px wordmark.
- `design/reference.html:313-314` — the authority page renders that lockup with a 10px gap and 26px wordmark.
## Required outcome
- The rendered reference and prose must specify one byte-exact lockup geometry.
- The design-review checklist must enforce the new completion and logo accent semantics rather than the superseded list.GPT Sol | 𝕏
Thomas discarded emerald on 2026-08-15. Green binds the brand to a success verdict rather than to a state, and it places Orbit in the green-checkmark habit-tracker slot that BRAND.md names as a positioning failure. Two candidates remain, both as a dark fill with white on it: warm orange around #C4530F, and rose #BF4D8A at hue 350. Neighbouring warm-orange values are still open. The exact byte is no longer settled here. It moves to the Claude Design canvas, which carries its own colour controls, and is written back into DESIGN.md once chosen. design/reference.html has done its job: it narrowed ten hues and two fill treatments down to two candidates and one treatment, and it settled spacing, the serif, the hover canon and the done unbinding along the way. The page now opens on orange, and both the picker and the brand grid carry two columns rather than three. The habit-colour palette keeps its own emerald, which is a habit colour and unrelated to the accent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Important
The expanded reference still contradicts the accent-rationing contract and publishes nonfunctional switch semantics, so it cannot yet serve as the authoritative implementation reference.
Reviewed changes Reviewed the three commits after the prior Pullfrog review, covering the accent-restraint fixes, expanded primitive reference, and narrowed accent shortlist.
- Tightened accent restraint: Neutralized badges, Astra, completion, and selection tint while adding field, OTP, switch, error, disabled, and listing specimens.
- Expanded primitive coverage: Added TabBar, FAB, ProgressRing, NavHeader, SettingsGroup, StatTile, InfoCard, PlanCard, and Satellite examples.
- Narrowed the shortlist: Removed emerald and kept warm orange and rose synchronized across the selector, tokens, and brand comparison.
⚠️ The expanded kit breaks the closed accent allowlist
The new kit section says it renders the sanctioned accent surfaces, but it also spends the accent on a completed 100% ring, InfoCard and PlanCard surfaces, and the Satellite arc. None is in the closed “whole list,” and selected state is limited to a glyph and label, so the rendered authority now directs future implementations to violate the prose.
Technical details
# Establish one accent-rationing contract
## Affected sites
- `DESIGN.md:214,217` — selected accent is limited to a glyph and label, and the six-use list declares itself exhaustive.
- `DESIGN.md:334-340` — the primitive table separately assigns accent tint or strokes to PlanCard, InfoCard, and Satellite.
- `design/reference.html:880` — the newly rendered 100% ProgressRing remains fully primary despite the ban on accent marking completion.
- `design/reference.html:914,919,923` — the newly rendered InfoCard, selected PlanCard, and Satellite use `--primary*` outside the closed list.
## Required outcome
- `DESIGN.md`, the rendered page, and the design-review checklist must define one exhaustive set of accent uses.
- Every rendered specimen must follow that set, including completion and selected-card treatment.
## Open questions for the human
- Are PlanCard, InfoCard, and Satellite intended additions to the allowlist, or should these specimens be neutralized?⚠️ The switch specimens have semantics but no operable control
The two new role="switch" specimens are keyboard-focusable, but the accessibility tree exposes both without a name and pressing Space leaves their state unchanged. This publishes a nonfunctional keyboard and screen-reader contract in the page that implementations are told to follow.
Technical details
# Make the rendered switch contract operable
## Affected sites
- `design/reference.html:779-780` — both switches use `role="switch"`, `tabindex="0"`, and fixed `aria-checked` values without an associated accessible name or activation behavior.
- `design/reference.html:992-1001` — the only script handles the mode and hue segmented controls; no switch click or keyboard path exists.
## Required outcome
- Each switch must have an accessible name and update its visual state plus `aria-checked` from click, Enter, and Space.
- If these are intentionally static illustrations, remove the interactive role, focusability, and pointer affordance instead of announcing controls that do nothing.
## Evidence
- A real `file://` browser accessibility snapshot reported two unnamed switches.
- Focusing the checked switch and pressing Space left both classes and `aria-checked` values unchanged.GPT Sol | 𝕏
Four findings, all real. P1, the drift workflow was inert. GitHub discovers push workflows from the TRIGGERING ref, and redesign-drift.yml existed only on redesign/main, so a push to main had no workflow on main to evaluate and the job could never start. It would have sat there looking like drift control while doing nothing for the whole redesign, which is exactly when it is needed. Removed from this PR; it needs its own pull request into main to ever fire. The expanded kit broke the accent allowlist two commits after that allowlist was written. A 100% ProgressRing was rendered entirely in the accent, which is precisely what "the accent never marks completion" forbids, and InfoCard, the selected PlanCard and the Satellite arc all painted the accent while sitting outside the closed six-item list. The list itself was the deeper defect: it enumerated COMPONENTS and then declared itself exhaustive, so it silently contradicted the primitives table, which separately assigns the accent to those same three. The rationing rule is now stated as four ROLES rather than six components: the next action, current position, progress toward something unfinished, and one element inside the logo mark. That is genuinely exhaustive, it covers the primitives table without special cases, and it stays restrictive. The 100% ring now goes neutral, and each specimen names the role it is exercising. The switch specimens published a broken accessibility contract: role=switch with no accessible name, and Space did nothing. They are real buttons now, named by aria-labelledby, with a handler that actually flips aria-checked. Also fixed a regex in my own repair that over-reached and neutralised all three progress rings instead of only the completed one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Important
The revised accent-role contract still assigns selection semantics to a static informational card, so the rendered authority does not yet define one enforceable rationing model.
Reviewed changes Reviewed 1a644a9, which removed the inert drift workflow and revised the fixes for accent rationing and switch operability.
- Removed inert drift automation: Deleted the workflow that could not observe pushes to
main, leaving it to land separately against the correct base. - Reframed accent rationing: Replaced the component allowlist with four semantic roles, neutralized the completed ProgressRing, and clarified the PlanCard and Satellite specimens.
- Made switches operable: Replaced the role-bearing
divelements with labelled native buttons and synchronized their visual and ARIA state on activation. - Expanded design-review checks: Added the newer glass, sparkle, pill, default-theme, and redundant-subtitle slop indicators.
ℹ️ The PR description advertises removed automation
1a644a9 deletes .github/workflows/redesign-drift.yml so the automation can land separately on main, but the PR description still lists that workflow as a delivered file. The review and merge record would therefore say D36 drift control shipped in this PR when it did not.
Technical details
# Keep the PR scope description current
## Affected sites
- PR description, `What this PR is` table — still lists `.github/workflows/redesign-drift.yml` as new.
- `1a644a9` — removes the workflow because a definition living only on `redesign/main` cannot observe pushes to `main`.
## Required outcome
- The PR description must no longer claim that this branch delivers drift automation.
- If a follow-up PR or ticket now owns the workflow, link it as deferred work rather than as a file shipped here.GPT Sol | 𝕏
Pullfrog's remaining finding, and it is right. The previous commit labelled the accent InfoCard a "selected state" so that it fit the four-role allowlist, but nothing is selected: it is a static informational callout. That is rationalising a specimen to fit the rule instead of obeying it, and the rule already says the accent is never decorative on a card. So the tone is deleted rather than a fifth role invented. InfoCard has one tone, --bg-elev with an fg-3 icon, in both the spec's primitive table and the rendered page. Removing a role keeps the accent nearer zero, which is the direction, and it means the four roles stay genuinely exhaustive rather than growing a carve-out every time a specimen does not fit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No new issues found in
69b5fc9.
The commit consistently removes the invalid static InfoCard accent variant from DESIGN.md and the rendered authority. Browser verification found two .infocard.quiet specimens, zero .infocard.accent specimens, and no page or console errors.
Previously reported PR-wide requested changes remain outstanding, so this incremental pass does not approve the PR.
GPT Sol | 𝕏
* ci: add the redesign/main drift-control job (D36) The redesign lands on the long-lived redesign/main integration branch, so that branch drifts behind main every time anything else merges, and the drift is invisible until an R ticket hits a conflict weeks later. This job merges main into redesign/main on every push to main. It has to live on main, which is why it is a separate pull request rather than part of #735. GitHub resolves push workflows from the TRIGGERING ref: a copy of this file sitting only on redesign/main would never be evaluated by a push to main, so the job could never start. Pullfrog caught exactly that on #735 and it was removed from there. It merges rather than rebases, and never force pushes, because redesign/main is shared: every open R-ticket branch is cut from it and rewriting its history would strand all of them. On conflict it fails loudly and stops, because resolving one is a decision about two real changes and a machine picking a side silently is how a redesign quietly loses a fix that landed on main. It is a no-op while redesign/main does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: fail closed when the branch probe cannot reach the remote (D36) Pullfrog is right and this was a silent-green path. `git ls-remote --exit-code` returns 2 for "no matching ref", but 128 for an authentication or transport failure, and the previous `else` collapsed every nonzero status into present=false. An unreachable remote would therefore have skipped the merge and finished the job green, which is a verifier error reported as a clean pass and is exactly what the standing fail-closed rule forbids. Only status 2 now means absent. Any other status prints the captured stderr, emits a workflow error and exits 1, so an unknown branch state stops the run rather than masquerading as a no-op. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the draft assets PR #735 shipped. Those existed only to make the accent decidable and are all deleted. design/brand/orbit-mark.svg a planet drawn as a ring with an open centre, an orbital band crossing in front low and passing behind at the upper right, and a small solid moon above right. design/brand/astra-mark.svg a letter A carrying the same orbital band and the same solid dot, so the pair reads as one family. They are told apart by SILHOUETTE, not by a detail: Orbit is a hollow ring, Astra is a solid letterform. That survives at 16px. It supersedes DESIGN.md's "anything with a core is Astra, anything empty in the middle is Orbit", which was written for the draft geometry and could only ever produce a second Saturn. Both are one evenodd path, transparent, no C2PA metadata, and carry var(--fg-1, #F4F4F6) so one file serves both modes. Generated on Recraft Basic, so both carry full commercial rights and were generated privately. Free tier output is Recraft owned, public and unlicensed and could not have shipped. The Orbit mark needed one repair: its source art carried a thin white crescent inside the planet. The fix merges that sliver into the planet's interior and emits a single hole boundary. Cutting it as a second evenodd subpath does not work, because overlapping holes XOR back to filled and the sliver returns as a hairline. Not yet built, and README says so: the 24 grid variant, the accent treatment, the native 16px redraw, the lockup, the platform icon and the PNG set. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…files Pullfrog is right that `currentColor` does not cross an image document boundary. README now states the rule that follows from it: inline these two files, never load one through `<img src>`, a CSS background-image or a React Native `<Image source>`, because the surrounding `color` does not reach the file's own document and the mark renders black. A surface that needs a flat file takes a baked raster from #80. SETUP.md was worse than the fill note Pullfrog caught. Its asset section listed NINE files, and not one of them is in the repository: five vectors (`mark-24-neutral.svg`, `mark-16.svg`, `astra-24.svg`, `lockup-horizontal-neutral.svg`, `icon-512-orange.svg`) and four `png/` rasters. Those were PR #735's drafts, which README already records as deleted, and `png/` no longer exists. It also told the reader to say the mark is a draft that #79 still owns, which stopped being true when the real marks merged. It now lists the two files that exist, names what is still to build and who owns it, and replaces the "invisible in an upload preview" note with what actually happens: the mark renders black, for the reason above. SETUP.md also carried the retired identity discriminator, "anything with a core is Astra, anything empty in the middle is Orbit". README and DESIGN.md:265 both supersede it with silhouette: a hollow ring is Orbit, a solid letterform is Astra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: paint the brand marks with currentColor, not a locked token (#79) Both marks carried `fill="var(--fg-1, #F4F4F6)"`. That locks each file to one colour and embeds a hex, so the mark could not render white on an accent tile without editing the asset. DESIGN.md:560 requires the opposite: "Icons use `currentColor` and take their states from colour and opacity, never from separate assets. Strip any hardcoded `fill` on import." #79's acceptance criteria say the same, and its test scenario greps the committed SVG for `#` and expects nothing. Both files now paint with `currentColor` and carry no hex. One file serves the `--fg-1` ink, white on an accent tile, and the warm orange. The consumer sets `color`. design/brand/README.md documented the old behaviour and warned the marks would look washed out on a white page. Both statements are now wrong, so the passage is rewritten to say who sets `color`. No code consumes these assets yet, so nothing renders differently today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: say these marks are inlined, and stop SETUP.md listing deleted files Pullfrog is right that `currentColor` does not cross an image document boundary. README now states the rule that follows from it: inline these two files, never load one through `<img src>`, a CSS background-image or a React Native `<Image source>`, because the surrounding `color` does not reach the file's own document and the mark renders black. A surface that needs a flat file takes a baked raster from #80. SETUP.md was worse than the fill note Pullfrog caught. Its asset section listed NINE files, and not one of them is in the repository: five vectors (`mark-24-neutral.svg`, `mark-16.svg`, `astra-24.svg`, `lockup-horizontal-neutral.svg`, `icon-512-orange.svg`) and four `png/` rasters. Those were PR #735's drafts, which README already records as deleted, and `png/` no longer exists. It also told the reader to say the mark is a draft that #79 still owns, which stopped being true when the real marks merged. It now lists the two files that exist, names what is still to build and who owns it, and replaces the "invisible in an upload preview" note with what actually happens: the mark renders black, for the reason above. SETUP.md also carried the retired identity discriminator, "anything with a core is Astra, anything empty in the middle is Orbit". README and DESIGN.md:265 both supersede it with silhouette: a hollow ring is Orbit, a solid letterform is Astra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: make SETUP.md's counts agree with the files that exist The previous commit replaced the asset list but left the numbers around it describing the old one. Pullfrog caught both. "Drag these 12 files" was 3 fonts + 5 vectors + 4 png. The png set and three of the vectors are deleted, so it is 5: three TTFs and two SVGs. The Field 2 description still said brand/ carries "the mark, the glyph, the lockup and the three type families". There is no lockup file; the horizontal lockup is listed as still to build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

Ticket 0 of the redesign. Rewrites
DESIGN.mdagainst D66 and shipsdesign/reference.html, the page that outranks it under D42. Closes nothing:#36stays open through both D13 human grants.Ticket: https://github.com/thomasluizon/orbit-tickets/issues/36 (migrated from ORB-30)
What this PR is
DESIGN.mddesign/reference.htmldesign/handoff/**.claude/agents/design-reviewer.md.github/workflows/redesign-drift.ymlredesign/mainBase is
redesign/main, cut fresh frommainfor this PR (D36).The tokens are derived, not picked
Every value was computed in OKLCH against the canvas and measured. The derivation forced four findings that were not in the ticket:
--fg-on-primaryis the canvas ink#020618(8.35:1), not white. The brand colour is not darkened to rescue white; the thing sitting on it changes instead.--status-frozencollided with the accent. The old#00D3F3sits 12.0 degrees from#24B4E2, inside the 15 degree band where two hues read as one colour, so streak-freeze and done would have looked identical. Frozen is retired as a hue and renders as a neutral chip plus the snowflake glyph. Thomas decided this in session.--fg-4failed the 3:1 non-text floor at 2.90:1 and is raised to#5D5D60(3.03:1).0.04 / 0.05 / 0.06 / 0.06resolved to#151517against#171719, which is not a step. Widened to0.04 / 0.06 / 0.08 / 0.12.The overlay library, per platform
The acceptance criteria required a named library with a rationale, and no candidate existed anywhere in the vault.
@base-ui/react1.7.0 (MIT, published 2026-08-04,react ^19peer,./dialogsubpath). Read live from the registry. Note@base-ui-components/reactis the deprecated old name frozen at1.0.0-rc.0; the registry message is "Package was renamed to @base-ui/react".@lodev09/react-native-true-sheet3.11.3, already installed. The library is not the defect, the wrapper is.The web root cause is visible in the markup:
app-overlay.tsxrenders<dialog open>rather than callingshowModal(), so it gets none of the platform's free behaviour and hand-rolls the focus trap, the body-scroll lock, the portal and Escape, then needsz-[9999]because it never enters the top layer. Thatz-[9999]violates this repo's ownno-arbitrary-zindexban. Its grabber also has no gesture handler, so web drag-to-dismiss is decorative today and nothing is lost by not replacing it.Migrating the 83 callers stays R-ticket work, per Out of scope.
Assumptions
Mechanical calls made during the rewrite. None is a Thomas-owned decision; each is sourced.
width="24" height="24" stroke-width="2"; an off-grid 22 renders with fractional scaling and looks soft.rgba()the authored notation.@react-native/normalize-colorshas nooklchbranch, read from the installed package, so anoklch()token is a runtime failure on Android.better-colorsprescribestext/border/accentoverfg/hairline/primary, but its own first principle says match the project's notation, and the--color-primaryversus--color-text-primarycollision it guards against does not exist here.prefers-contrast: moreis a token-override layer, not a third variant, so D66's "two variants, not twelve" holds.Manual steps
design/reference.htmlfrom the filesystem and approve the direction. This is human grant 1 (D13). Four controls at the top are the decisions: accent (three candidates), serif on/off, spacing scale A/B, plus light/dark. XP and levels are rendered both ways in section 01.@date-fns/tz ^1.2.0is a peer of@base-ui/reactand is not yet inapps/web. It installs with the R1 overlay ticket, not here.redesign/mainnow exists on the remote. It has no branch protection; decide whether it should have any.Verification
Every test scenario the ticket names, run against the committed files:
Fonts are embedded as
data:URIs: 8 latin woff2 faces, 195 KB binary, so the page renders Geist, Space Grotesk, Geist Mono and Instrument Serif with no network.What is deliberately not here
The 31-rule
eslint-rules/re-derivation, the spacing-scale codification, and the ninewarntoerrorflips. D66 decision 12 re-decides the spacing step values against the rendered reference, and several of those rules encode them, so writing them before grant 1 would bake in values Thomas has not chosen. They land in the next PR onredesign/main.The 86-rule and per-input verdicts are posted as a comment on the ticket rather than carried in
DESIGN.md.DESIGN.mdis the guidance an agent reads to build a screen; which external skill was rejected and why does not change how a surface gets built.🤖 Generated with Claude Code