-
Notifications
You must be signed in to change notification settings - Fork 93
Grida Canvas - Layout Prep - NodeId u64 #433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughIntroduces an internal numeric NodeId system with bidirectional mapping to external string IDs, refactors repositories/graphs/caches/painters to use NodeId, adds an IdConverter and Figma ID mapping, removes per-node id/name fields, adds a new layout module (types, Taffy conversions, demos), adjusts IO/export/hit-test/runtime flows, and updates examples/tests/docs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant App as UnknownTargetApplication
participant IO as IdConverter
participant SG as SceneGraph
participant Repo as NodeRepository
User->>App: load(JSONCanvasFile)
App->>IO: convert_json_canvas_file(file)
IO->>IO: map UserNodeId↔NodeId<br/>convert nodes/links
IO-->>App: Scene { graph, bg, name }
App->>App: store id_mapping/id_mapping_reverse
App-->>User: load result (ok/err)
sequenceDiagram
autonumber
actor User
participant App as UnknownTargetApplication
participant Exp as export::export_node_as
participant Geo as GeometryCache
participant Scene as Scene
User->>App: export_node_as(user_id, format)
App->>App: user_id_to_internal(user_id) -> NodeId?
alt found
App->>Exp: export_node_as(&Scene, &Geo, &Fonts, &Images, &NodeId, format)
Exp->>Geo: get_render_bounds(&NodeId)
Exp->>Scene: fetch node and layers
Exp-->>App: Exported
App-->>User: Exported
else not found
App-->>User: None / error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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 |
There was a problem hiding this 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
/// Inserts a node with automatic ID generation. | ||
/// Returns the generated NodeId. | ||
pub fn insert(&mut self, node: Node) -> NodeId { | ||
let id = node.id(); | ||
self.nodes.insert(id.clone(), node); | ||
let id = self.id_generator.next(); | ||
self.nodes.insert(id, node); | ||
id | ||
} | ||
|
||
/// Inserts a node with an explicit ID (for IO layer use). | ||
/// The ID must not already exist in the repository. | ||
pub fn insert_with_id(&mut self, id: NodeId, node: Node) { | ||
self.nodes.insert(id, node); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Increment ID generator when loading nodes with explicit IDs
NodeRepository::insert_with_id
stores an externally provided ID but never advances the internal NodeIdGenerator
. SceneGraph::new_from_snapshot
uses this path for every node loaded from disk, so the generator remains at 0 after deserialization. The next append_child
call (which uses insert
) will reuse ID 0 and overwrite the node already stored under that key, breaking scene links. The generator should be bumped to at least max(existing)+1 when explicit IDs are inserted to guarantee unique IDs for future nodes.
Useful? React with 👍 / 👎.
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
crates/grida-canvas/src/node/repository.rs (1)
86-92
: Fix FromIterator to preserve input IDs.The
FromIterator
implementation discards theNodeId
from the iterator's(NodeId, Node)
tuples and generates new IDs viainsert()
. This breaks the expected behavior of collecting a repository from ID-node pairs.Apply this diff to use
insert_with_id
instead:impl FromIterator<(NodeId, Node)> for NodeRepository { fn from_iter<T: IntoIterator<Item = (NodeId, Node)>>(iter: T) -> Self { let mut repo = Self::new(); - for (_, node) in iter { - repo.insert(node); + for (id, node) in iter { + repo.insert_with_id(id, node); } repo } }crates/grida-canvas/src/io/io_figma.rs (1)
624-666
: Critical: Links use different IDs than repository inserts — graph will not wire up correctlyYou generate internal IDs from Figma IDs and use them as link keys, but you insert nodes with repository.insert(..) which allocates different IDs. Result: links/roots won’t match node IDs in the repository.
Fix by inserting nodes with their pre-resolved internal ID. Minimal change: assign explicit IDs during insertion in convert_sub_canvas_node for node kinds that have Figma IDs (Frame, Instance, Section, Component, Group, BooleanOperation). Keep existing link creation as-is (it will reference the same IDs).
Apply these diffs:
- Use insert_with_id in convert_sub_canvas_node
- pub fn convert_sub_canvas_node(&mut self, node: &FigmaSubcanvasNode) -> Result<NodeId, String> { - let grid_node = match node { - FigmaSubcanvasNode::Frame(frame) => self.convert_frame(frame)?, - FigmaSubcanvasNode::Group(group) => self.convert_group(group)?, - FigmaSubcanvasNode::Vector(vector) => self.convert_vector(vector)?, - FigmaSubcanvasNode::BooleanOperation(boolean) => { - self.convert_boolean_operation(boolean)? - } - FigmaSubcanvasNode::Star(star) => self.convert_star(star)?, - FigmaSubcanvasNode::Line(line) => self.convert_line(line)?, - FigmaSubcanvasNode::Ellipse(ellipse) => self.convert_ellipse(ellipse)?, - FigmaSubcanvasNode::RegularPolygon(polygon) => self.convert_regular_polygon(polygon)?, - FigmaSubcanvasNode::Rectangle(rectangle) => self.convert_rectangle(rectangle)?, - FigmaSubcanvasNode::Text(text) => self.convert_text(text)?, - FigmaSubcanvasNode::Slice(slice) => self.convert_slice(slice)?, - FigmaSubcanvasNode::Component(component) => self.convert_component(component)?, - FigmaSubcanvasNode::ComponentSet(component_set) => { - self.convert_component_set(component_set)? - } - FigmaSubcanvasNode::Instance(instance) => self.convert_instance(instance)?, - FigmaSubcanvasNode::Section(section) => self.convert_section(section)?, - FigmaSubcanvasNode::LinkUnfurl(link) => self.convert_link(link)?, - FigmaSubcanvasNode::Connector(_) => Err("Connector nodes not supported".to_string())?, - FigmaSubcanvasNode::Embed(_) => Err("Embed nodes not supported".to_string())?, - FigmaSubcanvasNode::ShapeWithText(_) => { - Err("Shape with text nodes not supported".to_string())? - } - FigmaSubcanvasNode::Sticky(_) => Err("Sticky nodes not supported".to_string())?, - FigmaSubcanvasNode::TableCell(_) => Err("Table cell nodes not supported".to_string())?, - FigmaSubcanvasNode::Table(_) => Err("Table nodes not supported".to_string())?, - FigmaSubcanvasNode::WashiTape(_) => Err("Washi tape nodes not supported".to_string())?, - FigmaSubcanvasNode::Widget(_) => Err("Widget nodes not supported".to_string())?, - FigmaSubcanvasNode::TextPath(_) => Err("Text path nodes not supported".to_string())?, - FigmaSubcanvasNode::TransformGroup(_) => { - Err("Transform group nodes not supported".to_string())? - } - }; - - Ok(self.repository.insert(grid_node)) - } + pub fn convert_sub_canvas_node(&mut self, node: &FigmaSubcanvasNode) -> Result<NodeId, String> { + let (grid_node, explicit_id): (Node, Option<NodeId>) = match node { + FigmaSubcanvasNode::Frame(frame) => ( + self.convert_frame(frame)?, + Some(self.get_or_create_internal_id(&frame.id)), + ), + FigmaSubcanvasNode::Group(group) => ( + self.convert_group(group)?, + Some(self.get_or_create_internal_id(&group.id)), + ), + FigmaSubcanvasNode::Instance(instance) => ( + self.convert_instance(instance)?, + Some(self.get_or_create_internal_id(&instance.id)), + ), + FigmaSubcanvasNode::Section(section) => ( + self.convert_section(section)?, + Some(self.get_or_create_internal_id(§ion.id)), + ), + FigmaSubcanvasNode::Component(component) => ( + self.convert_component(component)?, + Some(self.get_or_create_internal_id(&component.id)), + ), + FigmaSubcanvasNode::BooleanOperation(boolean) => ( + self.convert_boolean_operation(boolean)?, + Some(self.get_or_create_internal_id(&boolean.id)), + ), + // Leaves or unsupported nodes: allocate IDs internally + FigmaSubcanvasNode::Vector(vector) => (self.convert_vector(vector)?, None), + FigmaSubcanvasNode::Star(star) => (self.convert_star(star)?, None), + FigmaSubcanvasNode::Line(line) => (self.convert_line(line)?, None), + FigmaSubcanvasNode::Ellipse(ellipse) => (self.convert_ellipse(ellipse)?, None), + FigmaSubcanvasNode::RegularPolygon(polygon) => (self.convert_regular_polygon(polygon)?, None), + FigmaSubcanvasNode::Rectangle(rectangle) => (self.convert_rectangle(rectangle)?, None), + FigmaSubcanvasNode::Text(text) => (self.convert_text(text)?, None), + FigmaSubcanvasNode::Slice(slice) => (self.convert_slice(slice)?, None), + FigmaSubcanvasNode::ComponentSet(component_set) => (self.convert_component_set(component_set)?, None), + FigmaSubcanvasNode::LinkUnfurl(link) => (self.convert_link(link)?, None), + FigmaSubcanvasNode::Connector(_) => Err("Connector nodes not supported".to_string())?, + FigmaSubcanvasNode::Embed(_) => Err("Embed nodes not supported".to_string())?, + FigmaSubcanvasNode::ShapeWithText(_) => Err("Shape with text nodes not supported".to_string())?, + FigmaSubcanvasNode::Sticky(_) => Err("Sticky nodes not supported".to_string())?, + FigmaSubcanvasNode::TableCell(_) => Err("Table cell nodes not supported".to_string())?, + FigmaSubcanvasNode::Table(_) => Err("Table nodes not supported".to_string())?, + FigmaSubcanvasNode::WashiTape(_) => Err("Washi tape nodes not supported".to_string())?, + FigmaSubcanvasNode::Widget(_) => Err("Widget nodes not supported".to_string())?, + FigmaSubcanvasNode::TextPath(_) => Err("Text path nodes not supported".to_string())?, + FigmaSubcanvasNode::TransformGroup(_) => Err("Transform group nodes not supported".to_string())?, + }; + + let id = if let Some(explicit) = explicit_id { + self.repository.insert_with_id(explicit, grid_node) + } else { + self.repository.insert(grid_node) + }; + Ok(id) + }
- Ensure NodeIdGenerator is in scope in this module
use crate::node::repository::NodeRepository; use crate::node::scene_graph::SceneGraph; use crate::node::schema::*; +use crate::node::id::NodeIdGenerator;
Note: Also confirm NodeRepository::insert_with_id updates its generator to prevent future insert() collisions.
Also applies to: 723-765, 776-806, 898-944, 1176-1183, 1391-1397, 880-883, 823-864, 1-21, 255-270, 286-296
crates/grida-canvas/src/cache/geometry.rs (1)
582-587
: TextSpan render-bounds ignore effects (blur/shadow).compute_render_bounds_for TextSpan uses LayerEffects::default(), clipping visual effects. Use n.effects.
Apply this diff:
- Node::TextSpan(n) => compute_render_bounds_from_style( - world_bounds, - n.stroke_width, - n.stroke_align, - &LayerEffects::default(), - ), + Node::TextSpan(n) => compute_render_bounds_from_style( + world_bounds, + n.stroke_width, + n.stroke_align, + &n.effects, + ),crates/grida-canvas/src/io/io_grida.rs (1)
963-1006
: Populate the new layout fields from JSONThe
ContainerNodeRec
conversion currently hardcodes every new layout attribute (layout_mode
,layout_direction
,layout_wrap
, alignments, gaps,padding
) to their defaults. The JSON schema already carries these values (layout
,direction
,mainAxisAlignment
,crossAxisAlignment
,mainAxisGap
,crossAxisGap
,padding
), so we now silently discard user data and every imported container stays inNormal
mode with zero gaps and padding. Please parse those JSON properties and map them onto the new enums/structs so flex containers actually deserialize with their intended settings.
🧹 Nitpick comments (22)
crates/grida-canvas/examples/golden_layout_flex_padding.rs (2)
105-112
: Remove unused ID hashing; avoid warning
id_u64
is unused. Rename the param and drop the hashing block.-fn create_child_container(id: &str, width: f32, height: f32) -> ContainerNodeRec { - // Use a simple hash of the string as u64 ID - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - let id_u64 = hasher.finish(); +fn create_child_container(_id: &str, width: f32, height: f32) -> ContainerNodeRec {
168-175
: Derive padding overlay from container values (not hardcoded 20)Use the container’s padding to keep the visualization correct if it changes.
- let padding_rect = Rect::from_xywh( - base_x + 20.0, // padding left - base_y + 20.0, // padding top - container.available_size().0 - 40.0, // width - padding - container.available_size().1 - 40.0, // height - padding - ); + let p = container.container.padding; + let padding_rect = Rect::from_xywh( + base_x + p.left, + base_y + p.top, + container.available_size().0 - (p.left + p.right), + container.available_size().1 - (p.top + p.bottom), + );crates/grida-canvas/src/layout/tmp_example.rs (4)
119-123
: Disambiguate NodeId to avoid confusion with project NodeIdQualify the Taffy NodeId; the project also defines
NodeId = u64
.- let child_nodes: Vec<NodeId> = children + let child_nodes: Vec<taffy::prelude::NodeId> = children .iter() .map(|child| taffy.new_leaf(child.to_taffy_style()).unwrap()) .collect();
93-99
: Remove placeholder id() returning 0Returning a dummy ID risks misuse and breaks identity guarantees. Drop this method in the demo helper.
- /// Get container ID - /// Note: ID should be provided by the caller, not stored in the node - pub fn id(&self) -> crate::node::schema::NodeId { - // TODO: Remove this method - ID should come from graph traversal - 0 - }
196-230
: Silence unused variable warning in test map
i
is unused. Replace with_
in the iterator.- let children: Vec<ContainerWithStyle> = (0..3) - .map(|i| { + let children: Vec<ContainerWithStyle> = (0..3) + .map(|_| { ContainerWithStyle::from_container(ContainerNodeRec {
280-314
: Silence unused variable warning in test map
i
is unused. Replace with_
in the iterator.- let children: Vec<ContainerWithStyle> = (0..4) - .map(|i| { + let children: Vec<ContainerWithStyle> = (0..4) + .map(|_| { ContainerWithStyle::from_container(ContainerNodeRec {crates/grida-canvas/examples/golden_layout_padding.rs (2)
12-24
: Remove unused ID hashing; avoid warning
id_u64
is unused. Rename the param and delete the hash block.-fn create_container_with_padding( - id: &str, +fn create_container_with_padding( + _id: &str, width: f32, height: f32, padding: f32, ) -> ContainerNodeRec { - // Use a simple hash of the string as u64 ID - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - let id_u64 = hasher.finish(); ContainerNodeRec {
63-69
: Use embedded typeface helper to avoid per-call FontMgrSimpler and consistent with the embedded font utilities.
- let font_data = cg::fonts::embedded::geist::BYTES; - let font_mgr = FontMgr::new(); - let typeface = font_mgr.new_from_data(font_data, None).unwrap(); + let typeface = cg::fonts::embedded::typeface(cg::fonts::embedded::geist::BYTES);crates/grida-canvas/examples/golden_layout_flex.rs (2)
12-19
: Remove unused ID hashing; avoid warning
id_u64
is unused. Rename the param and delete the hash block.-fn create_container_with_gap(id: &str, width: f32, height: f32, gap: f32) -> ContainerNodeRec { - // Use a simple hash of the string as u64 ID - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - let id_u64 = hasher.finish(); +fn create_container_with_gap(_id: &str, width: f32, height: f32, gap: f32) -> ContainerNodeRec {
56-61
: Use embedded typeface helper to avoid per-call FontMgrSimpler and consistent with the embedded font utilities.
- let font_data = cg::fonts::embedded::geist::BYTES; - let font_mgr = FontMgr::new(); - let typeface = font_mgr.new_from_data(font_data, None).unwrap(); + let typeface = cg::fonts::embedded::typeface(cg::fonts::embedded::geist::BYTES);crates/grida-canvas/src/node/factory.rs (1)
7-10
: Clarify the documentation about placeholder IDs.The documentation states "The factory creates nodes with a placeholder ID of 0", but based on the broader PR changes (removal of id/name fields from node records), nodes no longer contain ID fields. Consider updating this to clarify that nodes are created without IDs and that IDs are assigned externally when nodes are inserted into the repository or scene graph.
Apply this diff to clarify the documentation:
-/// Factory for creating nodes with default values. -/// -/// Note: The factory creates nodes with a placeholder ID of 0. -/// Actual IDs should be assigned by SceneRuntime or another ID management system. +/// Factory for creating nodes with default values. +/// +/// Note: Nodes are created without IDs. IDs are assigned when nodes are +/// inserted into NodeRepository or SceneGraph.crates/grida-canvas/src/layout/mod.rs (1)
32-59
: Consider making flex_shrink and flex_basis configurable.Lines 53-54 hardcode
flex_shrink: 1.0
andflex_basis: Dimension::auto()
. While these are sensible defaults, consider adding these as fields toLayoutStyle
for greater flexibility, or at least document why these specific values are always appropriate.If these should be configurable, apply this diff:
pub struct LayoutStyle { pub width: Dimension, pub height: Dimension, pub flex_grow: f32, + pub flex_shrink: f32, + pub flex_basis: Dimension, } impl Default for LayoutStyle { fn default() -> Self { Self { width: Dimension::auto(), height: Dimension::auto(), flex_grow: 0.0, + flex_shrink: 1.0, + flex_basis: Dimension::auto(), } } } impl LayoutStyle { pub fn to_taffy_style( &self, padding: Rect<LengthPercentage>, gap: Size<LengthPercentage>, flex_direction: FlexDirection, flex_wrap: FlexWrap, justify_content: Option<JustifyContent>, align_items: Option<AlignItems>, ) -> Style { Style { display: Display::Flex, flex_direction, flex_wrap, gap, size: Size { width: self.width, height: self.height, }, padding, justify_content, align_items, flex_grow: self.flex_grow, - flex_shrink: 1.0, - flex_basis: Dimension::auto(), + flex_shrink: self.flex_shrink, + flex_basis: self.flex_basis, ..Default::default() } } }crates/grida-canvas/src/node/scene_graph.rs (1)
98-105
: Optional: validate referential integrity of links/roots at constructionConsider verifying that all roots exist in nodes and all link endpoints exist; fail-fast or log in debug builds to avoid traversal errors later.
crates/grida-canvas/examples/golden_layout_flex_alignment.rs (1)
8-21
: Remove unused ID hashing; avoid dead code and warningsThe hashed id_u64 is never used. Drop the hashing and prefix the arg with “_” to silence warnings.
-fn create_container_with_alignment( - id: &str, +fn create_container_with_alignment( + _id: &str, @@ - // Use a simple hash of the string as u64 ID - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - let id_u64 = hasher.finish(); + // id unused for this example @@ -fn create_child_container(id: &str, width: f32, height: f32) -> ContainerNodeRec { - // Use a simple hash of the string as u64 ID - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - id.hash(&mut hasher); - let id_u64 = hasher.finish(); +fn create_child_container(_id: &str, width: f32, height: f32) -> ContainerNodeRec { + // id unused for this exampleAlso applies to: 47-54
crates/grida-canvas/src/cache/geometry.rs (1)
271-272
: Pass NodeId by value to paragraph measurements (avoid refs in caches).If ParagraphCache::measure stores the ID for caching, prefer taking Option and pass Some(*id) here to avoid lifetime/aliasing issues with references.
crates/grida-canvas/src/devtools/hit_overlay.rs (1)
128-129
: Avoid unused binding in focus lookup.Bindless check is clearer and avoids
_focus_layer
.Apply this diff:
- if let Some(_focus_layer) = cache.layers.layers.iter().find(|e| &e.id == focus_id) - { + if cache.layers.layers.iter().any(|e| &e.id == focus_id) {crates/grida-canvas/NODEID_MIGRATION.md (1)
96-96
: Add language to fenced code blocks (markdownlint MD040).Specify a language (e.g., text) for these code fences.
Apply this diff:
-``` +```text-``` +```textAlso applies to: 110-110
crates/grida-canvas/src/painter/layer.rs (4)
159-178
: Remove duplicate id from PainterPictureTextLayer (base.id already present).Text layer stores id twice (base.id and its own id). Drop the extra field and its initialization.
Apply this diff:
pub struct PainterPictureTextLayer { pub base: PainterPictureLayerBase, @@ pub text_align_vertical: TextAlignVertical, - pub id: NodeId, }
- text_align_vertical: n.text_align_vertical, - id: id.clone(), + text_align_vertical: n.text_align_vertical,Also applies to: 625-651
194-201
: Clarify single source of truth for IDs.LayerEntry pairs IDs with layers but layers still embed base.id. Consider deprecating base.id in a follow-up to avoid drift, using LayerEntry as canonical.
581-587
: Consistently filter invisible paints for Line strokes.Other nodes use filter_visible_paints; apply the same here.
Apply this diff:
- strokes: n.strokes.clone(), + strokes: Self::filter_visible_paints(&n.strokes),
845-847
: Fix doc param name (‘repo’ → ‘graph’).The parameter is graph, not repo.
Apply this diff:
- /// - `repo`: The node repository containing all nodes + /// - `graph`: The node graph containing all nodescrates/grida-canvas/src/io/id_converter.rs (1)
80-85
: Minor: prefercloned()
on Option.Use links.get(&scene_id).cloned().unwrap_or_default() for brevity.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
crates/grida-canvas/goldens/layout_flex.png
is excluded by!**/*.png
crates/grida-canvas/goldens/layout_flex_alignment.png
is excluded by!**/*.png
crates/grida-canvas/goldens/layout_flex_padding.png
is excluded by!**/*.png
crates/grida-canvas/goldens/layout_padding.png
is excluded by!**/*.png
📒 Files selected for processing (64)
crates/grida-canvas/AGENTS.md
(1 hunks)crates/grida-canvas/NODEID_MIGRATION.md
(1 hunks)crates/grida-canvas/benches/bench_rectangles.rs
(0 hunks)crates/grida-canvas/examples/app_grida.rs
(1 hunks)crates/grida-canvas/examples/golden_container_stroke.rs
(0 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/examples/golden_pdf.rs
(0 hunks)crates/grida-canvas/examples/golden_svg.rs
(0 hunks)crates/grida-canvas/examples/golden_type_stroke.rs
(0 hunks)crates/grida-canvas/examples/grida_basic.rs
(0 hunks)crates/grida-canvas/examples/grida_blendmode.rs
(0 hunks)crates/grida-canvas/examples/grida_booleans.rs
(0 hunks)crates/grida-canvas/examples/grida_container.rs
(0 hunks)crates/grida-canvas/examples/grida_effects.rs
(0 hunks)crates/grida-canvas/examples/grida_fills.rs
(0 hunks)crates/grida-canvas/examples/grida_gradients.rs
(0 hunks)crates/grida-canvas/examples/grida_image.rs
(0 hunks)crates/grida-canvas/examples/grida_images.rs
(0 hunks)crates/grida-canvas/examples/grida_lines.rs
(0 hunks)crates/grida-canvas/examples/grida_mask.rs
(0 hunks)crates/grida-canvas/examples/grida_nested.rs
(0 hunks)crates/grida-canvas/examples/grida_paint.rs
(0 hunks)crates/grida-canvas/examples/grida_shapes.rs
(0 hunks)crates/grida-canvas/examples/grida_shapes_ellipse.rs
(0 hunks)crates/grida-canvas/examples/grida_strokes.rs
(0 hunks)crates/grida-canvas/examples/grida_texts.rs
(0 hunks)crates/grida-canvas/examples/grida_vector.rs
(0 hunks)crates/grida-canvas/examples/grida_webfonts.rs
(0 hunks)crates/grida-canvas/examples/sys_camera.rs
(0 hunks)crates/grida-canvas/src/cache/geometry.rs
(2 hunks)crates/grida-canvas/src/cache/scene.rs
(1 hunks)crates/grida-canvas/src/cg/types.rs
(1 hunks)crates/grida-canvas/src/devtools/hit_overlay.rs
(3 hunks)crates/grida-canvas/src/devtools/stroke_overlay.rs
(2 hunks)crates/grida-canvas/src/dummy/mod.rs
(0 hunks)crates/grida-canvas/src/export/mod.rs
(2 hunks)crates/grida-canvas/src/hittest/hit_tester.rs
(6 hunks)crates/grida-canvas/src/io/id_converter.rs
(1 hunks)crates/grida-canvas/src/io/io_figma.rs
(15 hunks)crates/grida-canvas/src/io/io_grida.rs
(1 hunks)crates/grida-canvas/src/io/mod.rs
(1 hunks)crates/grida-canvas/src/layout/into_taffy.rs
(1 hunks)crates/grida-canvas/src/layout/mod.rs
(1 hunks)crates/grida-canvas/src/layout/tmp_example.rs
(1 hunks)crates/grida-canvas/src/lib.rs
(1 hunks)crates/grida-canvas/src/node/factory.rs
(2 hunks)crates/grida-canvas/src/node/id.rs
(1 hunks)crates/grida-canvas/src/node/mod.rs
(1 hunks)crates/grida-canvas/src/node/repository.rs
(2 hunks)crates/grida-canvas/src/node/scene_graph.rs
(18 hunks)crates/grida-canvas/src/node/schema.rs
(2 hunks)crates/grida-canvas/src/painter/geometry.rs
(2 hunks)crates/grida-canvas/src/painter/layer.rs
(22 hunks)crates/grida-canvas/src/painter/painter_debug_node.rs
(9 hunks)crates/grida-canvas/src/runtime/scene.rs
(2 hunks)crates/grida-canvas/src/window/application.rs
(8 hunks)crates/grida-canvas/tests/export_as_pdf.rs
(0 hunks)crates/grida-canvas/tests/hit_test.rs
(2 hunks)crates/grida-canvas/tests/scene_cache.rs
(2 hunks)crates/grida-canvas/tests/vector_corner_radius.rs
(0 hunks)justfile
(1 hunks)
💤 Files with no reviewable changes (28)
- crates/grida-canvas/examples/grida_gradients.rs
- crates/grida-canvas/tests/export_as_pdf.rs
- crates/grida-canvas/examples/grida_webfonts.rs
- crates/grida-canvas/examples/grida_vector.rs
- crates/grida-canvas/examples/grida_fills.rs
- crates/grida-canvas/examples/grida_texts.rs
- crates/grida-canvas/examples/grida_shapes_ellipse.rs
- crates/grida-canvas/examples/grida_basic.rs
- crates/grida-canvas/examples/grida_container.rs
- crates/grida-canvas/benches/bench_rectangles.rs
- crates/grida-canvas/examples/grida_shapes.rs
- crates/grida-canvas/examples/grida_strokes.rs
- crates/grida-canvas/src/dummy/mod.rs
- crates/grida-canvas/examples/grida_mask.rs
- crates/grida-canvas/examples/grida_paint.rs
- crates/grida-canvas/examples/grida_nested.rs
- crates/grida-canvas/examples/grida_image.rs
- crates/grida-canvas/examples/grida_effects.rs
- crates/grida-canvas/examples/golden_svg.rs
- crates/grida-canvas/examples/golden_container_stroke.rs
- crates/grida-canvas/examples/golden_type_stroke.rs
- crates/grida-canvas/examples/grida_booleans.rs
- crates/grida-canvas/examples/golden_pdf.rs
- crates/grida-canvas/examples/grida_blendmode.rs
- crates/grida-canvas/examples/grida_lines.rs
- crates/grida-canvas/examples/grida_images.rs
- crates/grida-canvas/examples/sys_camera.rs
- crates/grida-canvas/tests/vector_corner_radius.rs
🧰 Additional context used
📓 Path-based instructions (2)
crates/grida-canvas/**/*.rs
📄 CodeRabbit inference engine (crates/grida-canvas/AGENTS.md)
crates/grida-canvas/**/*.rs
: Use the skia-safe crate for all painting in the 2D real-time rendering engine
Use the math2 crate for geometry and common math operations
Ensure source is formatted with rustfmt (cargo fmt)
Files:
crates/grida-canvas/src/node/mod.rs
crates/grida-canvas/tests/scene_cache.rs
crates/grida-canvas/src/lib.rs
crates/grida-canvas/examples/app_grida.rs
crates/grida-canvas/src/node/factory.rs
crates/grida-canvas/src/io/mod.rs
crates/grida-canvas/src/hittest/hit_tester.rs
crates/grida-canvas/src/cache/geometry.rs
crates/grida-canvas/src/node/id.rs
crates/grida-canvas/examples/golden_layout_flex_padding.rs
crates/grida-canvas/src/layout/tmp_example.rs
crates/grida-canvas/tests/hit_test.rs
crates/grida-canvas/src/runtime/scene.rs
crates/grida-canvas/src/node/scene_graph.rs
crates/grida-canvas/examples/golden_layout_flex.rs
crates/grida-canvas/src/node/repository.rs
crates/grida-canvas/src/io/io_figma.rs
crates/grida-canvas/src/layout/into_taffy.rs
crates/grida-canvas/src/painter/geometry.rs
crates/grida-canvas/src/devtools/hit_overlay.rs
crates/grida-canvas/src/window/application.rs
crates/grida-canvas/src/devtools/stroke_overlay.rs
crates/grida-canvas/src/cache/scene.rs
crates/grida-canvas/examples/golden_layout_padding.rs
crates/grida-canvas/src/layout/mod.rs
crates/grida-canvas/src/painter/layer.rs
crates/grida-canvas/examples/golden_layout_flex_alignment.rs
crates/grida-canvas/src/node/schema.rs
crates/grida-canvas/src/io/io_grida.rs
crates/grida-canvas/src/export/mod.rs
crates/grida-canvas/src/cg/types.rs
crates/grida-canvas/src/io/id_converter.rs
crates/grida-canvas/src/painter/painter_debug_node.rs
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/node/mod.rs
crates/grida-canvas/tests/scene_cache.rs
crates/grida-canvas/src/lib.rs
crates/grida-canvas/examples/app_grida.rs
crates/grida-canvas/src/node/factory.rs
crates/grida-canvas/src/io/mod.rs
crates/grida-canvas/src/hittest/hit_tester.rs
crates/grida-canvas/src/cache/geometry.rs
crates/grida-canvas/AGENTS.md
crates/grida-canvas/src/node/id.rs
crates/grida-canvas/examples/golden_layout_flex_padding.rs
crates/grida-canvas/src/layout/tmp_example.rs
crates/grida-canvas/tests/hit_test.rs
crates/grida-canvas/src/runtime/scene.rs
crates/grida-canvas/src/node/scene_graph.rs
crates/grida-canvas/examples/golden_layout_flex.rs
crates/grida-canvas/src/node/repository.rs
crates/grida-canvas/src/io/io_figma.rs
crates/grida-canvas/src/layout/into_taffy.rs
crates/grida-canvas/NODEID_MIGRATION.md
crates/grida-canvas/src/painter/geometry.rs
crates/grida-canvas/src/devtools/hit_overlay.rs
crates/grida-canvas/src/window/application.rs
crates/grida-canvas/src/devtools/stroke_overlay.rs
crates/grida-canvas/src/cache/scene.rs
crates/grida-canvas/examples/golden_layout_padding.rs
crates/grida-canvas/src/layout/mod.rs
crates/grida-canvas/src/painter/layer.rs
crates/grida-canvas/examples/golden_layout_flex_alignment.rs
crates/grida-canvas/src/node/schema.rs
crates/grida-canvas/src/io/io_grida.rs
crates/grida-canvas/src/export/mod.rs
crates/grida-canvas/src/cg/types.rs
crates/grida-canvas/src/io/id_converter.rs
crates/grida-canvas/src/painter/painter_debug_node.rs
🧠 Learnings (2)
📚 Learning: 2025-09-26T09:30:18.313Z
Learnt from: CR
PR: gridaco/grida#0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-09-26T09:30:18.313Z
Learning: Use cargo check and cargo check --all-targets --all-features to validate builds
Applied to files:
justfile
📚 Learning: 2025-09-26T09:30:18.313Z
Learnt from: CR
PR: gridaco/grida#0
File: crates/grida-canvas/AGENTS.md:0-0
Timestamp: 2025-09-26T09:30:18.313Z
Learning: Applies to crates/grida-canvas/**/*.rs : Use the skia-safe crate for all painting in the 2D real-time rendering engine
Applied to files:
crates/grida-canvas/AGENTS.md
🧬 Code graph analysis (27)
crates/grida-canvas/src/node/mod.rs (2)
crates/grida-canvas/src/layout/tmp_example.rs (1)
id
(95-98)crates/grida-canvas/src/painter/layer.rs (2)
id
(99-99)id
(106-112)
crates/grida-canvas/tests/scene_cache.rs (2)
crates/grida-canvas/src/layout/tmp_example.rs (1)
id
(95-98)crates/grida-canvas/src/painter/layer.rs (2)
id
(99-99)id
(106-112)
crates/grida-canvas/examples/app_grida.rs (3)
crates/grida-canvas/src/io/io_figma.rs (1)
new
(262-271)crates/grida-canvas/src/node/scene_graph.rs (1)
new
(69-75)crates/grida-canvas/src/io/id_converter.rs (1)
new
(17-23)
crates/grida-canvas/src/node/factory.rs (1)
crates/grida-canvas/src/cg/types.rs (12)
default
(114-116)default
(136-138)default
(268-270)default
(332-334)default
(360-362)default
(376-378)default
(406-408)default
(430-432)default
(532-534)default
(544-546)default
(617-619)default
(730-732)
crates/grida-canvas/src/hittest/hit_tester.rs (3)
crates/grida-canvas/src/painter/layer.rs (6)
transform
(101-101)transform
(122-128)shape
(102-102)shape
(130-136)id
(99-99)id
(106-112)crates/grida-canvas/src/sk/mod.rs (1)
sk_matrix
(3-6)crates/grida-canvas/src/cache/scene.rs (1)
intersects
(126-135)
crates/grida-canvas/src/cache/geometry.rs (2)
crates/grida-canvas/src/layout/tmp_example.rs (1)
id
(95-98)crates/grida-canvas/src/painter/layer.rs (2)
id
(99-99)id
(106-112)
crates/grida-canvas/src/node/id.rs (4)
packages/grida-canvas-schema/grida.ts (1)
NodeIdGenerator
(257-358)crates/grida-canvas/src/io/io_figma.rs (1)
new
(262-271)crates/grida-canvas/src/node/repository.rs (1)
new
(15-20)crates/grida-canvas/src/io/id_converter.rs (1)
new
(17-23)
crates/grida-canvas/examples/golden_layout_flex_padding.rs (2)
crates/grida-canvas/src/layout/tmp_example.rs (3)
compute_flex_layout_for_container
(112-153)from_container
(27-32)id
(95-98)crates/grida-canvas/src/fonts/embedded/mod.rs (1)
typeface
(12-17)
crates/grida-canvas/src/layout/tmp_example.rs (1)
crates/grida-canvas/src/layout/mod.rs (2)
default
(18-24)to_taffy_style
(32-58)
crates/grida-canvas/tests/hit_test.rs (1)
crates/grida-canvas/src/hittest/hit_tester.rs (2)
hit_first_fast
(33-45)hit_first
(77-99)
crates/grida-canvas/src/runtime/scene.rs (1)
crates/grida-canvas/src/painter/layer.rs (2)
id
(99-99)id
(106-112)
crates/grida-canvas/examples/golden_layout_flex.rs (3)
crates/grida-canvas/src/layout/tmp_example.rs (3)
compute_flex_layout_for_container
(112-153)id
(95-98)from_container
(27-32)crates/grida-canvas/src/fonts/embedded/mod.rs (1)
typeface
(12-17)crates/grida-canvas/src/cg/types.rs (3)
from_rgb
(59-61)a
(72-74)b
(69-71)
crates/grida-canvas/src/node/repository.rs (5)
packages/grida-canvas-schema/grida.ts (1)
NodeIdGenerator
(257-358)crates/grida-canvas/src/io/io_figma.rs (1)
new
(262-271)crates/grida-canvas/src/node/scene_graph.rs (1)
new
(69-75)crates/grida-canvas/src/io/id_converter.rs (1)
new
(17-23)crates/grida-canvas/src/node/id.rs (1)
new
(18-20)
crates/grida-canvas/src/io/io_figma.rs (4)
crates/grida-canvas/src/node/repository.rs (1)
new
(15-20)crates/grida-canvas/src/node/scene_graph.rs (2)
new
(69-75)new_from_snapshot
(86-105)crates/grida-canvas/src/cg/types.rs (17)
new
(16-18)new
(711-716)new
(1173-1179)new
(1532-1539)default
(114-116)default
(136-138)default
(268-270)default
(332-334)default
(360-362)default
(376-378)default
(406-408)default
(430-432)default
(532-534)default
(544-546)default
(617-619)default
(730-732)default
(750-752)crates/grida-canvas/src/io/id_converter.rs (1)
get_or_create_internal_id
(26-37)
crates/grida-canvas/src/layout/into_taffy.rs (3)
crates/grida-canvas/src/layout/tmp_example.rs (2)
wrap
(79-81)gap
(63-65)packages/grida-canvas-cg/lib.ts (2)
MainAxisAlignment
(589-596)CrossAxisAlignment
(603-603)packages/grida-canvas-schema/grida.ts (1)
LengthPercentage
(1003-1003)
crates/grida-canvas/src/devtools/hit_overlay.rs (1)
crates/grida-canvas/src/painter/layer.rs (6)
id
(99-99)id
(106-112)shape
(102-102)shape
(130-136)transform
(101-101)transform
(122-128)
crates/grida-canvas/src/window/application.rs (2)
crates/grida-canvas-wasm/src/wasm_application.rs (7)
std
(203-203)std
(411-411)get_node_id_from_point
(337-350)get_node_ids_from_envelope
(376-397)get_node_absolute_bounding_box
(401-418)export_node_as
(428-466)to_vector_network
(470-491)crates/grida-canvas/src/window/application_emscripten.rs (6)
get_node_id_from_point
(118-120)get_node_ids_from_envelope
(122-124)get_node_absolute_bounding_box
(126-128)export_node_as
(130-136)to_vector_network
(138-140)new
(202-239)
crates/grida-canvas/src/devtools/stroke_overlay.rs (1)
crates/grida-canvas/src/painter/layer.rs (4)
id
(99-99)id
(106-112)transform
(101-101)transform
(122-128)
crates/grida-canvas/examples/golden_layout_padding.rs (3)
crates/grida-canvas/src/layout/tmp_example.rs (4)
compute_flex_layout_for_container
(112-153)id
(95-98)padding
(58-60)from_container
(27-32)crates/grida-canvas/src/fonts/embedded/mod.rs (1)
typeface
(12-17)crates/grida-canvas/src/cg/types.rs (3)
from_rgb
(59-61)a
(72-74)b
(69-71)
crates/grida-canvas/src/layout/mod.rs (2)
crates/grida-canvas/src/layout/tmp_example.rs (5)
to_taffy_style
(41-50)padding
(58-60)gap
(63-65)justify_content
(84-86)align_items
(89-91)packages/grida-canvas-schema/grida.ts (1)
LengthPercentage
(1003-1003)
crates/grida-canvas/src/painter/layer.rs (1)
crates/grida-canvas/src/painter/geometry.rs (2)
boolean_operation_shape
(301-308)boolean_operation_path
(244-298)
crates/grida-canvas/examples/golden_layout_flex_alignment.rs (3)
crates/grida-canvas/src/layout/tmp_example.rs (4)
compute_flex_layout_for_container
(112-153)id
(95-98)from_container
(27-32)with_layout
(35-38)crates/grida-canvas/src/cg/types.rs (4)
uniform
(703-708)from_rgb
(59-61)a
(72-74)b
(69-71)crates/grida-canvas/src/fonts/embedded/mod.rs (1)
typeface
(12-17)
crates/grida-canvas/src/node/schema.rs (4)
crates/grida-canvas/src/layout/tmp_example.rs (1)
id
(95-98)crates/grida-canvas/src/painter/layer.rs (2)
id
(99-99)id
(106-112)packages/grida-canvas-schema/grida.ts (1)
NodeIdGenerator
(257-358)packages/grida-canvas-cg/lib.ts (3)
Axis
(582-582)MainAxisAlignment
(589-596)CrossAxisAlignment
(603-603)
crates/grida-canvas/src/io/io_grida.rs (2)
crates/grida-canvas/src/cg/types.rs (16)
default
(114-116)default
(136-138)default
(268-270)default
(332-334)default
(360-362)default
(376-378)default
(406-408)default
(430-432)default
(532-534)default
(544-546)default
(617-619)default
(730-732)default
(750-752)default
(846-848)default
(868-870)default
(892-894)packages/grida-canvas-cg/lib.ts (3)
Axis
(582-582)MainAxisAlignment
(589-596)CrossAxisAlignment
(603-603)
crates/grida-canvas/src/cg/types.rs (1)
packages/grida-canvas-cg/lib.ts (3)
Axis
(582-582)MainAxisAlignment
(589-596)CrossAxisAlignment
(603-603)
crates/grida-canvas/src/io/id_converter.rs (4)
crates/grida-canvas/src/io/io_figma.rs (9)
new
(262-271)get_or_create_internal_id
(287-296)from
(26-33)from
(37-44)from
(48-167)from
(171-177)from
(181-188)from
(192-198)from
(202-208)crates/grida-canvas/src/node/repository.rs (1)
new
(15-20)crates/grida-canvas/src/node/scene_graph.rs (2)
new
(69-75)new_from_snapshot
(86-105)crates/grida-canvas/src/node/id.rs (1)
new
(18-20)
crates/grida-canvas/src/painter/painter_debug_node.rs (3)
crates/grida-canvas/src/layout/tmp_example.rs (1)
id
(95-98)crates/grida-canvas/src/painter/layer.rs (4)
id
(99-99)id
(106-112)shape
(102-102)shape
(130-136)crates/grida-canvas/src/painter/geometry.rs (1)
boolean_operation_shape
(301-308)
🪛 LanguageTool
crates/grida-canvas/AGENTS.md
[grammar] ~19-~19: There might be a mistake here.
Context: ...n layer maintains bidirectional mapping for API boundary See [NODEID_MIGRATION.md]...
(QB_NEW_EN)
crates/grida-canvas/NODEID_MIGRATION.md
[grammar] ~7-~7: There might be a mistake here.
Context: ...based ID for high-performance operations - UserNodeId: External String
ID for p...
(QB_NEW_EN)
[grammar] ~27-~27: There might be a mistake here.
Context: ... uses HashMap<NodeId, Node>
(u64 keys) - SceneGraph (src/node/scene_graph.rs
)...
(QB_NEW_EN)
[grammar] ~35-~35: There might be a mistake here.
Context: ...pdated method signatures from &str
to &NodeId
- ParagraphCache (`src/cache/paragraph.r...
(QB_NEW_EN)
[grammar] ~36-~36: There might be a mistake here.
Context: ...Already compatible (uses generic NodeId) - PictureCache (src/cache/picture.rs
):...
(QB_NEW_EN)
[grammar] ~37-~37: There might be a mistake here.
Context: ...c/cache/picture.rs): Already compatible - **VectorPathCache** (
src/cache/vector_pat...
(QB_NEW_EN)
[grammar] ~44-~44: There might be a mistake here.
Context: ... → internal u64 IDs during scene loading - Maintains bidirectional mapping between ...
(QB_NEW_EN)
[grammar] ~45-~45: There might be a mistake here.
Context: ...between user string IDs and internal IDs - Provides convert_json_canvas_file()
fo...
(QB_NEW_EN)
[grammar] ~69-~69: There might be a mistake here.
Context: ...tween external strings and internal u64: - get_node_id_from_point()
: Returns Option<String>
- `get_node...
(QB_NEW_EN)
[grammar] ~70-~70: There might be a mistake here.
Context: ... - get_node_id_from_point()
: Returns Option<String>
- get_node_ids_from_point()
: Returns Vec<String>
- `export_node...
(QB_NEW_EN)
[grammar] ~71-~71: There might be a mistake here.
Context: ... - get_node_ids_from_point()
: Returns Vec<String>
- export_node_as(id: &str)
: Converts to internal ID before export ...
(QB_NEW_EN)
[grammar] ~72-~72: There might be a mistake here.
Context: ...: Converts to internal ID before export -
highlight_strokes(ids: Vec)`: Converts to internal IDs for storage ...
(QB_NEW_EN)
[grammar] ~79-~79: There might be a mistake here.
Context: ...mentation noting IDs should be assigned by scene management layer ### 8. Other Up...
(QB_NEW_EN)
[grammar] ~83-~83: There might be a mistake here.
Context: ...ted export_node_as()
signature to use &NodeId
- layout/tmp_example.rs: Fixed taffy::No...
(QB_NEW_EN)
[grammar] ~128-~128: There might be a mistake here.
Context: ...*: Internal u64 IDs directly compatible with Taffy layout engine 3. *API Stability...
(QB_NEW_EN)
[grammar] ~130-~130: There might be a mistake here.
Context: ...4. Clean SDK: ID generation managed by system, not user 5. Type Safety: Cl...
(QB_NEW_EN)
[grammar] ~130-~130: There might be a mistake here.
Context: ...SDK**: ID generation managed by system, not user 5. Type Safety: Clear separati...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.18.1)
crates/grida-canvas/NODEID_MIGRATION.md
96-96: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
110-110: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (24)
justfile (2)
4-8
: LGTM! Aligns with build validation best practices.The
check
target appropriately combines TypeScript type checking with comprehensive Rust validation usingcargo check --all-targets --all-features
, which aligns with the recommended build validation approach.Based on learnings.
9-12
: LGTM! Clear and straightforward test target.The
test
target is well-defined and uses the standard turborepo test command.crates/grida-canvas/tests/scene_cache.rs (1)
55-55
: LGTM: assertions updated for direct NodeId fieldDirect comparisons against
rect_id
/container_id
match the new ID model.Also applies to: 66-67
crates/grida-canvas/src/node/mod.rs (1)
2-2
: LGTM!The addition of the
id
module export is appropriate and necessary to expose the new NodeId system introduced in this PR.crates/grida-canvas/src/lib.rs (1)
10-10
: LGTM!The addition of the
layout
module export expands the public API to support the box model and flexbox functionality mentioned in the PR objectives.crates/grida-canvas/src/io/mod.rs (1)
4-4
: LGTM!The addition of the
id_converter
module export is necessary to expose the IdConverter utility that handles conversion between string IDs and internal NodeIds.crates/grida-canvas/examples/app_grida.rs (1)
19-31
: LGTM!The refactoring to use
IdConverter::convert_json_canvas_file
simplifies the scene loading logic and properly integrates with the new dual-ID system. The panic-on-error approach is appropriate for example code.crates/grida-canvas/tests/hit_test.rs (1)
47-49
: LGTM!The test assertions correctly align with the updated hit testing API that now returns
NodeId
directly instead of string references, consistent with the dual-ID system migration.Also applies to: 51-53, 85-87
crates/grida-canvas/src/runtime/scene.rs (2)
566-570
: LGTM!The refactoring to use the Entry-based pattern (with
entry.id
andentry.layer
) is cleaner and more consistent with the new NodeId system architecture.
740-740
: LGTM!The test correctly uses a numeric ID (
9999
) instead of a string, consistent with theNodeId
type beingu64
.crates/grida-canvas/src/export/mod.rs (1)
13-13
: LGTM!The update to use
NodeId
instead of&str
for thenode_id
parameter aligns the export API with the dual-ID system migration and maintains type consistency across the codebase.Also applies to: 83-83
crates/grida-canvas/src/node/factory.rs (1)
165-171
: LGTM: Layout fields properly initialized.The new layout-related fields are correctly initialized using their Default implementations, aligning with the PR's objective to add basic box model and flexbox support.
crates/grida-canvas/src/painter/geometry.rs (1)
244-298
: LGTM: ID parameter threading is correct.The addition of the
id: &NodeId
parameter toboolean_operation_path
is necessary since nodes no longer carry their own ID field. The implementation correctly threads the ID through world transform lookups, child retrieval, and recursive calls.crates/grida-canvas/src/layout/into_taffy.rs (1)
1-72
: LGTM: Taffy conversion implementations are correct.The From trait implementations properly map schema types to Taffy equivalents. The documentation comment on lines 64-65 about gap semantics (main axis → width, cross axis → height) is particularly helpful for understanding the mapping.
crates/grida-canvas/src/node/repository.rs (2)
22-28
: LGTM: ID generation properly integrated.The
insert
method now correctly generates IDs using the internalid_generator
, returning the generatedNodeId
. This aligns with the PR's shift to centralized ID management.
30-34
: LGTM: New method supports explicit ID assignment.The
insert_with_id
method provides a necessary API for the IO layer to assign specific IDs during deserialization or import operations.crates/grida-canvas/src/node/id.rs (1)
1-50
: LGTM: Simple, focused internal ID modelType aliases and NodeIdGenerator are clear and tested. Works for ephemeral, per-session IDs.
Also applies to: 52-79
crates/grida-canvas/src/window/application.rs (1)
490-507
: No issues found; fields are publicly accessibleThe
string_to_internal
andinternal_to_string
fields are already markedpub
in theIdConverter
struct (lines 11 and 13 ofcrates/grida-canvas/src/io/id_converter.rs
), so cloning them inapplication.rs
will compile without errors.crates/grida-canvas/src/cache/geometry.rs (1)
348-351
: API shift to &NodeId looks good — verify all call sites updated.The move to numeric IDs at the cache boundary is consistent with the migration.
Also applies to: 352-355, 357-360, 362-365, 370-373
crates/grida-canvas/src/devtools/hit_overlay.rs (1)
59-75
: Entry-based layer access LGTM.Lookup by entry.id and accessing entry.layer.{shape,transform} aligns with LayerEntry.
crates/grida-canvas/src/painter/layer.rs (1)
825-833
: Filter method update LGTM.Iterating LayerEntry and filtering by entry.layer is correct.
crates/grida-canvas/src/io/id_converter.rs (2)
1-14
: IdConverter design LGTM.String↔u64 mapping, snapshot graph build, and roots handling are sound for the migration.
Also applies to: 16-23, 58-68, 86-94, 124-133
25-37
: Keep NodeIdGenerator starting at 0
0 is the intended first ID (tests assertnext() == 0
) and there’s no “unassigned” sentinel value—no change needed.Likely an incorrect or invalid review comment.
crates/grida-canvas/src/node/schema.rs (1)
11-11
: Re-export is clean; ID mapping infrastructure properly handles back-compat.Verification confirms no lingering String-based NodeId usage. The bidirectional
id_mapping
(HashMap<UserNodeId, NodeId>) in application.rs and dedicated converter modules in io/ handle the String ↔ u64 mapping for legacy documents. No serde issues detected—user string IDs are managed at the IO layer, not via type serialization.
2. Public APIs accept/return `UserNodeId` (String) for stability | ||
3. `IdConverter` handles conversion during .grida file loading | ||
4. `NodeRepository` auto-generates IDs for factory-created nodes (ID=0) | ||
5. Application layer maintains bidirectional mapping for API boundary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing punctuation.
Missing period after "API boundary" before "See".
Apply this diff:
-5. Application layer maintains bidirectional mapping for API boundary
+5. Application layer maintains bidirectional mapping for API boundary.
📝 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.
5. Application layer maintains bidirectional mapping for API boundary | |
5. Application layer maintains bidirectional mapping for API boundary. |
🧰 Tools
🪛 LanguageTool
[grammar] ~19-~19: There might be a mistake here.
Context: ...n layer maintains bidirectional mapping for API boundary See [NODEID_MIGRATION.md]...
(QB_NEW_EN)
🤖 Prompt for AI Agents
In crates/grida-canvas/AGENTS.md around line 19, the sentence ending with "API
boundary" is missing a period; insert a period after "API boundary" so it reads
"...API boundary." before the following "See" to correct punctuation.
let max_height = layouts | ||
.iter() | ||
.map(|l| l.y + l.height) | ||
.max_by(|a, b| a.partial_cmp(b).unwrap()) | ||
.unwrap_or(0.0); | ||
|
||
let container_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, max_height + 10.0); | ||
canvas.draw_rect(&container_rect, &container_paint); | ||
|
||
// Draw padding visualization | ||
if padding > 0.0 { | ||
let mut padding_paint = Paint::default(); | ||
padding_paint.set_anti_alias(true); | ||
padding_paint.set_color(Color::from_argb(30, 0, 100, 255)); | ||
padding_paint.set_style(skia_safe::PaintStyle::Fill); | ||
|
||
// Top padding | ||
let top_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, padding); | ||
canvas.draw_rect(&top_padding_rect, &padding_paint); | ||
|
||
// Bottom padding | ||
let bottom_padding_rect = Rect::from_xywh( | ||
base_x, | ||
base_y + 10.0 + max_height - padding, | ||
container_width, | ||
padding, | ||
); | ||
canvas.draw_rect(&bottom_padding_rect, &padding_paint); | ||
|
||
// Left padding | ||
let left_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, padding, max_height); | ||
canvas.draw_rect(&left_padding_rect, &padding_paint); | ||
|
||
// Right padding | ||
let right_padding_rect = Rect::from_xywh( | ||
base_x + container_width - padding, | ||
base_y + 10.0, | ||
padding, | ||
max_height, | ||
); | ||
canvas.draw_rect(&right_padding_rect, &padding_paint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix padding visualization to include bottom/side padding correctly
max_height
includes top padding and content height but not bottom padding. Compute container height and use it for the overlay rects.
- let container_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, max_height + 10.0);
+ let container_height = max_height + padding; // top padding already in max_height
+ let container_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, container_height);
@@
- let bottom_padding_rect = Rect::from_xywh(
- base_x,
- base_y + 10.0 + max_height - padding,
- container_width,
- padding,
- );
+ let bottom_padding_rect = Rect::from_xywh(
+ base_x,
+ base_y + 10.0 + container_height - padding,
+ container_width,
+ padding,
+ );
@@
- let left_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, padding, max_height);
+ let left_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, padding, container_height);
@@
- let right_padding_rect = Rect::from_xywh(
- base_x + container_width - padding,
- base_y + 10.0,
- padding,
- max_height,
- );
+ let right_padding_rect = Rect::from_xywh(
+ base_x + container_width - padding,
+ base_y + 10.0,
+ padding,
+ container_height,
+ );
📝 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.
let max_height = layouts | |
.iter() | |
.map(|l| l.y + l.height) | |
.max_by(|a, b| a.partial_cmp(b).unwrap()) | |
.unwrap_or(0.0); | |
let container_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, max_height + 10.0); | |
canvas.draw_rect(&container_rect, &container_paint); | |
// Draw padding visualization | |
if padding > 0.0 { | |
let mut padding_paint = Paint::default(); | |
padding_paint.set_anti_alias(true); | |
padding_paint.set_color(Color::from_argb(30, 0, 100, 255)); | |
padding_paint.set_style(skia_safe::PaintStyle::Fill); | |
// Top padding | |
let top_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, padding); | |
canvas.draw_rect(&top_padding_rect, &padding_paint); | |
// Bottom padding | |
let bottom_padding_rect = Rect::from_xywh( | |
base_x, | |
base_y + 10.0 + max_height - padding, | |
container_width, | |
padding, | |
); | |
canvas.draw_rect(&bottom_padding_rect, &padding_paint); | |
// Left padding | |
let left_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, padding, max_height); | |
canvas.draw_rect(&left_padding_rect, &padding_paint); | |
// Right padding | |
let right_padding_rect = Rect::from_xywh( | |
base_x + container_width - padding, | |
base_y + 10.0, | |
padding, | |
max_height, | |
); | |
canvas.draw_rect(&right_padding_rect, &padding_paint); | |
let max_height = layouts | |
.iter() | |
.map(|l| l.y + l.height) | |
.max_by(|a, b| a.partial_cmp(b).unwrap()) | |
.unwrap_or(0.0); | |
let container_height = max_height + padding; // top padding already in max_height | |
let container_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, container_height); | |
canvas.draw_rect(&container_rect, &container_paint); | |
// Draw padding visualization | |
if padding > 0.0 { | |
let mut padding_paint = Paint::default(); | |
padding_paint.set_anti_alias(true); | |
padding_paint.set_color(Color::from_argb(30, 0, 100, 255)); | |
padding_paint.set_style(skia_safe::PaintStyle::Fill); | |
// Top padding | |
let top_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, container_width, padding); | |
canvas.draw_rect(&top_padding_rect, &padding_paint); | |
// Bottom padding | |
let bottom_padding_rect = Rect::from_xywh( | |
base_x, | |
base_y + 10.0 + container_height - padding, | |
container_width, | |
padding, | |
); | |
canvas.draw_rect(&bottom_padding_rect, &padding_paint); | |
// Left padding | |
let left_padding_rect = Rect::from_xywh(base_x, base_y + 10.0, padding, container_height); | |
canvas.draw_rect(&left_padding_rect, &padding_paint); | |
// Right padding | |
let right_padding_rect = Rect::from_xywh( | |
base_x + container_width - padding, | |
base_y + 10.0, | |
padding, | |
container_height, | |
); | |
canvas.draw_rect(&right_padding_rect, &padding_paint); |
🤖 Prompt for AI Agents
In crates/grida-canvas/examples/golden_layout_padding.rs around lines 595 to
635, the padding overlay uses max_height (which omits bottom padding) when
computing bottom/side padding rects; compute a container_height that includes
bottom padding (e.g., let container_height = max_height + padding) and use that
for the container rect and for the bottom/left/right overlay rects so the bottom
padding y becomes base_y + 10.0 + container_height - padding and the left/right
overlay heights use container_height instead of max_height; keep top padding
as-is and update any container_rect height calculations to use container_height
where appropriate.
## Benefits Achieved | ||
|
||
1. **Performance**: All internal operations use u64 (faster HashMap lookups, no string cloning) | ||
2. **Taffy Integration**: Internal u64 IDs directly compatible with Taffy layout engine | ||
3. **API Stability**: External string IDs unchanged; editor integration unaffected | ||
4. **Clean SDK**: ID generation managed by system, not user | ||
5. **Type Safety**: Clear separation between internal and external IDs | ||
|
||
## Backward Compatibility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Align test count claims and generator semantics.
Numbers (“All 104 tests passing” vs “118 suites, 103+ unit tests”) conflict; unify or omit counts. Also ensure NodeIdGenerator::next never returns 0 if 0 denotes “placeholder”.
Also applies to: 159-164
🏁 Script executed:
#!/bin/bash
# Inspect NodeIdGenerator::next to confirm it doesn't emit 0
rg -n -C3 'struct\s+NodeIdGenerator|impl\s+NodeIdGenerator|fn\s+next\s*\(' --type=rust
Length of output: 1154
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where NodeId(0) or 0 as NodeId might be special (placeholder, null, etc.)
rg -n -B2 -A2 'NodeId\s*\(\s*0\s*\)|== 0|== NodeId' --type=rust
Length of output: 15474
🏁 Script executed:
#!/bin/bash
# Read the NODEID_MIGRATION.md to see exact test count claims
cat crates/grida-canvas/NODEID_MIGRATION.md | head -170 | tail -50
Length of output: 1988
🏁 Script executed:
#!/bin/bash
# Check for any const or static definitions of NodeId that might reserve 0
rg -n 'const|static' crates/grida-canvas/src/node/id.rs --type=rust -A2
Length of output: 0
node_pairs: impl IntoIterator<Item = (NodeId, Node)>, | ||
links: HashMap<NodeId, Vec<NodeId>>, | ||
roots: Vec<NodeId>, | ||
) -> Self { | ||
let mut graph = Self::new(); | ||
|
||
// Add all nodes to the repository | ||
for node in nodes { | ||
graph.nodes.insert(node); | ||
// Add all nodes to the repository with their explicit IDs | ||
for (id, node) in node_pairs { | ||
graph.nodes.insert_with_id(id, node); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Avoid future ID collisions: ensure insert_with_id advances the repository generator
If NodeRepository::insert_with_id doesn’t bump id_generator past the inserted id, later insert() calls may reuse an existing ID, corrupting the map.
Run to inspect insert_with_id:
🏁 Script executed:
#!/bin/bash
rg -n -C3 'struct\s+NodeRepository|impl\s+NodeRepository|insert_with_id' -g 'crates/**/repository.rs'
Length of output: 1597
Ensure insert_with_id advances id_generator
insert_with_id currently only inserts into nodes
without updating id_generator
, so subsequent insert()
calls can reuse IDs. Bump id_generator
past the inserted id
(e.g. self.id_generator.advance_to(id)
) to prevent collisions.
🤖 Prompt for AI Agents
In crates/grida-canvas/src/node/scene_graph.rs around lines 87 to 96,
insert_with_id currently inserts nodes without advancing the graph's
id_generator which allows future insert() calls to reuse those IDs; update the
code that loops over node_pairs so after graph.nodes.insert_with_id(id, node)
you also advance the id generator past the inserted id (e.g. call
graph.id_generator.advance_to(id) or the equivalent method on the id generator)
so the generator's next produced ID is greater than any manually inserted ID and
prevents collisions.
// [container layout - common layout properties that is applicapable to the parent] | ||
/// layout mode | ||
pub layout_mode: LayoutMode, | ||
/// layout direction | ||
pub layout_direction: Axis, | ||
/// layout wrap | ||
pub layout_wrap: LayoutWrap, | ||
/// layout main axis alignment | ||
pub layout_main_axis_alignment: MainAxisAlignment, | ||
/// layout cross axis alignment | ||
pub layout_cross_axis_alignment: CrossAxisAlignment, | ||
/// The gap of the container. | ||
pub layout_gap: LayoutGap, | ||
/// The padding of the container. | ||
pub padding: EdgeInsets, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
New container layout fields: align semantics and defaults need clarity.
Looks right for Flex prep. Please:
-
Clarify main vs cross semantics to match CSS/Taffy: main → justify_content, cross → align_items. Ensure "stretch" is not exposed for main-axis if it doesn't map.
-
Provide defaults/builders so existing ContainerNodeRec construction doesn’t break.
-
Confirm mapping to taffy 0.9.1: FlexDirection ↔ layout_direction, FlexWrap ↔ layout_wrap, justify_content ↔ layout_main_axis_alignment, align_items ↔ layout_cross_axis_alignment, gap ↔ layout_gap, padding ↔ padding.
-
Check for missing defaults:
Nit: fix typo “applicapable” → “applicable”.
🏁 Script executed:
#!/bin/bash
# Find ContainerNodeRec instantiations that now need the new fields
rg -nP --type=rust -C3 'ContainerNodeRec\s*\{'
Length of output: 17469
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the factory function to see if it initializes the new layout fields
sed -n '149,200p' crates/grida-canvas/src/node/factory.rs
Length of output: 2163
🏁 Script executed:
#!/bin/bash
# Check one of the example instantiations to see the pattern
sed -n '17,50p' crates/grida-canvas/examples/golden_layout_flex_padding.rs
Length of output: 1143
🏁 Script executed:
#!/bin/bash
# Check if Taffy is in Cargo.toml and its version
find . -name "Cargo.toml" -type f | head -5 | xargs grep -l "taffy" 2>/dev/null
Length of output: 32
🏁 Script executed:
#!/bin/bash
# Look for Taffy version specification
rg -nP "taffy\s*=" --type=toml | head -10
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Check the conversion from JSONContainerNode to see if it's missing layout fields
sed -n '962,1000p' crates/grida-canvas/src/io/io_grida.rs
Length of output: 1683
🏁 Script executed:
#!/bin/bash
# Look for any Default impl or builder for ContainerNodeRec
rg -nP "impl.*Default|impl.*Builder" --type=rust crates/grida-canvas/src/node/schema.rs
Length of output: 34
🏁 Script executed:
#!/bin/bash
# Check schema.rs around line 347 to see the full struct definition including layout fields
sed -n '346,395p' crates/grida-canvas/src/node/schema.rs
Length of output: 1852
Missing layout field mappings and clarify axis semantics
- In io/io_grida.rs’s From for ContainerNodeRec, only layout_mode and layout_direction are initialized; add the other new fields (
layout_wrap
,layout_main_axis_alignment
,layout_cross_axis_alignment
,layout_gap
,padding
) withDefault::default()
. - Rename/clarify:
layout_main_axis_alignment
maps to Taffy’sjustify_content
,layout_cross_axis_alignment
toalign_items
, and do not expose “stretch” on the main axis if unsupported. - Fix typo in schema.rs comment: “applicapable” → “applicable”.
// TODO: Pass id as parameter - using dummy 0 for now in debug mode | ||
let dummy_id = 0; | ||
self.painter.with_transform(&node.transform.matrix, || { | ||
let path = self.painter.cached_path(&node.id, &node.data); | ||
let path = self.painter.cached_path(&dummy_id, &node.data); | ||
let shape = PainterShape::from_path((*path).clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore real NodeId when caching debug painter resources
Both draw_path_node
and draw_text_span_node
now hand Skia caches a constant NodeId
(dummy_id = 0
). The painter’s caches are keyed by NodeId
, so every SVG path and text span rendered through this debug path will stomp each other’s entries. The first node drawn wins, and subsequent nodes (with different path data or text) reuse the stale cached result — incorrect output is guaranteed as soon as two distinct nodes are painted. Please thread the actual node id through these helpers (e.g., add an id: &NodeId
parameter) and pass it from the recursive callers so each cached asset stays unique to its node.
Also applies to: 239-249
🤖 Prompt for AI Agents
In crates/grida-canvas/src/painter/painter_debug_node.rs around lines 141-145
(and similarly at lines ~239-249), the code uses a hardcoded dummy_id = 0 when
caching painter resources which causes cache collisions; update the functions to
accept a real NodeId parameter and use that id when calling cached_path and any
other cache-keyed APIs. Specifically: change the signatures of draw_path_node
and draw_text_span_node (and any intermediate recursive helpers) to take id:
&NodeId (or NodeId by value as appropriate), propagate that id through all
recursive calls, replace dummy_id with the passed id when calling
self.painter.cached_path (and related cache access), and update all call sites
to forward the node’s actual id; ensure borrow/clone semantics match existing
types and run cargo build to fix any type errors.
// Use IdConverter to handle string ID to u64 ID conversion | ||
let mut converter = crate::io::id_converter::IdConverter::new(); | ||
|
||
// Get scene children from links | ||
let scene_children = links | ||
.get(&scene_id) | ||
.and_then(|c| c.clone()) | ||
.unwrap_or_default(); | ||
match converter.convert_json_canvas_file(file) { | ||
Ok(scene) => { | ||
// Store the ID mappings for future API calls | ||
self.id_mapping = converter.string_to_internal.clone(); | ||
self.id_mapping_reverse = converter.internal_to_string.clone(); | ||
|
||
// Convert all nodes (skip scene nodes) | ||
let nodes: Vec<Node> = file | ||
.document | ||
.nodes | ||
.into_iter() | ||
.filter(|(_, json_node)| !matches!(json_node, io_grida::JSONNode::Scene(_))) | ||
.map(|(_, json_node)| json_node.into()) | ||
.collect(); | ||
|
||
// Filter links to remove None values | ||
let filtered_links: std::collections::HashMap<String, Vec<String>> = links | ||
.into_iter() | ||
.filter_map(|(parent_id, children_opt)| { | ||
children_opt.map(|children| (parent_id, children)) | ||
}) | ||
.collect(); | ||
self.renderer.load_scene(scene); | ||
true | ||
} | ||
Err(err) => { | ||
eprintln!("Failed to convert canvas file: {}", err); | ||
false | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear ID mappings on load failure to avoid stale translations
If conversion fails, previous id_mapping values remain, risking wrong translations in later API calls.
match converter.convert_json_canvas_file(file) {
Ok(scene) => {
// Store the ID mappings for future API calls
self.id_mapping = converter.string_to_internal.clone();
self.id_mapping_reverse = converter.internal_to_string.clone();
self.renderer.load_scene(scene);
true
}
Err(err) => {
eprintln!("Failed to convert canvas file: {}", err);
- false
+ self.id_mapping.clear();
+ self.id_mapping_reverse.clear();
+ false
}
}
📝 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.
// Use IdConverter to handle string ID to u64 ID conversion | |
let mut converter = crate::io::id_converter::IdConverter::new(); | |
// Get scene children from links | |
let scene_children = links | |
.get(&scene_id) | |
.and_then(|c| c.clone()) | |
.unwrap_or_default(); | |
match converter.convert_json_canvas_file(file) { | |
Ok(scene) => { | |
// Store the ID mappings for future API calls | |
self.id_mapping = converter.string_to_internal.clone(); | |
self.id_mapping_reverse = converter.internal_to_string.clone(); | |
// Convert all nodes (skip scene nodes) | |
let nodes: Vec<Node> = file | |
.document | |
.nodes | |
.into_iter() | |
.filter(|(_, json_node)| !matches!(json_node, io_grida::JSONNode::Scene(_))) | |
.map(|(_, json_node)| json_node.into()) | |
.collect(); | |
// Filter links to remove None values | |
let filtered_links: std::collections::HashMap<String, Vec<String>> = links | |
.into_iter() | |
.filter_map(|(parent_id, children_opt)| { | |
children_opt.map(|children| (parent_id, children)) | |
}) | |
.collect(); | |
self.renderer.load_scene(scene); | |
true | |
} | |
Err(err) => { | |
eprintln!("Failed to convert canvas file: {}", err); | |
false | |
} | |
} | |
} | |
// Use IdConverter to handle string ID to u64 ID conversion | |
let mut converter = crate::io::id_converter::IdConverter::new(); | |
match converter.convert_json_canvas_file(file) { | |
Ok(scene) => { | |
// Store the ID mappings for future API calls | |
self.id_mapping = converter.string_to_internal.clone(); | |
self.id_mapping_reverse = converter.internal_to_string.clone(); | |
self.renderer.load_scene(scene); | |
true | |
} | |
Err(err) => { | |
eprintln!("Failed to convert canvas file: {}", err); | |
self.id_mapping.clear(); | |
self.id_mapping_reverse.clear(); | |
false | |
} | |
} |
🤖 Prompt for AI Agents
In crates/grida-canvas/src/window/application.rs around lines 490 to 507, the
code leaves previous id_mapping and id_mapping_reverse intact when
converter.convert_json_canvas_file(file) returns Err, which can cause stale ID
translations later; modify the Err branch to explicitly clear or reset
self.id_mapping and self.id_mapping_reverse (e.g., assign empty maps) before
logging the error and returning false so no stale mappings persist after a
failed load.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on November 5
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
self.painter.with_blendmode(node.blend_mode, || { | ||
self.painter.draw_text_span( | ||
&node.id, | ||
&dummy_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Debug Mode Cache ID Collision
The draw_path_node
and draw_text_span_node
methods use a hardcoded dummy_id = 0
for cache operations in debug mode. This can cause cache collisions, leading to incorrect cached data being reused and potential rendering issues when multiple nodes are drawn. It also creates a risk if ID 0 is ever used for a real node.
Flex layout support
This PR preps the behind basic box model + flexbox model
Core
NodeId
asu64
Box
padding
inset
Flex - on container
layout_mode
layout_direction
(flex-direction)layout_wrap
(flex-wrap)layout_gap
(gap)layout_main_axis_alignment
(align-items)layout_cross_axis_alignment
(justify-items)Flex - on content
Summary by CodeRabbit