Skip to content

Conversation

@softmarshmallow
Copy link
Member

@softmarshmallow softmarshmallow commented Dec 3, 2025

Goal:

  • import from .fig file directly (without api)
  • paste from figma clipboard payload (fig2grida)

Non goal:

  • authoring .fig or clipboard data (grida2fig)

Impls

Pt1. From Figma Into Grida (Clipboard)

day-305-grida-canvas-better-io-figma-pt1-copy-paste-from-figma-into-grida.mp4

Pt2. Inspector

https://canary.grida.co/tools/fig

Pt3. Import .fig file

day-306-grida-canvas-better-io-figma-pt2-import-fig-file.mp4

Note

Adds offline Figma import from .fig files and clipboard (Kiwi), with schema parsing, node conversion to Grida, SVG/text detection, and comprehensive tests.

  • Figma Import (Offline):
    • New @grida/io-figma package (Kiwi parser, blob parsers, schema) to parse .fig and clipboard payloads and convert to REST/Grida nodes.
    • Fig importer builds page trees (with sort keys) and packs scenes; supports vector networks, polygons/stars, arcs, fills/strokes/effects.
  • Clipboard/SVG:
    • Add isFigmaClipboard, isSvgText, extended filetype (incl. WebP), and richer decode results (svg-text, canbe-figma-clipboard).
  • Rendering/Traits:
    • Refactor node factory with composable traits; improved image/gradient/effect/stroke mapping; group handling.
  • Tests:
    • Extensive unit tests for Kiwi parsing, archive utilities, clipboard detection, and file imports (community/clipboard fixtures).
  • Docs/Config:
    • New READMEs (Kiwi spec, features, guides); Jest setup; dependencies & lock updates; fixtures via Git LFS.

Written by Cursor Bugbot for commit 0c17a42. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Figma import: .fig file and clipboard (Kiwi) parsing, inspector tool, file import dialogs, SVG paste detection, and file/image insertion flows
    • Environment variable fallback for Figma API key
  • Documentation

    • New/expanded docs: Figma import guides, Kiwi schema glossary, feature guides, tooling READMEs, and optimization notes
  • Tests

    • Extensive tests for Kiwi/.fig parsing, blob parsing, clipboard handling, and import flows
  • Chores

    • Git LFS enabled in CI, added fixtures, WASM optimization tweaks, new Slack logo component

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 3, 2025

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

Project Deployment Preview Comments Updated (UTC)
docs Ready Ready Preview Comment Dec 7, 2025 8:28pm
grida Ready Ready Preview Comment Dec 7, 2025 8:28pm
5 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
code Ignored Ignored Dec 7, 2025 8:28pm
legacy Ignored Ignored Dec 7, 2025 8:28pm
backgrounds Skipped Skipped Dec 7, 2025 8:28pm
blog Skipped Skipped Dec 7, 2025 8:28pm
viewer Skipped Skipped Dec 7, 2025 8:28pm

@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds full Figma .fig and clipboard import support: Kiwi archive & HTML parser, Kiwi→Grida translation layer, editor import UIs/tools, CLI helper, test fixtures, docs, CI LFS support, and related UI/component adjustments.

Changes

Cohort / File(s) Summary
CI & LFS
\.github/workflows/test-crates.yml, \.github/workflows/test.yml, fixtures/.gitattributes
Enable Git LFS in CI checkout and track *.fig via .gitattributes.
Fig → Kiwi CLI & Tools
.ref/figma/fig2kiwi.ts, .tools/README.md, .tools/pbdump.swift
Add Deno CLI to extract Kiwi schema from .fig files; add internal tools README and a macOS pasteboard dump script.
Docs — Figma / WG / Guides
\.ref/figma/README.md, docs/wg/*, docs/with-figma/*, docs/editor/features/*, docs/with-figma/guides/how-to-get-fig-file.md
New and expanded docs: Kiwi schema reference, Figma import guides, clipboard format docs, feature writeups and WG hub.
Fig-kiwi parser & blob parsing
packages/grida-canvas-io-figma/fig-kiwi/index.ts, .../blob-parser.ts, .../README.md
New package implementing Kiwi archive/HTML parsing, read/write of .fig-like archives, blob extraction, thumbnail/meta helpers and blob parsers.
IoFigma translation & API
packages/grida-canvas-io-figma/lib.ts, packages/grida-canvas-io-figma/index.ts, packages/grida-canvas-io-figma/package.json
New Kiwi→Figma/Grida translation layer: converters, factory/map helpers, FigImporter, public types and package manifest (deps/scripts).
Tests for fig/io/kiwi
packages/grida-canvas-io-figma/__tests__/*, packages/grida-canvas-io-figma/jest.config.ts, tsconfig.json
Add extensive unit tests for archive, blob parsing, HTML clipboard round-trips, and importer behaviors.
Clipboard & file insertion API
packages/grida-canvas-io/index.ts, packages/grida-canvas-io/__tests__/*, editor/grida-canvas-react/use-data-transfer.ts
Add isFigmaClipboard/isSvgText detectors, SVG text handling, WebP support, and rename/refactor useDataTransfer→useInsertFile with insertImage/insertSVG/insertFromFile and Figma clipboard handling.
Editor import UIs & tools
editor/grida-canvas-react-starter-kit/starterkit-import/*, editor/app/(tools)/tools/fig/*, editor/app/(tools)/tools/page.tsx
New ImportFromFigma/FromGrida dialogs, Fig parser tool UI, inspector, node icons, and tools page metadata/layout.
Playground / Hierarchy / Menu refactor
editor/grida-canvas-hosted/playground/*, editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx
Add DocumentHierarchy component, move/refactor playground menu into uxhost-menu, remove AI UI bits from toolbar/settings, and update SidebarLeft signature.
UI components & icons
editor/components/logos/*, editor/components/ui/empty.tsx, editor/components/ui/dropdown-menu.tsx, multiple UI files
Add SlackLogoIcon and barrel exports; add Empty-state components; tweak dropdown SVG styling; swap Slack icon usages and minor spacing tweaks across components.
WASM config & optimization notes
crates/OPTIMIZATION_SIZE.md, crates/grida-canvas-wasm/.cargo/config.toml, crates/grida-canvas/Cargo.toml
Add size-optimization documentation and per-package/target cargo release profiles and wasm flags; bump figma-api with wasm hints.
Fixtures — .fig & clipboard samples
fixtures/test-fig/**
Add many .fig LFS pointer fixtures, clipboard HTML fixtures, doc metadata and community fixture index.
Manifests / package tweaks
packages/grida-canvas-io-figma/package.json, packages/grida-canvas-schema/index.ts, packages/grida-canvas-io/*
Add scripts, new runtime deps (base64-js, fflate, fzstd, kiwi-schema), and a type re-export for vn.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditorUI
    participant Parser (fig-kiwi)
    participant Importer (iofigma)
    participant Canvas

    User->>EditorUI: open import tool / paste / select .fig
    alt Clipboard paste (HTML)
        EditorUI->>Parser: readHTMLMessage(html)
        Parser->>Parser: parse metadata + inflate/compile schema
        Parser->>EditorUI: ParsedFigmaHTML (meta + messages)
    else File import (.fig/.zip)
        EditorUI->>Parser: readFigFile(file)
        Parser->>Parser: parse header, extract blobs, unzip images
        Parser->>EditorUI: ParsedFigmaArchive (schema + blobs + preview)
    end
    EditorUI->>Importer: convertPageToScene(parsed)
    Importer->>Importer: map Kiwi NodeChange → Grida/Figma node(s)
    Importer->>Canvas: insert nodes/document
    Canvas->>EditorUI: render/update viewport
    EditorUI->>User: show imported content / toasts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Areas that may need extra attention:

  • Binary parsing and buffer safety in fig-kiwi (offsets, inflate/deflate, ZIP handling).
  • Blob parsers (parseVectorNetworkBlob / parseCommandsBlob) correctness and bounds checks.
  • iofigma mapping functions — ensure null-safe handling and fidelity of transforms/paints/effects.
  • Clipboard detection priority (Grida vs Figma) and useInsertFile integration paths.
  • New tests and fixtures — validate that fixtures are representative and CI LFS integration passes.

Possibly related PRs

Suggested reviewers

  • kmk142789

Poem

🐇 I nibble bytes and chew on blobs,

I hop through Kiwi, unzip the logs,
From clipboard span to canvas view,
I stitch the frames and bring them through,
A rabbit's cheer — imports anew! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.92% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Copy-Paste from Figma' directly summarizes the main objective: enabling clipboard pasting and file importing from Figma, which aligns with the comprehensive changeset implementing Figma import/paste functionality.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb53cb and 0c17a42.

📒 Files selected for processing (2)
  • editor/grida-canvas-react/use-data-transfer.ts (6 hunks)
  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts (1 hunks)

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

❤️ Share

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

Copy link

@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: 13

Caution

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

⚠️ Outside diff range comments (1)
editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx (1)

197-224: Bug: Color keys have incorrect naming pattern.

The color keys appear to have erroneous "red" suffixes: "greenred", "bluered", "yellowred", etc. This looks like a copy-paste error. While this may be pre-existing code, it could cause issues if these keys are used for lookups or display.

 const defaultColors: Record<string, kolor.colorformats.RGBA32F> =
   Object.fromEntries(
     [
       ["red", kolor.names.red],
-      ["greenred", kolor.names.green],
-      ["bluered", kolor.names.blue],
-      ["yellowred", kolor.names.yellow],
-      ["orangered", kolor.names.orange],
-      ["purplered", kolor.names.purple],
-      ["pinkred", kolor.names.pink],
-      ["cyanred", kolor.names.cyan],
-      ["magentared", kolor.names.magenta],
-      ["blackred", kolor.names.black],
-      ["whitered", kolor.names.white],
-      ["grayred", kolor.names.gray],
-      ["silverred", kolor.names.silver],
-      ["brownred", kolor.names.brown],
-      ["olivered", kolor.names.olive],
-      ["navyred", kolor.names.navy],
-      ["tealred", kolor.names.teal],
-      ["maroonred", kolor.names.maroon],
-      ["goldred", kolor.names.gold],
-      ["indigored", kolor.names.indigo],
+      ["green", kolor.names.green],
+      ["blue", kolor.names.blue],
+      ["yellow", kolor.names.yellow],
+      ["orange", kolor.names.orange],
+      ["purple", kolor.names.purple],
+      ["pink", kolor.names.pink],
+      ["cyan", kolor.names.cyan],
+      ["magenta", kolor.names.magenta],
+      ["black", kolor.names.black],
+      ["white", kolor.names.white],
+      ["gray", kolor.names.gray],
+      ["silver", kolor.names.silver],
+      ["brown", kolor.names.brown],
+      ["olive", kolor.names.olive],
+      ["navy", kolor.names.navy],
+      ["teal", kolor.names.teal],
+      ["maroon", kolor.names.maroon],
+      ["gold", kolor.names.gold],
+      ["indigo", kolor.names.indigo],
     ].map(([id, rgb]) => {
🧹 Nitpick comments (24)
docs/wg/feat-schema/naming-conventions.md (1)

125-128: Good addition—consider clarifying the intended usage context.

The regex pattern is a helpful diagnostic tool that correctly identifies camelCase and similar violations of the snake_case convention. The pattern logic is sound: it matches fields starting with lowercase followed by at least one uppercase letter before the colon.

For maximum utility, consider adding a brief note about the intended context (e.g., "for linting schema files", "for pattern matching in code reviews", etc.) so readers know when and how to apply this pattern.

Apply this diff to add context:

  ## Notes

- - regexp for searching non-snake_case fields:`\b(?=[a-z]*[A-Z])[a-z][a-zA-Z0-9]*\??\s*:` (finds `fieldName: T` / `fieldName:? T`)
+ - regexp for searching non-snake_case fields (use in schema linting tools or manual pattern searches): `\b(?=[a-z]*[A-Z])[a-z][a-zA-Z0-9]*\??\s*:` (finds `fieldName: T` / `fieldName:? T`)
crates/grida-dev/src/main.rs (2)

302-307: API key resolution in load_figma_scene is correct; consider centralizing logic

The resolved_api_key logic correctly prefers --api-key over X_FIGMA_TOKEN and surfaces a clear error when neither is present. The updated error message accurately documents both input paths.

You now have nearly identical resolution logic here and in load_scene_images_from_source; consider extracting a small helper like fn resolve_figma_api_key(args: &FigmaArgs) -> Option<String> to keep behavior in sync if this ever changes (e.g., different env var name). Also worth updating the dev docs/AGENTS to mention the X_FIGMA_TOKEN option. Based on learnings, this keeps the CLI behavior discoverable.

Also applies to: 333-341


373-377: Image-loading API key fallback matches scene loading; minor duplication only

The resolved_api_key in load_scene_images_from_source mirrors load_figma_scene semantics, and the error message again clearly mentions both --api-key and X_FIGMA_TOKEN. The branching still correctly skips API usage when archive_dir, images_dir, file, or --no-image are used, so no behavior regressions.

Same as above, the duplicated resolution/error text between the two functions could be folded into a shared helper for maintainability, but it’s not required.

Also applies to: 433-442

crates/OPTIMIZATION_SIZE.md (1)

132-136: Wrap bare URLs in markdown link syntax.

The linter flags bare URLs that should be wrapped in markdown link syntax [text](url) for proper rendering and consistency.

Apply this diff to wrap the URLs:

- https://github.com/johnthagen/min-sized-rust
- https://doc.rust-lang.org/cargo/reference/profiles.html
- https://rustwasm.github.io/docs/book/reference/code-size.html
- https://crates.io/crates/miniserde
- https://www.warp.dev/blog/reducing-wasm-binary-size
+ - [min-sized-rust](https://github.com/johnthagen/min-sized-rust)
+ - [Cargo profiles reference](https://doc.rust-lang.org/cargo/reference/profiles.html)
+ - [WASM code size](https://rustwasm.github.io/docs/book/reference/code-size.html)
+ - [miniserde](https://crates.io/crates/miniserde)
+ - [Reducing WASM binary size](https://www.warp.dev/blog/reducing-wasm-binary-size)
crates/grida-canvas/Cargo.toml (2)

28-32: Clarify size impact comments to distinguish measured deltas from active optimization levels.

The comments document size costs under different opt-level settings, but the wording "(+2mb wasm32-unknown-emscripten@opt-level=3)" and "(+0.25mb wasm32-unknown-emscripten@opt-level=3)" reads as if opt-level=3 is the active setting. However, per .cargo/config.toml, these crates are configured with opt-level="z" at build time.

Consider rewording to make clear these are measured reference points, not current settings:

- # (+2mb wasm32-unknown-emscripten@opt-level=3)
+ # (~2mb size delta vs. opt-level="z" when built with opt-level=3)
- # (+0.25mb wasm32-unknown-emscripten@opt-level=3)
+ # (~0.25mb size delta vs. opt-level="z" when built with opt-level=3)

Or, if the intention is to document what these crates alone contribute to bundle size, rename to be clearer:

- # (+2mb wasm32-unknown-emscripten@opt-level=3)
+ # usvg contributes ~2mb (unoptimized); ~0.5mb with opt-level="z"

39-40: Simplify and clarify figma-api size comment.

The comment "(+3mb@opt-level=3 +1.7mb@opt-level=z wasm32-unknown-emscripten)" is dense and ambiguous about which optimization is active. Reword to clearly convey the trade-off:

- # (+3mb@opt-level=3 +1.7mb@opt-level=z wasm32-unknown-emscripten)
+ # figma-api: ~3mb unoptimized (opt-level=3), ~1.7mb with opt-level="z" (38% savings)

This makes it explicit that opt-level="z" is the active choice (per .cargo/config.toml) and quantifies the benefit.

The version bump to 0.31.4 and optional=true marking are appropriate for conditional Figma feature support.

editor/grida-canvas-react-starter-kit/starterkit-import/from-grida.tsx (1)

29-44: Consider adding loading state during file parsing.

The loading state from useFilePicker only indicates whether the file picker is open. During io.load(f), which may take time for large files, there's no visual feedback. Users could click "Import" multiple times.

+  const [importing, setImporting] = useState(false);
+
   const handleFileImport = async () => {
     if (plainFiles.length > 0) {
+      setImporting(true);
       try {
         const f = plainFiles[0];
         const doc = await io.load(f);
         onImport?.(doc);
         toast.success("File successfully imported!");
         props.onOpenChange?.(false); // Close the dialog
       } catch (error) {
         toast.error("Failed to parse the file. Please check the format.");
         console.error(error);
+      } finally {
+        setImporting(false);
       }
     } else {
       toast.error("No file selected.");
     }
   };

Then use importing to disable the Import button and show a loading indicator.

editor/grida-canvas-react-starter-kit/starterkit-hierarchy/index.tsx (1)

53-91: Consider composing existing components instead of duplicating.

DocumentHierarchy duplicates the SidebarGroup structure from ScenesGroup (lines 24-38). While the layout differs (resizable panels vs fixed height), extracting the shared content could reduce duplication.

If intentional for layout flexibility, this is fine. Otherwise, consider extracting the inner content:

function ScenesContent() {
  const editor = useCurrentEditor();
  return (
    <>
      <SidebarGroupLabel>
        Scenes
        <SidebarGroupAction onClick={() => editor.commands.createScene()}>
          <PlusIcon />
          <span className="sr-only">New Scene</span>
        </SidebarGroupAction>
      </SidebarGroupLabel>
      <SidebarGroupContent className="flex-1 overflow-y-auto">
        <ScenesList />
      </SidebarGroupContent>
    </>
  );
}

Then reuse in both ScenesGroup and DocumentHierarchy.

editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (1)

77-83: Type parameter too permissive.

The set function accepts any but sessionStorage.setItem expects a string. Non-string values will be silently coerced, which may cause unexpected behavior.

   const set = React.useCallback(
-    (value: any) => {
+    (value: string) => {
       setState(value);
       sessionStorage.setItem(key, value);
     },
     [key]
   );
editor/grida-canvas-hosted/playground/uxhost-menu.tsx (1)

287-326: Use DropdownMenuItem for action items instead of DropdownMenuCheckboxItem.

Zoom actions (zoom in, zoom out, zoom to 100%, zoom to fit, zoom to selection) are one-time actions, not toggleable states. Using DropdownMenuCheckboxItem with checked={false} is semantically incorrect and may render unnecessary checkbox indicators.

-            <DropdownMenuCheckboxItem
-              checked={false}
-              onSelect={() => instance.camera.zoomIn()}
+            <DropdownMenuItem
+              onClick={() => instance.camera.zoomIn()}
               className="text-xs"
             >
               Zoom in
               <DropdownMenuShortcut>⌘+</DropdownMenuShortcut>
-            </DropdownMenuCheckboxItem>
+            </DropdownMenuItem>

Apply the same pattern to the other zoom action items (Lines 295-326).

editor/grida-canvas-react/use-data-transfer.ts (3)

43-154: useInsertFile hook is solid; only minor extension/name handling nits

The image/SVG insertion logic and coordinate handling look consistent with the rest of the editor API. Two small optional tweaks you might consider:

  • Use a more robust extension-stripper for name (e.g. file.name.replace(/\.[^/.]+$/i, "")) instead of split(".svg")[0] / split(".")[0] so filenames with multiple dots or uppercase extensions don’t get truncated unexpectedly.
  • In insertFromFile, unsupported type values currently no-op; for symmetry with the drop path (which toasts an error), you might optionally surface a toast or return a boolean to signal to callers that nothing was inserted.

163-271: Figma clipboard conversion: guid/id assumptions and bundling

The overall kiwi NodeChanges → flat Figma nodes → parent/child tree → Grida document pipeline is thoughtfully structured and has good failure messages. A couple of things to double-check/improve:

  • Tree building and root-node detection rely on node.id matching the normalized GUID used by iofigma.kiwi.guid(...). If these ever diverge, guidToKiwi.get(node.id) will fail and nodes may be misclassified as roots or lose parents. It may be worth adding an explicit invariant (assert/log) or a small test around this mapping.
  • Since this helper is only used on the Figma-clipboard path, consider lazy-loading the entire @grida/io-figma surface instead of statically importing iofigma and dynamically importing only readHTMLMessage, to keep the default editor bundle leaner and concentrate this cost behind the paste action.

343-368: Clipboard paste path: consider WebP support and explicit event consumption

The staged paste handling (Grida payload → Figma clipboard → SVG/text/images → local clipboard fallback) is clear and robust. Two potential refinements:

  • In the generic image branch you handle "image/gif" | "image/jpeg" | "image/png" | "image/svg+xml" but not "image/webp", even though insertFromFile supports WebP via drops. If OS clipboards commonly surface WebP, adding it here would keep behavior consistent between paste and drag-and-drop.
  • When a Figma payload, SVG text, plain text, or image payload is successfully handled, pasted_from_data_transfer is set but event.preventDefault() is only called in the vector or fallback branches. If you want to be absolutely sure the default browser paste never runs in these handled cases, you could gate a final if (pasted_from_data_transfer) event.preventDefault(); after the branch logic.

Also applies to: 447-512

docs/with-figma/guides/how-to-get-fig-file.md (2)

27-40: Consider using headings instead of bold text for subsections.

The static analysis flagged MD036 (emphasis used instead of heading). Using ### or #### headings for "macOS", "Windows", and "Linux" would improve document structure and accessibility.

-**macOS**
+### macOS

 - Default Downloads folder: `~/Downloads/`
 - Custom locations: Wherever you chose during save

-**Windows**
+### Windows

 - Default Downloads folder: `C:\Users\YourUsername\Downloads\`
 - Custom locations: Wherever you chose during save

-**Linux**
+### Linux

 - Default Downloads folder: `~/Downloads/`
 - Custom locations: Wherever you chose during save

76-92: Optional: Convert troubleshooting items to headings for consistency.

Similar to the OS sections, the troubleshooting items could use ### or #### headings instead of bold text to improve document structure.

-**Can't find "Save local copy" option**
+### Can't find "Save local copy" option

 - The file owner may have restricted copying and sharing
 - You may not have sufficient access permissions (need at least "can view")
 - Contact the file owner to request access or to download the file for you

-**"Failed to parse .fig file"**
+### "Failed to parse .fig file"

 - Ensure the file is a valid .fig file downloaded from Figma
 - Try downloading the file again
 - Check if the file is corrupted (file size should be reasonable)
 - The .fig format may have changed (see warning above)

-**"No pages found"**
+### "No pages found"

 - The .fig file may be empty or contain no canvas nodes
 - Open the file in Figma to verify it has content
packages/grida-canvas-io-figma/fig-kiwi/__tests__/blob-parser.test.ts (1)

46-93: Consider explicit test skip or failure for missing fixture data.

The early return at lines 74-77 creates a "soft skip" where the test passes even when commandsBlobId is undefined. This could hide test failures if the fixture structure changes unexpectedly.

Consider one of these approaches:

  • If commands blob is expected in the fixture, fail explicitly: expect(commandsBlobId).toBeDefined()
  • If it's optional, use test.skip() with a condition or document why the soft skip is acceptable

Example of explicit handling:

     if (commandsBlobId === undefined) {
-      // Skip if no commands blob found
-      return;
+      // If this fixture is expected to have commands blob, fail explicitly
+      throw new Error('Expected to find commandsBlob in fixture, but none was found');
     }
packages/grida-canvas-io-figma/fig-kiwi/README.md (1)

1-58: Excellent documentation! Minor: Add language identifier to diagram.

The README provides comprehensive, well-structured documentation for the fig-kiwi parser. The only minor issue is that the ASCII art diagram (lines 39-58) should have a language identifier for proper markdown rendering.

Apply this diff:

-```
+```text
 ┌─────────────────────────────────────────┐
 │  .fig File / HTML Clipboard             │

This addresses the markdownlint hint and ensures consistent rendering across markdown viewers.

editor/app/(tools)/tools/fig/node-type-icon.tsx (1)

14-51: Import the React type for ComponentType and consider narrowing type

nodeTypeIconMap is typed with React.ComponentType, but React is never imported. In a TS + automatic JSX setup this can fail type-checking because the React namespace isn’t guaranteed to exist.

You can avoid this by importing the type and using it directly, and optionally narrowing type to the map’s keys to catch typos:

-const nodeTypeIconMap: Record<
-  string,
-  React.ComponentType<{ className?: string }>
-> = {
+import type { ComponentType } from "react";
+
+const nodeTypeIconMap: Record<string, ComponentType<{ className?: string }>> = {
   // ...
 };
 
-export function NodeTypeIcon({
-  type,
-  className,
-}: {
-  type: string;
-  className?: string;
-}) {
+export function NodeTypeIcon({
+  type,
+  className,
+}: {
+  type: keyof typeof nodeTypeIconMap | string;
+  className?: string;
+}) {
   const Icon = nodeTypeIconMap[type] || BoxIcon;
   const props = { className: className || "size-4" };
   return <Icon {...props} />;
}

Please ensure your TS config/runtime actually exposes the React namespace; if not, the explicit import type { ComponentType } from "react"; is required for this file to type-check.

docs/editor/features/copy-paste-svg.md (1)

64-69: Consider documenting (or relaxing) the xmlns requirement for SVG text detection

The described detection logic for SVG text:

  • checks for xmlns="http://www.w3.org/2000/svg",
  • requires an opening <svg and closing </svg>.

This is a safe heuristic, but some valid SVG snippets (especially inline ones) omit the explicit xmlns attribute, so they would fall back to being treated as plain text.

Depending on your target sources, you might want to either:

  • explicitly note in this doc that SVG text must include the SVG namespace to be recognized, or
  • slightly relax detection in code/docs to accept <svg ...>/</svg> with or without xmlns, while still guarding against false positives.
.ref/figma/fig2kiwi.ts (1)

63-112: Code duplication with the main source.

The FigmaArchiveParser class is duplicated from packages/grida-canvas-io-figma/fig-kiwi/index.ts. While this is intentional for portability (as noted in the comment on line 61), this creates a maintenance burden: updates to parsing logic must be synchronized across both locations.

Consider adding a comment referencing the source file and its last sync date to help maintainers track changes:

-// --- Archive Parser (duplicated from main source) ---
+// --- Archive Parser (duplicated from main source) ---
+// Source: packages/grida-canvas-io-figma/fig-kiwi/index.ts
+// Last synced: 2025-12-03
packages/grida-canvas-io-figma/fig-kiwi/__tests__/figdata.test.ts (1)

80-155: Consider removing or moving the diagnostic test.

This skipped test appears to be a diagnostic tool for inspecting sortPosition values. Consider either:

  1. Moving it to a separate diagnostic script outside the test suite
  2. Removing it if the investigation is complete
  3. Converting it to a proper test with assertions if the behavior needs to be validated

The extensive console logging suggests this is a temporary investigation tool rather than a permanent test.

editor/app/(tools)/tools/fig/inspector.tsx (3)

106-106: Type assertion as any used for schema.

The as any cast here and on line 161 bypasses type checking. Consider defining a proper type or using a more specific assertion if the schema structure is known. Acceptable for an inspector/debugging tool.


175-184: String concatenation in loop has O(n²) complexity.

For typical thumbnail sizes this is fine, but for large images the string concatenation becomes inefficient. Consider using Buffer.from(bytes).toString('base64') uniformly with a polyfill for browser environments, or batch the characters.

 function uint8ArrayToBase64(bytes: Uint8Array): string {
-  let binary = "";
-  const len = bytes.byteLength;
-  for (let i = 0; i < len; i++) {
-    binary += String.fromCharCode(bytes[i]);
-  }
-  return typeof window !== "undefined"
-    ? window.btoa(binary)
-    : Buffer.from(binary).toString("base64");
+  // Buffer.from works in both Node.js and modern browsers
+  if (typeof Buffer !== "undefined") {
+    return Buffer.from(bytes).toString("base64");
+  }
+  // Fallback for environments without Buffer
+  const binary = String.fromCharCode(...bytes);
+  return window.btoa(binary);
 }

503-505: Consider importing guid from shared module to avoid duplication.

This formatGUID function duplicates iofigma.kiwi.guid from packages/grida-canvas-io-figma/lib.ts (line 972-974 in relevant snippets). Consider importing from the shared location.

+import { iofigma } from "@grida/io-figma";
+
 function formatGUID(guid: GUID): string {
-  return `${guid.sessionID}:${guid.localID}`;
+  return iofigma.kiwi.guid(guid);
 }

Comment on lines +31 to +86
The following capabilities are in scope for this feature:

- **Importing from `.fig` files** - Parse and translate Figma's binary file format into Grida's document model
- **Copy-pasting Figma clipboard payload into Grida** - Enable users to copy elements from Figma and paste them directly into Grida
- **Authoring Figma clipboard payload from Grida** - Allow users to copy elements from Grida and paste them into Figma

## Non-Goals

The following capabilities are explicitly **not** in scope:

- **Authoring `.fig` files** - We will not generate native Figma files
- **Exporting to Figma** - Direct export to Figma is not supported (though clipboard compatibility serves as a partial alternative)

## .fig Format Architecture

### File Structure

The `.fig` file format uses the Kiwi binary encoding protocol. A typical `.fig` file consists of:

1. **Header** - File prelude (`"fig-kiwi"` or `"fig-jam."`) and version number
2. **Chunk 1** - Compressed Kiwi schema definition
3. **Chunk 2+** - Compressed scene data encoded using the schema

### Key Findings

#### Self-Describing Format

**`.fig` files are self-describing** - Each file contains its own schema definition in chunk 1. This means:

- The schema can evolve between versions
- Files can be parsed without external schema files
- The parser reads the schema first, then uses it to decode subsequent chunks

#### Clipboard Payload Challenge

**Clipboard payloads do NOT include schema** - This is a critical difference from `.fig` files:

- When copying from Figma, only the encoded data is placed on the clipboard
- No schema definition is included
- **To support clipboard paste, we must maintain a pre-defined schema**

This is why we maintain the [fig.kiwi schema file](https://github.com/gridaco/grida/blob/ec18e4b716790e095c34b2f2535b58f62a8c7ca6/.ref/figma/fig.kiwi) extracted from recent `.fig` files. The clipboard implementation must use this pre-defined schema to decode clipboard payloads.

### Implementation Strategy

1. **For `.fig` file import:**

- Read schema from chunk 1
- Use embedded schema to decode remaining chunks
- More robust to schema changes

2. **For clipboard paste:**
- Use pre-defined schema from our repository
- Requires periodic updates as Figma evolves
- Schema version mismatch may cause decode failures

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -path "*/fixtures/test-fig/clipboard/README.md" -type f

Repository: gridaco/grida

Length of output: 96


🏁 Script executed:

find . -path "*/.tools/pbdump.swift" -type f

Repository: gridaco/grida

Length of output: 78


🏁 Script executed:

find . -path "*/fixtures/test-fig/clipboard/*" -type f | head -20

Repository: gridaco/grida

Length of output: 444


🏁 Script executed:

cat -n ./fixtures/test-fig/clipboard/README.md

Repository: gridaco/grida

Length of output: 3855


🏁 Script executed:

wc -l ./.tools/pbdump.swift

Repository: gridaco/grida

Length of output: 81


🏁 Script executed:

cd docs && find . -name "*.md" -type f -exec grep -l "grida2fig\|Authoring.*clipboard\|non-goal" {} \;

Repository: gridaco/grida

Length of output: 79


🏁 Script executed:

rg "grida2fig|Authoring.*clipboard.*Grida" docs/ -A 3 -B 3

Repository: gridaco/grida

Length of output: 616


🏁 Script executed:

find . -name "*.md" -type f -exec grep -l "Non-Goals\|non-goal" {} \; | head -10

Repository: gridaco/grida

Length of output: 172


🏁 Script executed:

rg "Non-Goals|non-goal" --type md -B 5 -A 5 | head -50

Repository: gridaco/grida

Length of output: 2600


🏁 Script executed:

cat -n docs/wg/feat-fig/index.md | sed -n '40,100p'

Repository: gridaco/grida

Length of output: 3041


🏁 Script executed:

rg -A 20 "## Non-Goals" docs/wg/feat-fig/index.md

Repository: gridaco/grida

Length of output: 726


Fix critical inconsistency about clipboard payload schema format

The documentation contradicts itself on whether clipboard payloads include schema:

  • index.md (line 66): States "Clipboard payloads do NOT include schema"
  • fixtures/test-fig/clipboard/README.md (line 39): States clipboard payloads include the schema in chunk 1, making them self-describing

The fixtures README is authoritative (dated 2025-12-04, references actual test fixtures). Update index.md to reflect that clipboard payloads do include schema and remove the statement about maintaining a pre-defined schema requirement. The implementation strategy section should be clarified accordingly.

Additionally, the Goals section lists "Authoring Figma clipboard payload from Grida" as in-scope, but this may conflict with actual implementation priorities—verify this reflects current scope.

Comment on lines +221 to +231
export function composeHTMLString(data: {
meta: FigmaMeta;
figma: Uint8Array;
}): string {
const metaStr = Buffer.from(JSON.stringify(data.meta) + "\n").toString(
"base64"
);
const figStr = Buffer.from(data.figma).toString("base64");

return `<meta charset="utf-8" /><span data-metadata="${HTML_MARKERS.metaStart}${metaStr}${HTML_MARKERS.metaEnd}"></span><span data-buffer="${HTML_MARKERS.figmaStart}${figStr}${HTML_MARKERS.figmaEnd}"></span><span style="white-space: pre-wrap"></span>`;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find all usages of composeHTMLString
rg -n "composeHTMLString" --type=ts --type=tsx -g '!*.test.*' -g '!__tests__' -A 2 -B 2

Repository: gridaco/grida

Length of output: 84


🏁 Script executed:

# Check the package.json to understand the module's purpose
cat packages/grida-canvas-io-figma/package.json 2>/dev/null || echo "File not found"

Repository: gridaco/grida

Length of output: 557


🏁 Script executed:

# Examine the entire index.ts file context
wc -l packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 109


🏁 Script executed:

# Check imports and context at the top of the file
head -50 packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 991


🏁 Script executed:

# Search for composeHTMLString usages with correct file type
rg -n "composeHTMLString" --type=ts -A 2 -B 2

Repository: gridaco/grida

Length of output: 782


🏁 Script executed:

# View the composeHTMLString function and surrounding context (lines 200-248)
sed -n '200,248p' packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 1335


🏁 Script executed:

# Check if there are any .tsx files and exports from index.ts
fd -e tsx packages/grida-canvas-io-figma/

Repository: gridaco/grida

Length of output: 486


🏁 Script executed:

# Find the decodeBase64String and decodeBase64 function definitions
sed -n '1,220p' packages/grida-canvas-io-figma/fig-kiwi/index.ts | grep -n -A 3 "function decodeBase64"

Repository: gridaco/grida

Length of output: 267


🏁 Script executed:

# Look for all base64-related functions in the file
rg -n "function.*[Bb]ase64|const.*[Bb]ase64" packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 225


🏁 Script executed:

# Check the full file to see helper functions
sed -n '1,220p' packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 5040


🏁 Script executed:

# Search for imports/usage of writeHTMLMessage and composeHTMLString across the codebase
rg -n "writeHTMLMessage|from.*fig-kiwi|from.*io-figma" --type=ts -g '!*.test.*' -g '!__tests__'

Repository: gridaco/grida

Length of output: 1192


🏁 Script executed:

# Check if there's any documentation or comments about platform requirements
fd -e md -e txt packages/grida-canvas-io-figma/ | xargs grep -l "node\|browser\|platform" 2>/dev/null || echo "No docs found"

Repository: gridaco/grida

Length of output: 517


🏁 Script executed:

# Look for any environment checks or conditionals in index.ts
rg -n "typeof.*Buffer|typeof.*window|typeof.*process|browser|node" packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 143


🏁 Script executed:

# Check specific usage in browser files to confirm they call composeHTMLString or writeHTMLMessage
rg -n "writeHTMLMessage|composeHTMLString" editor/grida-canvas-react/use-data-transfer.ts -A 3 -B 3

Repository: gridaco/grida

Length of output: 39


🏁 Script executed:

# Check if fromString variant exists or any encoding function
rg -n "encodeBase64|toBase64" packages/grida-canvas-io-figma/fig-kiwi/index.ts

Repository: gridaco/grida

Length of output: 39


composeHTMLString uses Node.js Buffer but is exported for browser usage.

This function uses Buffer.from().toString("base64"), which is Node.js specific. However, it's imported in browser code (editor/app/(tools)/tools/fig/*.tsx, editor/grida-canvas-react/use-data-transfer.ts, etc.). For cross-platform compatibility, use base64-js (already a dependency) like parseHTMLString does for decoding. Create an encoding function using fromByteArray and StringEncode to match the browser-compatible pattern used in parsing.

🤖 Prompt for AI Agents
In packages/grida-canvas-io-figma/fig-kiwi/index.ts around lines 221-231,
replace Node-specific Buffer usage with the browser-compatible base64-js
approach: import fromByteArray (and any existing StringEncode helper used
elsewhere), encode the meta JSON string to a Uint8Array (e.g., via StringEncode
or TextEncoder) and call fromByteArray to produce the base64 metaStr, and call
fromByteArray directly on the figma Uint8Array to produce figStr; update imports
accordingly so the function works in both browser and Node environments.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (2)

259-281: Potential unhandled rejection after toast.promise.

The await importPromise on line 270 can throw if the promise rejects, but there's no try-catch block. While toast.promise handles the rejection for UI feedback, the error will still propagate and could cause an unhandled promise rejection.

Apply this diff to handle the rejection:

   const handleImport = async () => {
     if (!parsed || plainFiles.length === 0 || !onImportFig) return;

     const importPromise = onImportFig(parsed);

     toast.promise(importPromise, {
       loading: "Importing scenes...",
       success: `Imported ${parsed.sceneCount} scene(s)`,
       error: "Failed to import",
     });

-    await importPromise;
+    try {
+      await importPromise;
+    } catch {
+      // Error already handled by toast.promise
+      return;
+    }

     // Clean up thumbnail URL

436-446: Unsafe cast: node-id query parameter can be null.

url.searchParams.get("node-id") returns null when the parameter is missing, but it's cast to string unconditionally on line 441. If the URL lacks node-id, this will cause normalize_node_id to receive the string "null", leading to incorrect form population.

Apply this diff to handle null values:

             onChange={(e) => {
               try {
                 const url = new URL(e.target.value);
                 const filekey = url.pathname.split("/")[2];
-                const nodeid = normalize_node_id(
-                  url.searchParams.get("node-id") as string
-                );
-                form.current!.filekey.value = filekey;
-                form.current!.nodeid.value = nodeid;
+                const rawNodeId = url.searchParams.get("node-id");
+                if (filekey) {
+                  form.current!.filekey.value = filekey;
+                }
+                if (rawNodeId) {
+                  form.current!.nodeid.value = normalize_node_id(rawNodeId);
+                }
               } catch (e) {}
             }}
🧹 Nitpick comments (6)
packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts (1)

38-38: Consider addressing type compatibility.

The pattern of casting to CompiledSchema (especially the double cast as unknown as CompiledSchema on lines 38 and 112) suggests the types from kiwi-schema might not align perfectly with your CompiledSchema interface. Consider updating the type definitions to eliminate the need for these casts.

Also applies to: 94-94, 112-112

editor/grida-canvas-react/use-data-transfer.ts (3)

130-148: Filename extraction may truncate names unexpectedly.

Using split(".svg")[0] (line 134) or split(".")[0] (line 145) can incorrectly truncate filenames containing multiple dots (e.g., my.icon.v2.pngmy).

Consider extracting the basename more robustly:

       if (type === "image/svg+xml") {
         const reader = new FileReader();
         reader.onload = (e) => {
           const svgContent = e.target?.result as string;
-          const name = file.name.split(".svg")[0];
+          const name = file.name.replace(/\.svg$/i, "");
           insertSVG(name, svgContent, position);
         };
         reader.readAsText(file);
         return;
       } else if (
         type === "image/png" ||
         type === "image/jpeg" ||
         type === "image/gif" ||
         type === "image/webp"
       ) {
-        const name = file.name.split(".")[0];
+        const name = file.name.replace(/\.[^.]+$/, "");
         insertImage(name, file, position);
         return;
       }

417-425: Consider logging suppressed errors for debugging.

Empty catch blocks here (and at line 494) silently swallow errors, which can make debugging difficult if paste operations fail unexpectedly.

         try {
           assert(vector_payload.type === "text");
           const net = JSON.parse(
             atob(vector_payload.text.slice("grida:vn:".length))
           );
           instance.commands.pasteVector(net);
           pasted_from_data_transfer = true;
-        } catch {}
+        } catch (e) {
+          console.warn("Failed to parse vector payload:", e);
+        }

46-84: Consider error handling for image creation failure.

If instance.createImage() fails (e.g., corrupted image data), the error will propagate unhandled. Consider wrapping in try-catch with user feedback.

   const insertImage = useCallback(
     async (
       name: string,
       file: File,
       position?: {
         clientX: number;
         clientY: number;
       }
     ) => {
+      try {
         const [x, y] = instance.camera.clientPointToCanvasPoint(
           position ? [position.clientX, position.clientY] : [0, 0]
         );

         const bytes = await file.arrayBuffer();
         const image = await instance.createImage(new Uint8Array(bytes));

         // Create rectangle node with image paint instead of image node
         const node = instance.commands.createRectangleNode();
         node.$.position = "absolute";
         node.$.name = name;
         node.$.left = x;
         node.$.top = y;
         node.$.width = image.width;
         node.$.height = image.height;
         node.$.fills = [
           {
             type: "image",
             src: image.url,
             fit: "cover",
             transform: cmath.transform.identity,
             filters: cg.def.IMAGE_FILTERS,
             blend_mode: cg.def.BLENDMODE,
             opacity: 1,
             active: true,
           } satisfies cg.ImagePaint,
         ];
+      } catch (error) {
+        console.error("Failed to insert image:", error);
+        toast.error("Failed to insert image");
+      }
     },
     [instance]
   );
packages/grida-canvas-io/index.ts (1)

194-211: Consider more robust SVG validation.

The current validation uses simple string matching for xmlns, <svg>, and </svg>. This could produce false negatives for valid SVGs where xmlns appears on nested elements or uses namespace prefixes, and false positives if these strings appear in comments or text content.

Consider parsing the XML structure or at minimum check for <svg as the opening tag:

 export function isSvgText(text: string): boolean {
   const trimmed = text.trim();
+  // Check that it starts with an SVG opening tag
+  const startsWithSvg = /^\s*<svg[\s>]/i.test(trimmed);
   return (
+    startsWithSvg &&
     trimmed.includes('xmlns="http://www.w3.org/2000/svg"') &&
-    trimmed.includes("<svg") &&
     trimmed.includes("</svg>")
   );
 }
editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (1)

392-421: Consider keeping dialog open on import failure.

The onClose() call in the finally block (line 413) closes the dialog even when the import fails. Users may prefer the dialog to remain open so they can see the error toast and retry without re-entering their credentials.

Apply this diff to keep the dialog open on error:

     const pr = fetchnode({
       filekey,
       id: nodeid,
       personalAccessToken: token,
     })
       .then((r) => {
         onImport?.(r);
+        onClose();
       })
       .catch((e) => {
         console.error(e);
         throw e;
-      })
-      .finally(() => {
-        onClose();
       });

     toast.promise(pr, {
       loading: "Importing...",
       success: "Imported",
       error: "Failed to import (see console)",
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4b18c4 and 7fb53cb.

📒 Files selected for processing (12)
  • .tools/pbdump.swift (1 hunks)
  • docs/wg/feat-fig/glossary/fig.kiwi.md (1 hunks)
  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (1 hunks)
  • editor/grida-canvas-react/use-data-transfer.ts (6 hunks)
  • fixtures/test-fig/clipboard/ellipse-circle-100x100-black.clipboard.html (1 hunks)
  • fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig (1 hunks)
  • packages/grida-canvas-io-figma/README.md (1 hunks)
  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/archive.test.ts (1 hunks)
  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/figdata.test.ts (1 hunks)
  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts (1 hunks)
  • packages/grida-canvas-io/__tests__/clipboard.fig.test.ts (1 hunks)
  • packages/grida-canvas-io/index.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/grida-canvas-io-figma/fig-kiwi/tests/archive.test.ts
  • packages/grida-canvas-io/tests/clipboard.fig.test.ts
  • .tools/pbdump.swift
  • packages/grida-canvas-io-figma/README.md
  • packages/grida-canvas-io-figma/fig-kiwi/tests/figdata.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript 5 as the main language for most apps
Use Lucide or Radix Icons for icons

Files:

  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS 4 for styling

Files:

  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Place documentation files in the ./docs directory, which is the source of truth

Files:

  • docs/wg/feat-fig/glossary/fig.kiwi.md
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React.js 19 for web UI development

Files:

  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-dev/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:36.510Z
Learning: Use `cargo run -p grida-dev -- figma` command with `--file-key`, `--api-key`, and `--scene-index` parameters to fetch and render Figma designs
📚 Learning: 2025-12-01T00:22:19.083Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas-wasm/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:19.083Z
Learning: Applies to crates/grida-canvas-wasm/**/main.rs : Update `grida-canvas-wasm.d.ts` TypeScript definitions file when new APIs are introduced via `main.rs`

Applied to files:

  • packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts
  • editor/grida-canvas-react/use-data-transfer.ts
  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use React hooks for state management (imageSrc, shape, grid, maxRadius, gamma, jitter, opacity, color, customShapeImage, imageDataRef, sizeRef)

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use Canvas 2D API with path commands for rendering geometric shapes (circle, square, triangle, etc.)

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Cache ImageData and dimensions in refs (imageDataRef, sizeRef) for efficient exports

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : For SVG export, convert circles to <circle> elements, rectangles to <rect> elements, and polygons to <polygon> elements with calculated points

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Custom images used as halftone shapes should be loaded as HTMLImageElement for efficient canvas rendering and preserve original image colors in as-is mode

Applied to files:

  • editor/grida-canvas-react/use-data-transfer.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new shape types, update the Shape type union, add cases in drawShape() function, add cases in shapeToSVG() function, and add SelectItem in UI

Applied to files:

  • packages/grida-canvas-io/index.ts
  • editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx
🪛 Gitleaks (8.30.0)
packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts

[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 56-56: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 61-61: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 66-66: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 168-168: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

fixtures/test-fig/clipboard/ellipse-circle-100x100-black.clipboard.html

[high] 3-3: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: cargo test
  • GitHub Check: test
🔇 Additional comments (11)
docs/wg/feat-fig/glossary/fig.kiwi.md (1)

1-276: Excellent documentation of the Kiwi schema with all formatting requirements met.

The glossary is comprehensive, well-structured, and aligns perfectly with the PR's Figma import objectives. The past Markdown formatting issue (missing language specifier at line 222) has been properly addressed. All code blocks now have correct language specifiers (typescript, text), and the technical content—covering node type detection, GROUP vs FRAME distinction, and component set structure—is documented with clear implementation logic and verification references. The tables and reference sections provide solid support for developers working with the fig-kiwi parser and translation layer.

fixtures/test-fig/clipboard/ellipse-circle-100x100-black.clipboard.html (1)

1-5: LGTM! Test fixture data is correctly structured.

This HTML fixture contains valid Figma clipboard test data. The Gitleaks warning about a "Generic API Key" is a false positive—the fileKey value is a test fixture identifier referenced in the test assertions, not a real API key.

packages/grida-canvas-io-figma/fig-kiwi/__tests__/fightml.test.ts (1)

25-25: Gitleaks warnings are false positives.

The fileKey values flagged by Gitleaks are test fixture identifiers, not real API keys or secrets. These values are used to validate that the clipboard HTML parser correctly extracts metadata from Figma clipboard payloads.

Also applies to: 51-51, 56-56, 61-61, 66-66, 168-168

editor/grida-canvas-react/use-data-transfer.ts (4)

163-271: LGTM - Robust Figma clipboard parsing implementation.

The tree reconstruction from flat nodes is well-structured, with proper GUID mapping and parent-child relationship building. The error handling wraps the entire operation and provides meaningful error messages.


317-341: LGTM!

Clean implementation of text node insertion with proper default styling.


343-368: LGTM - Good UX pattern with toast feedback.

The promise wrapper with toast.promise provides clear feedback during Figma clipboard import. The small delay to allow toast rendering is a thoughtful touch.


553-558: Good UX - Clear guidance for .fig file handling.

Helpful toast message guiding users to the correct import flow for .fig files instead of silently failing.

packages/grida-canvas-io/index.ts (4)

254-255: LGTM! WebP support added correctly.

The WebP image type is properly integrated following the existing pattern for other image formats.

Also applies to: 269-270


272-285: LGTM! Type extensions are well-structured.

The new svg-text and canbe-figma-clipboard variants properly extend the discriminated union, maintaining type safety.


347-350: LGTM! SVG detection properly prioritized.

Checking for SVG content before returning plain text ensures SVG clipboard data is correctly categorized.


355-366: The error handling change is intentional and properly documented.

The function's docstring explicitly states @throws If the item cannot be decoded or is of an unknown/unsupported type, and the only documented consumer (editor/grida-canvas-react/use-data-transfer.ts) properly handles the rejection with a try/catch block that gracefully returns null for unrecognized HTML. This is not a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant