Skip to content

Add multi-element support to react-grab package review - #4

Open
smb060606 wants to merge 5 commits into
mainfrom
coderabbit-test
Open

Add multi-element support to react-grab package review#4
smb060606 wants to merge 5 commits into
mainfrom
coderabbit-test

Conversation

@smb060606

@smb060606 smb060606 commented Dec 23, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for selecting and tracking multiple elements simultaneously within sessions.
    • Display element count indicator when multiple elements are selected.
    • New getElements() API to retrieve all selected elements for a session.
  • Refactor

    • Updated session callbacks to handle multiple selected elements.
    • Enhanced visual selection rendering to display multiple selection boxes.

✏️ Tip: You can customize this high-level summary in your review settings.

- Enhanced element validation logic in agent.ts to ensure valid HTML tag names are checked before proceeding.
- Updated session handling in core.tsx to support multiple elements, improving the follow-up session submission process.
- Adjusted session handling logic to ensure proper setting of session elements and status updates, enhancing overall session management.
@greptile-apps

greptile-apps Bot commented Dec 23, 2025

Copy link
Copy Markdown

Skipped: This PR was opened by one of your excluded authors: (smb060606)

@coderabbitai

coderabbitai Bot commented Dec 23, 2025

Copy link
Copy Markdown

Walkthrough

This pull request adds multi-element support to the react-grab package and related provider packages. Callback signatures across multiple files are updated from accepting single Element | undefined to Element[]. The state machine, session management, and UI rendering are refactored to track and display multiple frozen or selected elements. Additionally, numerous files receive trailing newline formatting fixes.

Changes

Cohort / File(s) Summary
Callback Signature Updates
packages/provider-visual-edit/src/client/index.ts, packages/react-grab/src/types.ts, packages/react-grab/src/core.tsx
Updated onStart, onComplete, onAbort, onUndo, and onDismiss callbacks to accept Element[] instead of Element | undefined. Implementation extracts first element where single-element logic is required.
Multi-Element Session & State Management
packages/react-grab/src/agent.ts, packages/react-grab/src/state/machine.ts
Added frozenElements: Element[] context to machine; introduced FREEZE_ELEMENTS event; new getElements() method in SessionOperations; session storage now tracks element arrays with accessor methods for single and multiple elements.
Multi-Element UI Rendering
packages/react-grab/src/components/renderer.tsx, packages/react-grab/src/components/selection-label/index.tsx
Introduced selectionBoundsMultiple and selectionElementsCount props; renderer now iterates over multiple bounds to render multiple SelectionBox components; label displays "N elements" when count exceeds 1.
Core Multi-Element Flow Refactoring
packages/react-grab/src/core.tsx
Updated element handling throughout lifecycle: restoreInputFromSession, clipboard operations, drag/drop handlers, and freeze messaging now operate on element arrays; rendering props extended to reflect multiple selections.
Type Definitions & Public API
packages/react-grab/src/types.ts
Updated AgentOptions callback signatures; added selectionBoundsMultiple and selectionElementsCount to ReactGrabRendererProps; added elementsCount to SelectionLabelProps.
Formatting Cleanup (Trailing Newlines)
packages/cli/src/utils/logger.ts, packages/provider-amp/CHANGELOG.md, packages/provider-amp/README.md, packages/provider-amp/tsconfig.json, packages/provider-visual-edit/src/client/code-validation.ts, packages/react-grab/src/components/icon-retry.tsx, packages/react-grab/src/core/copy.ts, packages/react-grab/src/core/events.ts, packages/react-grab/src/core/noop-api.ts, packages/react-grab/src/hooks/use-animated-position.ts, packages/react-grab/src/utils/get-tag-name.ts, packages/react-grab/src/utils/is-selection-backward.ts, packages/react-grab/src/utils/is-target-key-combination.ts, packages/react-grab/src/utils/key-matches-code.ts, packages/utils/package.json, packages/utils/tsconfig.json, packages/utils/tsup.config.ts, packages/website/app/blog/visual-edit/layout.tsx, packages/website/app/blog/visual-edit/page.tsx
Minor formatting: added trailing newlines with no functional impact.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Agent
    participant Session as Session Manager
    participant StateMachine as State Machine
    participant Renderer
    
    User->>Agent: Select multiple elements
    activate Agent
    Agent->>Session: startSession(elements: Element[])
    activate Session
    Session->>StateMachine: Send FREEZE_ELEMENTS event
    activate StateMachine
    StateMachine->>StateMachine: Update context.frozenElements
    StateMachine->>StateMachine: Set frozenElement = first element
    StateMachine-->>Session: State updated
    deactivate StateMachine
    Session->>Renderer: Pass selectionBoundsMultiple[]
    activate Renderer
    Renderer->>Renderer: Iterate bounds list
    Renderer->>Renderer: Render SelectionBox for each bound
    Renderer->>Renderer: Display "N elements" in SelectionLabel
    Renderer-->>User: Show multiple selection boxes
    deactivate Renderer
    deactivate Session
    
    User->>Agent: Complete/Abort action
    activate Agent
    Agent->>Session: Trigger onComplete(session, elements[])
    Session->>Session: Pass full elements array to callback
    Session-->>Agent: Callback executed
    deactivate Agent
    deactivate Agent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Multiple elements now dance in a row,
No longer just one, but an array's flow,
Selection boxes bloom on the screen,
The finest multi-pick you've ever seen!
With trailing newlines neat and clean,
This refactor's elegant and lean. 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '@coderabbitai review' is vague and generic, using a non-descriptive term that does not convey meaningful information about the actual changeset. Replace with a descriptive title that captures the main change, such as 'Support multiple selected elements in React Grab agent' to reflect the substantive refactoring across the codebase.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch coderabbit-test

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot changed the title @coderabbitai review Add multi-element support to react-grab package review Dec 23, 2025

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/provider-visual-edit/src/client/index.ts (1)

447-463: Same multi-element concern applies here.

Similar to onStart, this function accepts elements: Element[] but only processes elements[0]. The error handling on lines 460-463 appropriately handles the case where no element is found, but the same questions about multi-element support intent apply.

This has the same pattern and concerns as the onStart function reviewed above. Please address both consistently when clarifying the multi-element support strategy.

🧹 Nitpick comments (3)
packages/provider-visual-edit/src/client/index.ts (1)

136-140: Clarify the multi-element support intent and consider documenting the single-element behavior.

The function signature now accepts elements: Element[], but the implementation only uses elements[0]. While the subsequent null check on line 140 handles the case where the array is empty, this pattern may confuse API consumers who might expect all elements to be processed.

If this is intentional preparation for future multi-element support, consider adding a JSDoc comment explaining that only the first element is currently processed. If multiple elements should be handled now, the implementation needs to be updated accordingly.

Can you clarify whether:

  1. This is a partial implementation with full multi-element support planned for later?
  2. The API is being future-proofed while maintaining single-element behavior?
  3. All elements in the array should be processed in this PR?

If single-element behavior is intentional, consider adding documentation:

📝 Suggested documentation
+  /**
+   * Called when a visual edit session starts.
+   * Note: Currently only processes the first element from the array.
+   * @param session - The agent session
+   * @param elements - Array of target elements (only first element is used)
+   */
  const onStart = (session: AgentSession, elements: Element[]) => {

Optionally, make the single-element extraction more explicit:

-  const element = elements[0];
+  const element = elements[0]; // Currently only process first element
packages/react-grab/src/components/renderer.tsx (1)

28-39: Consider using index-based keying if bounds can be reordered.

Solid's <For> uses referential identity for tracking. If selectionBoundsList items can be reordered or replaced with different objects representing the same element, the SelectionBox animations may behave unexpectedly. If bounds objects are stable or order doesn't change, this is fine.

If needed, you could use <Index> instead for index-based tracking, or add a stable identifier to each bounds object.

packages/react-grab/src/core.tsx (1)

520-524: Redundant frozen element check in effectiveElement memo.

The logic checks isToggleFrozen() first (which returns early with frozenElement), then immediately checks frozenElement again. The second check (lines 520-523) appears redundant if isToggleFrozen() already covers the frozen state.

#!/bin/bash
# Check the state machine to understand when isToggleFrozen vs frozenElement differ
rg -n "frozenElement|isToggleFrozen|frozen" packages/react-grab/src/state/machine.ts -C3
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 939b490 and 0128e02.

📒 Files selected for processing (26)
  • packages/cli/src/utils/logger.ts
  • packages/provider-amp/CHANGELOG.md
  • packages/provider-amp/README.md
  • packages/provider-amp/tsconfig.json
  • packages/provider-visual-edit/src/client/code-validation.ts
  • packages/provider-visual-edit/src/client/index.ts
  • packages/react-grab/src/agent.ts
  • packages/react-grab/src/components/icon-retry.tsx
  • packages/react-grab/src/components/renderer.tsx
  • packages/react-grab/src/components/selection-label/index.tsx
  • packages/react-grab/src/core.tsx
  • packages/react-grab/src/core/copy.ts
  • packages/react-grab/src/core/events.ts
  • packages/react-grab/src/core/noop-api.ts
  • packages/react-grab/src/hooks/use-animated-position.ts
  • packages/react-grab/src/state/machine.ts
  • packages/react-grab/src/types.ts
  • packages/react-grab/src/utils/get-tag-name.ts
  • packages/react-grab/src/utils/is-selection-backward.ts
  • packages/react-grab/src/utils/is-target-key-combination.ts
  • packages/react-grab/src/utils/key-matches-code.ts
  • packages/utils/package.json
  • packages/utils/tsconfig.json
  • packages/utils/tsup.config.ts
  • packages/website/app/blog/visual-edit/layout.tsx
  • packages/website/app/blog/visual-edit/page.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
packages/react-grab/src/components/renderer.tsx (1)
packages/react-grab/src/components/selection-box.tsx (1)
  • SelectionBox (21-156)
packages/react-grab/src/types.ts (7)
packages/react-grab/src/core.tsx (3)
  • AgentSession (2518-2518)
  • AgentCompleteResult (2521-2521)
  • OverlayBounds (2514-2514)
packages/react-grab/src/index.ts (3)
  • AgentSession (24-24)
  • AgentCompleteResult (28-28)
  • OverlayBounds (14-14)
packages/provider-visual-edit/src/client/index.ts (1)
  • AgentCompleteResult (14-14)
packages/provider-ami/src/client.ts (1)
  • AgentCompleteResult (28-28)
packages/provider-claude-code/src/client.ts (1)
  • AgentCompleteResult (16-16)
packages/provider-cursor/src/client.ts (1)
  • AgentCompleteResult (16-16)
packages/provider-opencode/src/client.ts (1)
  • AgentCompleteResult (16-16)
packages/provider-visual-edit/src/client/index.ts (1)
packages/react-grab/src/types.ts (1)
  • AgentSession (121-133)
packages/react-grab/src/agent.ts (5)
packages/react-grab/src/core.tsx (1)
  • generateSnippet (2526-2526)
packages/react-grab/src/index.ts (1)
  • generateSnippet (8-8)
packages/react-grab/src/utils/generate-snippet.ts (1)
  • generateSnippet (7-24)
packages/react-grab/src/context.ts (1)
  • getNearestComponentName (70-84)
packages/react-grab/src/utils/agent-session.ts (1)
  • saveSessionById (56-63)
🔇 Additional comments (40)
packages/react-grab/src/components/icon-retry.tsx (1)

1-27: Formatting fix approved.

The trailing newline addition is a minor cleanup that aligns with standard file formatting conventions. The IconRetry component implementation itself is solid—proper TypeScript typing, clean Solid.js syntax, and efficient SVG rendering.

packages/react-grab/src/utils/is-target-key-combination.ts (1)

74-74: Formatting improvement applied.

Adding a trailing newline improves file consistency and is a standard practice. The function logic remains unchanged and correct.

packages/react-grab/src/utils/is-selection-backward.ts (1)

1-10: LGTM!

The selection backward detection logic is sound. The function correctly uses DOM compareDocumentPosition() to determine node ordering, with appropriate null checks and a fallback to offset comparison when nodes are identical. The trailing newline at line 10 is a formatting adjustment with no functional impact.

packages/website/app/blog/visual-edit/page.tsx (1)

504-505: Trailing newline formatting is a no-op change.

packages/website/app/blog/visual-edit/layout.tsx (1)

51-52: Trailing newline formatting is a no-op change.

packages/react-grab/src/utils/get-tag-name.ts (1)

1-3: No action required. The function signature is correct as-is.

The utility is designed to operate on individual Element instances, with callers handling array iteration where needed (as shown by .map((element) => getTagName(element)) patterns in the codebase). This is a proper separation of concerns and aligns with the PR's architecture.

Likely an incorrect or invalid review comment.

packages/cli/src/utils/logger.ts (1)

19-21: LGTM!

The break() method provides a useful utility for adding visual separation in logs, following the same pattern as existing logger methods.

packages/provider-visual-edit/src/client/index.ts (1)

520-522: LGTM! Signature updated for API consistency.

The signature update to accept _session and _elements parameters maintains consistency with the other callback signatures while correctly indicating these parameters are unused (via underscore prefix). The comment appropriately explains that the actual undo logic is handled elsewhere.

packages/react-grab/src/utils/key-matches-code.ts (1)

1-11: LGTM!

Formatting-only change adding trailing newline. The function logic is correct.

packages/react-grab/src/core/events.ts (1)

16-48: LGTM!

Clean abstraction for event listener management with proper AbortController-based cleanup. The trailing newline addition is a formatting-only change.

packages/react-grab/src/hooks/use-animated-position.ts (1)

11-68: LGTM!

Well-implemented animation hook with proper cleanup. The trailing newline is a formatting-only change.

packages/react-grab/src/core/noop-api.ts (1)

3-29: LGTM!

Formatting-only change adding trailing newline.

packages/react-grab/src/components/renderer.tsx (2)

16-24: LGTM!

The selectionBoundsList memo correctly handles the fallback logic: prioritizing selectionBoundsMultiple when available and non-empty, otherwise wrapping single selectionBounds or returning an empty array.


131-136: LGTM!

Correctly passes elementsCount to SelectionLabel to support the multi-element display.

packages/react-grab/src/core/copy.ts (1)

23-92: LGTM!

Formatting-only change adding trailing newline. The multi-element copy logic with fallbacks is well-structured.

packages/react-grab/src/components/selection-label/index.tsx (2)

138-148: LGTM!

Correctly added props.elementsCount to the measurement effect dependencies to ensure the label re-measures when the element count changes.


245-253: LGTM!

The tagDisplay logic correctly handles multi-element selection by showing "N elements" when elementsCount > 1, while preserving existing behavior for single-element cases.

packages/react-grab/src/state/machine.ts (6)

38-39: LGTM!

Adding frozenElements: Element[] to the context correctly extends the state to support multi-element freezing.


86-89: LGTM!

Correctly initialized frozenElements as an empty array in the initial context.


156-157: LGTM!

The new FREEZE_ELEMENTS event type is well-defined with an elements: Element[] payload.


286-303: LGTM!

The actions maintain consistency between frozenElement and frozenElements:

  • setFrozenElement wraps the single element in an array for frozenElements
  • setFrozenElements extracts the first element for frozenElement (backward compatibility)
  • clearFrozenElement clears both fields

This ensures code relying on either the single or multi-element representation works correctly.


555-562: LGTM!

resetActivationState correctly clears frozenElements alongside the other activation state.


744-746: LGTM!

The FREEZE_ELEMENTS event is correctly wired to the setFrozenElements action at the activation state level.

packages/react-grab/src/types.ts (3)

173-183: LGTM! Clean type signature updates for multi-element support.

The callback signatures are consistently updated from Element | undefined to Element[]. Using arrays is a better design choice as it handles both single and multiple element cases uniformly, and avoids undefined checks in favor of empty array checks.


288-289: LGTM! Additive props for multiple selection rendering.

New optional props selectionBoundsMultiple and selectionElementsCount extend the renderer capabilities without breaking existing consumers.


424-424: LGTM! Consistent addition of elementsCount prop.

Aligns with the multi-element model and follows the same optional pattern as other props.

packages/react-grab/src/agent.ts (7)

23-24: LGTM! Clear parameter type update.

StartSessionParams now properly accepts elements: Element[] for multi-element support.


37-38: LGTM! Public API expanded with getElements accessor.

Good addition maintaining backward compatibility with getElement returning the first element while exposing the full array via getElements.


200-201: Good defensive check for multi-element tagName format.

The isValidHtmlTagName check correctly identifies when the tagName contains a space (e.g., "3 elements"), preventing incorrect tag comparison during element reacquisition. This ensures reacquisition only occurs for single-element sessions with valid HTML tag names.


317-324: Well-designed tagName/componentName handling for multi-element sessions.

  • For multiple elements: displays "n elements" as tagName, omits componentName
  • For single element: uses actual tagName and attempts to resolve componentName

This provides clear user feedback while maintaining context accuracy.


465-466: Bounds update only considers first element for multi-element sessions.

For sessions with multiple elements, only the first element's bounds are tracked/updated on viewport changes. This is consistent with the existing single-element behavior and reasonable for positioning the session UI, but may cause visual drift if the first element moves differently than others.


496-503: LGTM! Clean accessor pattern for backward compatibility.

getSessionElement returns the first element (backward compatible), while getSessionElements returns the full array. The fallback to empty array (?? []) is consistent with the type signature.

Also applies to: 517-517


373-376: Consider whether dismissSession should proceed when elements array is empty.

The condition elements.length > 0 gates calling onDismiss, but the session cleanup (lines 377-384) still proceeds regardless. This is intentional—the session should be cleaned up even if no elements are found. Note that onUndo behaves differently, calling its callback without checking elements (line 392), suggesting that onDismiss is specifically intended to fire only when elements exist.

packages/react-grab/src/core.tsx (7)

547-556: LGTM! Clean memos for multi-element bounds tracking.

  • frozenElementsBounds correctly maps all frozen elements to their bounds
  • frozenElementsCount provides efficient count access
  • Both react to viewport changes via viewportVersion dependency

952-972: LGTM! Restore logic properly adapted for Element[].

The function accepts the full array but correctly uses the first element for positioning and UI restoration. The FREEZE_ELEMENTS message propagates the complete array to the state machine.


997-1007: LGTM! Robust elements array construction.

Properly handles three cases:

  1. Multiple frozen elements → uses frozenElements array
  2. Single frozen/target element → wraps in array
  3. No element → empty array (triggers early return on line 1001-1004)

1208-1211: Good fix for race condition with clear documentation.

The HACK comment explains why dragRect must be computed before DRAG_END—the send resets dragStart in state. Pre-computing avoids stale/incorrect bounds.


1247-1247: Correctly uses FREEZE_ELEMENTS for drag selection.

After drag-selecting multiple elements, they're all frozen together, enabling multi-element agent prompts.


2291-2292: LGTM! Renderer props and follow-up session properly wired.

  • selectionBoundsMultiple and selectionElementsCount passed to renderer
  • Follow-up session creation uses getElements() and validates elements.length > 0
  • Session start correctly receives the elements array

Also applies to: 2330-2332, 2340-2340


2462-2469: LGTM! setAgent API updated consistently.

The onAbort and onUndo handlers correctly receive Element[] and pass them through to both the user callback and restoreInputFromSession.

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