Skip to content

Edit React props (motion / three.js values) in the style panel#460

Open
aidenybai wants to merge 9 commits into
mainfrom
cursor/editable-react-props-3738
Open

Edit React props (motion / three.js values) in the style panel#460
aidenybai wants to merge 9 commits into
mainfrom
cursor/editable-react-props-3738

Conversation

@aidenybai

@aidenybai aidenybai commented Jun 7, 2026

Copy link
Copy Markdown
Owner

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 motion animation 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

  • Derivationbuild-prop-properties.ts walks 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's traverseProps. It collects top-level numbers plus the direct numeric members of motion-style object props (animate, initial, exit, transition, whileHover, whileTap, …). style is intentionally excluded so it doesn't duplicate the computed-style CSS rows.
  • Good ranges out of the boxprop-numeric-bounds.ts maps common motion/three.js prop names to sensible ranges/steps (opacity 0–1 by 0.05, duration 0–10s by 0.1, rotate -360–360 by 1, count by 1, …) and falls back to a value-derived range for unknown keys.
  • Live previewpreview-props.ts pushes overridden values onto the backing fiber with bippy's overrideProps, 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.
  • Prompt output — submitting copies the prop changes alongside any CSS changes. Prop edits render as a Props: list (animate.opacity: 1 → 0.95) so the agent can apply them at the call site or in the component.
  • Fractional steppingNumericEditableProperty gained a per-property step; CSS rows keep integer stepping (step: 1) while prop rows tune fractional values precisely.

Notes

  • This relies on bippy's 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 recommended beforeInteractive script-tag setup). The e2e app's main.tsx import order was adjusted to mirror this so overrides reach the live renderer.

Testing

  • New e2e/edit-panel-props.spec.ts covers prop derivation, prop rows leading the panel, live overrideProps updates, integer-vs-fractional stepping, discard restoring originals, and the copied prompt. Added a MotionishBox fixture to the e2e app.
  • Full suite green: 648 Playwright e2e + 211 CLI vitest tests. lint, typecheck, and format all clean.
Open in Web Open in Cursor 

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 EditPreview for reliable rollback; requires loading react-grab before React so bippy can hook into the renderer.

  • New Features

    • Finds the nearest component fiber via bippy and surfaces numeric props with a bounded search (up to 10 composites); includes top‑level numbers plus nested members of animate, transition, variants, etc. (excludes style).
    • Applies sensible ranges/steps by prop name (e.g., opacity 0–1 by 0.05); unknowns fall back to value‑derived bounds.
    • Live‑previews via bippy overrideProps; submit copies a "Props:" block, and CSS uses integer steps while props use per‑property fractional steps.
  • Bug Fixes

    • Prop edits persist across parent re‑renders via a sticky override registry that re‑applies overrides after each commit; discard clears the sticky entry and restores originals.
    • Always keep top‑level numeric props even when large variants maps hit the cap.
    • Restore props before inline styles to prevent CSS baseline clobber on mixed edits.
    • Use JSON‑stringified prop paths to prevent key collisions; improve bounds and rounding by allowing negative scale (−3..3) and nudging step snapping to round exact half‑steps correctly.

Written for commit 8d24b9e. Summary will update on new commits.

Review in cubic

cursoragent and others added 4 commits June 7, 2026 03:05
…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>
@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment Jun 7, 2026 4:37am
react-grab-website Ready Ready Preview, Comment Jun 7, 2026 4:37am

@pkg-pr-new

pkg-pr-new Bot commented Jun 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-grab/cli@460
npm i https://pkg.pr.new/grab@460
npm i https://pkg.pr.new/react-grab@460

commit: 8d24b9e

@aidenybai aidenybai marked this pull request as ready for review June 7, 2026 03:23
Comment thread packages/react-grab/src/core/edit-mode.ts
…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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/utils/collect-fiber-numeric-props.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/utils/prop-numeric-bounds.ts Outdated
Comment thread packages/react-grab/src/utils/format-css-value.ts Outdated
Comment thread packages/react-grab/src/core/edit-mode.ts
Comment thread packages/react-grab/src/utils/find-props-fiber.ts
Comment thread packages/react-grab/src/utils/preview-props.ts Outdated
Comment thread packages/react-grab/src/utils/collect-fiber-numeric-props.ts
- 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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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));
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 538f86d. Configure here.

Comment thread packages/react-grab/src/utils/format-css-value.ts Outdated
Comment thread packages/react-grab/src/utils/prop-numeric-bounds.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/utils/preview-props.ts Outdated
if (isEditableNumber(nextValue)) {
collected.push({ path: [propName], value: nextValue });
} else if (
collected.length < PROP_NUMERIC_MAX_COUNT &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +18 to +23
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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;
}

Comment on lines +23 to +28
setStickyPropOverride(fiber, propPath, value);
try {
overrideProps(fiber, buildNestedPropPartial(propPath, value));
} catch {
// overrideProps reaches into renderer internals; a failure must not
// tear down the panel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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.
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants