Refactor vector network handling and add corner radius comparison#555
Refactor vector network handling and add corner radius comparison#555softmarshmallow merged 5 commits intomainfrom
Conversation
- 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a new public Changes
Sequence DiagramsequenceDiagram
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
…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
There was a problem hiding this comment.
💡 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, |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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 | 🟡 MinorPotential panic on zero FPS values.
If
target_fps = 0is passed tonew()(line 27) ormax_fps = 0towith_max_fps()(line 38), the division1_000_000 / 0will 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 | 🟡 MinorResolve the typos CI failure on
alignement.Line 490 is currently flagged by the
typosworkflow. Please rename toalignment(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_radiusis not carried intoVectorNetwork. 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: Guardvn_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 fixeddiff_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
⛔ Files ignored due to path filters (4)
Cargo.lockis excluded by!**/*.lockcrates/grida-canvas-wasm/lib/bin/grida_canvas_wasm.wasmis excluded by!**/*.wasmcrates/grida-canvas/goldens/corner_radius_backends.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (30)
crates/grida-canvas-wasm/lib/bin/grida-canvas-wasm.jscrates/grida-canvas-wasm/lib/index.tscrates/grida-canvas-wasm/lib/modules/canvas.tscrates/grida-canvas-wasm/package.jsoncrates/grida-canvas-wasm/src/wasm_application.rscrates/grida-canvas/Cargo.tomlcrates/grida-canvas/examples/golden_corner_radius_backends.rscrates/grida-canvas/src/helpers/webfont_helper.rscrates/grida-canvas/src/io/io_grida.rscrates/grida-canvas/src/layout/into_taffy.rscrates/grida-canvas/src/node/schema.rscrates/grida-canvas/src/os/emscripten.rscrates/grida-canvas/src/shape/corner.rscrates/grida-canvas/src/shape/mod.rscrates/grida-canvas/src/shape/polygon.rscrates/grida-canvas/src/shape/regular_polygon.rscrates/grida-canvas/src/shape/regular_star.rscrates/grida-canvas/src/shape/rrect.rscrates/grida-canvas/src/shape/vector.rscrates/grida-canvas/src/sys/scheduler.rscrates/grida-canvas/src/vectornetwork/vn.rscrates/grida-canvas/src/window/application.rscrates/grida-canvas/src/window/application_emscripten.rscrates/grida-canvas/tests/flatten_rendiff.rscrates/grida-canvas/tests/shape_to_vector_network.rseditor/grida-canvas/backends/wasm.tseditor/grida-canvas/editor.i.tseditor/grida-canvas/reducers/methods/flatten.tspackages/grida-canvas-sdk-render-figma/package.jsonpackages/grida-canvas-vn/vn.ts
| 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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
editor/grida-canvas/reducers/methods/flatten.ts (1)
143-152:⚠️ Potential issue | 🟠 MajorPreserve rectangle corner radius in fallback flatten path.
When WASM flattening is unavailable, rounded rectangles are flattened as sharp rects and
corner_radiusis 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
📒 Files selected for processing (5)
crates/grida-canvas/src/shape/regular_polygon.rscrates/grida-canvas/src/shape/regular_star.rseditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.tseditor/grida-canvas/reducers/methods/flatten.ts
New Features
Bug Fixes
Tests