-
Notifications
You must be signed in to change notification settings - Fork 110
Copy-Paste from Figma #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas that may need extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 inload_figma_sceneis correct; consider centralizing logicThe
resolved_api_keylogic correctly prefers--api-keyoverX_FIGMA_TOKENand 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 likefn 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 theX_FIGMA_TOKENoption. 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 onlyThe
resolved_api_keyinload_scene_images_from_sourcemirrorsload_figma_scenesemantics, and the error message again clearly mentions both--api-keyandX_FIGMA_TOKEN. The branching still correctly skips API usage whenarchive_dir,images_dir,file, or--no-imageare 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
loadingstate fromuseFilePickeronly indicates whether the file picker is open. Duringio.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
importingto 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.
DocumentHierarchyduplicates theSidebarGroupstructure fromScenesGroup(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
ScenesGroupandDocumentHierarchy.editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (1)
77-83: Type parameter too permissive.The
setfunction acceptsanybutsessionStorage.setItemexpects 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: UseDropdownMenuItemfor action items instead ofDropdownMenuCheckboxItem.Zoom actions (zoom in, zoom out, zoom to 100%, zoom to fit, zoom to selection) are one-time actions, not toggleable states. Using
DropdownMenuCheckboxItemwithchecked={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 nitsThe 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 ofsplit(".svg")[0]/split(".")[0]so filenames with multiple dots or uppercase extensions don’t get truncated unexpectedly.- In
insertFromFile, unsupportedtypevalues 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 bundlingThe 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.idmatching the normalized GUID used byiofigma.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-figmasurface instead of statically importingiofigmaand dynamically importing onlyreadHTMLMessage, 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 consumptionThe 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 thoughinsertFromFilesupports 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_transferis set butevent.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 finalif (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 contentpackages/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
commandsBlobIdis 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 acceptableExample 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 forComponentTypeand consider narrowingtype
nodeTypeIconMapis typed withReact.ComponentType, butReactis never imported. In a TS + automatic JSX setup this can fail type-checking because theReactnamespace isn’t guaranteed to exist.You can avoid this by importing the type and using it directly, and optionally narrowing
typeto 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
Reactnamespace; if not, the explicitimport 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) thexmlnsrequirement for SVG text detectionThe described detection logic for SVG text:
- checks for
xmlns="http://www.w3.org/2000/svg",- requires an opening
<svgand closing</svg>.This is a safe heuristic, but some valid SVG snippets (especially inline ones) omit the explicit
xmlnsattribute, 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 withoutxmlns, while still guarding against false positives..ref/figma/fig2kiwi.ts (1)
63-112: Code duplication with the main source.The
FigmaArchiveParserclass is duplicated frompackages/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-03packages/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:
- Moving it to a separate diagnostic script outside the test suite
- Removing it if the investigation is complete
- 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 assertionas anyused for schema.The
as anycast 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 importingguidfrom shared module to avoid duplication.This
formatGUIDfunction duplicatesiofigma.kiwi.guidfrompackages/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); }
| 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 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -path "*/fixtures/test-fig/clipboard/README.md" -type fRepository: gridaco/grida
Length of output: 96
🏁 Script executed:
find . -path "*/.tools/pbdump.swift" -type fRepository: gridaco/grida
Length of output: 78
🏁 Script executed:
find . -path "*/fixtures/test-fig/clipboard/*" -type f | head -20Repository: gridaco/grida
Length of output: 444
🏁 Script executed:
cat -n ./fixtures/test-fig/clipboard/README.mdRepository: gridaco/grida
Length of output: 3855
🏁 Script executed:
wc -l ./.tools/pbdump.swiftRepository: 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 3Repository: gridaco/grida
Length of output: 616
🏁 Script executed:
find . -name "*.md" -type f -exec grep -l "Non-Goals\|non-goal" {} \; | head -10Repository: gridaco/grida
Length of output: 172
🏁 Script executed:
rg "Non-Goals|non-goal" --type md -B 5 -A 5 | head -50Repository: 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.mdRepository: 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.
fixtures/test-fig/community/784448220678228461-figma-auto-layout-playground.fig
Show resolved
Hide resolved
| 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>`; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find all usages of composeHTMLString
rg -n "composeHTMLString" --type=ts --type=tsx -g '!*.test.*' -g '!__tests__' -A 2 -B 2Repository: 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.tsRepository: 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.tsRepository: gridaco/grida
Length of output: 991
🏁 Script executed:
# Search for composeHTMLString usages with correct file type
rg -n "composeHTMLString" --type=ts -A 2 -B 2Repository: 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.tsRepository: 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.tsRepository: 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.tsRepository: 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.tsRepository: 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 3Repository: 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.tsRepository: 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.
… files in data transfer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (2)
editor/grida-canvas-react-starter-kit/starterkit-import/from-figma.tsx (2)
259-281: Potential unhandled rejection aftertoast.promise.The
await importPromiseon line 270 can throw if the promise rejects, but there's no try-catch block. Whiletoast.promisehandles 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-idquery parameter can be null.
url.searchParams.get("node-id")returnsnullwhen the parameter is missing, but it's cast tostringunconditionally on line 441. If the URL lacksnode-id, this will causenormalize_node_idto 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 castas unknown as CompiledSchemaon lines 38 and 112) suggests the types fromkiwi-schemamight not align perfectly with yourCompiledSchemainterface. 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) orsplit(".")[0](line 145) can incorrectly truncate filenames containing multiple dots (e.g.,my.icon.v2.png→my).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 wherexmlnsappears 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
<svgas 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 thefinallyblock (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
📒 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.tseditor/grida-canvas-react/use-data-transfer.tspackages/grida-canvas-io/index.tseditor/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.tseditor/grida-canvas-react/use-data-transfer.tspackages/grida-canvas-io/index.tseditor/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.tseditor/grida-canvas-react/use-data-transfer.tspackages/grida-canvas-io/index.tseditor/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.tseditor/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
fileKeyvalue 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.promiseprovides 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-textandcanbe-figma-clipboardvariants 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.
Goal:
Non goal:
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.
@grida/io-figmapackage (Kiwi parser, blob parsers, schema) to parse.figand clipboard payloads and convert to REST/Grida nodes.isFigmaClipboard,isSvgText, extendedfiletype(incl. WebP), and richer decode results (svg-text,canbe-figma-clipboard).Written by Cursor Bugbot for commit 0c17a42. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.