Skip to content

Grida Canvas - Parametric Scale (K)#471

Merged
softmarshmallow merged 23 commits intomainfrom
canary
Dec 14, 2025
Merged

Grida Canvas - Parametric Scale (K)#471
softmarshmallow merged 23 commits intomainfrom
canary

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Dec 14, 2025

assertions

  • preserve-auto
    • should NOT set width/height to fixed value, like plain resize but preserve 'auto'.
  • no-quntization
    • pick single primary axis dimension (width or height) use scale value from that, and no longer quantize the value.
  • handle zero-dimension or locked constraints
    • line height should be kept 0.

Summary by CodeRabbit

  • New Features

    • New Scale tool (shortcut K) with parametric scaling, uniform-factor control, 9-point origin alignment, dedicated UI panel and scale presets; updated cursors and toolbar icon.
  • Documentation

    • Parametric scaling docs expanded; top-level docs index simplified with pointer to agents doc.
  • Tests

    • New and updated tests covering parametric scaling, schema helpers, and vector path handling.
  • Chores

    • Test harness and package/test configs updated; several test mocks revised.

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

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 14, 2025

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

Project Deployment Review Updated (UTC)
docs Ready Ready Preview, Comment Dec 14, 2025 7:06pm
grida Ready Ready Preview, Comment Dec 14, 2025 7:06pm
5 Skipped Deployments
Project Deployment Review Updated (UTC)
code Ignored Ignored Dec 14, 2025 7:06pm
legacy Ignored Ignored Dec 14, 2025 7:06pm
backgrounds Skipped Skipped Dec 14, 2025 7:06pm
blog Skipped Skipped Dec 14, 2025 7:06pm
viewer Skipped Skipped Dec 14, 2025 7:06pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 14, 2025

Walkthrough

Adds a new parametric Scale tool (hotkey K) with editor API, reducer handlers, gesture orchestration, schema-based node scaling, UI controls, cursor assets, and tests to perform non-destructive uniform scaling of selected nodes with optional subtree and space options.

Changes

Cohort / File(s) Summary
Documentation
docs/AGENTS.md, docs/wg/feat-authoring/parametric-scaling.md, fixtures/test-grida/README.md
Added AGENTS.md and refined parametric-scaling docs; replaced directory doc entries with AGENTS pointer; added fixture README.
Cursor & Assets
editor/components/cursor/cursor-data.ts, editor/public/assets/css-cursors-grida/README.md
Added scale cursor PNGs and resize_handle_scale_cursor_map; wrapped cursor values with pngsetcss(...); expanded cursor README.
Toolbar & Icons
editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx, editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx, .../starterkit-toolbar/index.tsx, .../starterkit-toolbar/utils.ts
Introduced "scale" tool (value "scale", shortcut K), added UpscaleIcon, extended ToolbarToolType mappings and rendering to support scale.
Tool State & Hotkeys
editor/grida-canvas/editor.i.ts, editor/grida-canvas-react/viewport/hotkeys.tsx, editor/grida-canvas-react/provider.tsx
Added scale ToolMode variant, GestureScale fields for parametric mode, applyScale API; added K hotkey and cursor resolution for scale tool.
Editor API & Actions
editor/grida-canvas/action.ts, editor/grida-canvas/editor.ts
Added EditorApplyParametricScaleAction and public applyScale() on EditorDocumentStore to dispatch apply-scale actions with validation and defaults.
Reducers: scale integration
editor/grida-canvas/reducers/document.reducer.ts, editor/grida-canvas/reducers/surface.reducer.ts, editor/grida-canvas/reducers/event-target.reducer.ts
Wired apply-scale action to scale handler; delegated gesture start to methods scale module; refactored pointer/drag selection helpers to treat scale like cursor.
Scale gesture module
editor/grida-canvas/reducers/methods/scale.ts, editor/grida-canvas/reducers/methods/index.ts, editor/grida-canvas/reducers/methods/transform.ts
New comprehensive scale gesture logic (parametric and resize), auto-space anchoring helpers, snapshot-driven parametric updates; transform delegates scale updates to new module; exported scale methods.
Parametric scaling schema
editor/grida-canvas/reducers/schema/schema.ts, editor/grida-canvas/reducers/schema/index.ts, .../__tests__/schema.parametric-scale.test.ts
New schema.parametric_scale utilities to scale rects, numbers, effects, stroke profiles, vector networks, and node-level property application; tests added.
UI: floating bar & overlays
editor/grida-canvas-react/viewport/ui/floating-bar.tsx, editor/grida-canvas-react/viewport/surface.tsx, editor/grida-canvas-react/viewport/ui/surface-vector-editor.tsx
Removed side/sideOffset API from FloatingBar; surface overlays and resize handles now recognize scale tool and use scale cursors; Loops component also renders in scale mode.
Sidecontrol: Scale tool UI
editor/scaffolds/sidecontrol/chunks/scale-tool.tsx, editor/scaffolds/sidecontrol/controls/alignment9.tsx, .../controls/scale-factor.tsx, .../controls/fe.tsx, .../controls/font-size.tsx
Added ScaleToolSection with session-scoped gesture-aware scaling UI; Alignment9Control (9-point origin selector) and ScaleFactorControl (input + presets); shadow color opacity support; UI padding tweak.
Sidecontrol integration & inputs
editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx, editor/scaffolds/sidecontrol/ui/index.tsx, editor/scaffolds/sidecontrol/ui/number.tsx
Show ScaleToolSection when scale tool active; reduced PropertyLineLabel sizing; added suffix and scale props to numeric input control.
Tests & test infra
editor/jest.config.js, editor/jest.setup.ts, editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts, various test files
Enabled jest.setup.ts and centralized svg-pathdata mock; removed inline mocks from several tests; added apply-scale roundtrip fixture tests.
Vector network package changes
packages/grida-canvas-vn/jest.config.ts, packages/grida-canvas-vn/package.json, packages/grida-canvas-vn/tsconfig.json, packages/grida-canvas-vn/vn.ts, .../__tests__/vn.test.ts, .../__mocks__/svg-pathdata.ts (deleted)
Added ESM-compatible jest config; updated package.json exports and scripts; refactored path parsing logic; relaxed tests; removed local svg-pathdata mock (central mock moved to jest.setup).
Math utilities
packages/grida-cmath/index.ts
Added Alignment9 type and compass.fromAlignment9() mapping function.
Number input precision
packages/grida-number-input/src/n.ts, .../n.test.ts
Ensured step-based decimal precision is respected in formatting; added tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Scale Tool UI
    participant API as Editor API (applyScale)
    participant Reducer as Document Reducer / Methods
    participant Schema as parametric_scale
    participant State as Editor State

    User->>UI: choose targets, origin, factor
    UI->>API: applyScale(targets, factor, { origin, include_subtree, space })
    API->>Reducer: dispatch({ type: "apply-scale", ... })
    Reducer->>Reducer: self_apply_scale_by_factor(targets, factor, origin, include_subtree, space)
    Reducer->>Schema: schema.parametric_scale.apply_node(node, factor)
    Schema->>Schema: scale numeric fields, preserve non-numeric
    Schema->>Reducer: return updated authored properties
    Reducer->>State: commit node updates
    State->>UI: reflect changes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas to focus review on:

  • editor/grida-canvas/reducers/methods/scale.ts — dense gesture branching, snapshot lifecycle, auto-space root logic.
  • editor/grida-canvas/reducers/schema/schema.ts — field-by-field scaling rules and non-numeric preservation.
  • editor/grida-canvas/reducers/event-target.reducer.ts — selection/marquee refactor for cursor vs scale parity.
  • editor/grida-canvas/reducers/tests/apply-scale.roundtrip.test.ts — fixture setup, tolerances, and test isolation.

Possibly related PRs

Suggested labels

ux

Poem

🐰 I found a K and gave a nudge,

numbers stretch without a grudge,
corners, shadows, strokes align,
"auto" stays — the rest refine,
a tiny hop, a perfect nudge. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature being added: parametric scaling functionality with the K keyboard shortcut for Grida Canvas editor.
✨ 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 canary

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.

@chatgpt-codex-connector
Copy link
Copy Markdown

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
Copy Markdown

@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: 8

Caution

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

⚠️ Outside diff range comments (2)
docs/wg/feat-authoring/parametric-scaling.md (1)

86-141: Docs: fix “no anchor” vs “relative to anchor” inconsistency + remove accidental escaping
The text says “no anchor”, but the table still references anchor-based scaling; also \"auto\" will render with backslashes.

-| `left`, `top`, `right`, `bottom`         | layout      |           Y | Absolute/offset lengths; scale relative to anchor.                                   |
+| `left`, `top`, `right`, `bottom`         | layout      |           Y | Absolute/offset lengths; scale stored numeric values (no reflow / no anchor rewrite). |

-| `width`, `height`                        | layout      |         Y\* | Scale only numeric/px-like lengths. Do not scale `%`, viewport units, or `\"auto\"`. |
+| `width`, `height`                        | layout      |         Y\* | Scale only numeric/px-like lengths. Do not scale `%`, viewport units, or `"auto"`. |

(Style hint around Line 98 feels optional; I’d prioritize consistency over wording tweaks.)

editor/grida-canvas-react/viewport/ui/floating-bar.tsx (1)

11-32: Add top-0 to anchor the absolute title bar positioning
Without an explicit top value, the element defaults to top: auto, making its position layout-dependent. Adding top-0 ensures predictable positioning above the parent.

-      <div className="absolute left-0 right-0 -translate-y-full">
+      <div className="absolute top-0 left-0 right-0 -translate-y-full">
         <div {...porps} className={cn("max-w-full overflow-hidden", className)}>
           {children}
         </div>
       </div>
🧹 Nitpick comments (10)
packages/grida-canvas-vn/tsconfig.json (1)

7-8: Remove redundant noImplicitAny when strict is enabled.

The strict compiler option subsumes noImplicitAny, making the explicit declaration redundant.

Apply this diff to clean up the configuration:

  "compilerOptions": {
    "esModuleInterop": true,
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "target": "ES2020",
-   "noImplicitAny": true,
    "strict": true
  },
packages/grida-canvas-vn/jest.config.ts (1)

3-15: Fix coverage exclude glob + verify ESM preset works with your Jest/ts-jest versions.

"!dist/**" likely won’t exclude packages/**/dist/**; consider !**/dist/**.

Also please verify preset: "ts-jest/presets/default-esm" + extensionsToTreatAsEsm is sufficient for your current Jest + ts-jest versions (and that transforms behave as intended with ESM-only deps like svg-pathdata).

   collectCoverageFrom: [
     "**/*.ts",
     "!**/*.d.ts",
     "!**/node_modules/**",
-    "!dist/**",
+    "!**/dist/**",
   ],
editor/components/cursor/cursor-data.ts (1)

45-53: Use directional fallback keywords for scale cursors too (not auto).

For resize_handle_scale_cursor_map, falling back to "auto" may regress UX if the PNG can’t be loaded; consider using the directional resize keywords (like you do in resize_handle_cursor_map) so behavior stays correct even on asset load failure.

 export const resize_handle_scale_cursor_map = {
-  nw: pngsetcss(_nwse_resize_scale_png_url, 32, 32, "auto"),
-  n: pngsetcss(_ns_resize_scale_png_url, 32, 32, "auto"),
-  ne: pngsetcss(_nesw_resize_scale_png_url, 32, 32, "auto"),
-  e: pngsetcss(_ew_resize_scale_png_url, 32, 32, "auto"),
-  se: pngsetcss(_nwse_resize_scale_png_url, 32, 32, "auto"),
-  s: pngsetcss(_ns_resize_scale_png_url, 32, 32, "auto"),
-  sw: pngsetcss(_nesw_resize_scale_png_url, 32, 32, "auto"),
-  w: pngsetcss(_ew_resize_scale_png_url, 32, 32, "auto"),
+  nw: pngsetcss(_nwse_resize_scale_png_url, 32, 32, "nwse-resize"),
+  n: pngsetcss(_ns_resize_scale_png_url, 32, 32, "ns-resize"),
+  ne: pngsetcss(_nesw_resize_scale_png_url, 32, 32, "nesw-resize"),
+  e: pngsetcss(_ew_resize_scale_png_url, 32, 32, "ew-resize"),
+  se: pngsetcss(_nwse_resize_scale_png_url, 32, 32, "nwse-resize"),
+  s: pngsetcss(_ns_resize_scale_png_url, 32, 32, "ns-resize"),
+  sw: pngsetcss(_nesw_resize_scale_png_url, 32, 32, "nesw-resize"),
+  w: pngsetcss(_ew_resize_scale_png_url, 32, 32, "ew-resize"),
 } as const;

Also applies to: 89-123

editor/scaffolds/sidecontrol/controls/alignment9.tsx (1)

12-25: Make ALIGNMENTS immutable and type-checked.

Helps avoid accidental mutation and keeps the literal union intact.

-const ALIGNMENTS: Array<{
+const ALIGNMENTS = [
+  { key: "top-left", label: "Top left" },
+  { key: "top-center", label: "Top" },
+  { key: "top-right", label: "Top right" },
+  { key: "center-left", label: "Left" },
+  { key: "center", label: "Center" },
+  { key: "center-right", label: "Right" },
+  { key: "bottom-left", label: "Bottom left" },
+  { key: "bottom-center", label: "Bottom" },
+  { key: "bottom-right", label: "Bottom right" },
+] as const satisfies ReadonlyArray<{ key: cmath.Alignment9; label: string }>;
-}> = [
-  { key: "top-left", label: "Top left" },
-  { key: "top-center", label: "Top" },
-  { key: "top-right", label: "Top right" },
-  { key: "center-left", label: "Left" },
-  { key: "center", label: "Center" },
-  { key: "center-right", label: "Right" },
-  { key: "bottom-left", label: "Bottom left" },
-  { key: "bottom-center", label: "Bottom" },
-  { key: "bottom-right", label: "Bottom right" },
-];
editor/scaffolds/sidecontrol/controls/scale-factor.tsx (1)

41-44: Potentially redundant spinner-hiding CSS.

The [appearance:textfield] and webkit spinner overrides may be redundant here since InputPropertyNumber already applies these styles internally when appearance="none" (the default). Consider removing these classes to reduce duplication.

         className={cn(
           "overflow-hidden",
-          "[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
         )}
editor/grida-canvas/editor.ts (1)

933-957: applyScale API looks solid; clarify/validate allowed factor domain (<=0 / negative) and whether targets should be deduped.
Right now it silently no-ops for factor === 1 and non-finite values, but will pass through 0 / negative values—if those are unsupported (or cause downstream “locked constraint / zero dimension” edge cases), consider guarding here or explicitly documenting the behavior.

   public applyScale(
     target: ReadonlyArray<editor.NodeID> | "selection",
     factor: number,
     options?: {
       origin?: "center" | cmath.CardinalDirection;
       include_subtree?: boolean;
       space?: "auto" | "global";
     }
   ) {
@@
     if (!targets || targets.length === 0) return;
     if (!Number.isFinite(factor) || factor === 1) return;
+    // Decide policy: allow negative (flip) / zero (collapse) or treat as invalid.
+    // if (factor <= 0) return;

     this.dispatch({
       type: "apply-scale",
-      targets: Array.from(targets),
+      targets: Array.from(new Set(targets)),
       factor,
       origin: options?.origin ?? "center",
       include_subtree: options?.include_subtree ?? true,
       space: options?.space ?? "auto",
     });
   }
editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (1)

17-17: Toolbar “scale” option wiring is consistent; confirm custom UpscaleIcon is acceptable vs Lucide/Radix.
If the repo policy is “Lucide/Radix only” for icons, consider swapping UpscaleIcon to an existing icon (or moving this SVG under the same icon system). Based on learnings, prefer Lucide/Radix.

Also applies to: 150-151, 261-262, 286-288

editor/scaffolds/sidecontrol/chunks/scale-tool.tsx (1)

144-173: Division by zero guard missing for sessionScale.

Line 151 computes factorToApply = nextScale / sessionScale. If sessionScale is 0 (though unlikely given the Math.max(0.01, next) guard), this could produce Infinity. Consider adding a guard:

       const nextScale = Math.max(0.01, next);
+      if (sessionScale === 0) return;
       const factorToApply = nextScale / sessionScale;
editor/grida-canvas/reducers/methods/scale.ts (2)

48-52: Missing null check for bounding rects.

Line 51 uses non-null assertion (!) when mapping node IDs to rects. If getNodeAbsoluteBoundingRect returns null for any node, this could cause issues downstream.

   const rects = selection.map(
-    (node_id) => context.geometry.getNodeAbsoluteBoundingRect(node_id)!
+    (node_id) => context.geometry.getNodeAbsoluteBoundingRect(node_id)
+  ).filter((r): r is cmath.Rectangle => r !== null);
+
+  if (rects.length !== selection.length) {
+    // Some nodes don't have bounding rects, skip gesture start
+    return;
   );

304-308: Assertion message could be more helpful.

The assertion at Line 305-307 provides useful context, but consider including the selection context:

       assert(
         parent_rect,
-        "Parent rect must be defined : " + parent_id + "/" + node_id
+        `Parent rect must be defined for parent ${parent_id} (child: ${node_id}, selection: ${selection.join(",")})`
       );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6940a59 and 259a68a.

⛔ Files ignored due to path filters (16)
  • editor/public/assets/css-cursors-grida/ew-resize-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/ew-resize-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/ew-resize-scale-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/ew-resize-scale-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/nesw-resize-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/nesw-resize-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/nesw-resize-scale-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/nesw-resize-scale-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/ns-resize-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/ns-resize-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/ns-resize-scale-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/ns-resize-scale-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/nwse-resize-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/nwse-resize-64-x32y32-000000.svg is excluded by !**/*.svg
  • editor/public/assets/css-cursors-grida/nwse-resize-scale-64-x32y32-000000.png is excluded by !**/*.png
  • editor/public/assets/css-cursors-grida/nwse-resize-scale-64-x32y32-000000.svg is excluded by !**/*.svg
📒 Files selected for processing (52)
  • AGENTS.md (1 hunks)
  • docs/AGENTS.md (1 hunks)
  • docs/wg/feat-authoring/parametric-scaling.md (3 hunks)
  • editor/components/cursor/cursor-data.ts (2 hunks)
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx (1 hunks)
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx (1 hunks)
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (4 hunks)
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts (3 hunks)
  • editor/grida-canvas-react/provider.tsx (1 hunks)
  • editor/grida-canvas-react/viewport/hotkeys.tsx (2 hunks)
  • editor/grida-canvas-react/viewport/surface.tsx (8 hunks)
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx (1 hunks)
  • editor/grida-canvas-react/viewport/ui/surface-vector-editor.tsx (1 hunks)
  • editor/grida-canvas/action.ts (2 hunks)
  • editor/grida-canvas/editor.i.ts (3 hunks)
  • editor/grida-canvas/editor.ts (1 hunks)
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts (1 hunks)
  • editor/grida-canvas/reducers/__tests__/history.test.ts (0 hunks)
  • editor/grida-canvas/reducers/__tests__/image-paint-clipboard.test.ts (0 hunks)
  • editor/grida-canvas/reducers/__tests__/vector-cut.test.ts (0 hunks)
  • editor/grida-canvas/reducers/__tests__/vector-self-remove.test.ts (0 hunks)
  • editor/grida-canvas/reducers/document.reducer.ts (2 hunks)
  • editor/grida-canvas/reducers/event-target.reducer.ts (7 hunks)
  • editor/grida-canvas/reducers/methods/index.ts (1 hunks)
  • editor/grida-canvas/reducers/methods/scale.ts (1 hunks)
  • editor/grida-canvas/reducers/methods/tool.ts (2 hunks)
  • editor/grida-canvas/reducers/methods/transform.ts (2 hunks)
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts (1 hunks)
  • editor/grida-canvas/reducers/schema/index.ts (1 hunks)
  • editor/grida-canvas/reducers/schema/schema.ts (1 hunks)
  • editor/grida-canvas/reducers/surface.reducer.ts (2 hunks)
  • editor/jest.config.js (1 hunks)
  • editor/jest.setup.ts (1 hunks)
  • editor/public/assets/css-cursors-grida/README.md (1 hunks)
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/controls/fe.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/controls/font-size.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/controls/scale-factor.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx (7 hunks)
  • editor/scaffolds/sidecontrol/ui/index.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/ui/number.tsx (3 hunks)
  • fixtures/test-grida/README.md (1 hunks)
  • packages/grida-canvas-vn/__mocks__/svg-pathdata.ts (0 hunks)
  • packages/grida-canvas-vn/__tests__/vn.test.ts (3 hunks)
  • packages/grida-canvas-vn/jest.config.ts (1 hunks)
  • packages/grida-canvas-vn/package.json (1 hunks)
  • packages/grida-canvas-vn/tsconfig.json (1 hunks)
  • packages/grida-canvas-vn/vn.ts (4 hunks)
  • packages/grida-cmath/index.ts (2 hunks)
  • packages/grida-number-input/src/n.test.ts (1 hunks)
  • packages/grida-number-input/src/n.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • packages/grida-canvas-vn/mocks/svg-pathdata.ts
  • editor/grida-canvas/reducers/tests/vector-self-remove.test.ts
  • editor/grida-canvas/reducers/tests/image-paint-clipboard.test.ts
  • editor/grida-canvas/reducers/tests/vector-cut.test.ts
  • editor/grida-canvas/reducers/tests/history.test.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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-react/viewport/ui/surface-vector-editor.tsx
  • editor/scaffolds/sidecontrol/controls/font-size.tsx
  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts
  • editor/scaffolds/sidecontrol/controls/scale-factor.tsx
  • editor/scaffolds/sidecontrol/ui/index.tsx
  • editor/scaffolds/sidecontrol/ui/number.tsx
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
  • editor/grida-canvas/reducers/schema/index.ts
  • editor/components/cursor/cursor-data.ts
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts
  • editor/grida-canvas/reducers/surface.reducer.ts
  • packages/grida-canvas-vn/vn.ts
  • packages/grida-number-input/src/n.test.ts
  • editor/scaffolds/sidecontrol/controls/fe.tsx
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • packages/grida-cmath/index.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • packages/grida-canvas-vn/jest.config.ts
  • editor/grida-canvas/reducers/methods/transform.ts
  • editor/jest.setup.ts
  • editor/grida-canvas/editor.i.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/action.ts
  • editor/grida-canvas-react/viewport/surface.tsx
  • packages/grida-canvas-vn/__tests__/vn.test.ts
  • editor/grida-canvas/reducers/methods/scale.ts
  • editor/grida-canvas/reducers/schema/schema.ts
  • packages/grida-number-input/src/n.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React.js 19 for web UI development

Files:

  • editor/grida-canvas-react/viewport/ui/surface-vector-editor.tsx
  • editor/scaffolds/sidecontrol/controls/font-size.tsx
  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
  • editor/scaffolds/sidecontrol/controls/scale-factor.tsx
  • editor/scaffolds/sidecontrol/ui/index.tsx
  • editor/scaffolds/sidecontrol/ui/number.tsx
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
  • editor/scaffolds/sidecontrol/controls/fe.tsx
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS 4 for styling

Files:

  • editor/grida-canvas-react/viewport/ui/surface-vector-editor.tsx
  • editor/scaffolds/sidecontrol/controls/font-size.tsx
  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas/reducers/methods/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts
  • editor/scaffolds/sidecontrol/controls/scale-factor.tsx
  • editor/scaffolds/sidecontrol/ui/index.tsx
  • editor/scaffolds/sidecontrol/ui/number.tsx
  • editor/grida-canvas-react/viewport/ui/floating-bar.tsx
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
  • editor/grida-canvas/reducers/schema/index.ts
  • editor/components/cursor/cursor-data.ts
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts
  • editor/grida-canvas/reducers/surface.reducer.ts
  • packages/grida-canvas-vn/vn.ts
  • packages/grida-number-input/src/n.test.ts
  • editor/scaffolds/sidecontrol/controls/fe.tsx
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • packages/grida-cmath/index.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • packages/grida-canvas-vn/jest.config.ts
  • editor/grida-canvas/reducers/methods/transform.ts
  • editor/jest.setup.ts
  • editor/grida-canvas/editor.i.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx
  • editor/grida-canvas-react/viewport/hotkeys.tsx
  • editor/grida-canvas/action.ts
  • editor/grida-canvas-react/viewport/surface.tsx
  • packages/grida-canvas-vn/__tests__/vn.test.ts
  • editor/grida-canvas/reducers/methods/scale.ts
  • editor/grida-canvas/reducers/schema/schema.ts
  • packages/grida-number-input/src/n.ts
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Place documentation files in the ./docs directory, which is the source of truth

Files:

  • docs/wg/feat-authoring/parametric-scaling.md
  • docs/AGENTS.md
**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Shadcn UI for reusable UI components

Files:

  • editor/components/cursor/cursor-data.ts
🧠 Learnings (22)
📚 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-react/viewport/ui/surface-vector-editor.tsx
  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts
  • packages/grida-canvas-vn/vn.ts
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/jest.setup.ts
  • editor/grida-canvas/editor.i.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
  • packages/grida-canvas-vn/__tests__/vn.test.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-react/viewport/ui/surface-vector-editor.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
  • editor/components/cursor/cursor-data.ts
  • packages/grida-canvas-vn/vn.ts
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/jest.setup.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.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 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-react/viewport/ui/surface-vector-editor.tsx
  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts
  • packages/grida-canvas-vn/vn.ts
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • editor/grida-canvas/reducers/document.reducer.ts
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • editor/grida-canvas/reducers/event-target.reducer.ts
  • editor/jest.setup.ts
  • editor/grida-canvas/editor.i.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx
  • editor/grida-canvas/action.ts
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas/reducers/schema/schema.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 : 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/grida-canvas-react/viewport/ui/surface-vector-editor.tsx
  • editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
  • editor/scaffolds/sidecontrol/controls/scale-factor.tsx
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts
  • editor/scaffolds/sidecontrol/controls/fe.tsx
  • editor/grida-canvas/reducers/methods/tool.ts
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • editor/grida-canvas/editor.i.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.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 React hooks for state management (imageSrc, shape, grid, maxRadius, gamma, jitter, opacity, color, customShapeImage, imageDataRef, sizeRef)

Applied to files:

  • editor/grida-canvas-react/viewport/ui/surface-vector-editor.tsx
  • editor/scaffolds/sidecontrol/chunks/scale-tool.tsx
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/grida-canvas-react/viewport/surface.tsx
📚 Learning: 2025-12-01T00:22:41.045Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:41.045Z
Learning: Applies to editor/**/next-env.d.ts : Include `next-env.d.ts` file in git for the project to avoid requiring dev/build runs for typechecking in CI environments

Applied to files:

  • editor/jest.config.js
  • packages/grida-canvas-vn/tsconfig.json
  • packages/grida-canvas-vn/jest.config.ts
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to **/components/**/*.{ts,tsx} : Use Shadcn UI for reusable UI components

Applied to files:

  • editor/scaffolds/sidecontrol/controls/font-size.tsx
  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.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:

  • editor/grida-canvas-react/provider.tsx
  • editor/grida-canvas/reducers/methods/index.ts
  • fixtures/test-grida/README.md
  • editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts
  • packages/grida-canvas-vn/tsconfig.json
  • editor/grida-canvas/reducers/schema/index.ts
  • editor/components/cursor/cursor-data.ts
  • packages/grida-canvas-vn/vn.ts
  • editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts
  • packages/grida-canvas-vn/package.json
  • packages/grida-canvas-vn/jest.config.ts
  • editor/grida-canvas-react/viewport/surface.tsx
  • editor/grida-canvas/reducers/schema/schema.ts
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to **/*.{tsx,jsx} : Use React.js 19 for web UI development

Applied to files:

  • editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx
📚 Learning: 2025-12-01T00:22:28.164Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:28.164Z
Learning: Validate `.grida` files using the `tool_io_grida` CLI tool for debugging parsing issues and structure verification

Applied to files:

  • fixtures/test-grida/README.md
📚 Learning: 2025-12-01T00:22:06.800Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas-fonts/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:06.800Z
Learning: Applies to crates/grida-canvas-fonts/**/scenario_*.rs : Scenario-specific comprehensive tests should be organized in files matching `scenario_*.rs`

Applied to files:

  • fixtures/test-grida/README.md
  • AGENTS.md
📚 Learning: 2025-12-01T00:22:06.800Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas-fonts/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:06.800Z
Learning: Applies to crates/grida-canvas-fonts/**/scenario_level2_*.rs : Level 2+ placeholder and limitation tests should be organized in files matching `scenario_level2_*.rs`

Applied to files:

  • fixtures/test-grida/README.md
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript 5 as the main language for most apps

Applied to files:

  • packages/grida-canvas-vn/tsconfig.json
📚 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/**/+(grida-canvas-wasm.js|grida-canvas-wasm.wasm) : Include WASM artifacts (`grida-canvas-wasm.js` and `grida-canvas-wasm.wasm`) in git for faster CI builds

Applied to files:

  • packages/grida-canvas-vn/tsconfig.json
  • AGENTS.md
  • packages/grida-canvas-vn/package.json
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to {editor,apps}/**/app/**/*.{ts,tsx} : Use Next.js 15 as the web framework

Applied to files:

  • packages/grida-canvas-vn/tsconfig.json
📚 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/components/cursor/cursor-data.ts
📚 Learning: 2025-12-01T00:21:55.314Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: apps/docs/AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:55.314Z
Learning: Applies to apps/docs/docs/** : Edit documentation files in `/docs/` (root directory) as the source of truth for all documentation content

Applied to files:

  • docs/AGENTS.md
  • AGENTS.md
📚 Learning: 2025-12-01T00:21:55.314Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: apps/docs/AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:55.314Z
Learning: Applies to apps/docs/docs/** : DO NOT edit files under `apps/docs/docs/` directory directly - this directory is auto-generated and will be overwritten on build

Applied to files:

  • docs/AGENTS.md
  • AGENTS.md
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to docs/**/*.md : Place documentation files in the ./docs directory, which is the source of truth

Applied to files:

  • docs/AGENTS.md
  • AGENTS.md
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Place Rust crates in the /crates directory as a monorepo

Applied to files:

  • AGENTS.md
📚 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/reducers/__tests__/apply-scale.roundtrip.test.ts
📚 Learning: 2025-12-01T00:21:48.564Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-01T00:21:48.564Z
Learning: Applies to **/*.{ts,tsx} : Use Lucide or Radix Icons for icons

Applied to files:

  • editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx
🧬 Code graph analysis (13)
editor/scaffolds/sidecontrol/controls/scale-factor.tsx (1)
editor/scaffolds/sidecontrol/ui/number.tsx (1)
  • InputPropertyNumber (127-246)
editor/scaffolds/sidecontrol/controls/alignment9.tsx (1)
packages/grida-cmath/index.ts (1)
  • Alignment9 (176-185)
editor/grida-canvas/reducers/__tests__/apply-scale.roundtrip.test.ts (1)
editor/grida-canvas/reducers/index.ts (1)
  • ReducerContext (20-31)
editor/grida-canvas/editor.ts (2)
editor/grida-canvas/editor.i.ts (1)
  • NodeID (136-136)
packages/grida-cmath/index.ts (1)
  • CardinalDirection (164-172)
editor/grida-canvas/reducers/document.reducer.ts (2)
editor/grida-canvas/reducers/utils/immer.ts (1)
  • updateState (3-13)
editor/grida-canvas/reducers/methods/scale.ts (1)
  • self_apply_scale_by_factor (697-759)
editor/grida-canvas/reducers/event-target.reducer.ts (3)
editor/grida-canvas/reducers/methods/hover.ts (1)
  • self_updateSurfaceHoverState (5-48)
editor/grida-canvas/reducers/methods/selection.ts (2)
  • self_clearSelection (93-115)
  • self_selectNode (33-91)
editor/grida-canvas/reducers/tools/target.ts (1)
  • getMarqueeSelection (240-295)
editor/grida-canvas/reducers/methods/transform.ts (1)
editor/grida-canvas/reducers/methods/scale.ts (1)
  • self_update_gesture_scale (167-187)
editor/grida-canvas/editor.i.ts (2)
packages/grida-cmath/index.ts (2)
  • Rectangle (147-158)
  • CardinalDirection (164-172)
packages/grida-canvas-schema/grida.ts (1)
  • NodeID (1101-1101)
editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (1)
editor/grida-canvas-react-starter-kit/starterkit-icons/upscale.tsx (1)
  • UpscaleIcon (3-22)
editor/grida-canvas/action.ts (3)
editor/grida-canvas/editor.i.ts (1)
  • NodeID (136-136)
packages/grida-canvas-schema/grida.ts (1)
  • NodeID (1101-1101)
packages/grida-cmath/index.ts (1)
  • CardinalDirection (164-172)
editor/grida-canvas-react/viewport/surface.tsx (1)
editor/grida-canvas-react/provider.tsx (1)
  • useToolState (497-500)
editor/grida-canvas/reducers/methods/scale.ts (6)
editor/grida-canvas/editor.ts (2)
  • id (4483-4485)
  • scale (139-184)
editor/grida-canvas/reducers/tools/snap.ts (2)
  • getSnapTargets (184-217)
  • threshold (20-23)
editor/grida-canvas/reducers/tools/snap-resize.ts (1)
  • snapObjectsResize (651-975)
editor/grida-canvas/reducers/node-transform.reducer.ts (1)
  • updateNodeTransform (70-202)
editor/grida-canvas/reducers/schema/index.ts (1)
  • schema (1-1)
editor/app/(dev)/canvas/experimental/grid/core/state.ts (1)
  • initial (6-9)
editor/grida-canvas/reducers/schema/schema.ts (2)
packages/grida-canvas-schema/grida.ts (9)
  • ICornerRadius (1471-1473)
  • IRectangularCornerRadius (1478-1484)
  • IPositioning (1778-1786)
  • ICSSDimension (1768-1771)
  • IPadding (1489-1498)
  • IFlexContainer (1508-1563)
  • IStroke (1622-1673)
  • IRectangularStrokeWidth (1678-1695)
  • IEffects (1586-1592)
packages/grida-canvas-cg/lib.ts (6)
  • VariableWidthProfile (1365-1367)
  • FeShadow (1073-1076)
  • FeLayerBlur (1080-1088)
  • FeBackdropBlur (1090-1098)
  • FeLiquidGlass (1106-1172)
  • FeNoise (1219-1294)
🪛 LanguageTool
docs/wg/feat-authoring/parametric-scaling.md

[style] ~98-~98: Consider replacing this word to strengthen your wording.
Context: ...y rewriting is an implementation detail and is not required for the core parameter-...

(AND_THAT)

docs/AGENTS.md

[style] ~27-~27: In American English, abbreviations like “etc.” require a period.
Context: ...nts, architecture documents, todo list, etc | yes | | [/docs/reference](....

(ETC_PERIOD)


[style] ~34-~34: In American English, abbreviations like “etc.” require a period.
Context: ... | Contributing, Support, Community, etc | yes ...

(ETC_PERIOD)

Comment thread AGENTS.md
Comment thread docs/AGENTS.md
Comment thread editor/grida-canvas/reducers/schema/schema.ts
Comment thread editor/scaffolds/sidecontrol/controls/alignment9.tsx
Comment thread editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx Outdated
Comment thread fixtures/test-grida/README.md
Comment thread packages/grida-canvas-vn/__tests__/vn.test.ts
Comment thread packages/grida-cmath/index.ts
Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (3)
editor/grida-canvas/reducers/schema/schema.ts (3)

1-33: Namespace approach is fine, but consider whether namespace is intentional for bundling/tree-shaking.
If consumers only need a few helpers, a const schema = { parametric_scale: ... } style module often tree-shakes better than namespace (depending on TS config/output).


86-97: scale_rect_about_anchor math looks correct; consider clamping/validating s at the boundary.
If callers ever pass unclamped/NaN scale, this can propagate invalid geometry. (Might already be guaranteed upstream—just worth verifying.)

 export function scale_rect_about_anchor(
   rect: cmath.Rectangle,
   anchor: cmath.Vector2,
   s: number
 ): cmath.Rectangle {
+  s = _clamp_scale(s);
   return {
     x: anchor[0] + (rect.x - anchor[0]) * s,
     y: anchor[1] + (rect.y - anchor[1]) * s,
     width: rect.width * s,
     height: rect.height * s,
   };
 }

99-128: Helpers are pragmatic; consider tightening typing to avoid accidental mutation of readonly shapes.
Right now scale_number_in_place<T extends object>(obj: T, key: keyof T, s: number) will happily accept structurally readonly objects at compile-time depending on how it’s called, then mutate anyway. If these are only used on draft/mutable nodes, fine—otherwise consider constraining T to a mutable record shape.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 259a68a and 446804f.

📒 Files selected for processing (5)
  • editor/components/cursor/cursor-data.ts (2 hunks)
  • editor/grida-canvas/reducers/schema/schema.ts (1 hunks)
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx (1 hunks)
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx (6 hunks)
  • packages/grida-canvas-vn/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/grida-canvas-vn/tsconfig.json
  • editor/components/cursor/cursor-data.ts
  • editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
  • editor/scaffolds/sidecontrol/controls/alignment9.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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/schema/schema.ts
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS 4 for styling

Files:

  • editor/grida-canvas/reducers/schema/schema.ts
🧠 Learnings (3)
📚 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/schema/schema.ts
📚 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:

  • editor/grida-canvas/reducers/schema/schema.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 Canvas 2D API with path commands for rendering geometric shapes (circle, square, triangle, etc.)

Applied to files:

  • editor/grida-canvas/reducers/schema/schema.ts
⏰ 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). (2)
  • GitHub Check: cargo test
  • GitHub Check: test
🔇 Additional comments (2)
editor/grida-canvas/reducers/schema/schema.ts (2)

220-227: The code correctly assumes vector_network is present for vector nodes. According to the VectorNode type definition in the schema (packages/grida-canvas-schema/grida.ts), vector_network is a required property without an optional marker. The unconditional call in apply_node is appropriate—if a node has type === "vector", the type system guarantees vector_network exists. No defensive guard is necessary.


229-310: apply_node correctly preserves "auto" values and handles zero-dimensions, but does not respect node lock constraints.

The implementation validates:

  • "preserve-auto": width/height only scale when numeric (lines 238-239), preventing conversion of "auto" to fixed values
  • zero-dimension handling: numeric 0 remains 0 after scaling

However, the function unconditionally scales all numeric properties without checking for lock flags. Evidence in the codebase shows Grida nodes support a locked field (seen in editor state and UI controls), but apply_node does not reference or respect any lock constraints. If a node or property is marked as locked, it will still be scaled by this function.

Recommend verifying whether:

  1. Locked properties should skip scaling (e.g., locked width/height, aspect-ratio locks)
  2. Lock-checking should happen in apply_node or at the call site in scale.ts
  3. This behavior is intentional (scaling ignores locks) or a missing feature

Comment on lines +35 to +84
export function _clamp_scale(s: number) {
if (!Number.isFinite(s)) return 1;
return Math.max(0.01, s);
}

/**
* Calculates a uniform similarity scale factor from a movement delta.
*
* - Always returns a clamped scale \(s \ge 0.01\)
* - If `q` is provided, the return value is quantized to `q` precision
*
* Intended usage:
* - **Interactive gestures / UI**: pass `q = 0.01` to keep stable `0.00x` precision.
* - **Programmatic commands / API** (e.g. `applyScale(factor)`): use the factor as-is
* (developer intent) and do not route it through this helper.
*/
export function _uniform_scale_factor(
initial_bounds: cmath.Rectangle,
movement: cmath.Vector2,
q?: number
) {
const w = initial_bounds.width;
const h = initial_bounds.height;

if (w === 0 && h === 0) return 1;

const dominantAxis =
Math.abs(movement[0]) > Math.abs(movement[1]) ? "x" : "y";

let s: number;
if (dominantAxis === "x") {
if (w === 0) {
s = h !== 0 ? (h + movement[1]) / h : 1;
} else {
s = (w + movement[0]) / w;
}
} else {
if (h === 0) {
s = w !== 0 ? (w + movement[0]) / w : 1;
} else {
s = (h + movement[1]) / h;
}
}

const clamped = _clamp_scale(s);
if (typeof q === "number" && Number.isFinite(q) && q > 0) {
return cmath.quantize(clamped, q);
}
return clamped;
}
Copy link
Copy Markdown

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:

# Find all call sites of _uniform_scale_factor
rg "_uniform_scale_factor" --type ts --type tsx -n

Repository: gridaco/grida

Length of output: 84


🏁 Script executed:

# Also search for any references to quantization in the codebase
rg "quantize" --type ts --type tsx -B2 -A2

Repository: gridaco/grida

Length of output: 84


🏁 Script executed:

# Check the git context to understand the PR's requirements
git log --oneline -20

Repository: gridaco/grida

Length of output: 75


🏁 Script executed:

# Look for any PR descriptions or comments about no-quantization
git show --stat | head -50

Repository: gridaco/grida

Length of output: 2921


🏁 Script executed:

# Search for _uniform_scale_factor call sites using just ts type
rg "_uniform_scale_factor" -n --type ts

Repository: gridaco/grida

Length of output: 255


🏁 Script executed:

# Also search without type restriction to catch all files
rg "_uniform_scale_factor" -n

Repository: gridaco/grida

Length of output: 255


🏁 Script executed:

# Look at the specific file to understand its context and usage
head -150 editor/grida-canvas/reducers/schema/schema.ts | tail -100

Repository: gridaco/grida

Length of output: 2394


🏁 Script executed:

# Search for apply_node function mentioned in the review
rg "apply_node" -n --type ts -A3

Repository: gridaco/grida

Length of output: 2450


🏁 Script executed:

# Read the call site context in scale.ts around line 642
sed -n '630,680p' editor/grida-canvas/reducers/methods/scale.ts

Repository: gridaco/grida

Length of output: 1555


🏁 Script executed:

# Get more context about this function
sed -n '600,750p' editor/grida-canvas/reducers/methods/scale.ts

Repository: gridaco/grida

Length of output: 4426


🏁 Script executed:

# Check what happens with the returned value - look for where `s` is used
sed -n '640,670p' editor/grida-canvas/reducers/methods/scale.ts

Repository: gridaco/grida

Length of output: 915


🏁 Script executed:

# Check the git diff to understand the PR's actual changes and intent
git diff HEAD~1 HEAD --stat | head -20

Repository: gridaco/grida

Length of output: 243


🏁 Script executed:

# Look for any PR description or commit message mentioning quantization
git log --all --oneline | head -30

Repository: gridaco/grida

Length of output: 136


🏁 Script executed:

# Check if there are any other usages of the q parameter or quantization context
rg "no-quantization\|no quantization" -i

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Search for recent changes to schema.ts or scale.ts
git log -p --follow -S "_uniform_scale_factor" -- editor/grida-canvas/reducers/schema/schema.ts | head -100

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Check the apply_node function to confirm it directly modifies node geometry
sed -n '229,280p' editor/grida-canvas/reducers/schema/schema.ts

Repository: gridaco/grida

Length of output: 2144


🏁 Script executed:

# Look at the test file to understand expected behavior
cat editor/grida-canvas/reducers/schema/__tests__/schema.parametric-scale.test.ts | head -200

Repository: gridaco/grida

Length of output: 3203


Remove quantization from _uniform_scale_factor to prevent baking quantized geometry.

The helper currently applies cmath.quantize(clamped, q) before returning (lines 80-82), and this quantized value is directly passed to apply_node(node, s) at scale.ts:668, which persists all geometry properties (width, height, corner_radius, stroke_width, etc.) with the quantized scale factor. Instead, return the unquantized scale and let UI code apply quantization only for display purposes.

 export function _uniform_scale_factor(
   initial_bounds: cmath.Rectangle,
   movement: cmath.Vector2,
-  q?: number
 ) {
   const w = initial_bounds.width;
   const h = initial_bounds.height;
@@
   const clamped = _clamp_scale(s);
-  if (typeof q === "number" && Number.isFinite(q) && q > 0) {
-    return cmath.quantize(clamped, q);
-  }
   return clamped;
 }

Update the docstring and call site at scale.ts:642 accordingly.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +152 to +200
export function _fe_blur(initial: cg.FeLayerBlur, s: number): cg.FeLayerBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
// x1/y1/x2/y2 remain unchanged (normalized)
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
}
}

export function _fe_backdrop_blur(
initial: cg.FeBackdropBlur,
s: number
): cg.FeBackdropBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find the @grida/cg package and its type definitions
find . -name "*.ts" -o -name "*.d.ts" | grep -E "(grida|cg)" | head -20

Repository: gridaco/grida

Length of output: 1146


🏁 Script executed:

# Search for the type definitions of FeLayerBlur and FeBackdropBlur
rg "FeLayerBlur|FeBackdropBlur" --type ts --type tsx -B 2 -A 5 | head -100

Repository: gridaco/grida

Length of output: 84


🏁 Script executed:

# Look for the blur type definition
rg "blur.*:.*{.*type" --type ts -A 3 | head -50

Repository: gridaco/grida

Length of output: 165


🏁 Script executed:

# Check the imports in the target file to understand where cg comes from
head -30 editor/grida-canvas/reducers/schema/schema.ts

Repository: gridaco/grida

Length of output: 1132


🏁 Script executed:

# Find @grida/cg package location
find . -name "package.json" -type f | xargs grep -l "@grida/cg" | head -5

Repository: gridaco/grida

Length of output: 248


🏁 Script executed:

# Search for @grida/cg type definitions directory
find . -type d -name "cg" | grep -E "packages|src" | head -10

Repository: gridaco/grida

Length of output: 85


🏁 Script executed:

# Search for FeLayerBlur type definition
rg "FeLayerBlur" --type ts -B 3 -A 10 | head -100

Repository: gridaco/grida

Length of output: 5932


🏁 Script executed:

# Search for blur type definitions more broadly
rg "type.*blur" --type ts -i | head -30

Repository: gridaco/grida

Length of output: 2465


🏁 Script executed:

# Check if there's a .d.ts file for cg module
find . -name "*.d.ts" | xargs grep -l "FeLayerBlur\|FeBackdropBlur" 2>/dev/null

Repository: gridaco/grida

Length of output: 41


🏁 Script executed:

# Get the complete FeBlur and related type definitions
sed -n '1,300p' packages/grida-canvas-cg/lib.ts | grep -A 15 "export type FeBlur\|export type FeGaussian\|export type FeProgressive"

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Get more complete context around the blur type definitions
sed -n '1,500p' packages/grida-canvas-cg/lib.ts | tail -200

Repository: gridaco/grida

Length of output: 2656


🏁 Script executed:

# Check tsconfig.json for TypeScript strict settings
find . -name "tsconfig.json" -type f | head -3 | xargs cat

Repository: gridaco/grida

Length of output: 1604


🏁 Script executed:

# Verify the exact blur type union by finding the complete definition
rg "export type FeBlur.*=.*\|" packages/grida-canvas-cg/ -A 1

Repository: gridaco/grida

Length of output: 180


🏁 Script executed:

# Get the full blur type definitions
rg "export type FeGaussianBlur|export type FeProgressiveBlur|export interface IFeGaussian|export interface IFeProgressive" packages/grida-canvas-cg/ -A 10

Repository: gridaco/grida

Length of output: 2383


🏁 Script executed:

# Check if blur property could be undefined
rg "blur\?" packages/grida-canvas-cg/lib.ts | head -20

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Verify the complete FeLayerBlur and FeBackdropBlur definitions
rg "export type FeLayerBlur|export type FeBackdropBlur" packages/grida-canvas-cg/lib.ts -A 8

Repository: gridaco/grida

Length of output: 419


🏁 Script executed:

# Check actual usage in the codebase - do these functions get called with potentially invalid blur objects?
rg "_fe_blur|_fe_backdrop_blur" editor/grida-canvas/ -B 2 -A 5 | head -50

Repository: gridaco/grida

Length of output: 3664


🏁 Script executed:

# Verify if there's any code that creates FeLayerBlur/FeBackdropBlur with undefined blur
rg "FeLayerBlur|FeBackdropBlur" editor/grida-canvas/ -B 3 -A 3 | grep -A 3 -B 3 "blur.*undefined\|undefined.*blur"

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Check if there are any other switch statements on blur.type to see if they also lack defaults
rg "switch.*blur\.type\|switch.*blur\[" editor/grida-canvas/ -B 2 -A 15 | head -80

Repository: gridaco/grida

Length of output: 39


Add default case to _fe_blur and _fe_backdrop_blur for defensive programming.

Both functions implicitly return undefined if blur.type doesn't match either case. While the type system ensures blur.type is strictly "blur" | "progressive-blur" (see FeBlur = FeGaussianBlur | FeProgressiveBlur in packages/grida-canvas-cg/lib.ts), TypeScript strict mode is disabled in your config, so exhaustiveness checking isn't enforced. Adding a default fallback that returns initial is good defensive practice:

 export function _fe_blur(initial: cg.FeLayerBlur, s: number): cg.FeLayerBlur {
   const blur = initial.blur;
   switch (blur.type) {
     case "blur":
       return { ... };
     case "progressive-blur":
       return { ... };
+    default:
+      return initial;
   }
 }

Apply the same pattern to _fe_backdrop_blur.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function _fe_blur(initial: cg.FeLayerBlur, s: number): cg.FeLayerBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
// x1/y1/x2/y2 remain unchanged (normalized)
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
}
}
export function _fe_backdrop_blur(
initial: cg.FeBackdropBlur,
s: number
): cg.FeBackdropBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
}
}
export function _fe_blur(initial: cg.FeLayerBlur, s: number): cg.FeLayerBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
// x1/y1/x2/y2 remain unchanged (normalized)
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
default:
return initial;
}
}
export function _fe_backdrop_blur(
initial: cg.FeBackdropBlur,
s: number
): cg.FeBackdropBlur {
const blur = initial.blur;
switch (blur.type) {
case "blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
},
};
case "progressive-blur":
return {
...initial,
blur: {
...blur,
radius: blur.radius * s,
radius2: blur.radius2 * s,
},
};
default:
return initial;
}
}
🤖 Prompt for AI Agents
In editor/grida-canvas/reducers/schema/schema.ts around lines 152 to 200, the
switch statements in _fe_blur and _fe_backdrop_blur do not have a default branch
and can implicitly return undefined if blur.type is unexpected; add a default
case to each switch that returns the unchanged initial object (e.g., return
initial) so the functions always return a valid cg.FeLayerBlur /
cg.FeBackdropBlur and are defensive against unexpected values.

@softmarshmallow softmarshmallow merged commit 27828da into main Dec 14, 2025
11 checks passed
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.

1 participant