Skip to content

Embed viewer overhaul, Figma import fixes (geometry, strokes, lists), and CG bug fixes#621

Open
softmarshmallow wants to merge 25 commits intomainfrom
canary
Open

Embed viewer overhaul, Figma import fixes (geometry, strokes, lists), and CG bug fixes#621
softmarshmallow wants to merge 25 commits intomainfrom
canary

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Apr 2, 2026

Summary

A batch of improvements spanning the embed viewer, Figma import pipeline, and the core graphics (CG) rendering engine.

Embed Viewer

  • Overhaul embed endpoints: rename refigfigma, add new /embed/v1/ general viewer supporting .grida / .grida1 files
  • Add keyboard shortcuts for node navigation (Tab/Shift+Tab, Enter/Shift+Enter) and viewport zoom (Shift+1/2/0) in WASM embed viewer
  • Enhance embed debug page with mode switching (general / Figma)
  • Extract use-embed-viewer hook; decouple camera interface (SurfaceCamera) from full Editor

Figma Import (io-figma)

  • fillOverrideTable: Resolve per-region fill overrides for multi-region vectors, boolean operations, and outlined text
  • strokeAlign compositing: Handle INSIDE/CENTER/OUTSIDE as paint-order compositing instructions — INSIDE strokes deactivated (requires clip path), OUTSIDE uses fill-on-top ordering
  • Boolean operation stroke skip: Replace ad-hoc BOOLEAN_OPERATION stroke skip with universal strokeAlign handling
  • Container transform propagation: Propagate all non-identity container transforms (not just flips) into children geometry — fixes rotated containers (e.g. 180° Corner Bottom Right)
  • Faux-list rendering: Bake Figma bullet/ordered list formatting into text strings with synthetic prefixes and indentation (lossy, gated behind disable_faux_list flag); 26 new tests
  • Add stroke alignment analysis docs and test fixtures

Core Graphics (CG / Rust)

  • Content origin offset: Include rect().x/y origin in PathNode/PolygonNode/VectorNode geometry cache bounds — fixes incorrect R-tree AABBs, viewport culling drops, hit-test misses, and misplaced bounding box overlays
  • Container rotation radians: Convert Container/Tray rotation from degrees to radians at all 4 call sites feeding AffineTransform::new — adds regression test
  • New geometry cache and FBS roundtrip tests

I/O Format

  • FeNoise FlatBuffers fix: Build FeNoiseEffect table offsets outside vector block to prevent buffer corruption and Rust decoder panic

Tools

  • Add Affine Transform Visualizer interactive tool (/tools/affine-transform)

Test coverage

  • 26 new faux-list unit + integration tests
  • fillOverrideTable vector import tests
  • stroke compositing integration tests
  • Container rotation geometry cache regression test
  • FBS roundtrip test additions
  • Bench load-scene test additions

Summary by CodeRabbit

  • New Features

    • Affine Transform visualizer; new embed endpoints (general/figma/debug) with native .grida/.grida1 support; faux-list text formatting; scene selection navigation and zoom shortcuts; embed viewer hook and simplified refig editor integration.
  • Bug Fixes

    • Corrected rotation (degrees→radians) and content-origin handling; improved stroke compositing with INSIDE clipping; preserved character-style alignment after list rewrites.
  • Documentation

    • Embed endpoint docs, stroke-alignment guide, and FlatBuffers debugging tips.
  • Tests

    • Added cross-boundary, format roundtrip, geometry-cache, and extensive fixture-driven tests.

Modified the embed URLs in the documentation and debug page to reflect the new 'figma' endpoint. Added a new page for handling Figma embeds, including file parameter parsing and remote file fetching logic.
…format support

Updated the documentation to include detailed information about the new embed endpoints (`/embed/v1/`, `/embed/v1/figma`, and `/embed/v1/debug`). Added support for additional file formats (`.grida`, `.grida1`) in the embed viewer and debug page. Improved the embed debug page to allow switching between general and Figma modes, and updated the embed bridge to handle new file formats.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Apr 4, 2026 7:09pm
docs Ready Ready Preview, Comment Apr 4, 2026 7:09pm
grida Ready Ready Preview, Comment Apr 4, 2026 7:09pm
viewer Ready Ready Preview, Comment Apr 4, 2026 7:09pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
backgrounds Ignored Ignored Preview Apr 4, 2026 7:09pm
code Ignored Ignored Apr 4, 2026 7:09pm
legacy Ignored Ignored Apr 4, 2026 7:09pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds FlatBuffers debugging guidance and cross-boundary encode/decode tests; expands WASM Scene command surface and keyboard-driven viewport controls; fixes geometry by adding content-origin propagation and converting rotations to radians; introduces a shared embed viewer supporting .grida/.grida1 with converters; implements Figma faux-list rewriting and stroke compositing/path baking; adds an affine-transform visualizer and tests.

Changes

Cohort / File(s) Summary
FlatBuffers / io-grida & tests
.agents/skills/io-grida/SKILL.md, crates/grida-canvas/tests/fbs_roundtrip.rs, packages/grida-canvas-io/__tests__/format-roundtrip.test.ts, crates/grida-canvas-wasm/lib/__test__/bench-load-scene.test.ts
Docs: new FlatBuffers debugging guidance and CLI examples. New Rust/TS tests that discover .grida fixtures, verify decode invariants, and add TS→WASM cross-boundary encode→decode checks.
WASM Scene API & surface integration
crates/grida-canvas-wasm/lib/modules/canvas.ts, editor/grida-canvas/backends/wasm-surface-provider.ts, editor/grida-canvas-react/viewport/hooks/use-wasm-surface.ts, editor/grida-canvas/embed-bridge.ts
Added Scene methods for selection navigation and zoom; keyboard shortcuts mapped to these commands; CanvasSurfaceUI now accepts a SurfaceCamera camera dependency; embed load normalizes .grida1 filenames.
Geometry cache & scene graph transforms
crates/grida-canvas/src/cache/geometry.rs, crates/grida-canvas/src/node/scene_graph.rs, crates/grida-canvas/tests/geometry_cache.rs, crates/math2/src/transform.rs
Introduce content_origin_x/y propagation for correct leaf bounds; convert node rotations from degrees to radians when building AffineTransforms; add tests validating degree→radian behavior.
Embed viewer, protocol & scaffolds
editor/app/(embed)/embed/v1/page.tsx, editor/app/(embed)/embed/v1/debug/page.tsx, editor/app/(embed)/embed/v1/figma/page.tsx, editor/app/(embed)/embed/v1/layout.tsx, editor/scaffolds/embed/use-embed-viewer.ts, editor/scaffolds/embed/use-refig-editor.ts, editor/scaffolds/embed/grida-embed.ts, editor/grida-canvas/embed-protocol.ts
New /embed/v1/ viewer page and shared useEmbedViewer hook with converter and gzip handling; embed protocol and scaffolds extended to accept "grida"/"grida1"; use-refig-editor refactored to wrap shared hook.
Faux-list text rewriting (Figma import)
packages/grida-canvas-io-figma/faux-list.ts, packages/grida-canvas-io-figma/__tests__/faux-list.test.ts, packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.attributed-text.test.ts, packages/grida-canvas-io-figma/lib.ts
New faux-list module to synthesize per-line list prefixes from Figma lineTypes/lineIndentations; utilities to shift ranges/character-style overrides; integrated into REST→Grida import (opt-out via disable_faux_list) with tests.
Stroke compositing, path baking & per-path fills
packages/grida-canvas-io-figma/lib.ts, packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts, docs/wg/feat-fig/stroke-geometry-alignment.md, fixtures/test-figma/rest-api/L0/stroke.*, fixtures/test-fig/L0/stroke.fig
Implemented stroke compositing rules (CENTER/OUTSIDE) and INSIDE clipping via intersection boolean nodes, per-path fill override resolution, SVG path transform baking for non-representable transforms, and associated docs/fixtures/tests.
FlatBuffers encoding refactor
packages/grida-canvas-io/format.ts
Refactored FeNoise encoding to use helper to precompute FlatBuffers table offsets and create the vector via FlatBuffers API (no behavioral change).
Affine transform visualizer (dev tool)
editor/app/(tools)/tools/affine-transform/_page.tsx, editor/app/(tools)/tools/affine-transform/page.tsx, editor/app/(tools)/tools/page.tsx, editor/app/sitemap.ts
New interactive affine-transform visualizer page, metadata, tool registry entry, and sitemap entry.
Tests & fixtures
packages/grida-canvas-io-figma/__tests__/*, packages/grida-canvas-io/__tests__/*, crates/grida-canvas/tests/*, fixtures/*
Many new and updated tests: IO roundtrips, faux-list behavior, vector fill override cases, geometry cache rotation tests, and new fixtures for stroke alignment; added LFS pointer for a .fig fixture.
Miscellaneous packaging & docs
crates/grida-canvas-wasm/package.json, docs/canvas/sdk/embed/index.md, other small edits
Package version bump for canvas-wasm and prepublish guard change; embed SDK docs updated (endpoints, supported formats); assorted small formatting and doc updates.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant EmbedPage
    participant Fetcher
    participant Converter
    participant Editor
    participant WASM

    User->>Browser: Open /embed/v1/?file=...
    Browser->>EmbedPage: Load page
    EmbedPage->>Fetcher: Fetch file URL
    Fetcher-->>EmbedPage: ArrayBuffer + headers
    EmbedPage->>EmbedPage: Infer filename/extension
    EmbedPage->>EmbedPage: validateExt(ext, hasConverter)
    alt native .grida/.grida1
        EmbedPage->>Editor: onFile(File)
    else convertible format
        EmbedPage->>Converter: converter(ArrayBuffer)
        Converter-->>EmbedPage: .grida bytes
        EmbedPage->>Editor: onFile(File(.grida))
    end
    Editor->>WASM: io.load(File) → parse FlatBuffers
    WASM-->>Editor: document ready
    Editor->>WASM: camera.fit("<scene>")
    WASM-->>Browser: Render viewport
Loading
sequenceDiagram
    participant Input as Text Input
    participant FauxList as applyFauxList()
    participant State as Counters
    participant Output as Rewritten Text

    Input->>FauxList: text, lineTypes[], lineIndentations[]
    FauxList->>State: init counters per indentation level
    loop per line
        FauxList->>State: determine lineType
        alt UNORDERED
            FauxList->>Output: insert bullet prefix + indentation
        else ORDERED
            State->>State: increment counter for level
            FauxList->>Output: insert "N. " + indentation
        else NONE
            FauxList->>Output: insert indentation only
        end
        FauxList->>Output: record prefixLengths
    end
    FauxList-->>Output: return rewritten text + prefixLengths
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: embed viewer overhaul, Figma import fixes, and CG bug fixes. It directly reflects the substantial work across multiple systems documented in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 98.67% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch canary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

encodeFeNoises was creating tables inside a startVector/endVector block,
which corrupts the buffer — FlatBuffers only allows inline scalars/structs
in that context. The Rust decoder panicked on the invalid offsets, causing
load_scene_grida to fail silently and leaving loaded_scenes stale from
the initial empty document.

Build all FeNoiseEffect offsets first, then pass them to
createFeNoisesVector, matching the pattern used by encodeFeShadows.
Introduced a new Affine Transform Visualizer tool that allows users to interactively visualize 2D affine transformations. Updated the sitemap to include a link to the new tool and added relevant metadata for improved SEO. Enhanced the tools page with a new entry for the Affine Transform Visualizer, including descriptions and icons.
…zoom

Wire Tab/Shift+Tab (sibling nav), Enter/Shift+Enter (child/parent nav),
and Shift+1/2/0 (zoom to fit/selection/100%) to the WASM embed viewer.

Selection commands go through WASM _command() + redraw(). Zoom commands
go through the JS camera API since the camera is JS-driven (state.transform
→ WASM syncTransform). Adds SurfaceCamera interface to keep
CanvasSurfaceUI decoupled from the full Editor.
…metry cache bounds

PathNode, PolygonNode, and VectorNode can have shape data offset from
the transform origin (e.g. an SVG path starting at M4.5 11). The
geometry cache was computing local_bounds as (0, 0, width, height),
discarding the rect().x / rect().y origin offset from tight bounds.

This caused incorrect R-tree AABB entries, which made viewport culling
drop visible nodes during interactive rendering (flush path). The export
path (render_to_canvas / snapshot) was unaffected because it bypasses
viewport culling.

Add content_origin_x/y fields to NodeGeoData and GeoInput, populated
from rect().x/y for Path, Polygon, and Vector nodes (0.0 for all
others). Use these in the Leaf local_bounds computation so the
world-space bounding box correctly reflects where pixels are drawn.

Fixes: shapes rendered as outline-only (no fill) in interactive mode,
incorrect hit-test regions, misplaced bounding box overlays and size
labels for Path/Polygon/Vector nodes with non-zero content origin.
Implement fillOverrideTable lookup when converting Figma REST API
fillGeometry paths to Grida PathNode/VectorNode children. Previously,
all fill geometry paths received the node-level fills unconditionally,
ignoring per-region fill overrides.

Add resolveFillOverride() which checks each path's overrideID against
the node's fillOverrideTable:
- No overrideID or missing/null table entry → node-level fills
- PaintOverride with fills array → use those fills
- PaintOverride with empty fills [] → no fill (transparent)

This affects multi-region vectors (icons with mixed filled/unfilled
regions), boolean operations with per-region fills, and outlined text
with per-character colors.
…cene graph

AffineTransform::new(tx, ty, rotation) expects rotation in radians, but
Container/Tray nodes store rotation in degrees. The scene graph and
geometry cache passed the degree value directly, which was silently
treated as radians. For a 90° container, this produced cos(90 rad) ≈
-0.45 instead of cos(π/2) ≈ 0 — a completely garbled rotation matrix.

The bug was latent because no current code path produces non-zero
container rotation (the editor has no UI for it, and the Figma import
workaround sets rotation=0 for flipped containers).

Fix: call .to_radians() at all four call sites where container/tray
rotation feeds into AffineTransform::new.

Add a regression test that creates a container with rotation=90° and
verifies the geometry cache produces the correct world transform matrix
(cos≈0, sin≈1) for both the container and its child.
…hildren

The container flip propagation was only handling flips (det < 0) and
skipping pure rotations (det > 0, e.g. 180°). But the Figma
relativeTransform convention doesn't match the Grida container model
(position + rotation_degrees via from_box_center) for ANY non-identity
2x2 transform — not just flips.

Change the propagation check from '!identity && !pureRotation' to just
'!identity' so that rotated containers (e.g. Corner Bottom Right with
[[-1,0],[0,-1]] = 180° rotation) also get their transforms baked into
children's geometry.

This also includes: BOOLEAN_OPERATION fillGeometry support, SVG path
transform baking utility, RECTANGLE/ELLIPSE geometry-to-group
conversion for rotated shapes, and child graph merge for boolean ops.
@vercel vercel bot temporarily deployed to Preview – viewer April 4, 2026 13:43 Inactive
Model INSIDE strokes as BooleanPathOperationNode(intersection) that
clips strokeGeometry to fillGeometry at render time via Skia
Path::op(Intersect). No schema changes — reuses the existing boolean
operation node type.

For the common single-fill/single-stroke case the intersection node
has two direct children (fill clone + stroke path). When multiple
fill or stroke geometries exist, intermediate BooleanOp(union) nodes
merge them into single operands before the intersection, preventing
incorrect pairwise intersection across all children.

The boolean node carries the original stroke color as its fill paint
so the renderer paints the clipped stroke band with the correct color.
…ontent

Added a new configuration option `prefer_path_for_geometry` to the PlaygroundMenuContent, enhancing the geometry handling capabilities.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
docs/wg/feat-fig/stroke-geometry-alignment.md (2)

17-30: ⚠️ Potential issue | 🟡 Minor

The stroke-geometry model still contradicts the measurements.

The prose still says strokeGeometry is always the same CENTER expansion, but the table below shows CENTER as [-5, -5]..[25, 25] and INSIDE/OUTSIDE as [-10, -10]..[30, 30]. Please reword the model and the later observations so they match the fixture data.

Also applies to: 46-63

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

In `@docs/wg/feat-fig/stroke-geometry-alignment.md` around lines 17 - 30, The text
claims strokeGeometry is always a CENTER expansion but the examples/fixture data
show different bounding boxes per strokeAlign; update the prose and the later
observations so the model matches the fixtures: change the definition of
strokeGeometry (and any sentences mentioning it) to state that strokeGeometry is
the stroke outline after applying strokeAlign (so CENTER yields [-5,-5]..[25,25]
while INSIDE/OUTSIDE yield [-10,-10]..[30,30]), and adjust the table
rows/descriptions for CENTER, OUTSIDE, and INSIDE accordingly; ensure references
to fillGeometry, strokeGeometry, and strokeAlign are consistent with this new
definition.

74-99: ⚠️ Potential issue | 🟡 Minor

Label the pseudocode fences.

These three fenced blocks are still unlabeled, so markdownlint will keep flagging MD040 here. ```text is sufficient.

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

In `@docs/wg/feat-fig/stroke-geometry-alignment.md` around lines 74 - 99, The
three pseudocode fenced blocks under the OUTSIDE/INSIDE/CENTER examples are
unlabeled causing markdownlint MD040; update each fence to include a language
label (e.g., replace the opening ``` with ```text) for the blocks describing
OUTSIDE, INSIDE, and CENTER so they become fenced code blocks like ```text ...
``` and satisfy the linter while leaving the block contents unchanged.
packages/grida-canvas-io-figma/lib.ts (1)

537-554: ⚠️ Potential issue | 🟠 Major

H/V commands are still transformed without current-point tracking.

For non-diagonal matrices this leaves stale one-axis commands in the path, and even the diagonal branch computes the rebase bounds against y = 0 / x = 0 instead of the current point. Rotated/flipped paths that use H/V will still import shifted or malformed here.

Also applies to: 621-625

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

In `@packages/grida-canvas-io-figma/lib.ts` around lines 537 - 554, The H/V
handlers are updating g.nums without using the current point, so transformed
horizontal/vertical commands become stale under rotations/skews; fix by using
the current point when computing transformed coordinates and by converting
non-diagonal H/V into explicit L (line-to) commands. Specifically, inside the
"H" and "V" cases use the tracked current point (cx, cy from your track state)
instead of 0 when computing transformed coordinates with the matrix components
a,b,c,d (and any translation if present), and for non-diagonal matrices (when
Math.abs(b) or Math.abs(c) >= 1e-9) replace the command type (g.type) with "L"
and emit both x and y in g.nums after applying the full 2×2 transform and
translation so subsequent processing has correct absolute/relative points;
ensure you still call track(newX, newY) (or track(newX, cy) / track(cx, newY)
for diagonal cases) and preserve isRel semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@editor/grida-canvas-hosted/playground/uxhost-menu.tsx`:
- Around line 183-185: The playground is forcing render-only geometry by setting
prefer_path_for_geometry: true in the FactoryContext, which makes imported Path
nodes non-editable; change the FactoryContext construction (the context object
where gradient_id_generator and prefer_path_for_geometry are set) to not set
prefer_path_for_geometry by default, or gate it behind an explicit debug flag
(e.g., a playground debug option) so the property is only true when the debug
flag is enabled; update any initialization or config check that creates this
context to read the debug toggle and only set prefer_path_for_geometry when that
toggle is true.

In `@packages/grida-canvas-io-figma/lib.ts`:
- Around line 1627-1629: The OUTSIDE compositing branch currently assumes the
fill paint will occlude the inner half of the stroke, but resolveFillOverride()
can return an empty fills array or transparent paint so that occlusion never
happens; change the OUTSIDE rendering path (the code that uses fillGeometry and
composes the stroke) to perform a geometric subtraction/mask of fillGeometry
from the stroke geometry (e.g., compute strokeMinusFill =
strokeGeometry.subtract(fillGeometry) or apply a clip that excludes
fillGeometry) before drawing the stroke, and ensure you treat an explicit empty
fills array or fully transparent fill as “no fill” when building fillGeometry so
subtraction/masking is still applied; update the same logic used at the other
location referenced around lines 1833–1840.
- Around line 2183-2221: pNeedsBake branch folds parent translation into each
child's relativeTransform but leaves those translations in grandparent coords
while resetting the container to identity (rotation = 0) and not updating its
bounds, causing double-offset children and stale clipping; fix by converting the
children's relativeTransform translations back into the new container-local AABB
frame: compute the container AABB (aabbX,aabbY) and subtract it from each
child's relativeTransform[?][2] (ctx2/cty) so child positions become local to
the new container origin, then set processedNode.layout_inset_left/top to
aabbX/aabbY, update processedNode size (width/height) to the AABB extents, and
keep processedNode.rotation = 0 so downstream positioning_trait() reads
consistent container-local transforms (refer to pNeedsBake, relativeTransform,
processedNode.layout_inset_left/layout_inset_top, processedNode.rotation, and
size properties).

---

Duplicate comments:
In `@docs/wg/feat-fig/stroke-geometry-alignment.md`:
- Around line 17-30: The text claims strokeGeometry is always a CENTER expansion
but the examples/fixture data show different bounding boxes per strokeAlign;
update the prose and the later observations so the model matches the fixtures:
change the definition of strokeGeometry (and any sentences mentioning it) to
state that strokeGeometry is the stroke outline after applying strokeAlign (so
CENTER yields [-5,-5]..[25,25] while INSIDE/OUTSIDE yield [-10,-10]..[30,30]),
and adjust the table rows/descriptions for CENTER, OUTSIDE, and INSIDE
accordingly; ensure references to fillGeometry, strokeGeometry, and strokeAlign
are consistent with this new definition.
- Around line 74-99: The three pseudocode fenced blocks under the
OUTSIDE/INSIDE/CENTER examples are unlabeled causing markdownlint MD040; update
each fence to include a language label (e.g., replace the opening ``` with
```text) for the blocks describing OUTSIDE, INSIDE, and CENTER so they become
fenced code blocks like ```text ... ``` and satisfy the linter while leaving the
block contents unchanged.

In `@packages/grida-canvas-io-figma/lib.ts`:
- Around line 537-554: The H/V handlers are updating g.nums without using the
current point, so transformed horizontal/vertical commands become stale under
rotations/skews; fix by using the current point when computing transformed
coordinates and by converting non-diagonal H/V into explicit L (line-to)
commands. Specifically, inside the "H" and "V" cases use the tracked current
point (cx, cy from your track state) instead of 0 when computing transformed
coordinates with the matrix components a,b,c,d (and any translation if present),
and for non-diagonal matrices (when Math.abs(b) or Math.abs(c) >= 1e-9) replace
the command type (g.type) with "L" and emit both x and y in g.nums after
applying the full 2×2 transform and translation so subsequent processing has
correct absolute/relative points; ensure you still call track(newX, newY) (or
track(newX, cy) / track(cx, newY) for diagonal cases) and preserve isRel
semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e306ed67-6b4b-4b7c-af0d-c2af0c735397

📥 Commits

Reviewing files that changed from the base of the PR and between 43f5844 and 6f5f482.

📒 Files selected for processing (3)
  • docs/wg/feat-fig/stroke-geometry-alignment.md
  • editor/grida-canvas-hosted/playground/uxhost-menu.tsx
  • packages/grida-canvas-io-figma/lib.ts

Comment on lines 183 to +185
const context: iofigma.restful.factory.FactoryContext = {
gradient_id_generator: () => v4(),
prefer_path_for_geometry: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t force render-only geometry mode in the playground editor.

The importer documents prefer_path_for_geometry as a render-only switch and explicitly notes that the resulting Path nodes are not editable in the editor. Hardcoding it here turns normal Figma imports in the playground into non-editable geometry. Gate this behind an explicit debug option or leave it off by default.

Possible fix
           const context: iofigma.restful.factory.FactoryContext = {
             gradient_id_generator: () => v4(),
-            prefer_path_for_geometry: true,
             resolve_image_src: (ref) =>
               ref in images ? `res://images/${ref}` : null,
           };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-hosted/playground/uxhost-menu.tsx` around lines 183 -
185, The playground is forcing render-only geometry by setting
prefer_path_for_geometry: true in the FactoryContext, which makes imported Path
nodes non-editable; change the FactoryContext construction (the context object
where gradient_id_generator and prefer_path_for_geometry are set) to not set
prefer_path_for_geometry by default, or gate it behind an explicit debug flag
(e.g., a playground debug option) so the property is only true when the debug
flag is enabled; update any initialization or config check that creates this
context to read the debug toggle and only set prefer_path_for_geometry when that
toggle is true.

Comment on lines +1627 to +1629
// PaintOverride.fills is the per-region fill array.
// An empty array means explicitly no fill (transparent).
return override.fills ?? node.fills;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OUTSIDE compositing breaks when the fill does not actually paint.

resolveFillOverride() correctly preserves [] as “no fill”, but the OUTSIDE branch still relies on drawing the fill child on top to hide the inward half of the stroke. If a node/region has no fill, or the fill is transparent, that occlusion never happens and the stroke renders as the full band instead of outside-only. This needs geometric subtraction/masking against fillGeometry, not paint order alone.

Also applies to: 1833-1840

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

In `@packages/grida-canvas-io-figma/lib.ts` around lines 1627 - 1629, The OUTSIDE
compositing branch currently assumes the fill paint will occlude the inner half
of the stroke, but resolveFillOverride() can return an empty fills array or
transparent paint so that occlusion never happens; change the OUTSIDE rendering
path (the code that uses fillGeometry and composes the stroke) to perform a
geometric subtraction/mask of fillGeometry from the stroke geometry (e.g.,
compute strokeMinusFill = strokeGeometry.subtract(fillGeometry) or apply a clip
that excludes fillGeometry) before drawing the stroke, and ensure you treat an
explicit empty fills array or fully transparent fill as “no fill” when building
fillGeometry so subtraction/masking is still applied; update the same logic used
at the other location referenced around lines 1833–1840.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts (2)

593-600: Minor: Consider typed helper instead of as any cast.

The convert helper uses node as any to bypass type checking. While acceptable for tests, a more explicit type annotation on the parameter would preserve type safety:

function convert(node: figrest.VectorNode & figrest.HasGeometryTrait) {

This is a minor style suggestion given the fixture already uses type assertions.

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

In `@packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts`
around lines 593 - 600, Change the untyped cast in the test helper to a stricter
parameter type: update the convert function signature to accept a node typed as
figrest.VectorNode & figrest.HasGeometryTrait instead of using node as any, and
pass that typed node into iofigma.restful.factory.document (helper name:
convert; target call: iofigma.restful.factory.document).

732-745: Consider adding defensive assertions before non-null assertions.

The find() calls return PathNode | undefined, but the test uses the non-null assertion ! directly. If the naming convention changes and paths aren't found, the test would fail with an unhelpful "cannot read property" error rather than a clear assertion failure.

       const fill1 = paths.find((p) => p.name.includes("Fill 1"))!;
       const fill2 = paths.find((p) => p.name.includes("Fill 2"))!;
       const fill3 = paths.find((p) => p.name.includes("Fill 3"))!;
+
+      expect(fill1).toBeDefined();
+      expect(fill2).toBeDefined();
+      expect(fill3).toBeDefined();
 
       // Fill 1: overrideID=1 → empty fills → no fill_paints
       expect(fill1.fill_paints).toBeUndefined();

This is a minor robustness improvement for test maintainability.

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

In `@packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts`
around lines 732 - 745, The test uses non-null assertions on results of
paths.find for fill1, fill2, and fill3 which can yield undefined; add explicit
assertions that each find returned a value before dereferencing (e.g.,
expect(fill1).toBeDefined(), expect(fill2).toBeDefined(),
expect(fill3).toBeDefined()) so failures show clear messages; update the
references around the existing fill1/fill2/fill3 uses in the same test block to
use these defensive checks prior to accessing fill_paints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts`:
- Around line 593-600: Change the untyped cast in the test helper to a stricter
parameter type: update the convert function signature to accept a node typed as
figrest.VectorNode & figrest.HasGeometryTrait instead of using node as any, and
pass that typed node into iofigma.restful.factory.document (helper name:
convert; target call: iofigma.restful.factory.document).
- Around line 732-745: The test uses non-null assertions on results of
paths.find for fill1, fill2, and fill3 which can yield undefined; add explicit
assertions that each find returned a value before dereferencing (e.g.,
expect(fill1).toBeDefined(), expect(fill2).toBeDefined(),
expect(fill3).toBeDefined()) so failures show clear messages; update the
references around the existing fill1/fill2/fill3 uses in the same test block to
use these defensive checks prior to accessing fill_paints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 071d96ca-34ea-45cc-b85a-bb34f294417a

📥 Commits

Reviewing files that changed from the base of the PR and between 8815d39 and a859954.

📒 Files selected for processing (1)
  • packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.vector.test.ts

…accuracy

- Container flip propagation: subtract AABB origin from children's
  translations so they are container-local, not grandparent-space.
  Also set container layout_target_width/height to AABB dimensions.

- transformSvgPath H/V commands: add explicit TODO comments for the
  known limitation that non-diagonal transforms silently skip H/V
  (requires current-point tracking to promote to L). Low risk since
  Figma geometry=paths does not use H/V commands.

- Docs: fix inaccurate claim that strokeGeometry is 'always the same
  CENTER expansion' — CENTER produces sw band, INSIDE/OUTSIDE produce
  2×sw band. Add fence languages to pseudocode blocks.
…tive viewer

- Added information about the headless rendering capabilities of `@grida/refig`, including supported formats and links to documentation.
- Introduced a section for the interactive Embed Viewer, detailing its features and providing links to relevant SDK documentation.
- Updated README and other documentation files to reflect new features, including rich text support and improved export options.
- Replaced `fig2grida` with in-memory conversion functions `figBytesToGridaDocument` and `restJsonToGridaDocument` to enhance performance by avoiding unnecessary packing into `.grida` archives.
- Updated the `FileConverter` type to return a `GridaDocumentResult` containing the document and assets directly.
- Improved the Embed Debug Page UI for better clarity and usability, including updated labels and descriptions for various controls and states.
- Enhanced documentation comments to reflect the new conversion methods and their usage.
- Introduced a new `convert` subcommand to the CLI for converting Figma files to `.grida` and `.grida1` formats without rendering.
- Implemented in-memory processing for both FlatBuffers archives and JSON snapshots, enhancing performance and usability.
- Updated README with usage examples and detailed descriptions of the new conversion options and output formats.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working canvas/io canvas cg Core Graphics documentation Improvements or additions to documentation enhancement New feature or request packages refig tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant