Skip to content

Refactor vector network handling and add corner radius comparison#555

Merged
softmarshmallow merged 5 commits intomainfrom
canary
Mar 2, 2026
Merged

Refactor vector network handling and add corner radius comparison#555
softmarshmallow merged 5 commits intomainfrom
canary

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Mar 2, 2026

  • New Features

    • Flattened shape results now include optional corner-radius metadata so rendering can apply radius as an effect or use baked geometry.
    • Shape builders preserve corner-radius in geometry for more consistent flattening.
  • Bug Fixes

    • Improved conic subdivision for more accurate curve conversion and fidelity.
  • Tests

    • Added pixel-compare tests and goldens validating flattened outputs and corner-radius rendering.

- Refactored vector network construction methods to utilize `VectorGeometryShape`, enhancing shape representation with corner radius preservation.
- Replaced deprecated `build_*_vector_network` functions with `build_*_vector_geometry` counterparts for polygons and stars.
- Introduced new dependencies: `rendiff` for rendering differences and `imgref` for image reference handling.
- Updated `Cargo.toml` to include new dependencies and their versions.

This update aims to improve the accuracy and flexibility of vector shape handling in the grida-canvas project.
…ing logic

- Introduced a new example `golden_corner_radius_backends.rs` to visually compare the differences between Skia's native `rrect` and the `corner_path` PathEffect for corner radius rendering.
- Added a new PNG output for the visual comparison.
- Enhanced the `JSONFlattenResult` structure to include corner radius information, improving the flattening process for shapes with corner radius.
- Updated various methods to utilize the new `JSONFlattenResult`, ensuring accurate representation of vector networks with corner radius effects.

This update aims to improve the understanding of corner radius rendering differences and enhance the shape flattening capabilities in the grida-canvas project.
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 2, 2026 3:25pm
grida Ready Ready Preview, Comment Mar 2, 2026 3:25pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
code Ignored Ignored Mar 2, 2026 3:25pm
legacy Ignored Ignored Mar 2, 2026 3:25pm
backgrounds Skipped Skipped Mar 2, 2026 3:25pm
blog Skipped Skipped Mar 2, 2026 3:25pm
viewer Skipped Skipped Mar 2, 2026 3:25pm

Request Review

@softmarshmallow softmarshmallow added the cg Core Graphics label Mar 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

Walkthrough

Adds a new public FlattenResult (VectorNetwork + optional corner_radius) and propagates it through WASM, editor, and native layers; refactors shape builders to geometry-first representations, improves conic subdivision, introduces JSONFlattenResult, and adds pixel-accurate flattening tests and platform cfg gating for wasm32.

Changes

Cohort / File(s) Summary
FlattenResult Type Definition
crates/grida-canvas-wasm/lib/index.ts, packages/grida-canvas-vn/vn.ts
Adds exported FlattenResult interface extending VectorNetwork with optional corner_radius.
WASM Bindings & Surface API
crates/grida-canvas-wasm/lib/modules/canvas.ts, crates/grida-canvas-wasm/src/wasm_application.rs, editor/grida-canvas/backends/wasm.ts
Updates toVectorNetwork signatures/return types to `FlattenResult
Editor API & Reducer
editor/grida-canvas/editor.i.ts, editor/grida-canvas/editor.ts, editor/grida-canvas/reducers/methods/flatten.ts
Updates provider/action/editor method signatures to use FlattenResult; refactors flattening consumer logic and fallback to return FlattenResult; removes legacy modeCornerRadius.
JSON IO Shape
crates/grida-canvas/src/io/io_grida.rs
Adds JSONFlattenResult struct wrapping flattened vector_network and optional corner_radius with serde annotations.
Application Layer
crates/grida-canvas/src/window/application.rs, crates/grida-canvas/src/window/application_emscripten.rs
Changes to_vector_network to return Option<JSONFlattenResult>; preserves/propagates corner_radius; gates image/font channels behind non-wasm cfg.
Shape Builders (geometry-first)
crates/grida-canvas/src/shape/polygon.rs, crates/grida-canvas/src/shape/regular_polygon.rs, crates/grida-canvas/src/shape/regular_star.rs, crates/grida-canvas/src/shape/mod.rs
Replaces vector-network builders with geometry-returning builders (*_vector_geometry), preserving corner_radius in geometry representation and updating callers.
RRect & Corner Path Handling
crates/grida-canvas/src/shape/rrect.rs, crates/grida-canvas/src/shape/corner.rs
Switches rrect construction to path→VectorNetwork conversion, expands docs on corner backends, and adds tests/documentation on curve differences.
Vector Geometry → Network Conversion
crates/grida-canvas/src/shape/vector.rs
Implements From<VectorGeometryShape> for VectorNetwork to convert geometry to network (regions empty).
Conic Subdivision
crates/grida-canvas/src/vectornetwork/vn.rs
Changes conic->cubic handling: subdivide conic into multiple quads (pow2=2 → 4 quads), convert each to cubic and append as separate segments with fallback.
Tests: Pixel diff & Unit updates
crates/grida-canvas/tests/flatten_rendiff.rs, crates/grida-canvas/tests/shape_to_vector_network.rs, crates/grida-canvas/examples/golden_corner_radius_backends.rs
Adds comprehensive pixel-diff tests validating flattened output (rendiff), updates rrect test to check curved-segment counts, and adds example generating golden PNG for corner radius backends.
Scheduler & FFI Naming
crates/grida-canvas/src/sys/scheduler.rs, crates/grida-canvas/src/os/emscripten.rs
Adds with_max_fps, per-frame timing behind non-wasm cfg and provides wasm stubs; renames FFI parameter identifiers to snake_case.
Dependencies & Versioning
crates/grida-canvas/Cargo.toml, crates/grida-canvas-wasm/package.json, packages/grida-canvas-sdk-render-figma/package.json
Adds rendiff and imgref deps; bumps wasm package version to 0.90.0-canary.9 and updates SDK dependency.

Sequence Diagram

sequenceDiagram
    participant Node as Shape Node
    participant App as Application/Flatten Pipeline
    participant Builder as Geometry Builder / Path Builder
    participant VN as VectorNetwork Converter
    participant Result as FlattenResult / JSONFlattenResult

    Node->>App: to_vector_network(node_id)
    App->>Builder: build geometry or path for node
    alt geometry with corner radius
        Builder->>Result: return VectorGeometryShape (preserve corner_radius)
        Result->>VN: convert VectorGeometryShape -> VectorNetwork
    else path or baked geometry
        Builder->>VN: produce VectorNetwork (path->VN)
    end
    VN->>Result: assemble FlattenResult { vector_network, corner_radius? }
    Result-->>App: JSONFlattenResult / FlattenResult serialized
    App-->>Node: FlattenResult (vector_network + optional corner_radius)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

vector, wasm32, breaking

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: refactoring vector network handling to use geometry-based builders and adding corner radius comparison tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 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.

…attributes

- Prefix unused wasm stub param: url → _url in webfont_helper
- Rename FFI param names to snake_case in os/emscripten.rs
- Guard unreachable!() in application_emscripten with cfg(not(emscripten))
- cfg-gate native-only fields/methods in FrameScheduler and UnknownTargetApplication
- Suppress deprecated warnings on intentional Anchored match arms
@vercel vercel Bot temporarily deployed to Preview – viewer March 2, 2026 13:51 Inactive
@vercel vercel Bot temporarily deployed to Preview – grida March 2, 2026 13:51 Inactive
@vercel vercel Bot temporarily deployed to Preview – blog March 2, 2026 13:51 Inactive
@vercel vercel Bot temporarily deployed to Preview – backgrounds March 2, 2026 13:51 Inactive
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f81665ed5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

id: node.id,
active: node.active,
corner_radius: modeProperties.cornerRadius(node),
corner_radius: resultCornerRadius ?? undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve fallback corner radius when flattening without WASM

When context.vector is unavailable or throws (e.g. DOM backend/tests), self_flattenNode falls back to toVectorNetworkFallback, which currently returns only vertices/segments and never a corner_radius. Because this assignment now relies exclusively on resultCornerRadius, flattened polygon/star nodes lose their rounded corners and become sharp in non-WASM paths, whereas the previous implementation preserved radius from the source node. This is a behavior regression specifically in fallback environments.

Useful? React with 👍 / 👎.

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: 4

Caution

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

⚠️ Outside diff range comments (2)
crates/grida-canvas/src/sys/scheduler.rs (1)

23-40: ⚠️ Potential issue | 🟡 Minor

Potential panic on zero FPS values.

If target_fps = 0 is passed to new() (line 27) or max_fps = 0 to with_max_fps() (line 38), the division 1_000_000 / 0 will panic at runtime.

🛡️ Proposed fix to guard against zero
     pub fn new(target_fps: u32) -> Self {
+        let target_fps = target_fps.max(1); // Prevent division by zero
         Self {
             #[cfg(not(target_arch = "wasm32"))]
             last_frame_time: Instant::now(),
             target_frame_time: Duration::from_micros(1_000_000 / target_fps as u64),
     pub fn with_max_fps(mut self, max_fps: u32) -> Self {
+        let max_fps = max_fps.max(1); // Prevent division by zero
         self.max_frame_time = Some(Duration::from_micros(1_000_000 / max_fps as u64));
         self
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/sys/scheduler.rs` around lines 23 - 40, new() and
with_max_fps() currently divide by the provided FPS which will panic if
target_fps or max_fps is 0; fix by guarding against zero before the division
(e.g., use target_fps.max(1) and max_fps.max(1) or check for zero and substitute
1) when computing Duration::from_micros so the division cannot divide by zero;
update the calculations in the new() constructor and in with_max_fps() to use
the non-zero-guarded value (referencing new, with_max_fps, target_frame_time and
max_frame_time).
crates/grida-canvas/src/io/io_grida.rs (1)

490-490: ⚠️ Potential issue | 🟡 Minor

Resolve the typos CI failure on alignement.

Line 490 is currently flagged by the typos workflow. Please rename to alignment (preferred) or explicitly whitelist if this spelling is intentionally preserved for compatibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/io/io_grida.rs` at line 490, The identifier
"alignement" is misspelled; rename it to "alignment" throughout the codebase:
update the struct/field/key usage named alignement (e.g., the instance in
io_grida.rs where alignement: Alignment::CENTER is set), change the
declaration/definition to alignment, and update every reference (constructors,
pattern matches, serialization/deserialization keys, tests) to use alignment so
compilation and typos CI pass; if this name must remain for API compatibility,
whitelist the token in the typos config instead of renaming.
🧹 Nitpick comments (4)
crates/grida-canvas/src/shape/vector.rs (1)

87-95: Clarify intentional corner-radius drop in this conversion.

VectorGeometryShape.corner_radius is not carried into VectorNetwork. A short inline note here would prevent accidental misuse.

Suggested clarification
 impl From<VectorGeometryShape> for VectorNetwork {
     fn from(shape: VectorGeometryShape) -> Self {
+        // NOTE: `corner_radius` is intentionally excluded from `VectorNetwork`.
+        // Use flatten/geometry result types when corner-radius effect must be preserved.
         VectorNetwork {
             vertices: shape.vertices,
             segments: shape.segments,
             regions: vec![],
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/shape/vector.rs` around lines 87 - 95, The conversion
From<VectorGeometryShape> for VectorNetwork currently drops
VectorGeometryShape.corner_radius; add a brief inline comment above the impl (or
inside the function) clearly stating that corner_radius is intentionally omitted
during conversion (and why — e.g., network stores only raw vertices/segments and
corner radii are handled elsewhere), referencing
VectorGeometryShape.corner_radius and VectorNetwork to avoid accidental misuse
or future regressions.
crates/grida-canvas/tests/flatten_rendiff.rs (1)

87-90: Guard vn_paths[0] with an explicit non-empty assertion helper.

Current indexing panics with a generic out-of-bounds error. A labeled assert gives better failure diagnostics for flatten regressions.

♻️ Suggested refactor
+fn first_path<'a>(paths: &'a [skia_safe::Path], name: &str) -> &'a skia_safe::Path {
+    assert!(!paths.is_empty(), "{name}: no paths from vector network");
+    &paths[0]
+}
...
-    let flattened = render_to_rgba(&vn_paths[0], w, h);
+    let flattened = render_to_rgba(first_path(&vn_paths, "rect_no_radius"), w, h);

Also applies to: 113-116, 148-151, 179-182, 212-215, 245-247, 270-273, 298-301

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/tests/flatten_rendiff.rs` around lines 87 - 90, Add an
explicit non-empty assertion before you index vn_paths: after calling
build_rect_vector_network(...) and assigning vn_paths = vn.to_paths(), assert
!vn_paths.is_empty() with a descriptive message (e.g.
assert!(!vn_paths.is_empty(), "vn_paths must contain at least one path for
render_to_rgba(test-case-name)")) or use vn_paths.first().expect("vn_paths must
not be empty") when passing the first element into render_to_rgba; apply the
same change to every place you currently use vn_paths[0] (the occurrences around
the calls to build_rect_vector_network, vn.to_paths(), and render_to_rgba).
crates/grida-canvas/src/shape/rrect.rs (1)

136-143: Prefer a tolerance-based assertion over fixed diff_count > 100.

This absolute cutoff can become flaky across rasterization changes. A histogram/tolerance-style assertion (like the later rendiff test) will keep intent while improving CI stability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/shape/rrect.rs` around lines 136 - 143, Replace the
brittle absolute check on diff_count with a tolerance/histogram-style assertion:
compute a normalized metric (e.g., fraction = diff_count as f64 / total_pixels
or a percentile from the pixel-difference histogram used in the later rendiff
test) and assert that this fraction/percentile exceeds a small configurable
threshold rather than hardcoding 100; update the assertion around diff_count
(the code comparing rrect vs corner_path) to use that normalized threshold or
histogram percentile so CI is robust to rasterization variance.
crates/grida-canvas/examples/golden_corner_radius_backends.rs (1)

94-99: Harden output write by creating the parent directory first.

This avoids panics when goldens/ is absent in fresh environments.

Proposed robustness patch
     let out_path = concat!(
         env!("CARGO_MANIFEST_DIR"),
         "/goldens/corner_radius_backends.png"
     );
-    std::fs::write(out_path, data.as_bytes()).unwrap();
+    if let Some(parent) = std::path::Path::new(out_path).parent() {
+        std::fs::create_dir_all(parent).expect("create goldens directory");
+    }
+    std::fs::write(out_path, data.as_bytes()).expect("write golden image");
     println!("Written to {out_path}");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/examples/golden_corner_radius_backends.rs` around lines
94 - 99, The write to out_path can panic if the goldens directory doesn't exist;
before calling std::fs::write(out_path, ...).unwrap(), compute the parent
directory from Path::new(out_path).parent() and call
std::fs::create_dir_all(...) to ensure it exists, then proceed with the write
and handle or propagate errors consistently (replace .unwrap() with proper error
handling if desired). Use the existing out_path variable and std::fs APIs to
implement this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/grida-canvas/src/layout/into_taffy.rs`:
- Around line 156-159: The match arm for LayoutPositioningBasis::Anchored
currently calls unreachable!() which can panic on legacy/deserialized inputs;
replace that panic with a safe fallback by mapping Anchored to the same handling
as LayoutPositioningBasis::Relative (i.e., perform the Relative positioning
conversion in the into_taffy match arm for Anchored), and optionally emit a
debug/warn log (using the crate's logging/tracing mechanism if available)
indicating an obsolete Anchored value was encountered; update the match arm for
Anchored to reuse the Relative branch logic instead of panicking.

In `@crates/grida-canvas/src/shape/regular_star.rs`:
- Around line 72-76: The current check only guards points.is_empty() but
build_simple_polygon_vector_geometry (via SimplePolygonShape) asserts at least 3
points; change the guard to return VectorGeometryShape::new() when points.len()
< 3 to avoid assertion panics — locate the build_star_points call, the local
points variable, and the subsequent call to
build_simple_polygon_vector_geometry(&SimplePolygonShape { ... }) and replace
the is_empty() check with a length check (points.len() < 3) so 0–2 point cases
short-circuit safely.

In `@editor/grida-canvas/editor.i.ts`:
- Around line 2733-2745: The actions interface still returns vn.VectorNetwork |
null which drops FlattenResult metadata; update the return type on
IDocumentVectorInterfaceActions' toVectorNetwork (and any corresponding method
declarations/signatures) from vn.VectorNetwork | null to vn.FlattenResult | null
so the corner_radius and other flatten metadata are preserved at the API
boundary and adjust any callers/type references that depend on the old return
type.

In `@editor/grida-canvas/reducers/methods/flatten.ts`:
- Around line 66-89: The fallback flattening path can lose a rectangle's
corner_radius because flattenResult from toVectorNetworkFallback has no
corner_radius, and the code then clears it; change the assignment so that when
building vectornode (in this file's flatten logic) you preserve the original
node.corner_radius if resultCornerRadius is undefined (i.e., use
resultCornerRadius ?? (node.corner_radius ?? undefined)), but still clear
corner_radius when the rust-side result explicitly indicates geometry is baked
(when resultCornerRadius is explicitly null/absent and node is not a rounded
rect); reference the symbols flattenResult, toVectorNetworkFallback,
resultCornerRadius, corner_radius and vectornode to implement this conditional
preservation.

---

Outside diff comments:
In `@crates/grida-canvas/src/io/io_grida.rs`:
- Line 490: The identifier "alignement" is misspelled; rename it to "alignment"
throughout the codebase: update the struct/field/key usage named alignement
(e.g., the instance in io_grida.rs where alignement: Alignment::CENTER is set),
change the declaration/definition to alignment, and update every reference
(constructors, pattern matches, serialization/deserialization keys, tests) to
use alignment so compilation and typos CI pass; if this name must remain for API
compatibility, whitelist the token in the typos config instead of renaming.

In `@crates/grida-canvas/src/sys/scheduler.rs`:
- Around line 23-40: new() and with_max_fps() currently divide by the provided
FPS which will panic if target_fps or max_fps is 0; fix by guarding against zero
before the division (e.g., use target_fps.max(1) and max_fps.max(1) or check for
zero and substitute 1) when computing Duration::from_micros so the division
cannot divide by zero; update the calculations in the new() constructor and in
with_max_fps() to use the non-zero-guarded value (referencing new, with_max_fps,
target_frame_time and max_frame_time).

---

Nitpick comments:
In `@crates/grida-canvas/examples/golden_corner_radius_backends.rs`:
- Around line 94-99: The write to out_path can panic if the goldens directory
doesn't exist; before calling std::fs::write(out_path, ...).unwrap(), compute
the parent directory from Path::new(out_path).parent() and call
std::fs::create_dir_all(...) to ensure it exists, then proceed with the write
and handle or propagate errors consistently (replace .unwrap() with proper error
handling if desired). Use the existing out_path variable and std::fs APIs to
implement this.

In `@crates/grida-canvas/src/shape/rrect.rs`:
- Around line 136-143: Replace the brittle absolute check on diff_count with a
tolerance/histogram-style assertion: compute a normalized metric (e.g., fraction
= diff_count as f64 / total_pixels or a percentile from the pixel-difference
histogram used in the later rendiff test) and assert that this
fraction/percentile exceeds a small configurable threshold rather than
hardcoding 100; update the assertion around diff_count (the code comparing rrect
vs corner_path) to use that normalized threshold or histogram percentile so CI
is robust to rasterization variance.

In `@crates/grida-canvas/src/shape/vector.rs`:
- Around line 87-95: The conversion From<VectorGeometryShape> for VectorNetwork
currently drops VectorGeometryShape.corner_radius; add a brief inline comment
above the impl (or inside the function) clearly stating that corner_radius is
intentionally omitted during conversion (and why — e.g., network stores only raw
vertices/segments and corner radii are handled elsewhere), referencing
VectorGeometryShape.corner_radius and VectorNetwork to avoid accidental misuse
or future regressions.

In `@crates/grida-canvas/tests/flatten_rendiff.rs`:
- Around line 87-90: Add an explicit non-empty assertion before you index
vn_paths: after calling build_rect_vector_network(...) and assigning vn_paths =
vn.to_paths(), assert !vn_paths.is_empty() with a descriptive message (e.g.
assert!(!vn_paths.is_empty(), "vn_paths must contain at least one path for
render_to_rgba(test-case-name)")) or use vn_paths.first().expect("vn_paths must
not be empty") when passing the first element into render_to_rgba; apply the
same change to every place you currently use vn_paths[0] (the occurrences around
the calls to build_rect_vector_network, vn.to_paths(), and render_to_rgba).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 182288e and f81665e.

⛔ Files ignored due to path filters (4)
  • Cargo.lock is excluded by !**/*.lock
  • crates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasm is excluded by !**/*.wasm
  • crates/grida-canvas/goldens/corner_radius_backends.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.js
  • crates/grida-canvas-wasm/lib/index.ts
  • crates/grida-canvas-wasm/lib/modules/canvas.ts
  • crates/grida-canvas-wasm/package.json
  • crates/grida-canvas-wasm/src/wasm_application.rs
  • crates/grida-canvas/Cargo.toml
  • crates/grida-canvas/examples/golden_corner_radius_backends.rs
  • crates/grida-canvas/src/helpers/webfont_helper.rs
  • crates/grida-canvas/src/io/io_grida.rs
  • crates/grida-canvas/src/layout/into_taffy.rs
  • crates/grida-canvas/src/node/schema.rs
  • crates/grida-canvas/src/os/emscripten.rs
  • crates/grida-canvas/src/shape/corner.rs
  • crates/grida-canvas/src/shape/mod.rs
  • crates/grida-canvas/src/shape/polygon.rs
  • crates/grida-canvas/src/shape/regular_polygon.rs
  • crates/grida-canvas/src/shape/regular_star.rs
  • crates/grida-canvas/src/shape/rrect.rs
  • crates/grida-canvas/src/shape/vector.rs
  • crates/grida-canvas/src/sys/scheduler.rs
  • crates/grida-canvas/src/vectornetwork/vn.rs
  • crates/grida-canvas/src/window/application.rs
  • crates/grida-canvas/src/window/application_emscripten.rs
  • crates/grida-canvas/tests/flatten_rendiff.rs
  • crates/grida-canvas/tests/shape_to_vector_network.rs
  • editor/grida-canvas/backends/wasm.ts
  • editor/grida-canvas/editor.i.ts
  • editor/grida-canvas/reducers/methods/flatten.ts
  • packages/grida-canvas-sdk-render-figma/package.json
  • packages/grida-canvas-vn/vn.ts

Comment thread crates/grida-canvas/src/layout/into_taffy.rs
Comment thread crates/grida-canvas/src/shape/regular_star.rs
Comment thread editor/grida-canvas/editor.i.ts
Comment on lines +66 to +89
if (!flattenResult) {
const fallback = toVectorNetworkFallback(node, {
width: rect.width,
height: rect.height,
});
if (fallback) {
flattenResult = fallback;
}
}
if (!v) return null;
if (!flattenResult) return null;

// Extract corner_radius from rust-side result (if curves are NOT baked in).
// When corner_radius is present, the vector node should keep it as a
// rendering effect. When absent, geometry is baked — clear corner_radius.
const { corner_radius: resultCornerRadius, ...vectorNetwork } = flattenResult;

const vectornode: grida.program.nodes.VectorNode = {
...(node as grida.program.nodes.UnknownNode),
type: "vector",
id: node.id,
active: node.active,
corner_radius: modeProperties.cornerRadius(node),
corner_radius: resultCornerRadius ?? undefined,
fill_rule: (node as grida.program.nodes.UnknownNode).fill_rule ?? "nonzero",
vector_network: v,
vector_network: vectorNetwork,
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

Fallback flattening can drop rectangle corner radius.

When wasm flatten is unavailable, fallback VN for rectangles is sharp, and this path now clears corner_radius. Rounded rectangles can flatten into non-rounded vectors.

Proposed fix (preserve radius on fallback)
   if (!flattenResult) {
     const fallback = toVectorNetworkFallback(node, {
       width: rect.width,
       height: rect.height,
     });
     if (fallback) {
-      flattenResult = fallback;
+      const fallbackCornerRadius =
+        node.type === "rectangle" && "corner_radius" in node
+          ? (node as any).corner_radius
+          : undefined;
+
+      flattenResult =
+        fallbackCornerRadius != null
+          ? { ...fallback, corner_radius: fallbackCornerRadius }
+          : fallback;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas/reducers/methods/flatten.ts` around lines 66 - 89, The
fallback flattening path can lose a rectangle's corner_radius because
flattenResult from toVectorNetworkFallback has no corner_radius, and the code
then clears it; change the assignment so that when building vectornode (in this
file's flatten logic) you preserve the original node.corner_radius if
resultCornerRadius is undefined (i.e., use resultCornerRadius ??
(node.corner_radius ?? undefined)), but still clear corner_radius when the
rust-side result explicitly indicates geometry is baked (when resultCornerRadius
is explicitly null/absent and node is not a rounded rect); reference the symbols
flattenResult, toVectorNetworkFallback, resultCornerRadius, corner_radius and
vectornode to implement this conditional preservation.

- Updated `build_regular_polygon_vector_geometry` and `build_star_vector_geometry` functions to ensure they return an empty shape when fewer than 3 points are provided, enhancing robustness.
- Modified the `toVectorNetwork` method to return `vn.FlattenResult` instead of `vn.VectorNetwork`, aligning with recent changes in the vector network structure.
- Enhanced the flattening logic for polygons and stars to preserve corner radius information, improving the accuracy of shape representation.

This update aims to enhance the handling of vector shapes and ensure proper rendering in the grida-canvas project.
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.

♻️ Duplicate comments (1)
editor/grida-canvas/reducers/methods/flatten.ts (1)

143-152: ⚠️ Potential issue | 🟠 Major

Preserve rectangle corner radius in fallback flatten path.

When WASM flattening is unavailable, rounded rectangles are flattened as sharp rects and corner_radius is dropped, changing appearance after flatten. Keep radius metadata in fallback output so vector rendering can preserve rounded corners.

💡 Proposed fix
     case "rectangle": {
-      // Rectangle does NOT carry corner_radius here. Native rrect uses
-      // conic arcs while corner_path uses quadratic Bézier — different
-      // curves. The Rust backend bakes rrect geometry; this fallback
-      // produces a sharp rect (acceptable degradation when WASM is absent).
-      return vn.fromRect({
-        x: 0,
-        y: 0,
-        width: size.width,
-        height: size.height,
-      });
+      const cr = node.corner_radius;
+      return {
+        ...vn.fromRect({
+          x: 0,
+          y: 0,
+          width: size.width,
+          height: size.height,
+        }),
+        ...(cr && cr > 0 ? { corner_radius: cr } : {}),
+      };
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas/reducers/methods/flatten.ts` around lines 143 - 152, The
fallback path currently returns a sharp rectangle via
vn.fromRect({x:0,y:0,width:size.width,height:size.height}) and drops the rounded
corner metadata; modify the fallback in flatten.ts to preserve corner radius by
including the original corner radius in the returned vector (e.g., pass
corner_radius or cornerRadius metadata from the source shape into vn.fromRect or
attach it to the returned node), referencing the same size and the source's
corner_radius so vector rendering can retain rounded corners when WASM
flattening is unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@editor/grida-canvas/reducers/methods/flatten.ts`:
- Around line 143-152: The fallback path currently returns a sharp rectangle via
vn.fromRect({x:0,y:0,width:size.width,height:size.height}) and drops the rounded
corner metadata; modify the fallback in flatten.ts to preserve corner radius by
including the original corner radius in the returned vector (e.g., pass
corner_radius or cornerRadius metadata from the source shape into vn.fromRect or
attach it to the returned node), referencing the same size and the source's
corner_radius so vector rendering can retain rounded corners when WASM
flattening is unavailable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f81665e and 3643ec3.

📒 Files selected for processing (5)
  • crates/grida-canvas/src/shape/regular_polygon.rs
  • crates/grida-canvas/src/shape/regular_star.rs
  • editor/grida-canvas/editor.i.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/reducers/methods/flatten.ts

@softmarshmallow softmarshmallow merged commit aa7c069 into main Mar 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cg Core Graphics

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant