Skip to content

Conversation

@softmarshmallow
Copy link
Member

@softmarshmallow softmarshmallow commented Oct 28, 2025

core

  • rectangular (RSuperEllipse)
  • cubic

editor

  • corner smoothing control

corner_smoothing

Update

The core shape feature is implemented, but we could not figure out a reasonably simple pipeline to make corner smoothing / superellipse to work with all types of shapes, thus we are not including this on editor's feature.
(yet the shape is accessible via api.)

Summary by CodeRabbit

  • New Features

    • Added corner smoothing control for shapes: rounded rectangles, containers, and images can now use smooth, superellipse-style corners instead of standard circular corners.
  • Documentation

    • Added reference documentation for superellipse curves and G² curve-blending mathematics underlying the corner smoothing feature.

@codesandbox
Copy link

codesandbox bot commented Oct 28, 2025

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel
Copy link

vercel bot commented Oct 28, 2025

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

Project Deployment Preview Comments Updated (UTC)
backgrounds Ready Ready Preview Comment Oct 29, 2025 8:41am
blog Ready Ready Preview Comment Oct 29, 2025 8:41am
docs Ready Ready Preview Comment Oct 29, 2025 8:41am
grida Ready Ready Preview Comment Oct 29, 2025 8:41am
viewer Ready Ready Preview Comment Oct 29, 2025 8:41am
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
code Ignored Ignored Oct 29, 2025 8:41am
legacy Ignored Ignored Oct 29, 2025 8:41am

@coderabbitai
Copy link

coderabbitai bot commented Oct 28, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR introduces corner smoothing (G² curvature blending) to the Grida canvas rendering system. It adds a new CornerSmoothing type, an OrthogonalSmoothRRect shape variant, and integrates corner smoothing through node records, I/O layers, shape rendering, and editor controls. Supporting documentation on G² curve blending is included.

Changes

Cohort / File(s) Summary
Core Type Definition
crates/grida-canvas/src/cg/types.rs
Added CornerSmoothing(f32) struct with new(), value(), is_zero() methods; implements Default, Debug, Clone, Copy, PartialEq, PartialOrd; includes documentation on G² curvature blending.
Shape Module Extensions
crates/grida-canvas/src/shape/mod.rs, crates/grida-canvas/src/shape/srrect_orthogonal.rs
Added new srrect_orthogonal module with OrthogonalSmoothRRectShape struct and build_orthogonal_smooth_rrect_path() function; extended Shape enum with Rect and OrthogonalSmoothRRect variants; migrated to_vector_network() to Into<VectorNetwork> trait impl; added supporting helpers build_rect_vector_network() and build_orthogonal_smooth_rrect_vector_network().
Node Record Schemas
crates/grida-canvas/src/node/schema.rs
Added corner_smoothing: CornerSmoothing field to ContainerNodeRec, RectangleNodeRec, ImageNodeRec; updated RectangleNodeRec::to_shape() to produce Rect, RRect, or OrthogonalSmoothRRect based on corner radius and smoothing values.
RRect Shape Support
crates/grida-canvas/src/shape/rrect.rs
Implemented Into<skia_safe::RRect> for &RRectShape to enable direct conversion.
Node Factory Defaults
crates/grida-canvas/src/node/factory.rs
Added corner_smoothing: Default::default() field initialization to RectangleNodeRec, ContainerNodeRec, ImageNodeRec creation methods.
Painter Geometry Pipeline
crates/grida-canvas/src/painter/geometry.rs
Extended PainterShape::from_shape() to handle Rect and RRect; updated build_shape() for Node::Container and Node::Image to generate smooth rounded paths when corner smoothing is enabled; refactored Node::Rectangle to use to_shape() delegation.
I/O: Figma Integration
crates/grida-canvas/src/io/io_figma.rs
Added corner_smoothing: CornerSmoothing field initialization in Frame, Component, Instance, Rectangle, Section, and Vector node conversion paths; maps origin.corner_smoothing / component.corner_smoothing / instance.corner_smoothing or defaults to 0.0.
I/O: Grida Format
crates/grida-canvas/src/io/io_grida.rs
Added corner_smoothing: Option<f32> to JSONUnknownNodeProperties (serde alias "cornerSmoothing"); populated corner_smoothing: CornerSmoothing in ContainerNodeRec, RectangleNodeRec, ImageNodeRec from node base properties; added deserialization tests.
Golden Example Files
crates/grida-canvas/benches/bench_rectangles.rs, crates/grida-canvas/examples/golden_*.rs
Added corner_smoothing: CornerSmoothing::default() or Default::default() field initialization to rectangle and container node constructions.
New Visual Example
crates/grida-canvas/examples/golden_corner_smoothing.rs
New example rendering side-by-side comparison of circular (red) vs. smoothed (blue) corners, saving to goldens/corner_smoothing.png.
Editor TypeScript API
editor/grida-canvas/editor.i.ts, editor/grida-canvas/editor.ts
Added changeNodePropertyCornerSmoothing(node_id: NodeID, cornerSmoothing: number): void method to IDocumentNodeChangeActions and EditorDocumentStore; dispatches corner smoothing property change action.
Editor Reducer
editor/grida-canvas/reducers/node.reducer.ts
Added cornerSmoothing property handler in safe_properties; applies clamping to [0, 1] range.
Schema Type Extension
packages/grida-canvas-schema/grida.ts
Added optional cornerSmoothing?: number property to IRectangularCornerRadius interface.
Math Documentation
docs/math/superellipse.md, docs/math/g2-curve-blending.md
New comprehensive documentation on superellipse curves, corner smoothing parameter mappings, and G² curvature-continuous corner blending techniques (Bézier, superelliptic, biarc, clothoid).

Sequence Diagram(s)

sequenceDiagram
    participant App as Editor/App
    participant Factory as NodeFactory
    participant Schema as NodeSchema
    participant Shape as ShapeModule
    participant Painter as PainterGeometry
    participant Renderer as SkiaRenderer

    App->>Factory: create_rectangle_node(...)
    Factory->>Schema: RectangleNodeRec { corner_radius, corner_smoothing }
    activate Schema
    Schema->>Shape: to_shape()
    alt corner_radius == 0
        Shape-->>Schema: Rect(...)
    else corner_smoothing == 0
        Shape-->>Schema: RRect(...)
    else corner_smoothing > 0
        Shape-->>Schema: OrthogonalSmoothRRect(...)
    end
    deactivate Schema
    Schema-->>Factory: ✓ node created
    App->>Painter: build_shape(node)
    activate Painter
    alt is RectangleNode
        Painter->>Shape: n.to_shape()
        Shape-->>Painter: Shape variant
        Painter->>Painter: PainterShape::from_shape(...)
    else is ContainerNode with smoothing
        Painter->>Shape: build_orthogonal_smooth_rrect_path(...)
        Shape-->>Painter: skia_safe::Path with Bézier curves
    end
    Painter-->>App: PainterShape ready
    deactivate Painter
    App->>Renderer: render(painter_shape)
    Renderer->>Renderer: stroke/fill with Skia
    Renderer-->>App: ✓ rendered
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Key areas requiring close attention:
    • crates/grida-canvas/src/shape/srrect_orthogonal.rs: New Bézier curve generation logic for smooth corner path construction; verify correctness of compute_corner_params() and cubic Bézier positioning at corner transitions.
    • crates/grida-canvas/src/painter/geometry.rs: Conditional shape generation logic for Container and Image nodes; ensure shape variants (Rect/RRect/OrthogonalSmoothRRect) are correctly dispatched and rendered.
    • crates/grida-canvas/src/node/schema.rs: RectangleNodeRec::to_shape() case branching; verify all three shape types are produced correctly based on corner_radius and corner_smoothing.
    • I/O integration: Cross-check io_figma.rs and io_grida.rs for consistent default handling and proper deserialization from external formats.

Possibly related PRs

Suggested labels

canvas, cg

Poem

🐰 A rabbit hops through smoothing curves so fine,
With G² blending where the corners align,
No sharp edges—just Bézier dreams,
Superellipses flowing like gentle streams,
Canvas grows rounder with every commit! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.00% 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 PR title "Grida Canvs - squircle" is directly related to the main change in this pull request. The changeset's primary objective is to implement rectangular superellipse (squircle) support through the addition of a CornerSmoothing type and field to multiple node types, along with comprehensive rendering support. The term "squircle" is a recognized design and graphics terminology that accurately refers to the superellipse corner smoothing feature being introduced. The title successfully identifies this core feature, and developers familiar with design systems would immediately understand the intended change. However, there is a minor typo ("Canvs" instead of "Canvas") that should ideally be corrected, though this does not affect the semantic clarity of the title.
✨ 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.

Copy link

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

ℹ️ 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".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (15)
crates/math2/tests/bezier.rs (1)

12-22: Remove commented-out duplicate helper or gate it properly.

Keeping dead code in tests hurts signal. Either drop it or restore behind cfg(test) with tolerant comparisons when you re-enable the SVG path test.

Apply to clean up now:

-// /// Converts the output of `bezier_a2c` to an SVG path string.
-// fn a2c_to_svg_path(x1: f32, y1: f32, data: &[f32]) -> String {
-//     let mut path = format!("M {} {}", x1, y1);
-//     for chunk in data.chunks(6) {
-//         if let [c1x, c1y, c2x, c2y, x, y] = *chunk {
-//             path.push_str(&format!(" C {} {}, {} {}, {} {}", c1x, c1y, c2x, c2y, x, y));
-//         }
-//     }
-//     path
-// }
packages/grida-canvas-schema/grida.ts (1)

1478-1483: Document expected range for cornerSmoothing (and default).

Clarify semantics to avoid misuse; typical domain is 0..1 with 0 = off.

 export interface IRectangularCornerRadius {
   cornerRadiusTopLeft?: number;
   cornerRadiusTopRight?: number;
   cornerRadiusBottomLeft?: number;
   cornerRadiusBottomRight?: number;
-  cornerSmoothing?: number;
+  /**
+   * Corner smoothing factor in [0..1].
+   * 0 = no smoothing (standard rounded corners), 1 = maximum G² smoothing.
+   * Defaults to 0 when omitted.
+   */
+  cornerSmoothing?: number;
 }
crates/grida-canvas/benches/bench_rectangles.rs (1)

26-27: Optional: add a smoothing-on variant to benchmark overhead.

Keep default 0 here, but consider a “with_smoothing” bench (e.g., 0.5) to quantify path-building cost.

crates/grida-canvas/src/shape/rrect.rs (1)

14-18: Prefer implementing From<&RRectShape> to get Into for free.

This is fine, but idiomatic Rust implements From and derives Into automatically, reducing trait impl surface. Consider switching.

-impl Into<skia_safe::RRect> for &RRectShape {
-    fn into(self) -> skia_safe::RRect {
-        build_rrect(self)
-    }
-}
+impl From<&RRectShape> for skia_safe::RRect {
+    fn from(shape: &RRectShape) -> Self {
+        build_rrect(shape)
+    }
+}
crates/grida-canvas/src/shape/rect.rs (1)

25-50: Vector network builder looks correct.

Vertices and segments form a closed clockwise rectangle; suitable for downstream consumers. Optionally, document winding and populate regions later if your consumers rely on it.

Also applies to: 37-44

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

1665-1675: Clamp corner smoothing to [0,1] at the API edge.

Prevents invalid values entering state; keeps UI and WASM in sync.

   changeNodePropertyCornerSmoothing(
     node_id: editor.NodeID,
     cornerSmoothing: number
   ): void {
-    this.dispatch({
+    const v = Math.max(0, Math.min(1, cornerSmoothing));
+    this.dispatch({
       type: "node/change/*",
       node_id: node_id,
-      cornerSmoothing,
+      cornerSmoothing: v,
     });
   }
crates/grida-canvas/src/cg/types.rs (1)

983-1007: Type is solid; consider From/Into ergonomics (and serde if needed).

Add From/Into to ease conversions; optionally #[serde(transparent)] if you plan to serialize this internally (IO already maps via f32).

 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
 pub struct CornerSmoothing(pub f32);

+impl From<f32> for CornerSmoothing {
+    fn from(v: f32) -> Self { CornerSmoothing::new(v) }
+}
+
+impl From<CornerSmoothing> for f32 {
+    fn from(cs: CornerSmoothing) -> Self { cs.0 }
+}
crates/grida-canvas/src/painter/geometry.rs (2)

200-216: Unify smoothing checks and micro‑nits.

  • Prefer !n.corner_smoothing.is_zero() over value() > 0.0 for consistency with Rectangle::to_shape() and to avoid magic thresholds.
  • Optional: extract the Container RRect construction into a helper to reduce duplication with the Image branch.

225-237: Reduce duplication by reusing shape conversion.

Mirror Rectangle by introducing ImageNodeRec::to_shape() (Rect/RRect/OrthogonalSmoothRRect) and then call PainterShape::from_shape(&shape). This centralizes branching and keeps painter lean.

docs/math/g2-curve-blending.md.md (2)

5-5: Hyphenate compound adjective.

Use “Computer‑Aided Geometric Design”.

-This document ... from CAGD (Computer Aided Geometric Design) ...
+This document ... from CAGD (Computer‑Aided Geometric Design) ...

53-57: Consistent term: parameterization vs parametrization.

Pick one form and use consistently; suggest “parameterization” to match typical CG literatures.

-**Parametrization (first quadrant):**
+**Parameterization (first quadrant):**

Also applies to: 148-149

crates/grida-canvas/src/io/io_grida.rs (1)

1238-1239: Propagation OK; add clamp tests and NaN guard.

  • Using CornerSmoothing::new(node.base.corner_smoothing.unwrap_or(0.0)) is fine and clamps [0,1]. Add tests for inputs <0, >1, and NaN (treat NaN as 0.0).

Apply tests via this diff:

@@
 #[cfg(test)]
 mod tests {
@@
+    #[test]
+    fn deserialize_corner_smoothing_out_of_range_is_clamped() {
+        let json_hi = r#"{
+            "id":"rect-hi","type":"rectangle","width":100,"height":100,
+            "cornerRadius":10,"cornerSmoothing": 1.7
+        }"#;
+        let node_hi: JSONNode = serde_json::from_str(json_hi).unwrap();
+        if let JSONNode::Rectangle(r) = node_hi {
+            let n: Node = r.into();
+            if let Node::Rectangle(rec) = n {
+                assert_eq!(rec.corner_smoothing.value(), 1.0);
+            }
+        }
+
+        let json_lo = r#"{
+            "id":"rect-lo","type":"rectangle","width":100,"height":100,
+            "cornerRadius":10,"cornerSmoothing": -0.3
+        }"#;
+        let node_lo: JSONNode = serde_json::from_str(json_lo).unwrap();
+        if let JSONNode::Rectangle(r) = node_lo {
+            let n: Node = r.into();
+            if let Node::Rectangle(rec) = n {
+                assert_eq!(rec.corner_smoothing.value(), 0.0);
+            }
+        }
+    }
+
+    #[test]
+    fn deserialize_corner_smoothing_nan_falls_back_to_zero() {
+        // serde rejects non-numeric tokens, so simulate by constructing directly:
+        let s = CornerSmoothing::new(f32::NAN);
+        // If new() doesn't sanitize NaN, we should ensure downstream treats it as 0.0.
+        // For now, assert it's not finite and flag follow-up:
+        assert!(s.value().is_nan());
+    }

If you prefer to sanitize NaN in CornerSmoothing::new, I can open a follow-up patch.



Also applies to: 1462-1463, 1560-1561

</blockquote></details>
<details>
<summary>crates/grida-canvas/src/shape/srrect_orthogonal.rs (2)</summary><blockquote>

`43-84`: **Numerical safety for Bézier spans.**

`b = ((p - l) - (1+d_to_c)*c)/3` may go negative near extreme aspect ratios; consider clamping `a,b,c,d ≥ 0` to avoid inverted control handles.


Happy to provide a follow-up with clamps and visual tests across angle/radius grids.

---

`243-253`: **Strengthen tests.**

Also assert closure; this catches future regressions.


```diff
-        let path = build_orthogonal_smooth_rrect_path(&shape);
-        assert!(!path.is_empty());
+        let path = build_orthogonal_smooth_rrect_path(&shape);
+        assert!(!path.is_empty());
+        assert!(path.is_last_contour_closed());
crates/grida-canvas/src/node/schema.rs (1)

1016-1023: Consistency nit: use is_zero() idiomatically.

Minor: replace if self.corner_smoothing.is_zero() with a match on smoothing state across all call sites for readability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa6555d and 4ed128a.

⛔ Files ignored due to path filters (1)
  • crates/grida-canvas/goldens/corner_smoothing.png is excluded by !**/*.png
📒 Files selected for processing (23)
  • crates/grida-canvas/benches/bench_rectangles.rs (1 hunks)
  • crates/grida-canvas/examples/golden_corner_smoothing.rs (1 hunks)
  • crates/grida-canvas/examples/golden_layout_flex.rs (1 hunks)
  • crates/grida-canvas/examples/golden_layout_flex_alignment.rs (1 hunks)
  • crates/grida-canvas/examples/golden_layout_flex_padding.rs (1 hunks)
  • crates/grida-canvas/examples/golden_layout_padding.rs (1 hunks)
  • crates/grida-canvas/src/cg/types.rs (1 hunks)
  • crates/grida-canvas/src/io/io_figma.rs (6 hunks)
  • crates/grida-canvas/src/io/io_grida.rs (5 hunks)
  • crates/grida-canvas/src/node/factory.rs (3 hunks)
  • crates/grida-canvas/src/node/schema.rs (5 hunks)
  • crates/grida-canvas/src/painter/geometry.rs (4 hunks)
  • crates/grida-canvas/src/shape/mod.rs (4 hunks)
  • crates/grida-canvas/src/shape/rect.rs (2 hunks)
  • crates/grida-canvas/src/shape/rrect.rs (1 hunks)
  • crates/grida-canvas/src/shape/srrect_orthogonal.rs (1 hunks)
  • crates/math2/tests/bezier.rs (1 hunks)
  • docs/math/g2-curve-blending.md.md (1 hunks)
  • docs/math/superellipse.md (1 hunks)
  • editor/grida-canvas/editor.i.ts (1 hunks)
  • editor/grida-canvas/editor.ts (2 hunks)
  • editor/grida-canvas/reducers/node.reducer.ts (1 hunks)
  • packages/grida-canvas-schema/grida.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Author and maintain all documentation under the root docs directory; it is the source of truth

Files:

  • docs/math/superellipse.md
  • docs/math/g2-curve-blending.md.md
packages/grida-canvas-*/**

📄 CodeRabbit inference engine (AGENTS.md)

Name canvas-related packages with the grida-canvas-* prefix under /packages

Files:

  • packages/grida-canvas-schema/grida.ts
crates/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep Rust crates under /crates; this is the monorepo’s Rust workspace for the Canvas implementation

Files:

  • crates/grida-canvas/benches/bench_rectangles.rs
  • crates/grida-canvas/src/cg/types.rs
  • crates/grida-canvas/examples/golden_layout_flex_alignment.rs
  • crates/grida-canvas/src/shape/srrect_orthogonal.rs
  • crates/grida-canvas/examples/golden_corner_smoothing.rs
  • crates/grida-canvas/src/shape/rrect.rs
  • crates/grida-canvas/src/node/factory.rs
  • crates/grida-canvas/examples/golden_layout_flex.rs
  • crates/grida-canvas/examples/golden_layout_padding.rs
  • crates/grida-canvas/src/shape/rect.rs
  • crates/grida-canvas/src/painter/geometry.rs
  • crates/grida-canvas/examples/golden_layout_flex_padding.rs
  • crates/grida-canvas/src/node/schema.rs
  • crates/math2/tests/bezier.rs
  • crates/grida-canvas/src/io/io_grida.rs
  • crates/grida-canvas/src/shape/mod.rs
  • crates/grida-canvas/src/io/io_figma.rs
crates/grida-canvas/src/**/*.rs

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

crates/grida-canvas/src/**/*.rs: All internal structs (e.g., NodeRecs, SceneGraph, caches) must use NodeId (u64) for identifiers and not serialize them
Public APIs must accept and return UserNodeId (String) for stability and .grida serialization

Files:

  • crates/grida-canvas/src/cg/types.rs
  • crates/grida-canvas/src/shape/srrect_orthogonal.rs
  • crates/grida-canvas/src/shape/rrect.rs
  • crates/grida-canvas/src/node/factory.rs
  • crates/grida-canvas/src/shape/rect.rs
  • crates/grida-canvas/src/painter/geometry.rs
  • crates/grida-canvas/src/node/schema.rs
  • crates/grida-canvas/src/io/io_grida.rs
  • crates/grida-canvas/src/shape/mod.rs
  • crates/grida-canvas/src/io/io_figma.rs
editor/grida-*/**

📄 CodeRabbit inference engine (AGENTS.md)

Use editor/grida-* directories to isolate domain-specific modules pending promotion to /packages

Files:

  • editor/grida-canvas/editor.i.ts
  • editor/grida-canvas/editor.ts
  • editor/grida-canvas/reducers/node.reducer.ts
🧠 Learnings (2)
📚 Learning: 2025-10-27T06:28:21.863Z
Learnt from: CR
PR: gridaco/grida#0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-10-27T06:28:21.863Z
Learning: Applies to crates/grida-canvas/src/**/*.rs : All internal structs (e.g., NodeRecs, SceneGraph, caches) must use NodeId (u64) for identifiers and not serialize them

Applied to files:

  • crates/grida-canvas/src/node/schema.rs
📚 Learning: 2025-09-15T10:31:32.864Z
Learnt from: CR
PR: gridaco/grida#0
File: crates/grida-canvas-fonts/AGENTS.md:0-0
Timestamp: 2025-09-15T10:31:32.864Z
Learning: Applies to crates/grida-canvas-fonts/tests/serde_test.rs : Put JSON serialization tests in tests/serde_test.rs

Applied to files:

  • crates/grida-canvas/src/io/io_grida.rs
🧬 Code graph analysis (11)
editor/grida-canvas/editor.i.ts (1)
packages/grida-canvas-schema/grida.ts (1)
  • NodeID (1100-1100)
editor/grida-canvas/editor.ts (2)
editor/grida-canvas/editor.i.ts (1)
  • NodeID (134-134)
packages/grida-canvas-schema/grida.ts (1)
  • NodeID (1100-1100)
crates/grida-canvas/src/shape/srrect_orthogonal.rs (1)
crates/grida-canvas/src/cg/types.rs (9)
  • b (79-81)
  • a (82-84)
  • new (20-22)
  • new (823-828)
  • new (987-989)
  • new (1330-1336)
  • new (1689-1696)
  • circular (868-873)
  • circular (933-935)
crates/grida-canvas/examples/golden_corner_smoothing.rs (6)
packages/grida-canvas-cg/lib.ts (1)
  • AffineTransform (65-68)
crates/grida-canvas/src/cg/types.rs (32)
  • new (20-22)
  • new (823-828)
  • new (987-989)
  • new (1330-1336)
  • new (1689-1696)
  • circular (868-873)
  • circular (933-935)
  • default (43-45)
  • default (124-126)
  • default (146-148)
  • default (278-280)
  • default (342-344)
  • default (356-358)
  • default (376-378)
  • default (392-394)
  • default (422-424)
  • default (446-448)
  • default (548-550)
  • default (560-562)
  • default (572-574)
  • default (605-607)
  • default (653-658)
  • default (729-731)
  • from (88-94)
  • from (263-265)
  • from (1083-1085)
  • from (1740-1742)
  • from (1746-1748)
  • from (1776-1781)
  • from (1786-1791)
  • from (1919-1921)
  • from_rgb (69-71)
crates/grida-canvas/src/node/factory.rs (1)
  • new (14-16)
crates/grida-canvas/src/runtime/scene.rs (1)
  • new_from_raster (141-144)
crates/grida-canvas/src/runtime/camera.rs (1)
  • new_from_bounds (56-73)
crates/math2/src/rect.rs (1)
  • from_xywh (39-46)
crates/grida-canvas/src/shape/rrect.rs (2)
crates/grida-canvas/src/shape/mod.rs (2)
  • into (47-60)
  • into (64-83)
crates/grida-canvas/src/shape/rect.rs (2)
  • into (11-13)
  • into (17-22)
crates/grida-canvas/src/shape/rect.rs (2)
crates/grida-canvas-wasm/lib/index.ts (2)
  • VectorNetwork (65-68)
  • VectorNetworkSegment (59-64)
crates/grida-canvas/src/vectornetwork/vn.rs (2)
  • vertices (448-448)
  • ab (15-22)
crates/grida-canvas/src/painter/geometry.rs (3)
crates/math2/src/rect.rs (1)
  • from_xywh (39-46)
crates/grida-canvas/src/shape/srrect_orthogonal.rs (1)
  • build_orthogonal_smooth_rrect_path (86-210)
crates/grida-canvas/src/shape/rrect.rs (1)
  • build_rrect (20-36)
crates/grida-canvas/src/node/schema.rs (2)
crates/grida-canvas/src/painter/geometry.rs (1)
  • to_path (121-138)
crates/grida-canvas/src/window/application.rs (2)
  • to_vector_network (46-46)
  • to_vector_network (313-364)
crates/grida-canvas/src/io/io_grida.rs (1)
crates/grida-canvas/src/cg/types.rs (7)
  • new (20-22)
  • new (823-828)
  • new (987-989)
  • new (1330-1336)
  • new (1689-1696)
  • value (993-995)
  • value (1339-1341)
crates/grida-canvas/src/shape/mod.rs (3)
crates/grida-canvas/src/shape/rrect.rs (3)
  • build_rrect_path (38-42)
  • into (15-17)
  • build_rrect_vector_network (50-156)
crates/grida-canvas/src/shape/srrect_orthogonal.rs (2)
  • build_orthogonal_smooth_rrect_path (86-210)
  • build_orthogonal_smooth_rrect_vector_network (212-221)
crates/grida-canvas/src/shape/rect.rs (3)
  • into (11-13)
  • into (17-22)
  • build_rect_vector_network (25-50)
crates/grida-canvas/src/io/io_figma.rs (1)
crates/grida-canvas/src/cg/types.rs (5)
  • new (20-22)
  • new (823-828)
  • new (987-989)
  • new (1330-1336)
  • new (1689-1696)
🪛 LanguageTool
docs/math/g2-curve-blending.md.md

[uncategorized] ~5-~5: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...dates established techniques from CAGD (Computer Aided Geometric Design) and relates them to p...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~53-~53: Do not mix variants of the same word (‘parametrization’ and ‘parameterization’) within a single text.
Context: ...produces Apple‑style “icon” corners. Parametrization (first quadrant): $$ x(t)=a,|\cos t...

(EN_WORD_COHERENCY)


[uncategorized] ~148-~148: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...e. - Hoschek & Lasser, Fundamentals of Computer Aided Geometric Design — blending and fairne...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🔇 Additional comments (17)
crates/grida-canvas/examples/golden_layout_flex_alignment.rs (1)

27-27: LGTM: default corner_smoothing initialized.

Consistent with factory defaults; no behavioral change for this example.

crates/grida-canvas/examples/golden_layout_padding.rs (1)

33-33: LGTM: corner_smoothing default added.

Keeps examples aligned with schema without altering rendering.

crates/grida-canvas/examples/golden_layout_flex_padding.rs (1)

126-127: LGTM: default corner_smoothing set.

Consistent with other initializations; no change to layout demo.

crates/grida-canvas/src/node/factory.rs (1)

62-63: All verification points confirmed. Corner smoothing wiring is correct.

  • IO layer properly maps numeric cornerSmoothing values to CornerSmoothing runtime type via deserialization (unwrap_or default to 0.0).
  • Painter correctly applies smoothing only when both corner_smoothing.value() > 0.0 AND corner radius is non-zero.
  • Tests comprehensively verify deserialization for rectangle, container, and image nodes.
  • Reducer layer properly exposes the field.
crates/grida-canvas/examples/golden_layout_flex.rs (1)

31-31: Good addition — default-initialize new field.

corner_smoothing: Default::default() aligns with the new field and maintains backward compatibility in the example constructor.

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

4294-4294: No-op formatting change.

Terminating semicolon on changeRotation is fine.

crates/grida-canvas/examples/golden_corner_smoothing.rs (1)

68-69: Remove unnecessary async/tokio – but note tokio is already available as a dev-dependency.

The example performs no async work; create_scene() and main() are sync functions wrapped unnecessarily with async. Remove the async/tokio machinery to keep the example simple. Note: this won't cause a build failure since tokio is already declared in dev-dependencies; however, removing it improves code clarity.

-async fn create_scene() -> Scene {
+fn create_scene() -> Scene {
   let nf = NodeFactory::new();
   let mut graph = SceneGraph::new();
   …
-    }
-}
+    }
+}
 
-#[tokio::main]
-async fn main() {
+fn main() {
   println!("=== Corner Smoothing Visual Test ===\n");
-  // Render scene
-  let scene = create_scene().await;
+  // Render scene
+  let scene = create_scene();
   …
 }

Also applies to: 14-16

Likely an incorrect or invalid review comment.

crates/grida-canvas/src/painter/geometry.rs (2)

88-92: Nice extension of PainterShape::from_shape.

Direct handling for Rect and RRect reduces path conversions and keeps bounds accurate. LGTM.


190-192: Good delegation to Rectangle::to_shape().

Keeps painter logic thin and centralizes shape branching in schema. Approved.

crates/grida-canvas/src/io/io_grida.rs (1)

763-765: JSON API surface looks right.

cornerSmoothing added with #[serde(rename = "cornerSmoothing", default)] is compatible and non‑breaking. Approved.

crates/grida-canvas/src/node/schema.rs (3)

851-853: Corner smoothing fields added to nodes: good.

Consistent modeling across Container/Rectangle/Image. Approved.

Also applies to: 949-952, 1087-1089


1009-1029: Shape branching reads well.

  • Rect when radius=0, RRect when smoothing=0, otherwise OrthogonalSmoothRRect. Clear and extensible. Approved.

1031-1037: Centralizing to_path/to_vector_network on Shape is the right call.

Note: Rectangle VN now relies on OrthogonalSmoothRRect VN; please ensure it’s implemented (currently returns empty). See suggested fix in srrect_orthogonal.rs.

Also applies to: 1251-1253

crates/grida-canvas/src/shape/mod.rs (3)

11-11: Re-export wiring LGTM.

Module added and publicly re-exported cleanly.

Also applies to: 26-26


34-34: Enum extensions are coherent.

New variants integrate cleanly with downstream matches.

Also applies to: 36-36


49-49: No issue found; the impl Into<skia_safe::Path> for &RectShape exists.

The verification confirms that impl Into<skia_safe::Path> for &RectShape is defined at crates/grida-canvas/src/shape/rect.rs:16. The code Shape::Rect(shape) => shape.into() will compile and work correctly with no changes needed.

crates/grida-canvas/src/io/io_figma.rs (1)

766-766: Corner smoothing mapping is correct.

Figma's cornerSmoothing field uses a 0.0–1.0 range, so the current implementation using CornerSmoothing::new(component.corner_smoothing.unwrap_or(0.0) as f32) is appropriate. No scaling is needed. The safe [0,1] clamping via CornerSmoothing::new() and sensible defaults for unsupported nodes are correct.

Also applies to: 884-884, 956-956, 1105-1105, 1345-1345, 1633-1633

Comment on lines +63 to 71
impl Into<VectorNetwork> for &Shape {
fn into(self) -> VectorNetwork {
match self {
Shape::Rect(shape) => build_rect_vector_network(shape),
Shape::RRect(shape) => build_rrect_vector_network(shape),
Shape::OrthogonalSmoothRRect(shape) => {
build_orthogonal_smooth_rrect_vector_network(shape)
}
Shape::SimplePolygon(shape) => build_simple_polygon_vector_network(shape),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Vector network for smooth rrect: avoid returning an empty network; add safe fallback.

Current builder returns an empty network, which can break consumers expecting non-empty geometry. Guard with a fallback to regular RRect when smoothing is zero; keep the smooth version for non-zero smoothing.

Apply this diff to make the match safer:

-            Shape::OrthogonalSmoothRRect(shape) => {
-                build_orthogonal_smooth_rrect_vector_network(shape)
-            }
+            Shape::OrthogonalSmoothRRect(shape) => {
+                if shape.corner_smoothing.value() == 0.0 {
+                    // Fallback: ignore smoothing and emit standard RRect network
+                    build_rrect_vector_network(&RRectShape {
+                        width: shape.width,
+                        height: shape.height,
+                        corner_radius: shape.corner_radius,
+                    })
+                } else {
+                    build_orthogonal_smooth_rrect_vector_network(shape)
+                }
+            }

Optionally, implement the fallback directly in srrect_orthogonal::build_orthogonal_smooth_rrect_vector_network so callers don’t need to special-case.

📝 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
impl Into<VectorNetwork> for &Shape {
fn into(self) -> VectorNetwork {
match self {
Shape::Rect(shape) => build_rect_vector_network(shape),
Shape::RRect(shape) => build_rrect_vector_network(shape),
Shape::OrthogonalSmoothRRect(shape) => {
build_orthogonal_smooth_rrect_vector_network(shape)
}
Shape::SimplePolygon(shape) => build_simple_polygon_vector_network(shape),
impl Into<VectorNetwork> for &Shape {
fn into(self) -> VectorNetwork {
match self {
Shape::Rect(shape) => build_rect_vector_network(shape),
Shape::RRect(shape) => build_rrect_vector_network(shape),
Shape::OrthogonalSmoothRRect(shape) => {
if shape.corner_smoothing.value() == 0.0 {
// Fallback: ignore smoothing and emit standard RRect network
build_rrect_vector_network(&RRectShape {
width: shape.width,
height: shape.height,
corner_radius: shape.corner_radius,
})
} else {
build_orthogonal_smooth_rrect_vector_network(shape)
}
}
Shape::SimplePolygon(shape) => build_simple_polygon_vector_network(shape),
🤖 Prompt for AI Agents
In crates/grida-canvas/src/shape/mod.rs around lines 63 to 71, the
OrthogonalSmoothRRect arm can return an empty VectorNetwork when smoothing is
zero; change the match arm to check the shape's smoothing value and when
smoothing is zero call build_rrect_vector_network(shape) as a safe fallback,
otherwise call build_orthogonal_smooth_rrect_vector_network(shape);
alternatively implement this zero-smoothing guard inside
srrect_orthogonal::build_orthogonal_smooth_rrect_vector_network so callers need
not special-case it.

Comment on lines +121 to +127
// Circular arc
let arc_rect =
skia_safe::Rect::from_xywh(w - tr.radius * 2.0, 0.0, tr.radius * 2.0, tr.radius * 2.0);
let start_angle = 270.0 + tr.angle_bezier;
let sweep_angle = 90.0 - 2.0 * tr.angle_bezier;
path.arc_to(arc_rect, start_angle, sweep_angle, false);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid degenerate Skia arcs when sweep=0.

At full smoothing s=1, sweep_angle = 0. Guard arc emission to skip zero sweeps.

-        let start_angle = 270.0 + tr.angle_bezier;
-        let sweep_angle = 90.0 - 2.0 * tr.angle_bezier;
-        path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        let start_angle = 270.0 + tr.angle_bezier;
+        let sweep_angle = 90.0 - 2.0 * tr.angle_bezier;
+        if sweep_angle > 0.0 {
+            path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        }
@@
-        let start_angle = br.angle_bezier;
-        let sweep_angle = 90.0 - 2.0 * br.angle_bezier;
-        path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        let start_angle = br.angle_bezier;
+        let sweep_angle = 90.0 - 2.0 * br.angle_bezier;
+        if sweep_angle > 0.0 {
+            path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        }
@@
-        let start_angle = 90.0 + bl.angle_bezier;
-        let sweep_angle = 90.0 - 2.0 * bl.angle_bezier;
-        path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        let start_angle = 90.0 + bl.angle_bezier;
+        let sweep_angle = 90.0 - 2.0 * bl.angle_bezier;
+        if sweep_angle > 0.0 {
+            path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        }
@@
-        let start_angle = 180.0 + tl.angle_bezier;
-        let sweep_angle = 90.0 - 2.0 * tl.angle_bezier;
-        path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        let start_angle = 180.0 + tl.angle_bezier;
+        let sweep_angle = 90.0 - 2.0 * tl.angle_bezier;
+        if sweep_angle > 0.0 {
+            path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        }

Also applies to: 152-155, 173-178, 196-199

🤖 Prompt for AI Agents
In crates/grida-canvas/src/shape/srrect_orthogonal.rs around lines 121-127 (and
similarly at 152-155, 173-178, 196-199), the code emits Skia arcs even when
sweep_angle becomes zero at full smoothing, which produces degenerate arcs;
detect when the computed sweep_angle is zero (or effectively zero within a small
epsilon) and skip calling path.arc_to in that case (i.e., only call path.arc_to
when sweep_angle.abs() > epsilon), ensuring any related start_angle logic
remains consistent when the arc is skipped.

Comment on lines +70 to +75
For $n ≥ 2$:

- **C¹ continuous** at all points (smooth tangents, no kinks)
- **C^∞ continuous** (infinitely differentiable) everywhere
- Curvature transitions are smooth and continuous

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Revisit differentiability claim; likely too strong.

“C^∞ for n ≥ 2” is generally not true for non-integer exponents; smoothness at the axes depends on n. Suggest softening and citing a source.

Proposed wording:

-For $n ≥ 2$:
-- **C¹ continuous** at all points (smooth tangents, no kinks)
-- **C^∞ continuous** (infinitely differentiable) everywhere
-- Curvature transitions are smooth and continuous
+For $n \ge 2$:
+- **C¹ continuous** everywhere (smooth tangents)
+- Higher-order smoothness near the axes depends on the exponent (integer vs non‑integer n).
+- Curvature transitions are smooth; for larger n the shape approaches rectilinear corners.

Please verify with a reputable math reference.


🌐 Web query:

Is the superellipse |x/a|^n + |y/b|^n = 1 C-infinity for n >= 2? How does differentiability at axes depend on n (integer vs non-integer)?

💡 Result:

Short answer: No — the superellipse is C^∞ only in the special n = 2 case (the ordinary ellipse). For every other n > 0 the boundary fails to be C^∞ at the four axis points (±a,0) and (0,±b); in fact for n > 2 and for 1 < n < 2 the curve is not C^1 at those axis points.

Reason (sketch): use the first-quadrant parametrisation
x(t)=a|cos t|^{2/n} sgn(cos t), y(t)=b|sin t|^{2/n} sgn(sin t), t→0+
so y(t) ~ b t^{2/n}. If 2/n = 1 (i.e. n=2) this is smooth; if 2/n < 1 (n>2) or 2/n < 1 (also true when 1<n<2) then y(t) has a non‑integer power <1 near the axis and its derivative with respect to t (hence the tangent vector) blows up or is not finite at t=0. Thus the curve is not C^1 at the axis points unless n=2. (Parametric form and these regularity facts are standard; see e.g. the Superellipse entry and discussions of the parametric form.). [1][2]

References:
[1] Wikipedia: “Superellipse” (Lamé curve) — equation and parametric form, comments on behaviour at the extreme points.
[2] Math StackExchange discussion of the superellipse parametric equation (parametrisation x=a|cos t|^{2/n}, y=b|sin t|^{2/n}).


Remove overstated differentiability claims; distinguish n=2 from n>2.

The superellipse is C^∞ only when n=2 (the ordinary ellipse); for every other n>0 the curve fails to be C^∞ at the four axis points. In fact, for n>2 and 1<n<2 the curve is not C^1 at those axis points. The reason: near the axis the parametric form behaves as y(t)~b·t^{2/n}, and when 2/n<1 the derivative is not finite at the axis.

Proposed wording:

-For $n ≥ 2$:
-- **C¹ continuous** at all points (smooth tangents, no kinks)
-- **C^∞ continuous** (infinitely differentiable) everywhere
-- Curvature transitions are smooth and continuous
+For $n = 2$ (ordinary ellipse): **C^∞ continuous** everywhere.
+
+For $n > 2$: **C¹ continuous** except at the four axis points (±a, 0) and (0, ±b), where the curve has a cusp (no smooth tangent).
🤖 Prompt for AI Agents
In docs/math/superellipse.md around lines 70 to 75, remove the blanket claims
that superellipses are C¹ and C^∞ for n≥2 and instead distinguish cases by
exponent n: state that only n=2 gives C^∞ (ellipse), for 1<n<2 and n>2 the curve
fails to be C¹ or C^∞ at the four axis points respectively, and add a short
justification noting the parametric/local behavior y(t) ~ b·t^{2/n} which makes
the derivative non-finite when 2/n<1; update the three bullet lines accordingly
to reflect these corrected differentiability classes and include the brief
explanatory phrase about the 2/n exponent condition.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/grida-canvas/src/shape/srrect_orthogonal.rs (1)

121-127: Guard against degenerate arcs when sweep_angle is zero.

At full smoothing (s=1.0), sweep_angle = 90.0 - 2.0 * 45.0 = 0.0, which produces a degenerate arc in Skia. This issue was flagged in a previous review and remains unresolved. Skip the arc_to call when the sweep is effectively zero.

Apply this diff to all four corners (also at lines 152-155, 173-178, 196-199):

         let start_angle = 270.0 + tr.angle_bezier;
         let sweep_angle = 90.0 - 2.0 * tr.angle_bezier;
-        path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        if sweep_angle.abs() > f32::EPSILON {
+            path.arc_to(arc_rect, start_angle, sweep_angle, false);
+        }
🧹 Nitpick comments (9)
editor/grida-canvas/reducers/node.reducer.ts (1)

332-336: LGTM! Past review feedback correctly implemented.

The clamping logic mirrors the innerRadius pattern (lines 343-347) and ensures state integrity by keeping values in the normalized [0, 1] range.

Optional: Consider adding type assertion for consistency.

For consistency with similar properties like innerRadius (line 344), you could optionally add a type assertion:

 cornerSmoothing: defineNodeProperty<"cornerSmoothing">({
+  assert: (node) => typeof node.cornerSmoothing === "number",
   apply: (draft, value, prev) => {
     (draft as UN).cornerSmoothing = cmath.clamp(value, 0, 1);
   },
 }),
docs/math/g2-curve-blending.md (2)

5-5: Hyphenate compound adjectives.

Lines 5 and 148 use "Computer Aided Geometric Design" as a compound adjective modifying a noun. English grammar requires hyphenation when compound adjectives precede the noun they modify.

Apply this diff:

- This document formalizes the problem of replacing a sharp join between two curve segments with a **curvature‑continuous (G²)** transition. It consolidates established techniques from CAGD (Computer Aided Geometric Design) and relates them to practical implementations used in modern editors.
+ This document formalizes the problem of replacing a sharp join between two curve segments with a **curvature‑continuous (G²)** transition. It consolidates established techniques from CAGD (Computer-Aided Geometric Design) and relates them to practical implementations used in modern editors.

And:

- - Hoschek & Lasser, _Fundamentals of Computer Aided Geometric Design_ — blending and fairness.
+ - Hoschek & Lasser, _Fundamentals of Computer-Aided Geometric Design_ — blending and fairness.

Also applies to: 148-148


53-53: Standardize spelling: use "parameterization" throughout.

The document mixes "Parametrization" (line 53) with "parameterization" (lines 101, 103, 106). For consistency and professional presentation, use the American English spelling "parameterization" everywhere.

Apply this diff:

- **Parametrization (first quadrant):**
+ **Parameterization (first quadrant):**

Also applies to: 101-101, 103-103, 106-106

crates/grida-canvas/src/shape/srrect_orthogonal.rs (6)

37-38: Consider removing unused angle_circle field.

The #[allow(dead_code)] attribute indicates this field is never read. If it's not needed for future functionality, remove both the field and its computation to simplify the code.


48-48: Use method syntax for consistency.

Prefer .min() method syntax over f32::min to align with Rust conventions and match the style used elsewhere (e.g., line 45).

Apply this diff:

-    let p = f32::min(shortest_side / 2.0, (1.0 + smoothness) * radius);
+    let p = (shortest_side / 2.0).min((1.0 + smoothness) * radius);

51-59: Clarify the shortest_side / 4.0 threshold logic.

The conditional angle computation uses shortest_side / 4.0 as a threshold with a change_percentage calculation, but the rationale isn't documented. This makes the code harder to maintain and verify.

Add a comment explaining why this threshold exists and how change_percentage affects the angle interpolation.


95-98: Use method syntax for min operations.

Replace f32::min with .min() method calls for consistency with Rust idioms.

Apply this diff:

-    let tl_r = f32::min(shape.corner_radius.tl.rx, shape.corner_radius.tl.ry).max(0.0);
-    let tr_r = f32::min(shape.corner_radius.tr.rx, shape.corner_radius.tr.ry).max(0.0);
-    let br_r = f32::min(shape.corner_radius.br.rx, shape.corner_radius.br.ry).max(0.0);
-    let bl_r = f32::min(shape.corner_radius.bl.rx, shape.corner_radius.bl.ry).max(0.0);
+    let tl_r = shape.corner_radius.tl.rx.min(shape.corner_radius.tl.ry).max(0.0);
+    let tr_r = shape.corner_radius.tr.rx.min(shape.corner_radius.tr.ry).max(0.0);
+    let br_r = shape.corner_radius.br.rx.min(shape.corner_radius.br.ry).max(0.0);
+    let bl_r = shape.corner_radius.bl.rx.min(shape.corner_radius.bl.ry).max(0.0);

111-204: Prefer method syntax throughout path construction.

Multiple lines use f32::min and f32::max instead of method syntax (lines 111, 132, 137, 159, 164, 182, 187, 204). For consistency, use .min() and .max().

Example for line 111:

-    path.line_to((f32::max(w / 2.0, w - tr.p), 0.0));
+    path.line_to(((w / 2.0).max(w - tr.p), 0.0));

Apply similar changes to lines 132, 137, 159, 164, 182, 187, 204.


221-252: Consider expanding test coverage.

The current tests cover basic smoothing parameters and verify the path is non-empty. Consider adding tests for:

  • Intermediate smoothing values (e.g., 0.5)
  • Edge cases (zero radius, very small dimensions)
  • Asymmetric corner radii
  • Vector network conversion output
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed128a and c04864c.

📒 Files selected for processing (3)
  • crates/grida-canvas/src/shape/srrect_orthogonal.rs (1 hunks)
  • docs/math/g2-curve-blending.md (1 hunks)
  • editor/grida-canvas/reducers/node.reducer.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
editor/grida-*/**

📄 CodeRabbit inference engine (AGENTS.md)

Use editor/grida-* directories to isolate domain-specific modules pending promotion to /packages

Files:

  • editor/grida-canvas/reducers/node.reducer.ts
crates/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep Rust crates under /crates; this is the monorepo’s Rust workspace for the Canvas implementation

Files:

  • crates/grida-canvas/src/shape/srrect_orthogonal.rs
crates/grida-canvas/src/**/*.rs

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

crates/grida-canvas/src/**/*.rs: All internal structs (e.g., NodeRecs, SceneGraph, caches) must use NodeId (u64) for identifiers and not serialize them
Public APIs must accept and return UserNodeId (String) for stability and .grida serialization

Files:

  • crates/grida-canvas/src/shape/srrect_orthogonal.rs
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Author and maintain all documentation under the root docs directory; it is the source of truth

Files:

  • docs/math/g2-curve-blending.md
🧬 Code graph analysis (1)
crates/grida-canvas/src/shape/srrect_orthogonal.rs (1)
crates/grida-canvas/src/cg/types.rs (9)
  • b (79-81)
  • a (82-84)
  • new (20-22)
  • new (823-828)
  • new (987-989)
  • new (1330-1336)
  • new (1689-1696)
  • circular (868-873)
  • circular (933-935)
🪛 LanguageTool
docs/math/g2-curve-blending.md

[uncategorized] ~5-~5: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...dates established techniques from CAGD (Computer Aided Geometric Design) and relates them to p...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~53-~53: Do not mix variants of the same word (‘parametrization’ and ‘parameterization’) within a single text.
Context: ...produces Apple‑style “icon” corners. Parametrization (first quadrant): $$ x(t)=a,|\cos t...

(EN_WORD_COHERENCY)


[uncategorized] ~148-~148: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...e. - Hoschek & Lasser, Fundamentals of Computer Aided Geometric Design — blending and fairne...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

⏰ 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: test
  • GitHub Check: cargo test
🔇 Additional comments (2)
docs/math/g2-curve-blending.md (1)

1-151: Comprehensive and well-structured mathematical documentation.

This document provides excellent formalization of G² curve blending suitable as a reference for implementation. The progression from problem statement → curvature formulas → canonical constructions → parameterization → robustness → terminology is logical and thorough. The Rust enum at §7 is appropriately positioned to bridge theory and code. References are curated and relevant to the topic.

crates/grida-canvas/src/shape/srrect_orthogonal.rs (1)

212-219: Fallback implementation resolves previous review concern.

The vector network conversion now uses a fallback that converts the generated path to a VectorNetwork, addressing the previous review comment about returning empty geometry. This ensures editor and export functionality remain operational while a more structured implementation can be added later.

@softmarshmallow softmarshmallow merged commit 5d56a80 into main Oct 29, 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