Give the components the knobs they were pretending to have - #34
Open
pablostanley wants to merge 2 commits into
Open
Give the components the knobs they were pretending to have#34pablostanley wants to merge 2 commits into
pablostanley wants to merge 2 commits into
Conversation
The stat card's Trend was the tell: pick "down" and you got a down arrow
next to "+12.5%" and a sparkline still climbing. The control wasn't dead
exactly — it moved a 12px glyph — it just didn't touch either of the two
things that carry the meaning. An audit of all 150 defs found the same
shape of problem everywhere.
Four kinds of broken, now fixed:
Controls that did nothing. Ranges whose top half was inert — profile-screen
offered 2-9 cards and drew 3, gallery dropped any tile that overflowed,
file-upload pinned Files to 0 unless a second control was set just so. Each
one either reflows to honour the range or admits the honest maximum.
Components arguing with themselves. A cart header reading "3 things" beside
an Items control set to 5, kanban badges fixed at 6/3/9/1, an order summary
whose total ignored its line items, a usage meter forever "14 of 20 used".
These now derive from the prop they used to contradict.
Copy with no way in. Every navbar CTA said "Sign up", every footer said
"squig", both hero buttons and the chart title were literals. 71 new
controls, mostly text fields over strings that were previously unreachable.
Options too thin to matter. contact-form-block's "Split layout" was a
yes/no select defaulting to boolean true, so it rendered blank.
feature-grid and pricing-block each marked four controls quick when the
context row hard-slices at three, so the fourth silently vanished.
On Trend specifically: it now drives the arrow, the sparkline's slope, and
the delta's sign. An unsigned delta picks up the trend's sign — the default
is "12.5%" now — but a sign you typed yourself is left alone, because
arguing with someone's own input is worse than the contradiction.
Saved documents keep rendering the same. Nodes freeze {...def.defaults}
into props at drop time, so an old node never gains a new key; every added
prop falls back to the literal it replaced. Verified by rendering both
versions of each def across the old prop set and diffing prims.
Adds scripts/test-defs.ts: every def at 60/100/150%, asserting it doesn't
throw, draws something, has no NaN, stays inside its own box, keeps every
control key in defaults, keeps select defaults among their options, and
respects the three-quick-control cap. 1200 checks over 150 components. It
caught five defs that had been spilling content out of the box when scaled
down — login by 130px — which are also fixed here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Second pass over the library, aimed at what the first one missed. The screens had nothing to say. landing-page is the biggest thing you can drop — 1000x830 — and its 34px headline was a string literal, so every landing page anyone wireframed said the same sentence. app-shell had three chrome toggles and no way to name the page you were on. pricing shipped two count knobs against frozen arrays of plan names and prices, which is everything a pricing table exists to say. chat-screen was permanently a conversation with Maya. inbox-screen's 20px subject was the largest type on the screen and hardcoded. The first sweep made its own inconsistencies. Seven marketing blocks got a quick heading toggle and no text control, so you could switch the heading on and off and were stuck with the joke it shipped with. profile-header gained a name control, but profile-screen composed it and dropped the prop, so the person the screen is about was uneditable one level up. "Heading" was a toggle in ten defs and a text box in four. A button's text field went by three different names. spinner labelled its toggle "Label" and its text field "Text", exactly backwards from the six defs it should match. Blocks that still argued with themselves: onboarding-checklist's progress readout was off by one against its own Current step, product-grid drew stars unconditionally while its sibling gated them, command-palette pinned the highlight to row 0, payment-methods to card 0. Number controls accepted fractions. The field parsed "2.5" and only clamped to min/max, and defs index arrays and divide grids by that raw value, so a typed fraction wasn't a smaller count, it was a broken layout. All 70 number controls are counts, indices or percents, so this rounds once at commit in variant-controls.tsx, covering the panel and the quick row while geometry fields keep their decimals. 48 controls added, one removed: button-group's Segments, which fought its own comma-separated label list — accordion lost the same fight last commit, so it now follows the same rule. Saved documents still honour a frozen count. Saved documents keep rendering the same, verified by diffing every def's prims against the previous commit across the old prop set rather than by eye. No prop key was renamed; relabelling is safe because labels aren't persisted, which is why most of the consistency fixes cost nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pablostanley
marked this pull request as ready for review
July 30, 2026 22:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Started from one bug: pick Trend "down" on a stat card and you get a down arrow beside +12.5% with a sparkline still climbing. Two audit rounds later, that turned out to be a pattern rather than an incident.
Two commits. 119 controls added, 4 removed. 150 components audited twice.
Round one — controls that lied
234 findings across two audits, each put through an adversarial verify pass before it was allowed to matter.
Controls that did nothing.
profile-screenoffered 2–9 cards and drew 3 — its own default of 6 was inert.gallerywent to 9 tiles and silently dropped any that overflowed.file-uploadpinned Files to 0 unless a second control was set just so. Four more ranges whose top values were identical to a lower one.Components arguing with themselves.
cart's header read "3 things" beside an Items control set to anything else.kanban-boardbadges fixed at 6/3/9/1, wrong even at the default.order-summaryshowed one $24.00 line above a $48.00 total.usage-metersaid "14 of 20 used" whatever you set Value to.Copy with no way in. Every navbar CTA said "Sign up", every footer said "squig", both hero buttons and the chart title were literals.
Options too thin.
contact-form-block's "Split layout" was a yes/no select whose default was the booleantrue, so it rendered blank.feature-gridandpricing-blockeach marked four controls quick when the context row hard-slices at three.Trend, specifically
An unsigned delta picks up the trend's sign — the default is
12.5%now — but a sign you typed yourself is left alone. Arguing with someone's own input is worse than the contradiction.Round two — what the first pass missed, and what it broke
The big screens couldn't say anything.
landing-pageis 1000×830 and its 34px headline was a literal, so every landing page said the same sentence.app-shellhad three chrome toggles and no way to name the page you were on.pricingshipped two count knobs against frozen arrays of plan names and prices.chat-screenwas permanently a conversation with Maya.The first sweep created its own inconsistencies. Seven marketing blocks got a quick
headingtoggle and no text control, so you could switch the heading on and off and were stuck with the joke it shipped with.profile-headergained anamecontrol, butprofile-screencomposed it and dropped the prop, so the person the screen is about was uneditable one level up. "Heading" was a toggle in ten defs and a text box in four. A button's text field went by three different names.spinnerlabelled its toggle "Label" and its text field "Text" — exactly backwards from the six defs it should match.Blocks still contradicting themselves:
onboarding-checklist's progress readout was off by one against its own Current step;product-griddrew stars unconditionally while its sibling gated them;command-palettepinned the highlight to row 0.Number controls accepted fractions. The field parsed
2.5and only clamped to min/max, and defs index arrays and divide grids by that raw value — so a typed fraction wasn't a smaller count, it was a broken layout. All 70 number controls are counts, indices or percents, so this rounds once at commit invariant-controls.tsx, covering both the panel and the quick row while geometry fields keep their decimals.Saved documents keep rendering the same
Nodes freeze
{...def.defaults}into props at drop time, so an old node never gains a newly-added key. Every added prop falls back to the literal it replaced.Verified mechanically rather than by eye: both versions of every def rendered across the old prop set at several scales and the prims diffed. No prop key was renamed — relabelling is safe because labels aren't persisted, which is why most of the consistency fixes cost nothing. Four controls were removed (
divider.showLabel,file-upload.variant,accordion.count,button-group.count); each stays readable inrender(), so existing nodes are unaffected.New:
scripts/test-defs.tsEvery def at 60/100/150%, asserting it doesn't throw, draws something, has no NaN, stays inside its own box, keeps every control key in
defaults, keepsselectdefaults among theiroptions, and respects the three-quick-control cap. 1200 checks over 150 components, wired intopnpm test.It immediately caught five defs that had been spilling content out of the box when scaled down —
loginby 130px,signupby 84px — all fixed here.frameis exempted: its name hangs above the artboard on purpose, the way Figma does.Verification
pnpm lintclean,pnpm test1305 checks pass,pnpm buildsucceeds+12.5%while correctly flipping its sparkline2.4into Plans commits as2One thing I left alone
logo-cloudrenders "Teams that draw badly on purpose", which sits awkwardly against framing squig as low-fidelity rather than bad. It predates this branch, so it wasn't mine to change — say the word.🤖 Generated with Claude Code