Edit React props (motion / three.js values) in the style panel#460
Edit React props (motion / three.js values) in the style panel#460aidenybai wants to merge 9 commits into
Conversation
…yle panel Derive numeric prop rows from the nearest component fiber via bippy's traverseProps (top-level numbers plus motion-style object members like animate.opacity and transition.duration), live-preview edits with overrideProps, and emit prop changes in the copied prompt. Numeric rows now carry a per-property step so fractional values (opacity, duration) tune precisely. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
…eview Collapse the panel's two preview fields into one EditPreview that dispatches by property source. Removes the source branch in commit and the dual restore/forget/hasApplied calls, and fixes prop overrides leaking on dismiss paths that only call preview.restore() (editMode.dismiss / closePreservingRenderer). Also make roundEditableNumericValue single-exit. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Recurse object-valued props (animate, transition, variants, dragConstraints, ...) to collect numeric leaves at any depth, so the common framer-motion variants pattern works: initial/animate/whileHover are string variant names and the real values live in variants.<name>.<value> (incl. nested transition.duration). Previously only one level inside inline target props was read, so variant-based components surfaced no editable rows. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/utils/collect-fiber-numeric-props.ts">
<violation number="1" location="packages/react-grab/src/utils/collect-fiber-numeric-props.ts:54">
P2: Depth-first nested collection can exhaust the shared prop cap and hide later top-level numeric props.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
5 issues found across 20 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/utils/prop-numeric-bounds.ts">
<violation number="1" location="packages/react-grab/src/utils/prop-numeric-bounds.ts:16">
P2: Scale props are bounded to `min: 0`, so the panel cannot step them into negative values.</violation>
</file>
<file name="packages/react-grab/src/utils/format-css-value.ts">
<violation number="1" location="packages/react-grab/src/utils/format-css-value.ts:34">
P2: Fractional step snapping here is vulnerable to floating-point under-rounding, so some prop edits can snap to the wrong increment.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- collect-fiber-numeric-props: always keep top-level numeric props so a large nested variants map can't starve the cap and hide scalar knobs like count/speed - edit-preview: restore props before inline styles so the overrideProps re-render can't clobber the restored CSS baseline on mixed edits - find-props-fiber: widen the composite walk (6 -> 10) to clear common wrapper layers (forwardRef/memo/context/LazyMotion) - preview-props: key captured originals with a NUL separator so path segments can't alias Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 538f86d. Configure here.
| return; | ||
| } | ||
| stylePreview.apply(property.cssProperties, formatEditableValue(property, value)); | ||
| }; |
There was a problem hiding this comment.
CSS rows stale after prop preview
Medium Severity
Live prop previews re-render the component (e.g. inline style from animate), but CSS editable rows stay frozen from panel open. After tweaking a prop, computed-style rows such as opacity can still show pre-preview numbers while the DOM already reflects the override, misleading further edits or mixed CSS/prop submissions.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 538f86d. Configure here.
There was a problem hiding this comment.
2 issues found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/utils/collect-fiber-numeric-props.ts">
<violation number="1" location="packages/react-grab/src/utils/collect-fiber-numeric-props.ts:57">
P2: The shared cap still lets top-level numeric props starve later nested prop leaves, so components with many numeric props can hide editable motion/three.js values.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if (isEditableNumber(nextValue)) { | ||
| collected.push({ path: [propName], value: nextValue }); | ||
| } else if ( | ||
| collected.length < PROP_NUMERIC_MAX_COUNT && |
There was a problem hiding this comment.
P2: The shared cap still lets top-level numeric props starve later nested prop leaves, so components with many numeric props can hide editable motion/three.js values.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/utils/collect-fiber-numeric-props.ts, line 57:
<comment>The shared cap still lets top-level numeric props starve later nested prop leaves, so components with many numeric props can hide editable motion/three.js values.</comment>
<file context>
@@ -46,11 +46,18 @@ const collectNumericLeaves = (
collected.push({ path: [propName], value: nextValue });
- } else if (EDITABLE_OBJECT_PROP_KEYS.has(propName) && isPlainObject(nextValue)) {
+ } else if (
+ collected.length < PROP_NUMERIC_MAX_COUNT &&
+ EDITABLE_OBJECT_PROP_KEYS.has(propName) &&
+ isPlainObject(nextValue)
</file context>
- prop-numeric-bounds: allow negative scale (mirroring) with a symmetric -3..3 floor - format-css-value: nudge step snapping by a tiny epsilon so exact half-steps (0.35/0.1) round half-up instead of flipping down via FP error - preview-props: key captured originals with JSON.stringify(path) for a fully collision-proof rollback map Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
overrideProps is a one-shot override that React discards on the next parent re-render (e.g. a setState that rebuilds a motion variants object), so prop tweaks reverted as soon as the component re-rendered. Add a sticky-override registry that re-asserts each override after every commit (guarded by a value check so it can't loop, alternate-aware fiber matching since bippy getFiberId is unreliable for id 0, and prunes unmounted fibers). Edits now persist like CSS inline styles; discard clears the sticky entry and restores the original. Extract shared readPropAtPath/buildNestedPropPartial into prop-path.js. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/utils/prop-path.ts">
<violation number="1" location="packages/react-grab/src/utils/prop-path.ts:18">
P2: Build nested override objects with null-prototype maps (or block special keys); plain `{}` is vulnerable to prototype-pollution-style path names like `__proto__`.</violation>
</file>
<file name="packages/react-grab/src/utils/preview-props.ts">
<violation number="1" location="packages/react-grab/src/utils/preview-props.ts:23">
P2: Sticky prop overrides are recorded even when the initial overrideProps call fails, which can leave stale retries and unexpected later prop mutations.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| const root: Record<string, unknown> = {}; | ||
| let cursor = root; | ||
| for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) { | ||
| const next: Record<string, unknown> = {}; | ||
| cursor[path[segmentIndex]] = next; | ||
| cursor = next; |
There was a problem hiding this comment.
P2: Build nested override objects with null-prototype maps (or block special keys); plain {} is vulnerable to prototype-pollution-style path names like __proto__.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/utils/prop-path.ts, line 18:
<comment>Build nested override objects with null-prototype maps (or block special keys); plain `{}` is vulnerable to prototype-pollution-style path names like `__proto__`.</comment>
<file context>
@@ -0,0 +1,27 @@
+ path: readonly string[],
+ value: unknown,
+): Record<string, unknown> => {
+ const root: Record<string, unknown> = {};
+ let cursor = root;
+ for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) {
</file context>
| const root: Record<string, unknown> = {}; | |
| let cursor = root; | |
| for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) { | |
| const next: Record<string, unknown> = {}; | |
| cursor[path[segmentIndex]] = next; | |
| cursor = next; | |
| const root: Record<string, unknown> = Object.create(null); | |
| let cursor = root; | |
| for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) { | |
| const next: Record<string, unknown> = Object.create(null); | |
| cursor[path[segmentIndex]] = next; | |
| cursor = next; | |
| } |
| setStickyPropOverride(fiber, propPath, value); | ||
| try { | ||
| overrideProps(fiber, buildNestedPropPartial(propPath, value)); | ||
| } catch { | ||
| // overrideProps reaches into renderer internals; a failure must not | ||
| // tear down the panel. |
There was a problem hiding this comment.
P2: Sticky prop overrides are recorded even when the initial overrideProps call fails, which can leave stale retries and unexpected later prop mutations.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/utils/preview-props.ts, line 23:
<comment>Sticky prop overrides are recorded even when the initial overrideProps call fails, which can leave stale retries and unexpected later prop mutations.</comment>
<file context>
@@ -1,46 +1,28 @@
if (!originalByKey.has(key)) {
originalByKey.set(key, { path: propPath, value: readPropAtPath(fiber, propPath) });
}
+ setStickyPropOverride(fiber, propPath, value);
try {
- overrideProps(fiber, buildNestedPartial(propPath, value));
</file context>
| setStickyPropOverride(fiber, propPath, value); | |
| try { | |
| overrideProps(fiber, buildNestedPropPartial(propPath, value)); | |
| } catch { | |
| // overrideProps reaches into renderer internals; a failure must not | |
| // tear down the panel. | |
| try { | |
| overrideProps(fiber, buildNestedPropPartial(propPath, value)); | |
| setStickyPropOverride(fiber, propPath, value); | |
| } catch { | |
| // overrideProps reaches into renderer internals; a failure must not | |
| // tear down the panel. | |
| } |


What
Extends the style panel so you can adjust a component's React props directly in the browser — not just CSS. This targets the cases from the request: tuning
motionanimation values (animate.opacity,transition.duration, …) and three.js wrapper props (count,speed, …) live, with feedback, without leaving the page.When you open the style panel (right-click → Style) on an element backed by a component, the panel now leads with that component's editable numeric props, followed by the usual CSS rows.
How it works
build-prop-properties.tswalks up from the selected host element to the nearest composite fiber that exposes editable numeric props (bounded walk so unrelated ancestors aren't surfaced), using bippy'straverseProps. It collects top-level numbers plus the direct numeric members of motion-style object props (animate,initial,exit,transition,whileHover,whileTap, …).styleis intentionally excluded so it doesn't duplicate the computed-style CSS rows.prop-numeric-bounds.tsmaps common motion/three.js prop names to sensible ranges/steps (opacity0–1by0.05, duration0–10sby0.1, rotate-360–360by1, count by1, …) and falls back to a value-derived range for unknown keys.preview-props.tspushes overridden values onto the backing fiber with bippy'soverrideProps, so the component re-renders immediately (a motion target re-animates, a count re-spawns). Originals are captured per touched path so discarding rolls every change back.Props:list (animate.opacity: 1 → 0.95) so the agent can apply them at the call site or in the component.NumericEditablePropertygained a per-propertystep; CSS rows keep integer stepping (step: 1) while prop rows tune fractional values precisely.Notes
overrideProps, which needs the React renderer registered in bippy's DevTools hook. That requires react-grab to install its hook before React injects its renderer — i.e. load react-grab early (the recommendedbeforeInteractivescript-tag setup). The e2e app'smain.tsximport order was adjusted to mirror this so overrides reach the live renderer.Testing
e2e/edit-panel-props.spec.tscovers prop derivation, prop rows leading the panel, liveoverridePropsupdates, integer-vs-fractional stepping, discard restoring originals, and the copied prompt. Added aMotionishBoxfixture to the e2e app.lint,typecheck, andformatall clean.Summary by cubic
Edit React props directly in the style panel for component-backed elements (motion/three.js), with live preview, per‑prop steps, motion variants support, and persistence across re‑renders. Unifies CSS and prop previews into a single
EditPreviewfor reliable rollback; requires loadingreact-grabbefore React sobippycan hook into the renderer.New Features
bippyand surfaces numeric props with a bounded search (up to 10 composites); includes top‑level numbers plus nested members ofanimate,transition,variants, etc. (excludesstyle).bippyoverrideProps; submit copies a "Props:" block, and CSS uses integer steps while props use per‑property fractional steps.Bug Fixes
variantsmaps hit the cap.Written for commit 8d24b9e. Summary will update on new commits.