@agent-native/core 0.119.0 → 0.119.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
|
@@ -568,7 +568,7 @@ const enUS = {
|
|
|
568
568
|
figmaTokenDocs: "Get a token",
|
|
569
569
|
figmaTokenPlaceholder: "Paste your Figma personal access token",
|
|
570
570
|
figmaTokenDescription:
|
|
571
|
-
"
|
|
571
|
+
'Needs "File content" and "Current user" scopes. Saved securely — never added to chat.',
|
|
572
572
|
importFigmaUrl: "Import from Figma",
|
|
573
573
|
saveKeyAndImport: "Save key and import",
|
|
574
574
|
figmaUrlSuccess: "Imported from Figma.",
|
|
@@ -586,12 +586,53 @@ const enUS = {
|
|
|
586
586
|
"Couldn't match this to specific Figma nodes. Paste a frame link instead for an exact import.",
|
|
587
587
|
figmaPasteRestLabel: "Imported via Figma API",
|
|
588
588
|
figmaPasteHtmlLabel: "Imported from clipboard preview",
|
|
589
|
+
figmaPasteLocalKiwiLabel:
|
|
590
|
+
"Imported without token — geometry and text only",
|
|
591
|
+
figmaPasteImagesNeedToken:
|
|
592
|
+
"{{count}} image{{plural}} need Figma access to load.",
|
|
593
|
+
figmaHydrationDialogTitle: "Fill in the missing images",
|
|
594
|
+
figmaHydrationDialogDescription:
|
|
595
|
+
"{{count}} image{{plural}} in the imported screen{{screensPlural}} couldn't come through the paste — Figma's clipboard leaves image data out. Fill from the original .fig, or fetch the exact images from the copied frame.",
|
|
596
|
+
figmaHydrationConnectAndLoad: "Connect & fetch images",
|
|
597
|
+
figmaHydrationSuccess: "Images loaded successfully",
|
|
598
|
+
figmaHydrationSuccessDescription:
|
|
599
|
+
"{{count}} image{{plural}} filled in from Figma.",
|
|
600
|
+
figmaHydrationRecommended: "Recommended",
|
|
601
|
+
figmaHydrationFigTitle: "Have the original .fig file?",
|
|
602
|
+
figmaHydrationFigOption:
|
|
603
|
+
"Fastest — pulls every image straight from the file. No token, no rate limits.",
|
|
604
|
+
figmaHydrationChooseFig: "Fill images from .fig",
|
|
605
|
+
figmaHydrationOrToken: "Or fetch from Figma",
|
|
606
|
+
figmaHydrationTokenDescription:
|
|
607
|
+
'Fetches the exact images from the copied frame\'s Figma file. Needs "File content" and "Current user" scopes; saved securely, never shown in chat.',
|
|
608
|
+
figmaHydrationRateLimit:
|
|
609
|
+
"Figma rate-limits its API by seat — Viewer/Collab seats get only a few requests, Dev/Full seats more. If it's cooling down, use the .fig above.",
|
|
610
|
+
figmaHydrationFigSuccessDescription:
|
|
611
|
+
"{{count}} image{{plural}} filled in from the .fig file.",
|
|
612
|
+
figmaHydrationInvalidFig: "Choose a .fig file exported from Figma.",
|
|
613
|
+
figmaHydrationFigError: "Couldn't read images from that .fig file.",
|
|
589
614
|
figUploadTitle: "Upload .fig",
|
|
590
615
|
figUploadDescription:
|
|
591
|
-
"
|
|
616
|
+
"Local import using Figma's .fig format — no API quota. Embedded images are included. Format may change between Figma versions. Maximum 50 MB.",
|
|
592
617
|
chooseFigFile: "Choose .fig file",
|
|
593
618
|
figUploadUploading: "Uploading {{progress}}%",
|
|
594
619
|
figUploadProcessing: "Converting…",
|
|
620
|
+
figUploadDescriptionShort:
|
|
621
|
+
"Local import — no Figma API quota used. Embedded images included.",
|
|
622
|
+
figmaPasteBodyUnlimited:
|
|
623
|
+
"Works without a Figma token — geometry, layout, and text import immediately.",
|
|
624
|
+
figmaPasteBodyImages:
|
|
625
|
+
"Image fills may be missing without a token. Upload the .fig file to include embedded images.",
|
|
626
|
+
rateLimitTitle: "Figma paused this import",
|
|
627
|
+
rateLimitLowSeat:
|
|
628
|
+
"Your seat type (Viewer/Collab) has a limited Figma API quota for file imports — up to 6 requests per month per the official Figma docs.",
|
|
629
|
+
rateLimitGeneric:
|
|
630
|
+
"Figma rate-limited this import. The quota resets automatically.",
|
|
631
|
+
rateLimitRetryIn: "Try again in {{time}}.",
|
|
632
|
+
rateLimitAlternatives: "No-quota alternatives:",
|
|
633
|
+
rateLimitUsePaste: "Paste from Figma — unlimited",
|
|
634
|
+
rateLimitUseFig: "Upload .fig — unlimited",
|
|
635
|
+
rateLimitUpgrade: "View Figma plan options →",
|
|
595
636
|
htmlTitle: "Import HTML",
|
|
596
637
|
htmlDescription:
|
|
597
638
|
"Paste or upload standalone HTML. Design stores it as a new screen without injecting it into this editor UI.",
|
|
@@ -11781,6 +11822,16 @@ const designImportOverrides = {
|
|
|
11781
11822
|
"無法比對到特定的 Figma 節點。請改貼上畫框連結以進行精確匯入。",
|
|
11782
11823
|
figmaPasteRestLabel: "透過 Figma API 匯入",
|
|
11783
11824
|
figmaPasteHtmlLabel: "從剪貼簿預覽匯入",
|
|
11825
|
+
figmaPasteLocalKiwiLabel: "已在未登入狀態下匯入 — 僅含幾何與文字",
|
|
11826
|
+
figmaPasteImagesNeedToken:
|
|
11827
|
+
"{{count}} 個圖片{{plural}}需要 Figma 存取權才能載入。",
|
|
11828
|
+
figmaHydrationDialogTitle: "連結 Figma 以載入圖片",
|
|
11829
|
+
figmaHydrationDialogDescription:
|
|
11830
|
+
"輸入您的 Figma 存取權杖,以載入已匯入螢幕{{screensPlural}}中 {{count}} 個缺少的圖片{{plural}}。",
|
|
11831
|
+
figmaHydrationConnectAndLoad: "連結並載入圖片",
|
|
11832
|
+
figmaHydrationSuccess: "圖片載入成功",
|
|
11833
|
+
figmaHydrationSuccessDescription:
|
|
11834
|
+
"已從 Figma 填入 {{count}} 個圖片{{plural}}。",
|
|
11784
11835
|
figUploadTitle: "上傳 .fig",
|
|
11785
11836
|
figUploadDescription:
|
|
11786
11837
|
"實驗性功能:Figma 的 .fig 格式為專有格式且可能變更。支援的圖層會轉為可編輯螢幕,部分功能可能不同。上限為 50 MB。",
|
|
@@ -11834,6 +11885,16 @@ const designImportOverrides = {
|
|
|
11834
11885
|
"无法匹配到特定的 Figma 节点。请改为粘贴画框链接以进行精确导入。",
|
|
11835
11886
|
figmaPasteRestLabel: "通过 Figma API 导入",
|
|
11836
11887
|
figmaPasteHtmlLabel: "从剪贴板预览导入",
|
|
11888
|
+
figmaPasteLocalKiwiLabel: "已在未登录状态下导入 — 仅包含几何与文字",
|
|
11889
|
+
figmaPasteImagesNeedToken:
|
|
11890
|
+
"{{count}} 张图片{{plural}}需要 Figma 访问权限才能加载。",
|
|
11891
|
+
figmaHydrationDialogTitle: "连接 Figma 以加载图片",
|
|
11892
|
+
figmaHydrationDialogDescription:
|
|
11893
|
+
"输入您的 Figma 访问令牌,以加载已导入屏幕{{screensPlural}}中缺少的 {{count}} 张图片{{plural}}。",
|
|
11894
|
+
figmaHydrationConnectAndLoad: "连接并加载图片",
|
|
11895
|
+
figmaHydrationSuccess: "图片加载成功",
|
|
11896
|
+
figmaHydrationSuccessDescription:
|
|
11897
|
+
"已从 Figma 填入 {{count}} 张图片{{plural}}。",
|
|
11837
11898
|
figUploadTitle: "上传 .fig",
|
|
11838
11899
|
figUploadDescription:
|
|
11839
11900
|
"实验性功能:Figma 的 .fig 格式为专有格式且可能变化。支持的图层会转换为可编辑屏幕,部分功能可能有所不同。最大 50 MB。",
|
|
@@ -11889,6 +11950,17 @@ const designImportOverrides = {
|
|
|
11889
11950
|
"No se pudo hacer coincidir con nodos específicos de Figma. Pega un enlace de marco para una importación exacta.",
|
|
11890
11951
|
figmaPasteRestLabel: "Importado mediante la API de Figma",
|
|
11891
11952
|
figmaPasteHtmlLabel: "Importado desde la vista previa del portapapeles",
|
|
11953
|
+
figmaPasteLocalKiwiLabel:
|
|
11954
|
+
"Importado sin token — solo geometría y texto",
|
|
11955
|
+
figmaPasteImagesNeedToken:
|
|
11956
|
+
"{{count}} imagen{{plural}} necesita{{plural}} acceso a Figma para cargarse.",
|
|
11957
|
+
figmaHydrationDialogTitle: "Conectar Figma para cargar imágenes",
|
|
11958
|
+
figmaHydrationDialogDescription:
|
|
11959
|
+
"Introduce tu token de acceso de Figma para cargar {{count}} imagen{{plural}} faltante{{plural}} en la pantalla{{screensPlural}} importada{{screensPlural}}.",
|
|
11960
|
+
figmaHydrationConnectAndLoad: "Conectar y cargar imágenes",
|
|
11961
|
+
figmaHydrationSuccess: "Imágenes cargadas correctamente",
|
|
11962
|
+
figmaHydrationSuccessDescription:
|
|
11963
|
+
"{{count}} imagen{{plural}} rellenada{{plural}} desde Figma.",
|
|
11892
11964
|
figUploadTitle: "Subir .fig",
|
|
11893
11965
|
figUploadDescription:
|
|
11894
11966
|
"Experimental: el formato .fig de Figma es propietario y puede cambiar. Las capas compatibles se convierten en pantallas editables; algunas funciones pueden variar. Máximo 50 MB.",
|
|
@@ -11946,6 +12018,17 @@ const designImportOverrides = {
|
|
|
11946
12018
|
"Impossible de faire correspondre à des nœuds Figma précis. Collez un lien de cadre pour un import exact.",
|
|
11947
12019
|
figmaPasteRestLabel: "Importé via l'API Figma",
|
|
11948
12020
|
figmaPasteHtmlLabel: "Importé depuis l'aperçu du presse-papiers",
|
|
12021
|
+
figmaPasteLocalKiwiLabel:
|
|
12022
|
+
"Importé sans token — géométrie et texte uniquement",
|
|
12023
|
+
figmaPasteImagesNeedToken:
|
|
12024
|
+
"{{count}} image{{plural}} nécessite{{plural}} un accès Figma pour être chargée{{plural}}.",
|
|
12025
|
+
figmaHydrationDialogTitle: "Connecter Figma pour charger les images",
|
|
12026
|
+
figmaHydrationDialogDescription:
|
|
12027
|
+
"Saisissez votre token d'accès Figma pour charger {{count}} image{{plural}} manquante{{plural}} dans l'écran{{screensPlural}} importé{{screensPlural}}.",
|
|
12028
|
+
figmaHydrationConnectAndLoad: "Connecter et charger les images",
|
|
12029
|
+
figmaHydrationSuccess: "Images chargées avec succès",
|
|
12030
|
+
figmaHydrationSuccessDescription:
|
|
12031
|
+
"{{count}} image{{plural}} remplie{{plural}} depuis Figma.",
|
|
11949
12032
|
figUploadTitle: "Téléverser .fig",
|
|
11950
12033
|
figUploadDescription:
|
|
11951
12034
|
"Expérimental : le format .fig de Figma est propriétaire et peut évoluer. Les calques pris en charge deviennent des écrans modifiables ; certaines fonctions peuvent différer. Maximum 50 Mo.",
|
|
@@ -12003,6 +12086,17 @@ const designImportOverrides = {
|
|
|
12003
12086
|
"Konnte nicht mit bestimmten Figma-Nodes abgeglichen werden. Füge stattdessen einen Frame-Link für einen exakten Import ein.",
|
|
12004
12087
|
figmaPasteRestLabel: "Über die Figma-API importiert",
|
|
12005
12088
|
figmaPasteHtmlLabel: "Aus der Zwischenablage-Vorschau importiert",
|
|
12089
|
+
figmaPasteLocalKiwiLabel:
|
|
12090
|
+
"Ohne Token importiert — nur Geometrie und Text",
|
|
12091
|
+
figmaPasteImagesNeedToken:
|
|
12092
|
+
"{{count}} Bild{{plural}} benötigt{{plural}} Figma-Zugriff zum Laden.",
|
|
12093
|
+
figmaHydrationDialogTitle: "Figma verbinden, um Bilder zu laden",
|
|
12094
|
+
figmaHydrationDialogDescription:
|
|
12095
|
+
"Gib deinen Figma-Zugriffstoken ein, um {{count}} fehlendes{{plural}} Bild{{plural}} in den importierten Screen{{screensPlural}} zu laden.",
|
|
12096
|
+
figmaHydrationConnectAndLoad: "Verbinden und Bilder laden",
|
|
12097
|
+
figmaHydrationSuccess: "Bilder erfolgreich geladen",
|
|
12098
|
+
figmaHydrationSuccessDescription:
|
|
12099
|
+
"{{count}} Bild{{plural}} aus Figma ausgefüllt.",
|
|
12006
12100
|
figUploadTitle: ".fig hochladen",
|
|
12007
12101
|
figUploadDescription:
|
|
12008
12102
|
"Experimentell: Das .fig-Format von Figma ist proprietär und kann sich ändern. Unterstützte Ebenen werden zu bearbeitbaren Screens; einige Funktionen können abweichen. Maximal 50 MB.",
|
|
@@ -12059,6 +12153,17 @@ const designImportOverrides = {
|
|
|
12059
12153
|
"特定の Figma ノードと一致しませんでした。正確にインポートするにはフレームのリンクを貼り付けてください。",
|
|
12060
12154
|
figmaPasteRestLabel: "Figma API 経由でインポート",
|
|
12061
12155
|
figmaPasteHtmlLabel: "クリップボードプレビューからインポート",
|
|
12156
|
+
figmaPasteLocalKiwiLabel:
|
|
12157
|
+
"トークンなしでインポート — ジオメトリとテキストのみ",
|
|
12158
|
+
figmaPasteImagesNeedToken:
|
|
12159
|
+
"{{count}} 枚の画像{{plural}}を読み込むには Figma へのアクセスが必要です。",
|
|
12160
|
+
figmaHydrationDialogTitle: "Figma を接続して画像を読み込む",
|
|
12161
|
+
figmaHydrationDialogDescription:
|
|
12162
|
+
"Figma アクセストークンを入力して、インポートされた画面{{screensPlural}}の不足している {{count}} 枚の画像{{plural}}を読み込んでください。",
|
|
12163
|
+
figmaHydrationConnectAndLoad: "接続して画像を読み込む",
|
|
12164
|
+
figmaHydrationSuccess: "画像の読み込みが完了しました",
|
|
12165
|
+
figmaHydrationSuccessDescription:
|
|
12166
|
+
"Figma から {{count}} 枚の画像{{plural}}が入力されました。",
|
|
12062
12167
|
figUploadTitle: ".fig をアップロード",
|
|
12063
12168
|
figUploadDescription:
|
|
12064
12169
|
"試験的機能:Figma の .fig 形式は独自仕様で、変更される可能性があります。対応レイヤーは編集可能な画面になりますが、一部の機能は異なる場合があります。最大 50 MB。",
|
|
@@ -12116,6 +12221,16 @@ const designImportOverrides = {
|
|
|
12116
12221
|
"특정 Figma 노드와 일치시킬 수 없습니다. 정확한 가져오기를 위해 프레임 링크를 붙여넣으세요.",
|
|
12117
12222
|
figmaPasteRestLabel: "Figma API로 가져옴",
|
|
12118
12223
|
figmaPasteHtmlLabel: "클립보드 미리보기에서 가져옴",
|
|
12224
|
+
figmaPasteLocalKiwiLabel: "토큰 없이 가져옴 — 기하학적 구조와 텍스트만",
|
|
12225
|
+
figmaPasteImagesNeedToken:
|
|
12226
|
+
"{{count}}개의 이미지{{plural}}를 로드하려면 Figma 접근이 필요합니다.",
|
|
12227
|
+
figmaHydrationDialogTitle: "Figma를 연결하여 이미지 로드",
|
|
12228
|
+
figmaHydrationDialogDescription:
|
|
12229
|
+
"Figma 액세스 토큰을 입력하여 가져온 화면{{screensPlural}}의 누락된 이미지 {{count}}개{{plural}}를 로드하세요.",
|
|
12230
|
+
figmaHydrationConnectAndLoad: "연결하고 이미지 로드",
|
|
12231
|
+
figmaHydrationSuccess: "이미지가 성공적으로 로드되었습니다",
|
|
12232
|
+
figmaHydrationSuccessDescription:
|
|
12233
|
+
"Figma에서 {{count}}개의 이미지{{plural}}가 채워졌습니다.",
|
|
12119
12234
|
figUploadTitle: ".fig 업로드",
|
|
12120
12235
|
figUploadDescription:
|
|
12121
12236
|
"실험적 기능: Figma의 .fig 형식은 독점 형식이며 변경될 수 있습니다. 지원되는 레이어는 편집 가능한 화면으로 변환되지만 일부 기능은 다를 수 있습니다. 최대 50MB.",
|
|
@@ -12174,6 +12289,17 @@ const designImportOverrides = {
|
|
|
12174
12289
|
figmaPasteRestLabel: "Importado via API do Figma",
|
|
12175
12290
|
figmaPasteHtmlLabel:
|
|
12176
12291
|
"Importado da pré-visualização da área de transferência",
|
|
12292
|
+
figmaPasteLocalKiwiLabel:
|
|
12293
|
+
"Importado sem token — apenas geometria e texto",
|
|
12294
|
+
figmaPasteImagesNeedToken:
|
|
12295
|
+
"{{count}} imagem{{plural}} precisa{{plural}} de acesso ao Figma para carregar.",
|
|
12296
|
+
figmaHydrationDialogTitle: "Conectar o Figma para carregar imagens",
|
|
12297
|
+
figmaHydrationDialogDescription:
|
|
12298
|
+
"Insira seu token de acesso do Figma para carregar {{count}} imagem{{plural}} ausente{{plural}} na tela{{screensPlural}} importada{{screensPlural}}.",
|
|
12299
|
+
figmaHydrationConnectAndLoad: "Conectar e carregar imagens",
|
|
12300
|
+
figmaHydrationSuccess: "Imagens carregadas com sucesso",
|
|
12301
|
+
figmaHydrationSuccessDescription:
|
|
12302
|
+
"{{count}} imagem{{plural}} preenchida{{plural}} do Figma.",
|
|
12177
12303
|
figUploadTitle: "Enviar .fig",
|
|
12178
12304
|
figUploadDescription:
|
|
12179
12305
|
"Experimental: o formato .fig do Figma é proprietário e pode mudar. As camadas compatíveis viram telas editáveis; alguns recursos podem ser diferentes. Máximo de 50 MB.",
|
|
@@ -12231,6 +12357,17 @@ const designImportOverrides = {
|
|
|
12231
12357
|
"विशिष्ट Figma नोड्स से मेल नहीं खाया। सटीक आयात के लिए इसके बजाय एक frame लिंक paste करें।",
|
|
12232
12358
|
figmaPasteRestLabel: "Figma API के ज़रिए आयात किया गया",
|
|
12233
12359
|
figmaPasteHtmlLabel: "क्लिपबोर्ड पूर्वावलोकन से आयात किया गया",
|
|
12360
|
+
figmaPasteLocalKiwiLabel:
|
|
12361
|
+
"बिना token के आयात किया गया — केवल geometry और text",
|
|
12362
|
+
figmaPasteImagesNeedToken:
|
|
12363
|
+
"{{count}} छवि{{plural}} को लोड करने के लिए Figma की पहुँच चाहिए।",
|
|
12364
|
+
figmaHydrationDialogTitle: "छवियाँ लोड करने के लिए Figma जोड़ें",
|
|
12365
|
+
figmaHydrationDialogDescription:
|
|
12366
|
+
"आयातित screen{{screensPlural}} में {{count}} गायब छवि{{plural}} लोड करने के लिए अपना Figma access token दर्ज करें।",
|
|
12367
|
+
figmaHydrationConnectAndLoad: "जोड़ें और छवियाँ लोड करें",
|
|
12368
|
+
figmaHydrationSuccess: "छवियाँ सफलतापूर्वक लोड हुईं",
|
|
12369
|
+
figmaHydrationSuccessDescription:
|
|
12370
|
+
"Figma से {{count}} छवि{{plural}} भरी गई{{plural}}।",
|
|
12234
12371
|
figUploadTitle: ".fig अपलोड करें",
|
|
12235
12372
|
figUploadDescription:
|
|
12236
12373
|
"प्रायोगिक: Figma का .fig format proprietary है और बदल सकता है। समर्थित layers editable screens बनती हैं; कुछ features अलग हो सकते हैं। अधिकतम 50 MB।",
|
|
@@ -12287,6 +12424,17 @@ const designImportOverrides = {
|
|
|
12287
12424
|
"تعذّرت المطابقة مع عُقد Figma محددة. الصق رابط الإطار بدلاً من ذلك للحصول على استيراد دقيق.",
|
|
12288
12425
|
figmaPasteRestLabel: "تم الاستيراد عبر واجهة Figma البرمجية",
|
|
12289
12426
|
figmaPasteHtmlLabel: "تم الاستيراد من معاينة الحافظة",
|
|
12427
|
+
figmaPasteLocalKiwiLabel:
|
|
12428
|
+
"تم الاستيراد بدون رمز — الأشكال الهندسية والنص فقط",
|
|
12429
|
+
figmaPasteImagesNeedToken:
|
|
12430
|
+
"{{count}} صورة{{plural}} تحتاج إلى الوصول إلى Figma للتحميل.",
|
|
12431
|
+
figmaHydrationDialogTitle: "ربط Figma لتحميل الصور",
|
|
12432
|
+
figmaHydrationDialogDescription:
|
|
12433
|
+
"أدخل رمز الوصول إلى Figma لتحميل {{count}} صورة{{plural}} مفقودة في الشاشة{{screensPlural}} المستوردة.",
|
|
12434
|
+
figmaHydrationConnectAndLoad: "ربط وتحميل الصور",
|
|
12435
|
+
figmaHydrationSuccess: "تم تحميل الصور بنجاح",
|
|
12436
|
+
figmaHydrationSuccessDescription:
|
|
12437
|
+
"تم ملء {{count}} صورة{{plural}} من Figma.",
|
|
12290
12438
|
figUploadTitle: "رفع .fig",
|
|
12291
12439
|
figUploadDescription:
|
|
12292
12440
|
"ميزة تجريبية: تنسيق .fig في Figma مملوك وقد يتغير. تتحول الطبقات المدعومة إلى شاشات قابلة للتحرير، وقد تختلف بعض الميزات. الحد الأقصى 50 ميغابايت.",
|
|
@@ -85,3 +85,88 @@ export function uploadDesignFile({
|
|
|
85
85
|
xhr.send(form);
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
export interface FigHydrationResult extends ImportResult {
|
|
90
|
+
importKind?: "fig-hydrate";
|
|
91
|
+
results?: Array<{
|
|
92
|
+
fileId: string;
|
|
93
|
+
resolved: number;
|
|
94
|
+
missing: number;
|
|
95
|
+
skipped: number;
|
|
96
|
+
}>;
|
|
97
|
+
totalResolved?: number;
|
|
98
|
+
totalMissing?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface HydrateImagesFromFigOptions {
|
|
102
|
+
designId: string;
|
|
103
|
+
file: File;
|
|
104
|
+
/** design_files ids from a no-token clipboard paste to fill images for. */
|
|
105
|
+
fileIds: string[];
|
|
106
|
+
fallbackErrorMessage: string;
|
|
107
|
+
onProgress?: (progress: DesignFileUploadProgress) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Token-free image hydration: uploads the original `.fig` and fills the
|
|
112
|
+
* `about:blank` placeholders left by a no-token clipboard paste with the
|
|
113
|
+
* `.fig`'s embedded image bytes. Same authenticated multipart route as
|
|
114
|
+
* `uploadDesignFile`, plus a `hydrateFileIds` field that switches the server
|
|
115
|
+
* into hydrate mode instead of creating new screens.
|
|
116
|
+
*/
|
|
117
|
+
export function hydrateImagesFromFig({
|
|
118
|
+
designId,
|
|
119
|
+
file,
|
|
120
|
+
fileIds,
|
|
121
|
+
fallbackErrorMessage,
|
|
122
|
+
onProgress,
|
|
123
|
+
}: HydrateImagesFromFigOptions): Promise<FigHydrationResult> {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const xhr = new XMLHttpRequest();
|
|
126
|
+
const form = new FormData();
|
|
127
|
+
form.append("designId", designId);
|
|
128
|
+
form.append("hydrateFileIds", fileIds.join(","));
|
|
129
|
+
form.append("file", file, file.name);
|
|
130
|
+
|
|
131
|
+
xhr.open(
|
|
132
|
+
"POST",
|
|
133
|
+
`/api/import-design-file?designId=${encodeURIComponent(designId)}`,
|
|
134
|
+
true,
|
|
135
|
+
);
|
|
136
|
+
xhr.withCredentials = true;
|
|
137
|
+
xhr.timeout = 5 * 60 * 1000;
|
|
138
|
+
|
|
139
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
140
|
+
const total = event.lengthComputable ? event.total : 0;
|
|
141
|
+
onProgress?.({
|
|
142
|
+
loaded: event.loaded,
|
|
143
|
+
total,
|
|
144
|
+
percent:
|
|
145
|
+
total > 0
|
|
146
|
+
? Math.min(100, Math.round((event.loaded / total) * 100))
|
|
147
|
+
: null,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
xhr.addEventListener("load", () => {
|
|
152
|
+
void parseUploadResponse<FigHydrationResult>(
|
|
153
|
+
{
|
|
154
|
+
ok: xhr.status >= 200 && xhr.status < 300,
|
|
155
|
+
status: xhr.status,
|
|
156
|
+
text: async () => xhr.responseText,
|
|
157
|
+
},
|
|
158
|
+
fallbackErrorMessage,
|
|
159
|
+
).then(resolve, reject);
|
|
160
|
+
});
|
|
161
|
+
xhr.addEventListener("error", () =>
|
|
162
|
+
reject(new Error(fallbackErrorMessage)),
|
|
163
|
+
);
|
|
164
|
+
xhr.addEventListener("timeout", () =>
|
|
165
|
+
reject(new Error(fallbackErrorMessage)),
|
|
166
|
+
);
|
|
167
|
+
xhr.addEventListener("abort", () =>
|
|
168
|
+
reject(new Error(fallbackErrorMessage)),
|
|
169
|
+
);
|
|
170
|
+
xhr.send(form);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -27,11 +27,19 @@ export interface ImportResult {
|
|
|
27
27
|
warnings?: string[];
|
|
28
28
|
error?: string;
|
|
29
29
|
/** Set by import-figma-clipboard: which path actually produced the screen(s). */
|
|
30
|
-
strategy?: "restNodes" | "htmlFallback";
|
|
30
|
+
strategy?: "restNodes" | "htmlFallback" | "localKiwi";
|
|
31
31
|
/** Set by import-figma-clipboard when it fell back because no Figma token is configured. */
|
|
32
32
|
figmaApiKeyMissing?: boolean;
|
|
33
|
+
/** Set by import-figma-clipboard when strategy is "localKiwi": number of IMAGE fills that couldn't be resolved without a Figma token. */
|
|
34
|
+
unresolvedImages?: number;
|
|
35
|
+
/** Set by .fig file upload: number of IMAGE fills not in the embedded blobs (need Figma API to resolve). */
|
|
36
|
+
unresolvedImageRefCount?: number;
|
|
33
37
|
/** Set by import-figma-clipboard when it fell back: why the REST match didn't happen. */
|
|
34
38
|
matchStatus?: "matched" | "ambiguous" | "none" | "error";
|
|
39
|
+
rateLimitRetryAfter?: number;
|
|
40
|
+
rateLimitPlanTier?: string;
|
|
41
|
+
rateLimitType?: string;
|
|
42
|
+
rateLimitUpgradeUrl?: string;
|
|
35
43
|
fidelityReport?: FigmaFidelityReport;
|
|
36
44
|
guidance?: string;
|
|
37
45
|
}
|
|
@@ -42,6 +50,16 @@ export interface ImportResultNotification {
|
|
|
42
50
|
description?: string;
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
export function isFigmaRateLimitImportError(
|
|
54
|
+
result: ImportResult | undefined,
|
|
55
|
+
): boolean {
|
|
56
|
+
return (
|
|
57
|
+
(typeof result?.rateLimitRetryAfter === "number" &&
|
|
58
|
+
result.rateLimitRetryAfter > 0) ||
|
|
59
|
+
result?.rateLimitType === "low"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
export const VISUAL_EDIT_CONNECT_COMMAND =
|
|
46
64
|
"npx @agent-native/core@latest design connect --url 'http://localhost:<port>' --root . --daemon";
|
|
47
65
|
|
|
@@ -36,6 +36,7 @@ const FIGMA_DATA_BUFFER_ELEMENT_RE =
|
|
|
36
36
|
const FIGMA_BUFFER_COMMENT_RE = /<!--\s*\(figma\)[\s\S]*?\(\/figma\)\s*-->/gi;
|
|
37
37
|
const ESCAPED_FIGMA_BUFFER_COMMENT_RE =
|
|
38
38
|
/<!--\s*\(figma\)[\s\S]*?\(\/figma\)\s*-->/gi;
|
|
39
|
+
const DATA_BUFFER_ATTR_RE = /\bdata-buffer\s*=\s*(["'])([\s\S]*?)\1/i;
|
|
39
40
|
|
|
40
41
|
function decodeBase64Json(raw: string): unknown {
|
|
41
42
|
const trimmed = raw.trim();
|
|
@@ -112,6 +113,39 @@ export function stripFigmaBinaryClipboardBuffer(html: string): string {
|
|
|
112
113
|
.replace(ESCAPED_FIGMA_BUFFER_COMMENT_RE, "");
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
const FIGMA_BUFFER_BASE64_RE = /\(figma\)([A-Za-z0-9+/=\s]+?)\(\/figma\)/i;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract the base64-encoded fig-kiwi binary from a Figma clipboard HTML
|
|
120
|
+
* payload. Returns null when no buffer is present (e.g. the clipboard came
|
|
121
|
+
* from an older Figma client, a plain-text copy, or the buffer was already
|
|
122
|
+
* stripped).
|
|
123
|
+
*
|
|
124
|
+
* The buffer lives inside a `data-buffer` attribute as an HTML comment:
|
|
125
|
+
* `data-buffer="<!--(figma)BASE64(/figma)-->"`
|
|
126
|
+
* Some variants embed the comment directly in the HTML text instead. Both
|
|
127
|
+
* are handled defensively.
|
|
128
|
+
*/
|
|
129
|
+
export function extractFigmaBuffer(
|
|
130
|
+
html: string | null | undefined,
|
|
131
|
+
): string | null {
|
|
132
|
+
if (!html) return null;
|
|
133
|
+
|
|
134
|
+
// Try the data-buffer attribute path first (current Figma clipboard format).
|
|
135
|
+
const attrMatch = html.match(DATA_BUFFER_ATTR_RE);
|
|
136
|
+
if (attrMatch?.[2]) {
|
|
137
|
+
const inner = attrMatch[2];
|
|
138
|
+
const bufMatch = inner.match(FIGMA_BUFFER_BASE64_RE);
|
|
139
|
+
if (bufMatch?.[1]) return bufMatch[1].replace(/\s/g, "");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fall back to a bare (figma) comment anywhere in the HTML.
|
|
143
|
+
const directMatch = html.match(FIGMA_BUFFER_BASE64_RE);
|
|
144
|
+
if (directMatch?.[1]) return directMatch[1].replace(/\s/g, "");
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
115
149
|
/**
|
|
116
150
|
* Extracts and decodes the `figmeta` payload from a clipboard's raw HTML (or
|
|
117
151
|
* plain text), if present. Returns `null` for anything that isn't a
|
|
@@ -140,31 +174,50 @@ export function extractFigmeta(
|
|
|
140
174
|
return null;
|
|
141
175
|
}
|
|
142
176
|
|
|
177
|
+
// Sentinel URL scheme for unresolved Figma image fills in local-kiwi imports.
|
|
178
|
+
// Must start with "about:" so fig-file-to-html's imageUrl() returns it as-is.
|
|
179
|
+
// The fragment carries the hex hash so hydrate-figma-paste-images can replace
|
|
180
|
+
// each occurrence individually.
|
|
181
|
+
export const FIGMA_IMAGE_REF_SENTINEL_PREFIX = "about:blank#figma-image-ref=";
|
|
182
|
+
|
|
183
|
+
export function figmaImageRefSentinel(hexHash: string): string {
|
|
184
|
+
return `${FIGMA_IMAGE_REF_SENTINEL_PREFIX}${hexHash}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
143
187
|
export type FigmaApiKeyStatus = "configured" | "missing" | "unknown";
|
|
144
|
-
export type FigmaPasteStrategy =
|
|
188
|
+
export type FigmaPasteStrategy =
|
|
189
|
+
| "rest"
|
|
190
|
+
| "local-kiwi"
|
|
191
|
+
| "html-fallback"
|
|
192
|
+
| "not-figma";
|
|
145
193
|
|
|
146
194
|
/**
|
|
147
195
|
* Pure decision matrix for what a paste should attempt, given whether it
|
|
148
196
|
* decoded a `figmeta` payload and (if known) whether the Figma access token
|
|
149
197
|
* is configured:
|
|
150
198
|
*
|
|
151
|
-
* - No `figmeta`
|
|
152
|
-
* - `figmeta` present
|
|
153
|
-
*
|
|
154
|
-
* -
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
199
|
+
* - No `figmeta` → not a Figma paste at all (`"not-figma"`).
|
|
200
|
+
* - `figmeta` present + key known-missing → try local kiwi decode of the
|
|
201
|
+
* binary buffer (`"local-kiwi"`). This always produces something editable
|
|
202
|
+
* (geometry, text, auto-layout) even without a token; images are left as
|
|
203
|
+
* annotated placeholders for retroactive hydration.
|
|
204
|
+
* - `figmeta` present + key configured (or status unknown) → attempt the
|
|
205
|
+
* REST import (`"rest"`). The server is the authority on whether the key
|
|
206
|
+
* actually works; `"unknown"` means "try REST, let the server degrade."
|
|
158
207
|
*/
|
|
159
208
|
export function decideFigmaPasteStrategy(
|
|
160
209
|
figmeta: FigmetaPayload | null,
|
|
161
210
|
apiKeyStatus: FigmaApiKeyStatus = "unknown",
|
|
162
211
|
): FigmaPasteStrategy {
|
|
163
212
|
if (!figmeta) return "not-figma";
|
|
164
|
-
if (apiKeyStatus === "missing") return "
|
|
213
|
+
if (apiKeyStatus === "missing") return "local-kiwi";
|
|
165
214
|
return "rest";
|
|
166
215
|
}
|
|
167
216
|
|
|
217
|
+
// 8 MB decoded binary hard cap for clipboard buffer transport. Buffers above
|
|
218
|
+
// this threshold should use an upload handle instead (Phase 1 future work).
|
|
219
|
+
const MAX_CLIPBOARD_BUFFER_DECODED_BYTES = 8 * 1024 * 1024;
|
|
220
|
+
|
|
168
221
|
export type FigmaPasteImportCall =
|
|
169
222
|
| {
|
|
170
223
|
action: "import-figma-clipboard";
|
|
@@ -173,6 +226,8 @@ export type FigmaPasteImportCall =
|
|
|
173
226
|
selectedNodeIds?: string[];
|
|
174
227
|
selectedNodeIdsTruncated?: boolean;
|
|
175
228
|
clipboardHtml: string;
|
|
229
|
+
/** Base64-encoded fig-kiwi buffer, present when strategy is local-kiwi. */
|
|
230
|
+
clipboardBuffer?: string;
|
|
176
231
|
originalName?: string;
|
|
177
232
|
};
|
|
178
233
|
}
|
|
@@ -193,9 +248,10 @@ export type FigmaPasteImportCall =
|
|
|
193
248
|
export function resolveFigmaPasteImportCall(
|
|
194
249
|
content: string,
|
|
195
250
|
originalName = "figma-paste.html",
|
|
251
|
+
apiKeyStatus: FigmaApiKeyStatus = "unknown",
|
|
196
252
|
): FigmaPasteImportCall {
|
|
197
253
|
const figmeta = extractFigmeta(content);
|
|
198
|
-
const strategy = decideFigmaPasteStrategy(figmeta,
|
|
254
|
+
const strategy = decideFigmaPasteStrategy(figmeta, apiKeyStatus);
|
|
199
255
|
|
|
200
256
|
if (strategy === "not-figma") {
|
|
201
257
|
return {
|
|
@@ -204,6 +260,49 @@ export function resolveFigmaPasteImportCall(
|
|
|
204
260
|
};
|
|
205
261
|
}
|
|
206
262
|
|
|
263
|
+
// For local-kiwi: include the binary buffer so the server can decode it
|
|
264
|
+
// without a Figma token. Strip the buffer from clipboardHtml to keep the
|
|
265
|
+
// action payload lean; the server reconstructs the full document from the
|
|
266
|
+
// buffer instead of the HTML.
|
|
267
|
+
if (strategy === "local-kiwi") {
|
|
268
|
+
const bufferBase64 = extractFigmaBuffer(content);
|
|
269
|
+
const bufferPayload: { clipboardBuffer?: string } = {};
|
|
270
|
+
if (bufferBase64) {
|
|
271
|
+
// Enforce the decoded-byte cap before base64 decoding on the server to
|
|
272
|
+
// prevent the action schema from accepting an oversized payload.
|
|
273
|
+
const decodedBytes = Math.floor((bufferBase64.length * 3) / 4);
|
|
274
|
+
if (decodedBytes <= MAX_CLIPBOARD_BUFFER_DECODED_BYTES) {
|
|
275
|
+
bufferPayload.clipboardBuffer = bufferBase64;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
action: "import-figma-clipboard",
|
|
280
|
+
payload: {
|
|
281
|
+
figmetaFileKey: figmeta!.fileKey,
|
|
282
|
+
...(figmeta!.selectedNodeIds
|
|
283
|
+
? { selectedNodeIds: figmeta!.selectedNodeIds }
|
|
284
|
+
: {}),
|
|
285
|
+
...(figmeta!.selectedNodeIdsTruncated
|
|
286
|
+
? { selectedNodeIdsTruncated: true }
|
|
287
|
+
: {}),
|
|
288
|
+
clipboardHtml: stripFigmaBinaryClipboardBuffer(content),
|
|
289
|
+
...bufferPayload,
|
|
290
|
+
originalName,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// REST strategy: strip the buffer from clipboardHtml when exact node ids are
|
|
296
|
+
// available (they make the buffer redundant), but always include clipboardBuffer
|
|
297
|
+
// so the server can fall back to local-kiwi decode if the token is missing.
|
|
298
|
+
const restBufferPayload: { clipboardBuffer?: string } = {};
|
|
299
|
+
const restBufferBase64 = extractFigmaBuffer(content);
|
|
300
|
+
if (restBufferBase64) {
|
|
301
|
+
const restDecodedBytes = Math.floor((restBufferBase64.length * 3) / 4);
|
|
302
|
+
if (restDecodedBytes <= MAX_CLIPBOARD_BUFFER_DECODED_BYTES) {
|
|
303
|
+
restBufferPayload.clipboardBuffer = restBufferBase64;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
207
306
|
return {
|
|
208
307
|
action: "import-figma-clipboard",
|
|
209
308
|
payload: {
|
|
@@ -217,6 +316,7 @@ export function resolveFigmaPasteImportCall(
|
|
|
217
316
|
clipboardHtml: figmeta!.selectedNodeIds?.length
|
|
218
317
|
? stripFigmaBinaryClipboardBuffer(content)
|
|
219
318
|
: content,
|
|
319
|
+
...restBufferPayload,
|
|
220
320
|
originalName,
|
|
221
321
|
},
|
|
222
322
|
};
|