feat(fork): design change requests carry component props and ask for scope judgment - #79
Conversation
…scope judgment Design mode's change requests told the agent to scope every edit to its call site and skip anything touching a shared component — so selecting a Button and nudging its padding produced one-off className overrides that drift from the design system, and the agent could not see which variant it was even looking at. Two halves, one concern: - SCOPE_GUARDRAIL now asks the agent to judge component-wide vs one-off intent from the request's context, prefer project tokens over hard-coded values, and disclose which scope it chose. A guard test pins the wording against upstream re-syncs, like NO_PREVIEW's. - The desktop resolver snapshots the rendering component's primitive props off the composite fiber whose displayName matches the reported component name (bippy, same pinned version react-grab bundles). The snapshot is validated on both sides of the page-shared global, rides the element as data-t3-props, is re-validated when the request builder reads the page-controlled attribute back, and renders as "Rendered by `<Button>` — props: `variant="ghost" size="sm"`". Fable 5 via Claude Code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
Thermo-nuclear code quality review — do not approve as-is.
Three structural issues fail the approval bar: non-atomic stale DOM stamps for the new props evidence, avoidable source-duplication of the props normalizer, and a first-party bippy root import that pulls DevTools-hook side effects for two fiber helpers.
SCOPE_GUARDRAIL rewrite + guard pin look fine. Triple validation at boundaries earns its keep — the problem is forking the implementation, not calling it more than once. request.ts stays under 1k; no file-size blocker.
Ordered by review priority; details on the inline threads.
Sent by Cursor Automation: Thermo-nuclear PR review
…ps atomically
The preview-pick-preload entry only marked react-grab as alwaysBundle, so the
new bippy import stayed external. That webview runs sandboxed, where require()
cannot resolve npm packages — the packed preload threw before installing either
the picker or the design source resolver, taking the whole preview-pick path
down in packaged builds. Verified against the artifact: require("bippy") is
gone, only Electron's synthesized require remains. A guard test now pins the
bundling rule so a config sweep cannot silently reintroduce it.
Props stamps are also atomic with the resolution now: a resolve that comes back
nameless clears both data-t3-component and data-t3-props instead of leaving the
previous component's vocabulary on the element as current evidence.
|
Rebased onto `custom` (now carrying #77 and #78) and addressed the open review threads in `dfdfe3761`. P1 — bippy left external in the packaged preload. Real, and it would have taken the whole preview-pick/design-mode path down in desktop builds. Added `bippy` to the `alwaysBundle` predicate for the `preview-pick-preload` entry, matching the `@clerk/electron` entry directly above. Verified against the artifact both ways with `vp pack`:
Electron synthesizes that last one for sandboxed preloads, so it is the expected floor. Pinned with a guard test in `forkDesignMode.test.ts`, confirmed to fail when the predicate is reverted. Props stamps were not atomic. A resolve that came back nameless left both `data-t3-component` and `data-t3-props` in place, so the request could render a previous component's vocabulary as current evidence — mild for the name alone, material once props feed the scope judgment. The Declined, with reasoning in-thread: sharing the props normalizer across Manifest now watches Verification: Model: Claude Opus 5 (1M context), harness: Claude Code. |


Problem
Design mode's change requests told the agent to scope every edit to its call site and to skip anything that would touch a shared component. Selecting a Button and nudging its padding therefore produced one-off className overrides that drift from the design system — and the agent couldn't see which variant it was looking at, so it had no basis to do better.
Fix
Two halves of one concern: give the agent license to make the component-vs-one-off call, and give it the evidence to ground that call.
Scope guardrail (
engine/vendor/shared/guardrails.ts):SCOPE_GUARDRAILnow asks the agent to judge whether the user means the component everywhere or just this instance, prefer project tokens and suggested utilities over hard-coded values, and state which scope it chose instead of pausing to ask.forkDesignModeCssOrigin.test.tspins the wording against upstream re-syncs, the same wayNO_PREVIEW_GUARDRAILis pinned.Component props snapshot: the desktop resolver reads the rendering component's primitive props off the composite fiber whose
displayNamematches the component name react-grab reported — no name match, no props, so props are never attributed to the wrong component. Requests now read:The snapshot is primitives-only (strings/numbers/booleans,
childrenexcluded, 12 entries max, 64 chars per string, control characters rejected) and is validated three times on its way to the request:normalizeResolvedPropson the preload side of the page-shared global, its guest twinreadDesignProps(newdesignProps.ts, thecssOrigin.tsfence-crossing pattern) before stampingdata-t3-props, and again when the request builder parses that page-controlled attribute back. It only ever rides beside a component name.bippyis imported by its root export on purpose:bippy/core's published d.ts exports mangled names, and the root's hook-install side effect is guarded double work beside react-grab's bundled copy (verified in both dists). Same pinned version (0.5.41). Desktop-preview-only by architecture, exactly like component names already are.Verified
apps/desktop: 23 resolver tests (7 new for props normalization and carrying), typecheck cleanapps/web: 10 newdesignPropstests, manifest + design-mode bundle guard tests, lint-cleanliness guard, engine-island tsc, app typecheck — all greenFable 5 via Claude Code.
🤖 Generated with Claude Code