Repository navigation
chore(ds): finish the button axis rename + drift fixes (secondary/ghost, attention, input size, press gate) (TASK-22817) - #3302
Conversation
The board (17802:61527) calls the three live rows primary, secondary and ghost. The code called two of them 'stroke' and 'transparent', so every review of a button had to translate between the two vocabularies, and the press-physics spec named a test after a word the board does not use. Renames the union members, the class (.btn-stroke -> .btn-secondary) and every call site. The legacy tail (transparent-light|dark, primary-soft) keeps its names: it has no board row and waits on a page rebuild. No visual change — the class bodies and the variant strings are untouched.
The card renders a Callout with priority="attention" — the board's name for the state — but the props that choose it were called 'warning'. Reading the file meant mapping one word onto the other, and a new caller had no way to guess which name the board uses. Renames the LimitsWarningType member and the titleKind value, plus the message key they build (warningCard.warningTitle -> .attentionTitle) in the three locales that carry it. Same copy, same colour, same branch.
sm/md are heights (40/48px), which is what every other primitive in the DS calls size. Calling it 'variant' made it read like a look, so call sites reached for className instead and forced the height by hand. Renames the prop and the local type, and moves SearchInput onto size="sm": it already overrode the horizontal padding with px-10, so the class list it renders is byte-identical. CopyField keeps its className h-10 — sm also narrows the padding to px-3, which would move its text 4px. The native `size` attribute is omitted from the props; nothing passed it.
No product screen has ever passed it. Its only caller was the /dev/ds page that documents it, so the doc page was the whole demand — and a documented prop reads as one somebody should use. A disabled back button is also not a state the navigation board draws: the flows that wanted it use hideBackBtn.
Both were private, so a caller building a props object for a Button or a Card had to retype the union or reach for `as const` and hope. Card's was also called ShadowSize while holding a different set of members than Button's — two names for two things is the rule, so Card's becomes CardShadowSize. Type-only change; the unions and every call site are untouched.
The comment said "peanut pink", and the file above it defines a peanut_pink palette entry whose background is #FFD5F6. Anyone matching the two would pick the wrong hex. #FF90E8 is action-primary.
The spec ran only inside ds-shots, which is deliberately not in ci-success.needs, so a change that broke the press state could merge green. The contract has broken twice and a class-name unit test cannot catch it: the bug is which state wins in the cascade, readable only off computed style in a real browser. Adds one lean job that builds and runs that single spec, and puts it in ci-success.needs. ds-shots keeps its advisory role.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: peanutprotocol/peanut-ui/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code-analysis diffPainscore total: 8694.58 → 8705.18 (+10.6) 🆕 New findings (60)
…and 40 more. ✅ Resolved (62)
…and 42 more. |
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
Seven design-system drift fixes, all rename/delete/comment plus one new required CI gate. The mechanical sweep is complete and typechecked (nothing outside the intentionally frozen audit fixture still uses the old names), the SearchInput/CopyField padding calls were verified against twMerge conflict rules, the i18n key rename touches exactly the three locales that carry it, and no PostHog consumer reads the one capture value that changes. Two major findings: the mono design rulebook still records the opposite naming resolution for the button variants, and the new required press-contract job drops the apt-stall retry its own template documents.
Findings
-
MAJOR · src/components/0_Bruddle/Button.tsx:94 · DS rulebook still records stroke/transparent as the code names
The rename reverses a recorded resolution without updating the record. mono design/design.md's open-questions row for button variant names (TASK-22817) says "stroke=secondary and transparent=ghost keep their names", and its recipes still instruct variant="stroke" / variant="transparent" (design.md:144, 224, 489; components.md:17). The open mono PR #220 from this author corrects only the press colour, not the naming row. After this merges, the rulebook that agents build screens from contradicts the code union: misuse fails typecheck loudly, but every recipe line keeps teaching the old vocabulary — re-creating the two-vocabulary translation cost this PR exists to remove — and the reversal of a recorded ruling goes unrecorded (design.md law 6: never silently decide). Fix: extend mono #220 or open a mono PR updating the variant-names row, the recipes, and components.md. -
MAJOR · .github/workflows/tests.yml:794 · press-contract's install-deps step drops the documented apt-stall retry
ds-shots documents three consecutive 6-minute npx playwright install-deps stalls on the runner's Azure mirrors (2026-08-19, PR #2755) and hardened its step with 3 attempts, a mirror fallback after the first failure, and a 9-minute cap (tests.yml ~509-533). press-contract copies the step without any of that. ds-shots is advisory, but press-contract is in ci-success.needs, so the same transient stall now fails a required check and blocks merges until a manual re-run — the exact failure mode the hardening exists for, moved onto a gate. Fix: copy ds-shots's retry block into this step.
Checked clean
- Worktree HEAD verified at 9fd74aa; full 126-file diff vs base f9372c0 reviewed, including the two stacked #3301 nav commits it carries
- Rename completeness: no variant="stroke"/"transparent" and no BaseInput variant prop survives outside the intentionally frozen dev/ds/audit fixture; no raw .btn-stroke class remains in product or e2e code
- SearchInput size="sm" swap verified: twMerge yields the same final class list (h-10 + px-10) as the old h-10 override, matching the byte-identical claim; CopyField's h-10 correctly left alone to keep 16px padding
- i18n: warningTitle -> attentionTitle in exactly the three locales carrying limits.warningCard; es-AR never had the key (same fallback as before); the unrelated crypto-deposit warningTitle untouched
- PostHog production checked: no insight or dashboard filters on limits_check_link_navigation or its type property, so the 'warning'->'attention' capture value change breaks no consumer
- CI at this head: press-contract green, plus unit, typecheck, eslint, ds-lint, screen-tests, format, native-export, human-authors, bot-approval; ci-success green; ds-shots still in flight (advisory, PR expects zero diffs)
- e2e rewrite matches the 2026-09-21 one-colour nav-circle ruling (documented in open mono #220); SetupWrapper circles + NAV_CIRCLE_BUTTON_CLASSES twMerge precedence verified and the /setup/finish white-chip regression covered by the new test
- AVATAR_LINK_BG comment verified against the token table: #FF90E8 is action-primary; the peanut_pink palette entry's lightShade is #FFD5F6
- LimitsWarningCard: no caller still passes type="warning"; getLimitsWarningCardProps is the only type producer and now emits 'attention'
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
The usual first reviewer was out of plan, so this review was done by openrouter/z-ai/glm-5.3.
Exact head: 9fd74aaf7c8e · Context: repo, mono, notion, production · Took 13m
🖼 Visual diff — 3 screens moved5 of 146 shots changed · 141 identical · baseline
job summary · before/after/diff images — artifact Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data. |
|
English · Español · Español (Argentina) · Português (Brasil) After merge: 3e2ce1e → b194ae8. Capture complete in all locales. |
UserHeader had zero call sites — the home header was rebuilt during the consolidation and nothing renders it any more. All 12 imports of the module pull VerifiedUserLabel instead, which stays and keeps its import path. It was also the fifth primary-soft call site, so removing it is one less thing to migrate in the variant collapse.
kush 2026-09-22: the union is the board — primary/secondary/ghost. The three legacy variants had no board row and kept a fourth, fifth and sixth way to ask for a button that the board already answers. primary-soft -> secondary (4 sites). primary-soft was bg-white with no shadow, so every call site paired it with shadowSize="4"; --color-background-default is #ffffff and .btn-secondary already ships the same 4px shadow, so the swap is pixel-identical at rest and the redundant shadowSize goes. The sites gain secondary's 1px residual shadow when disabled, which is the board's disabled state. transparent-dark -> ghost (3 sites). The fourth was not a button at all: the UnsupportedBrowserModal paste hint was an ActionModal cta with no onClick — a button that did nothing. It is now a footer note, which is also where "Then paste it in your preferred browser." has to read, after the copy cta. transparent-light -> ghost (QRScanner, 2 sites). The only visual change. The scanner overlay circles now use NAV_CIRCLE_BUTTON_CLASSES with white border, text and fill, so they match every other icon-only nav button: white ring and white glyph at rest, pink fill with a black glyph on press. The hard-coded fill="white" on the inner Icon goes with it. Also drops .btn-transparent-light and .btn-transparent-dark from globals.css and the three variants from the /dev/ds button page, whose usage counts were stale anyway — the variants list now names each row's job instead.
…te must not fail on a transient mirror stall (chip review)
dev's new RequestCreatedView is the one semantic conflict: it was written against the old variant vocabulary while this branch renamed it. stroke -> secondary and transparent -> ghost, the same pure rename 526d911 applied to every other call site. No visual change — the class bodies are untouched. That single type error is what failed typecheck, native-export, press-contract, ds-shots, Deploy-Preview and ci-success: all five build steps type check, so all five inherited it from the one file.
dev landed the ButtonVariant collapse (#3302) while this branch was open, so the two sides met on the same two marketing files. Conflict: src/components/Jobs/Careers.tsx. dev's only edit to the Ready? section was the variant rename stroke -> secondary (526d911); this branch had rebuilt the whole section into the marketing CTA card (mascot behind the Card, centered, inline buttons). Union rule: keep this branch's composition, take dev's variant name on top of it. Nothing of either side is dropped. press/page.tsx auto-merged for the same reason — dev renamed the download pill's variant, this branch added its w-auto/whitespace-nowrap classes on the same element, and git kept both. Swept the branch for the other legacy variant names dev retired (stroke -> secondary, transparent -> ghost, transparent-light|dark -> ghost, primary-soft -> secondary). The only live site left was the careers CTA button, which this branch added after dev's migration ran — it is renamed here. The remaining hits are the /dev/ds audit datasets, which quote historical class names as data and are allowlisted out of ds-lint. ds-lint baseline: two debt metrics move up, both recorded with reasons. offScaleSpacing 85 -> 87 is this branch's own arrears — the Ready? rebuild added pt-24/md:pt-28 without a baseline bump, so the ratchet was already red before this merge. That padding is the mascot's overhang reservation, the geometry-tied-to-what-it-overlaps category the counter already exempts for inset offsets. hoverNoActiveFiles 20 -> 21 arrives with dev (UnsupportedBrowserModal/useCopyLinkActions.ts, added in the variant collapse); 21 is still under dev's own baseline of 22. Gates: prettier clean, tsc clean, 719/719 jest suites pass, ds-lint ratchet ok. Checked /careers and /en/content at 375px — the CTA still reads as the white outlined button it did under 'stroke', and the tab track keeps its weld, its 1px gutter and its single scroll axis.
dev collapsed ButtonVariant to primary | secondary | ghost (#3302): stroke became secondary, transparent became ghost, and transparent-light, transparent-dark and primary-soft were deleted with their call sites migrated. BaseInput's height axis became `size`. This branch recreates real call sites on every primitive and pattern page, so the merge is not a text merge: each recreation had to be read against the file it cites. Three conflicts: - DocSidebar.tsx — ours. The old sidebar is gone; the file is now a 15-line shim over DocNavList, and the mobile drawer it used to hold lives in DocNavDrawer, mounted from the layout. dev's side only renamed variants inside the body this branch deleted, so nothing was left to port. - primitives/button/page.tsx — both. Takes dev's three board rows and their roles, keeps this branch's usage count beside each one, and re-counts them against the merged tree: primary 199 (97 explicit plus 102 that pass no variant), secondary 52, ghost 24, over 275 Button call sites with /dev and tests excluded. The header said 120+; it says 275. The rows for the three deleted variants are gone. - scripts/ds-lint-baseline.json — dev's, unchanged. No number was hand-merged and no metric ends above dev's value. Recreations reconciled against the file each cites: PaymentSuccessView, Landing, BridgeTosStep, LockCardModal and the drawer, modal, toast and divider trigger buttons all read secondary now; Profile's nav circle and the DS nav drawer read ghost; ProfileEditField and the nav search read size="sm". The clip-gate comment and the layout comment name .btn-secondary, which is what globals.css calls it. One spacing change: the progress-bar recreation paired its label and bar with a 6px gap copied from the real file. That is one off-scale site the app already counts once, and a second copy pushed offScaleSpacing past dev's 87. The showcase uses the on-scale step instead — the quoted code never showed the wrapper, so nothing it claims changed. The audit pages are left alone. They are a dated snapshot of the codebase and dev did not touch them either; rewriting a record of what was true in September would not make it truer. Gates: tsc clean, 117 dev/ds tests pass, ds-lint ratchet ok, prettier clean.
Eight drift fixes in the design system: names that no longer match the board, three variants the board never had, a prop the board does not draw, two types nobody could import, a comment that named the wrong colour, and a contract test that could not fail a merge.
Everything except H is a rename, a deletion or a comment. H has one deliberate visual change, named below.
Stacks on #3301
Branched off
fix/nav-circle-one-color-press, notdev, because #3301 touches the same button call sites. Merge #3301 first. After it lands, this PR shows only its own 7 commits.A — the button variants are named after the board rows
The board (17802:61527) calls the three live rows primary, secondary and ghost. The code called two of them
strokeandtransparent.ButtonVariant:stroke→secondary,transparent→ghost.btn-stroke→.btn-secondary(the ghost row is styled inline, so there is no.btn-transparentto rename)transparent-light,transparent-dark,primary-soft) kept its names here, then H removes it outrightdev/ds/audit/is untouched by designB — the non-blocking limits state is 'attention'
LimitsWarningCardrenders aCalloutwithpriority="attention", but the props that chose it saidwarning.LimitsWarningType:'warning'→'attention'titleKind:'warning'→'attention'limits.warningCard.warningTitle→.attentionTitle, in the 3 locales that carry it (es-AR has no title keys and falls back)Same copy, same colour, same branch.
Analytics scope.
typereaches PostHog:LimitsWarningCardcaptures it onlimits_check_link_navigation, so that property's value becomesattentionfrom this PR on. Nothing reads it — no saved insight or dashboard in project 138913 references the event at all (system.insights, checked; Chip found the same). The break is a value split in raw events only, so any later query on that property has to accept both words across the rename date.C — BaseInput's height axis is
sizesm/mdare heights (40/48px). Calling the propvariantmade it read like a look, so callers forced the height by className instead.BaseInputVariant→BaseInputSize)ProfileEditField) and 12 on /dev pagessizeattribute is omitted from the props — nothing passed itsize="sm"and drops itsh-10. It already overrode the horizontal padding withpx-10, so the class list it renders is byte-identical.className="h-10".smish-10 px-3,mdish-12 px-4— the size does not differ in height alone, and switching CopyField would narrow its padding from 16px to 12px. That is a visual change, so it waits for a ruling.D —
disableBackBtnis goneNo product screen ever passed it. Its only caller was the /dev/ds page documenting it, so the doc page was the whole demand. A disabled back button is also not a state the navigation board draws; flows that wanted it use
hideBackBtn.Removed: the prop, its destructure, the
disabled={...}it fed, and the two doc-page rows plus the live demo usage.E — the shadow-size unions are exported
Both were private, so a caller building a props object had to retype the union. Card's was also called
ShadowSizewhile holding a different set of members than Button's, so it becomesCardShadowSize. Type-only: no union changes, no call-site changes.F — AVATAR_LINK_BG is the brand pink
The comment said "peanut pink". The same file defines a
peanut_pinkpalette entry whose background is#FFD5F6.#FF90E8is action-primary. One line.G — the press-physics contract can now fail a merge
e2e/flows/button-press-physics.spec.tsran only insideds-shots, which is deliberately not inci-success.needs. A change that broke the press state could merge green. The contract has broken twice, and a class-name unit test cannot catch it: the bug is which state wins in the cascade, readable only off computed style in a real browser.New
press-contractjob: checkout with submodules, install, cached Playwright chromium,pnpm build, then that one spec. No screenshot or diff machinery. Added toci-success.needs.ds-shotskeeps its advisory role unchanged.Prettier reflowed the
needs:array when the entry pushed it past the print width — that is why the diff there is larger than one line.H —
ButtonVariantisprimary | secondary | ghostkush 2026-09-22: the union is the board. The three legacy variants had no board row and kept a fourth, fifth and sixth way to ask for a button the board already answers. All 10 call sites are migrated and the type, the
buttonVariantsrecord and the two CSS classes are gone.primary-soft→secondary(4 sites, no visual change at rest.)primary-softwasbg-whitewith no shadow, so every call site paired it withshadowSize="4".--color-background-defaultis#ffffffand.btn-secondaryalready ships the same 4px shadow, so the swap is pixel-identical and the redundantshadowSizegoes with it.Profile— "Log out"Success.link.send.view— "Cancel link"AddMoneyBankDetails— the "Share details"ShareButtonCopyToClipboard— "Copy code" (button mode)The disabled state changes: 4px → 1px.
primary-softcarried no shadow of its own, so the whole shadow on these four came fromshadowSize="4"→.btn-shadow-primary-4, which has nodisabled:scope (globals.css). A disabledprimary-softtherefore still painted the full 4px offset, only dimmed by.btn'sdisabled:opacity-40..btn-secondaryscopes its owndisabled:shadow-[0.0625rem…], so the four now drop to the 1px residual instead. That is the board's disabled state, so it is a gain, not a regression — but the delta is 4px to 1px, not flat to 1px.ShareButton's own hard-codedshadowSize="4"stays —ProfileHeaderpasses itvariant="ghost", which has no shadow of its own and would lose one if it were dropped.transparent-dark→ghost(3 sites, no visual change.)SetupWrapper("Skip") and the twoBadgesRowscroll arrows. No className changes.The fourth
transparent-darkwas not a button at all. TheUnsupportedBrowserModalpaste hint was anActionModalcta with noonClick— a button that did nothing, styled down toh-2to stop looking like one. It is now a<p className="text-body-xs text-foreground-secondary">in the modalfooter. Footer, notcontent: "Then paste it in your preferred browser." has to read after the copy cta, andcontentrenders above the ctas. Net spacing below the button moves 20px → 24px. The test'sActionModalmock gainedfooterand lost its now-deadonClick ? button : pbranch.transparent-light→ghost(QRScanner, 2 sites) — the one visual change. The scanner overlay circles now useNAV_CIRCLE_BUTTON_CLASSESwithborder-white text-white fill-white, so they match every other icon-only nav button in the app. At rest: white ring, white glyph on the camera feed — same as today. On press: pink fill with a black glyph, which is the ruled ghost icon-only state and is what changes. The hard-codedfill="white"on the inner<Icon>goes, so the glyph now inherits from the button and can invert.On a desktop pointer the circles also take the pink fill on hover, not only on press.
NAV_CIRCLE_BUTTON_CLASSESpaints hover and press with the sameaction-primary— one colour for both states is the 2026-09-21 ruling, so this is the ruled behaviour and not a second change. Touch has no hover, so the scanner's own users see press only.Deleted:
.btn-transparent-lightand.btn-transparent-darkfromglobals.css; the three variants fromButtonVariant,buttonVariantsand the Button docblock; the three from the/dev/ds/primitives/buttonplayground and variants list. That list's usage counts were stale, so it now names each row's job instead of a number nobody regenerates. The frozen audit fixture underdev/ds/audit/is untouched.Separate commit — the dead
UserHeaderis deleted. It was the fifthprimary-softcall site and had zero callers; the home header was rebuilt during the consolidation. All 12 imports of@/components/UserHeaderpullVerifiedUserLabel, which stays in place with its import path unchanged.Not done — each waits on a ruling
small|medium|large, BaseInput usessm|md)FieldandFieldColumninto one primitiveGates
prettier --check .·typecheck·TZ=UTC npm test(717 suites, 9133 tests) ·npm run build· fullpnpm test:e2e:regressionagainst the production build (112 passed) — all green locally, re-run after H. The press-physics spec is unaffected and passes.ds-shotsshould now report two classes of difference, both intended:primary-softbuttons, whose shadow drops from 4px to the 1px residualAnything else is a regression.
Extends TASK-22817. The id is in the title only — the branch was already pushed and open, and renaming a branch closes its PR, so the second mention the linker wants is not available here. Follow-up fixes are in #3317.
🤖 Generated with Claude Code