Skip to content

Conversation

@softmarshmallow
Copy link
Member

@softmarshmallow softmarshmallow commented Dec 24, 2025

  • fix: scene list order
  • ux: new scene background color from current
  • ux: better selection handling with shift key
  • fix: prevent paste being sequentially nested
  • fix: prevent insert loop being sequentially nested

Summary by CodeRabbit

  • New Features

    • Added stroke align/join/miter controls, mixed gap and mixed padding support, and improved paste flows (including accessible paste and targeted pastes).
    • Scene creation now preserves UX defaults and inserted nodes return/restore selection.
  • Bug Fixes

    • Clarified selection timing (pointerdown vs click vs drag) to reduce accidental drags and toggles.
    • Fixed floating title bar stacking so it sits above overlays.
  • Documentation

    • New UX assertions and detailed selection timing guidance.

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

@vercel
Copy link

vercel bot commented Dec 24, 2025

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

Project Deployment Review Updated (UTC)
docs Ready Ready Preview, Comment Dec 27, 2025 6:04am
grida Ready Ready Preview, Comment Dec 27, 2025 6:04am
5 Skipped Deployments
Project Deployment Review Updated (UTC)
code Ignored Ignored Dec 27, 2025 6:04am
legacy Ignored Ignored Dec 27, 2025 6:04am
backgrounds Skipped Skipped Dec 27, 2025 6:04am
blog Skipped Skipped Dec 27, 2025 6:04am
viewer Skipped Skipped Dec 27, 2025 6:04am

@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Refactors insertion/paste to use explicit targets and surface APIs, replaces selection mutations with decision-based selection UX, moves paint-resolution into editor namespace, extends schema for corner radii and stroke controls, and adds related UI, utils, and tests.

Changes

Cohort / File(s) Summary
Geometry & Painter
crates/grida-canvas/src/painter/geometry.rs
Comment-only clarification: Group nodes unsupported inside boolean operations; no behavior change.
Docs & Assertions
docs/wg/feat-editor/ux-surface/selection.md, editor/grida-canvas/ASSERTIONS.md
Expanded selection timing rules (immediate vs deferred) and added UX assertions (floating bar z-index, clipboard priority).
Surface API & Editor Core
editor/grida-canvas/editor.i.ts, editor/grida-canvas/editor.ts, editor/grida-canvas/action.ts
Introduced explicit-target signatures for insert, paste, order, ungroup; added surfaceCreateScene() and a11yPaste(); added/changed several action types and __deferred_selection state.
Surface / Insert/ Paste Utilities
editor/grida-canvas/utils/insertion.ts, editor/grida-canvas/utils/insertion-targeting.ts, editor/grida-canvas/utils/paint-resolution.ts
Removed hitTestNestedInsertionTarget and deleted paint-resolution.ts; added resolveInsertTargetParent() and resolvePasteTargetParents() for target resolution.
Reducers & Event Handling
editor/grida-canvas/reducers/document.reducer.ts, editor/grida-canvas/reducers/event-target.reducer.ts, editor/grida-canvas/reducers/event-target.cem-*.reducer.ts
Added vector/gradient/paste vector-network handling; replaced hit-test fallbacks with explicit target handling; integrated decision-based selection flow (pointerdown/click/drag-start) and insertion-target helpers.
Selection / Methods
editor/grida-canvas/reducers/methods/select.ts, .../selection.ts, .../index.ts, .../duplicate.ts, .../tool.ts, .../wrap.ts
New select.ts APIs (self_selectNode, self_clearSelection, vector selection helpers); replaced prior selection reducer with decision functions (decidePointerDownSelection, decideClickSelection, decideDragStartAction); adjusted ungroup/wrap signatures and prototype fields (corner_radius).
UI / Surface Components & Config
editor/grida-canvas-react/ui-config.ts, editor/grida-canvas-react/viewport/ui/floating-bar.tsx, editor/grida-canvas-react/viewport/surface.tsx, editor/grida-canvas-react/viewport/ui/*-editor.tsx
Added FLOATING_BAR_Z_INDEX and applied it; root frames filter honors active !== false; paint resolution calls routed through editor.resolvePaints.
React Starter & Playground
editor/grida-canvas-hosted/playground/uxhost-menu.tsx, editor/grida-canvas-react-starter-kit/*
Switched to surface-scoped APIs (surface.insert, surface.surfaceCreateScene); reset tool to cursor after insertion; adjusted scene creation calls.
Context Menu / Hotkeys / Data Transfer
editor/grida-canvas-react/use-context-menu-actions.ts, viewport/hotkeys.tsx, use-data-transfer.ts
Replaced editor.commands.* with editor.surface.* equivalents; batching insertions via surface.insert; uses surface.a11yPaste() for paste flows.
Mixed Properties & Sidecontrol UI
editor/grida-canvas-react/use-mixed-properties.ts, editor/scaffolds/sidecontrol/controls/*, sidecontrol-node-selection.tsx
Added stroke_align/join/miter and gap/padding setters; mixed-value support for gap/padding; new UI controls and wiring for stroke/gap/padding.
Schema & Node Factory
packages/grida-canvas-schema/grida.ts
Added ICornerRadius to BooleanPathOperationNode, introduced DEFAULT_RECTANGULAR_CORNER_RADIUS, and explicit container instantiation applying defaults.
IO / Clipboard
packages/grida-canvas-io/index.ts, packages/grida-canvas-io/__tests__/clipboard.test.ts
Added uint8ArrayToBase64() (chunked encoding) and updated clipboard encoding; added large-payload encode/decode test.
Tests & Tests Updates
editor/grida-canvas/reducers/__tests__/history.test.ts, editor/grida-canvas/reducers/methods/__tests__/selection.test.ts, editor/grida-canvas/utils/__tests__/insertion.test.ts
Updated deletion tests to use explicit NodeID[] targets; added comprehensive selection decision tests; removed tests referencing hitTestNestedInsertionTarget.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Surface as EditorSurface
    participant Store as EditorDocumentStore
    participant Reducer as document.reducer
    participant State as editor.state

    User->>Surface: surface.insert(payload)
    Surface->>Surface: resolveInsertTargetParent(selection)
    Surface->>Store: insert(payload, target)
    activate Store
    Store->>Reducer: dispatch insert action
    activate Reducer
    Reducer->>Reducer: resolve parent from target, compute position
    Reducer->>State: add nodes, update selection
    deactivate Reducer
    Store-->>Surface: return newIds
    deactivate Store
    Surface-->>User: [newNodeIds]
Loading
sequenceDiagram
    participant User
    participant Event as EventTargetReducer
    participant Decide as SelectionDecision
    participant State as editor.state

    User->>Event: onPointerDown(event)
    Event->>Decide: decidePointerDownSelection(context)
    Decide-->>Event: SelectionOperation (immediate | deferred | none)
    alt immediate
        Event->>State: apply selection, clear deferred
    else deferred
        Event->>State: store __deferred_selection
    else none
        Event->>State: no selection change
    end

    User->>Event: onClick(event)
    Event->>Decide: decideClickSelection(context)
    Decide-->>Event: SelectionOperation
    alt immediate
        Event->>State: apply selection, clear deferred
    else none
        Event->>State: clear deferred
    end
Loading
sequenceDiagram
    participant User
    participant DataTransfer as use-data-transfer
    participant Surface as EditorSurface
    participant Store as EditorDocumentStore

    User->>DataTransfer: paste(items)
    DataTransfer->>DataTransfer: tryInsertFromGridaClipboard / decode
    DataTransfer->>Surface: surface.a11yPaste()
    Surface->>Surface: resolvePasteTargetParents(selection, copiedIds)
    Surface->>Store: paste(targets)
    activate Store
    Store->>Store: insert per target, return insertedIds
    Store-->>Surface: insertedIds
    deactivate Store
    Surface->>Surface: updateSelection(insertedIds)
    Surface-->>DataTransfer: paste result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

migration, breaking

Poem

🐰 A hop through selection's deferred dance,
paints gathered, grouped, then given chance,
inserts land where targets say,
clipboards whisper bytes away,
corners rounded, tools reset—hip, hop hooray! 🎨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Daily RC' is vague and does not convey meaningful information about the changeset. It lacks specificity about the primary changes. Consider using a more descriptive title that highlights the main change, such as 'Refactor selection handling and insertion APIs' or similar, to clearly communicate the primary intent to reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 053065e and 94b078e.

📒 Files selected for processing (2)
  • editor/grida-canvas/utils/insertion-targeting.ts
  • editor/scaffolds/sidecontrol/controls/gap.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vercel vercel bot temporarily deployed to Preview – viewer December 24, 2025 13:38 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds December 24, 2025 13:38 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds December 27, 2025 05:34 Inactive
@vercel vercel bot temporarily deployed to Preview – viewer December 27, 2025 05:34 Inactive
@vercel vercel bot temporarily deployed to Preview – blog December 27, 2025 05:34 Inactive
@softmarshmallow softmarshmallow changed the title RC Daily RC Dec 27, 2025
@softmarshmallow softmarshmallow marked this pull request as ready for review December 27, 2025 05:34
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx (1)

729-805: Duplicate Layout section found.

This entire block (lines 729-805) is a duplicate of the Layout section at lines 412-528. Both sections render when config.layout !== "off", resulting in two Layout sections appearing in the UI. The second section is also missing the newly added Gap and Padding controls.

This appears to be leftover code that should be removed.

🔎 Suggested fix: Remove duplicate section
-      <SidebarSection
-        hidden={config.layout === "off"}
-        className="border-b pb-4"
-      >
-        <SidebarSectionHeaderItem>
-          <SidebarSectionHeaderLabel>Layout</SidebarSectionHeaderLabel>
-        </SidebarSectionHeaderItem>
-        <SidebarMenuSectionContent className="space-y-2">
-          <PropertyLine hidden={config.size === "off"}>
-            <PropertyLineLabel>Width</PropertyLineLabel>
-            <LengthPercentageControl
-              value={width?.value}
-              onValueCommit={change.width}
-            />
-          </PropertyLine>
-          <PropertyLine hidden={config.size === "off"}>
-            <PropertyLineLabel>Height</PropertyLineLabel>
-            <LengthPercentageControl
-              value={height?.value}
-              onValueCommit={change.height}
-            />
-          </PropertyLine>
-          {types.has("container") && (
-            <PropertyLine>
-              <PropertyLineLabel>Flow</PropertyLineLabel>
-              <LayoutControl
-                value={
-                  layout?.value === grida.mixed ||
-                  direction?.value === grida.mixed ||
-                  layout?.value === undefined ||
-                  (layout?.value === "flex" && direction?.value === undefined)
-                    ? undefined
-                    : {
-                        layoutMode: layout?.value ?? "flow",
-                        direction:
-                          layout?.value === "flex"
-                            ? direction?.value
-                            : undefined,
-                      }
-                }
-                onValueChange={(value) => {
-                  change.layout(value.layoutMode);
-                  if (value.direction) {
-                    change.direction(value.direction);
-                  }
-                }}
-              />
-            </PropertyLine>
-          )}
-          <PropertyLine hidden={!has_flex_container}>
-            <PropertyLineLabel>Alignment</PropertyLineLabel>
-            <FlexAlignControl
-              className="w-full"
-              direction={
-                direction?.value === grida.mixed
-                  ? "horizontal"
-                  : (direction?.value ?? "horizontal")
-              }
-              value={
-                main_axis_alignment?.value === grida.mixed ||
-                cross_axis_alignment?.value === grida.mixed ||
-                main_axis_alignment?.value === undefined ||
-                cross_axis_alignment?.value === undefined
-                  ? undefined
-                  : {
-                      mainAxisAlignment: main_axis_alignment.value,
-                      crossAxisAlignment: cross_axis_alignment.value,
-                    }
-              }
-              onValueChange={(value) => {
-                change.main_axis_alignment(value.mainAxisAlignment);
-                change.cross_axis_alignment(value.crossAxisAlignment);
-              }}
-            />
-          </PropertyLine>
-        </SidebarMenuSectionContent>
-      </SidebarSection>
🧹 Nitpick comments (14)
crates/grida-canvas/src/painter/geometry.rs (1)

324-328: Clarify the catch-all case scope with explicit node type comment.

The TODO comment is helpful for documenting a known limitation, but the catch-all _ => return None silently handles other non-shape node types (TextSpan, InitialContainer) beyond Group. Consider making this explicit to avoid confusion about which node types fail in boolean operations.

🔎 Suggested improvement for clarity
                        Node::Error(n) => Node::Error(n.clone()),
~                       // TODO: Support Group nodes as children of boolean operations.
~                       // Currently, boolean operations like `boolop(group(a, b), c)` are not supported.
~                       // When a Group is encountered, this function returns None, causing the boolean
~                       // operation to fail rendering.
-                       _ => return None, // Non-shape nodes (including Group)
+                       // Non-shape nodes (Group, TextSpan, InitialContainer, etc.) are not supported
+                       // as children of boolean operations. They result in None.
+                       _ => return None,
editor/scaffolds/sidecontrol/controls/padding.tsx (1)

87-116: Consider extracting the value resolution logic to reduce repetition.

The pattern side === X ? newValue : typeof paddingValues.X === "number" ? paddingValues.X : 0 is repeated four times. This could be simplified with a helper.

🔎 Proposed refactor
 const handleIndividualChange = (
   side: "top" | "right" | "bottom" | "left",
   newValue: number | undefined
 ) => {
   if (newValue === undefined) return;
+  const resolveValue = (
+    key: "top" | "right" | "bottom" | "left"
+  ): number =>
+    side === key
+      ? newValue
+      : typeof paddingValues[key] === "number"
+        ? paddingValues[key]
+        : 0;
+
   onValueCommit?.({
-    padding_top:
-      side === "top"
-        ? newValue
-        : typeof paddingValues.top === "number"
-          ? paddingValues.top
-          : 0,
-    padding_right:
-      side === "right"
-        ? newValue
-        : typeof paddingValues.right === "number"
-          ? paddingValues.right
-          : 0,
-    padding_bottom:
-      side === "bottom"
-        ? newValue
-        : typeof paddingValues.bottom === "number"
-          ? paddingValues.bottom
-          : 0,
-    padding_left:
-      side === "left"
-        ? newValue
-        : typeof paddingValues.left === "number"
-          ? paddingValues.left
-          : 0,
+    padding_top: resolveValue("top"),
+    padding_right: resolveValue("right"),
+    padding_bottom: resolveValue("bottom"),
+    padding_left: resolveValue("left"),
   });
 };
editor/scaffolds/sidecontrol/controls/gap.tsx (2)

127-129: Variable shadowing: value shadows the prop.

The callback parameter _v is correctly used, but then value is re-declared, shadowing the outer value prop. Consider renaming for clarity.

🔎 Suggested rename
           onValueChange={(_v) => {
-            const value = parseInt(_v);
-            handleChange(value);
+            const numericValue = parseInt(_v, 10);
+            handleChange(numericValue);
           }}

96-99: Simplify useMemo dependencies.

isMixed is derived from value, so listing both is redundant. The dependency array can be simplified.

🔎 Simplified version
   const hasPreset = useMemo(() => {
-    if (isMixed) return false;
-    return typeof value === "number" && Object.values(twgap).some((preset) => preset.gap === value);
-  }, [value, isMixed]);
+    if (value === grida.mixed) return false;
+    return typeof value === "number" && Object.values(twgap).some((preset) => preset.gap === value);
+  }, [value]);
editor/grida-canvas-react/use-data-transfer.ts (1)

451-451: Consider adding error handling for fallback paste operations.

Multiple fallback code paths invoke instance.surface.a11yPaste() without error handling. If these operations can fail, errors would be silently swallowed.

🔎 Example of defensive error handling
 if (!event.clipboardData) {
-  instance.surface.a11yPaste();
+  try {
+    instance.surface.a11yPaste();
+  } catch (error) {
+    console.error("Fallback paste failed:", error);
+  }
   event.preventDefault();
   return;
 }

Apply similar pattern at lines 482, 512, 516, and 520.

Also applies to: 482-482, 512-512, 516-516, 520-520

editor/grida-canvas-react/use-mixed-properties.ts (1)

491-558: Performance optimization noted in TODO.

The refactored paint grouping logic correctly implements deep equality comparison and grouping, but as the TODO comment acknowledges, resolving and comparing all paints upfront is expensive. The suggested optimization approach (limiting by n initially, loading more on demand) would improve performance for large selections.

The current implementation is correct and follows the new paint resolution pattern. The performance optimization can be addressed in a follow-up.

💡 Future optimization approach

Consider implementing the lazy-loading pattern mentioned in the TODO:

  1. Initially compute only the first N paint groups (e.g., N=10)
  2. Track if there are more paints to load
  3. Expose a loadMore() method in the returned API
  4. UI can show a "Load more" button when needed

This would improve initial render performance while maintaining full functionality.

editor/grida-canvas/action.ts (1)

290-316: Explicit targets for paste/delete/order/insert are consistent, but paste type and runtime guard diverge slightly

Unifying the action surface around explicit targets (EditorPasteAction.target: NodeID | NodeID[], EditorDeleteAction.target: NodeID[], EditorHierarchyOrderAction.target: NodeID[], EditorUngroupAction.target: NodeID, DocumentEditorInsertNodeAction.target: NodeID | null) matches the PR’s goal of explicit targeting and will make reducers and callers easier to reason about.

One small consistency nit: EditorPasteAction.target is now non‑optional at the type level, but the reducer still defends with if (!action.target) break;. If you expect all typed call sites to always supply a target, you could either drop this guard, or widen the type to NodeID | NodeID[] | null to document the “no-op when target is missing” fallback more explicitly.

Also applies to: 325-343, 573-608, 616-619, 944-952

editor/grida-canvas/reducers/document.reducer.ts (1)

330-342: Paste refactor (prototypes and vector networks) aligns with explicit targets; shared vector paste path could be extracted

The revised "paste" logic now:

  • Uses editor.resolvePaints for image-paint clipboard, which centralizes paint resolution.
  • Requires user_clipboard.type === "prototypes" and a non-empty action.target, then:
    • Computes a viewport-space rect (with inset) from context.viewport and state.transform.
    • For each target parent and prototype, clones a packed subtree, nudges it into view via getViewportAwareDelta, and, when a parent exists, subtracts the parent’s absolute rect so children are correctly parent‑relative before calling self_insertSubDocument.

The new "paste-vector-network" action mirrors the existing vector-edit paste behavior (union into current vector node when in vector CEM, or creating a new VectorNode and inserting it under target/scene root), which fits the separation between document-level paste and content-edit-mode paste.

There’s notable duplication between the vector branch inside "paste" and the "paste-vector-network" case (offset handling, union, selection update). If this code evolves further, consider extracting a small helper like __pasteVectorNetworkIntoNode(draft, node_id, net, maybeOffset) to reduce divergence.

Also applies to: 419-478, 537-600, 601-696

editor/grida-canvas/reducers/methods/select.ts (1)

33-91: Scene-filtered self_selectNode looks solid; self_clearSelection behavior with non-vector CEM is worth a quick check

  • self_selectNode:

    • Filters out any IDs in draft.document.scenes_ref, preventing scenes from ever entering draft.selection.
    • Asserts that each remaining ID exists.
    • Applies reset/add/toggle modes with dq.pruneNestedNodes to normalize out parent+child selections.
    • Only resets active_duplication when a reset selection actually changes, which is good for duplication UX.
  • self_clearSelection:

    • When content_edit_mode.type === "vector", clears vector-geometry selection and related state but leaves draft.selection intact.
    • When no content_edit_mode is active, clears draft.selection.

Given EditorBlurAction delegates directly to self_clearSelection, it may be worth confirming that leaving draft.selection unchanged when content_edit_mode is non-vector (e.g., text or gradient modes) is the desired behavior, rather than also clearing the node selection in those cases.

Also applies to: 93-115

editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts (1)

861-887: Acknowledged: Duplicate helper with planned refactoring.

The TODO comments clearly document the duplication across three reducers and the planned improvements (locked container filtering, root node filtering, z-order preservation). Consider creating a tracking issue to ensure this refactoring is addressed, as maintaining three copies risks divergent behavior over time.

Would you like me to open an issue to track consolidating __get_insertion_target into a shared utility in utils/insertion-targeting.ts?

editor/grida-canvas/reducers/event-target.cem-bitmap.reducer.ts (1)

225-251: Duplicate helper function across reducers.

This is identical to the implementation in event-target.cem-vector.reducer.ts. The TODO comments appropriately document the planned consolidation. As noted in the other file, consider tracking this refactoring to avoid maintaining parallel implementations.

editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx (1)

15-16: Clarify the import aliasing.

The dual import creates potential confusion:

  • Line 15: editor for component-level usage (outside EditorUser)
  • Line 16: _editor alias for usage inside EditorUser where const editor = useCurrentEditor() shadows the module import

Consider renaming the hook result to avoid shadowing:

🔎 Suggested refactor
- import { editor } from "@/grida-canvas";
- import { editor as _editor } from "@/grida-canvas";
+ import { editor as editorModule } from "@/grida-canvas";

Then use editorModule.resolvePaints(...) consistently throughout, and rename the hook variable to something like editorInstance in EditorUser.

editor/grida-canvas/reducers/event-target.reducer.ts (1)

253-263: TODO: Compute is_empty_space_within_overlay from selection geometry.

The code currently passes undefined for is_empty_space_within_overlay, which causes the decision function to treat empty space as "outside overlay" and perform immediate clear. The TODO comment at lines 254-256 indicates this should be computed using selection geometry rects and pointer position.

According to the inline documentation, the proper implementation would:

  1. Get selection geometry rects from context.geometry
  2. Compute the union overlay
  3. Check if pointer position is within the overlay bounds

This logic is already implemented in __self_drag_start_selection_like_cursor at lines 372-383 and could be extracted into a shared helper function.

editor/grida-canvas/editor.ts (1)

930-948: Good documentation of technical debt.

The TODO comment clearly explains the naming inconsistency and potential refactoring directions. The implementation correctly guards against missing scene_id and uses explicit targeting.

Would you like me to open an issue to track the refactoring of pasteVector to either rename it to insertVector or align it with the clipboard-based paste flow?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cff289f and 053065e.

📒 Files selected for processing (42)
  • crates/grida-canvas/src/painter/geometry.rs
  • docs/wg/feat-editor/ux-surface/selection.md
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/ui-config.ts
  • editor/grida-canvas-react/use-context-menu-actions.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • editor/grida-canvas/ASSERTIONS.md
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/editor.i.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/reducers/__tests__/history.test.ts
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/event-target.cem-bitmap.reducer.ts
  • editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas/reducers/methods/__tests__/selection.test.ts
  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas/utils/__tests__/insertion.test.ts
  • editor/grida-canvas/utils/insertion-targeting.ts
  • editor/grida-canvas/utils/insertion.ts
  • editor/grida-canvas/utils/paint-resolution.ts
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/scaffolds/sidecontrol/controls/gap.tsx
  • editor/scaffolds/sidecontrol/controls/padding.tsx
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • packages/grida-canvas-io/index.ts
  • packages/grida-canvas-schema/grida.ts
💤 Files with no reviewable changes (3)
  • editor/grida-canvas/utils/paint-resolution.ts
  • editor/grida-canvas/utils/insertion.ts
  • editor/grida-canvas/utils/tests/insertion.test.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript 5 as the main language for most apps
Use Lucide or Radix Icons for icons

Files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react/use-context-menu-actions.ts
  • editor/grida-canvas/reducers/__tests__/history.test.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas/utils/insertion-targeting.ts
  • editor/scaffolds/sidecontrol/controls/gap.tsx
  • editor/grida-canvas-react/ui-config.ts
  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/scaffolds/sidecontrol/controls/padding.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/grida-canvas/reducers/event-target.cem-bitmap.reducer.ts
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts
  • editor/grida-canvas/reducers/methods/__tests__/selection.test.ts
  • packages/grida-canvas-schema/grida.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/editor.i.ts
{editor/**/*.{ts,tsx},packages/grida-canvas-*/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

Use DOM (plain DOM as canvas) for website builder canvas, bound with React

Files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react/use-context-menu-actions.ts
  • editor/grida-canvas/reducers/__tests__/history.test.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas/utils/insertion-targeting.ts
  • editor/scaffolds/sidecontrol/controls/gap.tsx
  • editor/grida-canvas-react/ui-config.ts
  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/scaffolds/sidecontrol/controls/padding.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/grida-canvas/reducers/event-target.cem-bitmap.reducer.ts
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts
  • editor/grida-canvas/reducers/methods/__tests__/selection.test.ts
  • packages/grida-canvas-schema/grida.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/editor.i.ts
editor/grida-*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use /editor/grida-* directories to isolate domain-specific modules; promote well-defined modules to /packages

Files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • editor/grida-canvas-react/use-context-menu-actions.ts
  • editor/grida-canvas/reducers/__tests__/history.test.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas/utils/insertion-targeting.ts
  • editor/grida-canvas-react/ui-config.ts
  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/event-target.cem-bitmap.reducer.ts
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts
  • editor/grida-canvas/reducers/methods/__tests__/selection.test.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/editor.i.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React.js 19 for web applications

Files:

  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/scaffolds/sidecontrol/controls/gap.tsx
  • editor/scaffolds/sidecontrol/controls/padding.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
packages/grida-canvas-*/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Packages under /packages/grida-canvas-* power the canvas; some are published to npm, refer to individual package README

Files:

  • packages/grida-canvas-io/index.ts
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • packages/grida-canvas-schema/grida.ts
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Use Rust 2024 edition for wasm builds and graphics core
Use Skia graphics backend for 2D graphics, bound with skia-safe
Rust crates in /crates directory are under rapid development and serve as the new rendering backend; ensure high quality implementations

Files:

  • crates/grida-canvas/src/painter/geometry.rs
crates/grida-canvas/**/*.rs

📄 CodeRabbit inference engine (crates/grida-canvas/AGENTS.md)

crates/grida-canvas/**/*.rs: Use NodeId (u64) for internal structs (NodeRecs, SceneGraph, caches) in the rendering engine for high-performance operations
Use UserNodeId (String) for public APIs that accept or return node IDs for stability and serialization
Handle NodeId to UserNodeId conversion via IdConverter during .grida file loading
Auto-generate IDs (ID=0) in NodeRepository for factory-created nodes
Maintain bidirectional mapping between NodeId and UserNodeId at the application layer for API boundary management
Use skia-safe crate for painting operations in the rendering engine
Use math2 crate for geometry and common math operations
Run cargo fmt to maintain code formatting standards
Run cargo clippy --no-deps --all-targets --all-features for linting to check style, performance, and correctness suggestions

Files:

  • crates/grida-canvas/src/painter/geometry.rs
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Documentation source of truth is in the ./docs directory; deployment is handled by apps/docs

Files:

  • docs/wg/feat-editor/ux-surface/selection.md
docs/{wg,reference}/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Only actively maintain docs/wg/** and docs/reference/** directories; see docs/AGENTS.md for contribution scope

Files:

  • docs/wg/feat-editor/ux-surface/selection.md
editor/scaffolds/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use /editor/scaffolds for feature-specific larger components, pages, and editors

Files:

  • editor/scaffolds/sidecontrol/controls/gap.tsx
  • editor/scaffolds/sidecontrol/controls/padding.tsx
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/scaffolds/sidecontrol/controls/color.tsx
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new shape types, update the Shape type union, add cases in drawShape() function, add cases in shapeToSVG() function, and add SelectItem in UI
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use Canvas 2D API with path commands for rendering geometric shapes (circle, square, triangle, etc.)
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new shape types, update the Shape type union, add cases in drawShape() function, add cases in shapeToSVG() function, and add SelectItem in UI

Applied to files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas/reducers/__tests__/history.test.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • editor/grida-canvas/reducers/methods/__tests__/selection.test.ts
  • packages/grida-canvas-schema/grida.ts
  • editor/grida-canvas/editor.i.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : For SVG export, convert circles to <circle> elements, rectangles to <rect> elements, and polygons to <polygon> elements with calculated points

Applied to files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use Canvas 2D API with path commands for rendering geometric shapes (circle, square, triangle, etc.)

Applied to files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • packages/grida-canvas-schema/grida.ts
  • editor/grida-canvas/editor.i.ts
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to {editor/**/*.{ts,tsx},packages/grida-canvas-*/**/*.{ts,tsx}} : Use DOM (plain DOM as canvas) for website builder canvas, bound with React

Applied to files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • editor/grida-canvas-react/use-context-menu-actions.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/tree-scene.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/methods/selection.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • editor/grida-canvas/reducers/event-target.cem-vector.reducer.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/editor.i.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use React hooks for state management (imageSrc, shape, grid, maxRadius, gamma, jitter, opacity, color, customShapeImage, imageDataRef, sizeRef)

Applied to files:

  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
📚 Learning: 2025-12-01T00:22:19.083Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas-wasm/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:19.083Z
Learning: Applies to crates/grida-canvas-wasm/**/main.rs : Update `grida-canvas-wasm.d.ts` TypeScript definitions file when new APIs are introduced via `main.rs`

Applied to files:

  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas/reducers/methods/select.ts
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • packages/grida-canvas-schema/grida.ts
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to packages/grida-canvas-*/**/*.{ts,tsx,js,jsx} : Packages under /packages/grida-canvas-* power the canvas; some are published to npm, refer to individual package README

Applied to files:

  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-artboard-list/index.tsx
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas/reducers/methods/wrap.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
  • packages/grida-canvas-io/__tests__/clipboard.test.ts
  • packages/grida-canvas-schema/grida.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/editor.i.ts
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Use `math2` crate for geometry and common math operations

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Use `NodeId` (u64) for internal structs (NodeRecs, SceneGraph, caches) in the rendering engine for high-performance operations

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Maintain bidirectional mapping between NodeId and UserNodeId at the application layer for API boundary management

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Use `skia-safe` crate for painting operations in the rendering engine

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Auto-generate IDs (ID=0) in `NodeRepository` for factory-created nodes

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
  • editor/grida-canvas/editor.ts
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Run `cargo fmt` to maintain code formatting standards

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Use `UserNodeId` (String) for public APIs that accept or return node IDs for stability and serialization

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Handle NodeId to UserNodeId conversion via `IdConverter` during .grida file loading

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-20T08:11:16.220Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-20T08:11:16.220Z
Learning: Applies to crates/grida-canvas/**/*.rs : Run `cargo clippy --no-deps --all-targets --all-features` for linting to check style, performance, and correctness suggestions

Applied to files:

  • crates/grida-canvas/src/painter/geometry.rs
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/scaffolds/**/*.{ts,tsx} : Use /editor/scaffolds for feature-specific larger components, pages, and editors

Applied to files:

  • editor/scaffolds/sidecontrol/controls/gap.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/grida-*/**/*.{ts,tsx} : Use /editor/grida-* directories to isolate domain-specific modules; promote well-defined modules to <root>/packages

Applied to files:

  • editor/grida-canvas/reducers/methods/duplicate.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas/editor.ts
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/lib/**/*.{ts,tsx} : Use /editor/lib for core, strictly designed modules with non-opinionated, reusable, and stable implementations

Applied to files:

  • editor/grida-canvas/reducers/methods/duplicate.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Custom images used as halftone shapes should be loaded as HTMLImageElement for efficient canvas rendering and preserve original image colors in as-is mode

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new parameters to the halftone tool, add state with useState, include in useEffect dependency array, pass to renderHalftone() function, use in rendering logic, and add UI control

Applied to files:

  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/scaffolds/sidecontrol/controls/color.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Calculate halftone dot radius using: radius = (1 - mapped) * maxRadius, where darker areas get larger dots

Applied to files:

  • editor/grida-canvas/reducers/event-target.reducer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Cache ImageData and dimensions in refs (imageDataRef, sizeRef) for efficient exports

Applied to files:

  • editor/grida-canvas-react/use-mixed-properties.ts
  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/components/ui/**/*.{ts,tsx} : Use /editor/components/ui for shadcn UI components

Applied to files:

  • editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx
  • editor/scaffolds/sidecontrol/controls/color.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to **/components/ui/**/*.{ts,tsx} : Use Shadcn UI for UI component library

Applied to files:

  • editor/scaffolds/sidecontrol/controls/color.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Image input should be automatically scaled down to a maximum of 1024px for performance

Applied to files:

  • editor/grida-canvas-react/viewport/ui/surface-image-editor.tsx
🧬 Code graph analysis (19)
editor/grida-canvas-hosted/playground/uxhost-menu.tsx (2)
crates/grida-canvas/src/io/io_figma.rs (1)
  • instance (902-906)
packages/grida-canvas-io-figma/lib.ts (1)
  • FigImporter (2800-2814)
editor/grida-canvas-react/use-context-menu-actions.ts (1)
editor/grida-canvas/index.ts (1)
  • editor (4-4)
editor/grida-canvas/reducers/__tests__/history.test.ts (1)
editor/grida-canvas/action.ts (1)
  • Action (9-14)
editor/grida-canvas/utils/insertion-targeting.ts (1)
editor/grida-canvas/editor.i.ts (1)
  • IEditorState (1476-1495)
editor/scaffolds/sidecontrol/controls/gap.tsx (1)
editor/scaffolds/sidecontrol/ui/number.tsx (1)
  • InputPropertyNumber (127-246)
editor/scaffolds/sidecontrol/controls/padding.tsx (1)
packages/grida-canvas-schema/grida.ts (1)
  • IPadding (1486-1507)
editor/grida-canvas-react/use-data-transfer.ts (1)
editor/grida-canvas/editor.i.ts (1)
  • InsertPayload (2803-2810)
editor/grida-canvas/reducers/methods/select.ts (1)
editor/grida-canvas/editor.i.ts (1)
  • VectorContentEditModeGeometryControlsSelection (1231-1249)
editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx (6)
editor/scaffolds/sidecontrol/controls/gap.tsx (1)
  • GapControl (210-242)
editor/scaffolds/sidecontrol/controls/padding.tsx (1)
  • PaddingControl (19-248)
packages/grida-canvas-io-svg/lib.ts (1)
  • stroke (147-178)
editor/scaffolds/sidecontrol/controls/stroke-align.tsx (1)
  • StrokeAlignControl (5-32)
editor/scaffolds/sidecontrol/controls/stroke-join.tsx (1)
  • StrokeJoinControl (5-32)
editor/scaffolds/sidecontrol/controls/stroke-miter-limit.tsx (1)
  • StrokeMiterLimitControl (28-59)
editor/grida-canvas/reducers/event-target.reducer.ts (2)
editor/grida-canvas/reducers/methods/selection.ts (3)
  • decideClickSelection (169-190)
  • decidePointerDownSelection (108-159)
  • decideDragStartAction (210-246)
editor/grida-canvas/reducers/methods/select.ts (2)
  • self_clearSelection (93-115)
  • self_selectNode (33-91)
editor/grida-canvas-react/use-mixed-properties.ts (6)
crates/grida-canvas/src/cg/types.rs (3)
  • value (585-587)
  • value (1234-1236)
  • value (1580-1582)
packages/grida-canvas-cg/lib.ts (3)
  • StrokeAlign (197-197)
  • StrokeJoin (191-191)
  • Paint (699-705)
crates/grida-canvas/src/io/io_figma.rs (1)
  • instance (902-906)
packages/grida-canvas-schema/grida.ts (1)
  • IPadding (1486-1507)
crates/grida-canvas/src/painter/layer.rs (1)
  • paints (220-224)
packages/grida-canvas-io-svg/lib.ts (1)
  • paint (35-123)
editor/grida-canvas/reducers/methods/wrap.ts (3)
editor/grida-canvas/editor.i.ts (1)
  • IDocumentGeometryQuery (2663-2694)
editor/grida-canvas/reducers/methods/move.ts (1)
  • self_moveNode (9-43)
editor/grida-canvas/reducers/methods/insert.ts (1)
  • self_insertSubDocument (9-61)
editor/grida-canvas-react/viewport/ui/floating-bar.tsx (1)
editor/grida-canvas-react/ui-config.ts (1)
  • FLOATING_BAR_Z_INDEX (76-76)
editor/grida-canvas-react/viewport/ui/surface-gradient-editor.tsx (1)
editor/grida-canvas/index.ts (1)
  • editor (4-4)
editor/scaffolds/sidecontrol/controls/color.tsx (1)
editor/scaffolds/sidecontrol/controls/color-picker.tsx (1)
  • ColorPicker32FWithOptions (110-124)
editor/grida-canvas/action.ts (4)
packages/grida-canvas-schema/grida.ts (1)
  • NodeID (1101-1101)
editor/grida-canvas/editor.i.ts (1)
  • NodeID (136-136)
packages/grida-canvas-io-figma/lib.ts (1)
  • VectorNetwork (53-65)
packages/grida-canvas-vn/vn.ts (1)
  • VectorNetwork (206-209)
editor/grida-canvas/reducers/methods/selection.ts (3)
packages/grida-canvas-schema/grida.ts (1)
  • INodesRepositoryRuntimeHierarchyContext (899-899)
crates/csscascade/src/tree/mod.rs (1)
  • node_id (267-270)
crates/grida-canvas/src/window/state.rs (1)
  • context (65-67)
packages/grida-canvas-io/__tests__/clipboard.test.ts (2)
packages/grida-canvas-schema/grida.ts (1)
  • NodePrototype (1246-1279)
packages/grida-canvas-io/index.ts (1)
  • ClipboardPayload (83-86)
editor/grida-canvas/reducers/methods/__tests__/selection.test.ts (1)
editor/grida-canvas/reducers/methods/selection.ts (6)
  • PointerDownContext (17-33)
  • decidePointerDownSelection (108-159)
  • ClickContext (35-41)
  • decideClickSelection (169-190)
  • DragStartContext (53-68)
  • decideDragStartAction (210-246)
🪛 LanguageTool
docs/wg/feat-editor/ux-surface/selection.md

[style] ~62-~62: This phrase is redundant. Consider writing “space”.
Context: ... Shift) → adds to selection - Target is empty space outside selection overlay (without Shif...

(EMPTY_HOLE)


[style] ~73-~73: This phrase is redundant. Consider writing “space”.
Context: ...→ defer reset to that child - Target is empty space within selection overlay (without Shift...

(EMPTY_HOLE)


[style] ~183-~183: This phrase is redundant. Consider writing “Space”.
Context: ...multi-selection. ### Multi-Selection - Empty Space Drag Within Overlay Scenario: - T...

(EMPTY_HOLE)


[style] ~188-~188: This phrase is redundant. Consider writing “space”.
Context: ... overlay - User presses pointer down on empty space within the selection overlay bounds (no...

(EMPTY_HOLE)


[style] ~198-~198: This phrase is redundant. Consider writing “space”.
Context: ..., not clear selection or start marquee. Empty space within the overlay bounds is part of th...

(EMPTY_HOLE)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test

Comment on lines +357 to +364
const padding = useCallback(
(value: grida.program.nodes.i.IPadding) => {
mixedProperties.padding_top?.ids.forEach((id) => {
instance.commands.changeContainerNodePadding(id, value);
});
},
[mixedProperties.padding_top?.ids, instance.commands]
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's locate and examine the file in question
find . -name "use-mixed-properties.ts" -type f

Repository: gridaco/grida

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Check if the file exists and get its size to determine how to read it
if [ -f "editor/grida-canvas-react/use-mixed-properties.ts" ]; then
  wc -l "editor/grida-canvas-react/use-mixed-properties.ts"
else
  echo "File not found at expected location"
  # Try finding it
  find . -name "use-mixed-properties.ts" -type f 2>/dev/null | head -5
fi

Repository: gridaco/grida

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Read the relevant section around lines 357-364
sed -n '340,380p' editor/grida-canvas-react/use-mixed-properties.ts

Repository: gridaco/grida

Length of output: 1414


🏁 Script executed:

#!/bin/bash
# Also look for the gap handler mentioned as comparison context
sed -n '1,400p' editor/grida-canvas-react/use-mixed-properties.ts | grep -A 10 "const gap"

Repository: gridaco/grida

Length of output: 447


🏁 Script executed:

#!/bin/bash
# Search for changeContainerNodePadding implementation
rg "changeContainerNodePadding" --type ts --type tsx -A 5 -B 2

Repository: gridaco/grida

Length of output: 84


🏁 Script executed:

#!/bin/bash
# Let's search for the changeContainerNodePadding command definition using rg with different approach
rg "changeContainerNodePadding" --type ts -A 5 -B 2

Repository: gridaco/grida

Length of output: 2235


🏁 Script executed:

#!/bin/bash
# Also search for command definitions or implementation files
fd "command" --type f | head -20

Repository: gridaco/grida

Length of output: 189


🏁 Script executed:

#!/bin/bash
# Look for where changeContainerNodePadding might be defined - likely in a commands or actions file
rg "changeContainerNodePadding" -A 10 | head -50

Repository: gridaco/grida

Length of output: 3164


🏁 Script executed:

#!/bin/bash
# Let's understand the structure of mixedProperties by looking at its definition
sed -n '1,100p' editor/grida-canvas-react/use-mixed-properties.ts | grep -A 20 "mixedProperties"

Repository: gridaco/grida

Length of output: 1260


🏁 Script executed:

#!/bin/bash
# Check the function/hook signature and how mixedProperties is created
head -100 editor/grida-canvas-react/use-mixed-properties.ts

Repository: gridaco/grida

Length of output: 2746


🏁 Script executed:

#!/bin/bash
# Let's check what the mixed library does and understand the structure better
# First, find where the mixed library is defined
find . -name "mixed-properties*" -o -name "*mixed*" | grep -E "(package\.json|index|src)" | head -10

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Look for mixed-properties package or definition
find . -path "*/node_modules" -prune -o -name "mixed.ts" -type f -print
find . -path "*/node_modules" -prune -o -name "mixed.d.ts" -type f -print
find . -path "*/node_modules" -prune -o -path "*mixed-properties*" -type f -name "*.ts" -print 2>/dev/null | head -5

Repository: gridaco/grida

Length of output: 209


🏁 Script executed:

#!/bin/bash
# Let's check the entire padding handler and surrounding context more carefully
sed -n '350,375p' editor/grida-canvas-react/use-mixed-properties.ts

Repository: gridaco/grida

Length of output: 858


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or comment explaining the mixed property pattern
sed -n '300,365p' editor/grida-canvas-react/use-mixed-properties.ts | cat -n

Repository: gridaco/grida

Length of output: 2648


🏁 Script executed:

#!/bin/bash
# Let's search for how padding is handled elsewhere - maybe in provider.tsx for single node
sed -n '1,100p' editor/grida-canvas-react/provider.tsx | grep -A 5 -B 5 "padding"

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check if there are any other padding-related handlers to understand the intent
rg "padding_top\|padding_left\|padding_right\|padding_bottom" --type ts -B 2 -A 2 | head -80

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Let's examine the mixed-properties package implementation
cat packages/grida-mixed-properties/index.ts

Repository: gridaco/grida

Length of output: 5102


🏁 Script executed:

#!/bin/bash
# Also check the test file to understand expected behavior
cat packages/grida-mixed-properties/__tests__/mixed.test.ts

Repository: gridaco/grida

Length of output: 5040


🏁 Script executed:

#!/bin/bash
# Let's also look at how other properties like stroke are handled in comparison
sed -n '200,250p' editor/grida-canvas-react/use-mixed-properties.ts | cat -n

Repository: gridaco/grida

Length of output: 1904


🏁 Script executed:

#!/bin/bash
# Check if there are any other places where padding is updated to see the pattern
rg "padding_top\|padding" editor/grida-canvas-react/ --type ts -B 3 -A 3 | head -100

Repository: gridaco/grida

Length of output: 39


Verify node selection for padding updates.

Similar to the gap handler, this uses only mixedProperties.padding_top?.ids when setting all four padding properties via IPadding. If nodes have inconsistent padding property definitions (e.g., a node with padding_left but no padding_top), some nodes might not receive the update.

Use the union of all padding property ids, or all container nodes from the selection.

🤖 Prompt for AI Agents
In editor/grida-canvas-react/use-mixed-properties.ts around lines 357 to 364,
the padding handler only iterates mixedProperties.padding_top?.ids so nodes
missing padding_top but having other padding keys (left/right/bottom) won't get
updated; change the logic to compute the union of all padding ids (padding_top,
padding_right, padding_bottom, padding_left) or gather all selected container
node ids, then iterate that union and call
instance.commands.changeContainerNodePadding(id, value) for each id to apply the
full IPadding object to every relevant node, and update the useCallback
dependency array to reference the unified id list (or selection source) instead
of only padding_top?.ids.

Comment on lines +41 to +74
export function resolvePasteTargetParents(
state: editor.state.IEditorState,
selection: string[],
copiedIds: string[]
): Array<string | null> {
return Array.from(
new Set(
selection
.map((node_id) => {
const node = dq.__getNodeById(state, node_id);

// If node is a container, use it as target parent (paste as child)
if (node.type === "container") {
return node_id;
}

// Otherwise, use its parent as target parent (paste as sibling)
const parent_id = dq.getParentId(state.document_ctx, node_id);

// Parent can be null (scene) or a container
if (!parent_id) return null;

const parent = dq.__getNodeById(state, parent_id);
// Only return valid container parents
return parent?.type === "container" ? parent_id : null;
})
.filter((target_id) => {
// Ensure target parent is not one of the originals
if (target_id && copiedIds.includes(target_id)) return false;
return true;
})
)
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null checks for retrieved nodes to prevent runtime errors.

The function retrieves nodes at lines 50 and 63 without validating they exist before accessing their properties:

  • Line 53: node.type accessed without checking if node is null/undefined
  • Line 65: parent?.type uses optional chaining but line 63 doesn't check if parent_id is valid before the lookup

If the selection contains invalid or deleted node IDs, these will cause TypeError exceptions.

🔎 Proposed fix
 export function resolvePasteTargetParents(
   state: editor.state.IEditorState,
   selection: string[],
   copiedIds: string[]
 ): Array<string | null> {
   return Array.from(
     new Set(
       selection
         .map((node_id) => {
           const node = dq.__getNodeById(state, node_id);
+
+          if (!node) return null;

           // If node is a container, use it as target parent (paste as child)
           if (node.type === "container") {
             return node_id;
           }

           // Otherwise, use its parent as target parent (paste as sibling)
           const parent_id = dq.getParentId(state.document_ctx, node_id);

           // Parent can be null (scene) or a container
           if (!parent_id) return null;

           const parent = dq.__getNodeById(state, parent_id);
+          if (!parent) return null;
+
           // Only return valid container parents
-          return parent?.type === "container" ? parent_id : null;
+          return parent.type === "container" ? parent_id : null;
         })
         .filter((target_id) => {
           // Ensure target parent is not one of the originals
           if (target_id && copiedIds.includes(target_id)) return false;
           return true;
         })
     )
   );
 }
🤖 Prompt for AI Agents
In editor/grida-canvas/utils/insertion-targeting.ts around lines 41 to 74, add
null checks for nodes returned from dq.__getNodeById to avoid runtime TypeErrors
when selection contains invalid/deleted IDs: after retrieving node (line ~50)
return null immediately if node is falsy before accessing node.type, and after
computing parent_id and retrieving parent (line ~63) return null immediately if
parent is falsy before checking parent.type; keep existing logic that returns
container IDs or null and still filter out copiedIds.

@vercel vercel bot temporarily deployed to Preview – viewer December 27, 2025 06:01 Inactive
@vercel vercel bot temporarily deployed to Preview – blog December 27, 2025 06:01 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds December 27, 2025 06:01 Inactive
@softmarshmallow softmarshmallow merged commit fc6ce77 into main Dec 27, 2025
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant