* fix(app): cancel large FIG opens before tab cleanup
- Abort tab-local preparation before awaiting recovery persistence so closing a decoding tab terminates its FIG worker immediately
- Add explicit graph-event unsubscription and lazy FIG resource release for disposed editor stores
- Keep staging-editor disposal separate from live graph resource ownership
- Let same-origin document fetches bypass all cross-origin font response limits
* refactor(fig): isolate worker sessions per document
- Give each lazy FIG document a dedicated MessageChannel and retained worker session
- Route manifest and page population traffic through the session port instead of global worker handlers
- Close the port and worker explicitly on cancellation or tab disposal
- Preserve current lazy source transfer and export fidelity until raw NodeChange patch export is implemented
* perf(fig): preserve unchanged archives in worker sessions
- Keep a second transferable copy of the original FIG archive inside the document worker session instead of main-thread memory
- Track whether user graph mutations invalidate exact archive reuse
- Return unchanged documents byte-for-byte through the session without full-page materialization or recompression
- Release archive request ownership with the page population worker and editor graph resources
* fix(fig): choose the first visible imported page
- Ignore internal-only canvases when selecting the initial active page\n- Keep internal component pages available for lazy instance population
* fix(fig): retain original archives across fallbacks
- Keep session-owned workers alive when population is oversized\n- Register the input archive when worker graph transfer falls back to the main thread\n- Preserve byte-identical unchanged saves without restoring lossy materialization
* fix(fig): harden worker session ownership
- Route concurrent archive replies through persistent request resolvers and reject stale in-flight archives after edits\n- Retain disposal-only handles for oversized sessions and guard Worker construction\n- Reuse the shared FIG import options contract and cover session disposal and invalidation
* test(fig): isolate Worker availability mutation
* fix(text): prepare browser fonts atomically
- Fetch approved Fontsource resources directly in browsers with bounded responses and retryable provider failures
- Limit page font resolution concurrency and retain live Inter substitution paragraphs after baked glyph invalidation
- Shape frame, section, and component labels through a bounded native Paragraph cache
- Cover real Geist, Geist Mono, and Roboto Mono browser loading plus substitution and cache lifecycle behavior
* test(canvas): cover substitution and label shaping
- Capture baked missing-font text before editing, live Inter substitution on first input, and visible undo output
- Assert text-picture and derived-glyph invalidation with finalized substituted readiness
- Cover shaped frame, rotated frame, section, component, component-set, ellipsis, kerning, and zoom label presentation
* fix(text): preserve same-origin fetches during font resolution
- Route same-origin application and fixture requests through native fetch while Unifont temporarily proxies global HTTP requests
- Keep the external provider HTTPS allowlist enforced for cross-origin font resources
- Cover the production race discovered while loading gold-preview.fig during provider initialization
* fix(text): preserve substituted path glyphs
- Keep imported derived curved glyphs for text-on-path layers when exact fonts finalize as substituted
- Cover substituted path rendering through the runtime renderer and refresh the corrected visual oracle
- Update shaped Inter measurement badges and merged typography panel snapshots after visual inspection
- Search virtualized font catalogs explicitly and restore the canvas screenshot helper used by broad E2E coverage
* fix(text): use browser font transport without desktop warning
- Keep the browser provider implementation aligned with current settings behavior after splitting from preparation
* test(text): inject same-origin browser font context
- Keep the transport test deterministic outside a Window global
* fix(text): preserve final browser font transport hardening
- Carry the same-origin large-document bypass and bounded cross-origin provider checks from the preserved integration snapshot
* fix(text): initialize font transport outside Window contexts
- Keep unit and headless module imports safe while browsers use their current origin
* test(text): satisfy async visual fixture contract
- Return from the resolver setup continuation after requesting render
* test(canvas): include paragraph cache lifecycle
* fix(text): cancel queued browser font loads
- Race serialized provider work against preparation cancellation\n- Release queue slots after cancelled waiters without disturbing active requests\n- Reuse one section-title paragraph cache entry for measurement and drawing
* fix(text): close font queue cancellation races
- Release reserved proxy queue slots when cancellation lands after queue acquisition\n- Skip paragraph work for zero-width section labels
* fix(text): abort active provider resolution
- Race active provider resolution against its preparation signal and restore the temporary fetch proxy promptly\n- Forward cancellation through proxied provider requests\n- Align the renderer font-readiness facade with substituted text
* feat(app): show atomic document loading progress
- Preserve the existing full-canvas pencil loader while adding phase, detail, accessible status, and honest determinate progress
- Keep one generation-safe load owner across FIG decoding, graph preparation, page population, fonts, fallbacks, layout, viewport fitting, and first-render fade
- Prevent nested page setup and viewport cleanup from revealing partially prepared documents
- Cover obsolete sessions, font-resolution ownership, and staged loader UI
* refactor(app): scope editor preparation per tab
- Replace the shared loading boolean with one reactive preparation snapshot and one imperative controller per editor store
- Keep Core page work progress-only and inject canvas suspension from the app boundary
- Route FIG, storage, recovery, DOM import, and page switching through reusable tab-local preparation handles
- Abort only the closing tab's operation and cover generation safety, multi-tab isolation, progress UI, and disposal
* fix(editor): commit prepared pages atomically
- Prepare population, fonts, fallbacks, and layout without changing the visible page
- Reject cancelled and stale prepared pages before committing viewport, selection, and page events
- Keep the preparation overlay until the committed scene version is presented
- Cover call order, cancellation, stale generations, and presentation acknowledgement
* fix(app): stage imported documents before commit
- Prepare imported graphs in an isolated Core editor before replacing the live document
- Share font loading while keeping live selection, graph, renderers, and history untouched during staging
- Preserve the previous graph when staging is cancelled or fails and remove the duplicate pre-font layout pass
* refactor(app): namespace preparation UI
- Move canvas and tab preparation presentations into focused subfolders with concise component names
- Share progress and phase presentation helpers across preparation surfaces
- Show tab-local preparation status without covering the active canvas for background work
* fix(app): cancel preparation work at source
- Publish typed per-store preparation lifecycle events with explicit completion, cancellation, and failure outcomes
- Propagate tab-local AbortSignals through FIG parsing, population workers, and browser font downloads
- Keep cancellable font requests outside shared in-flight caches while retaining globally completed font registrations
- Stop FIG manifest previews from replacing the live graph before atomic document commit
* fix(app): cancel storage and DOM preparation
- Propagate preparation signals through S3 downloads, byte progress, local-cache boundaries, and DOM/CSS conversion checkpoints
- Reuse merged diagnostics and localized toasts for document, storage, and presentation failures
- Replace manual font concurrency and presentation timers with es-toolkit limitAsync and withTimeout
- Guard stalled first presentation and fix the merged recovery dialog title bindings
* fix(app): stage reload and font retry
- Prepare reload graphs in isolation and preserve the current document on read, decode, font, or layout failure
- Restore page and viewport state only after atomic graph commit with cancellable reload reads
- Run font Retry as a tab-local preparation with cache reset, final layout, picture invalidation, and presentation acknowledgement
- Keep completed document pixels visible while Retry reports activity in the tab
* fix(app): enforce exclusive preparation outcomes
- Complete document, storage, recovery, and DOM preparations only after successful commit
- Keep failed and cancelled handles terminal so lifecycle events cannot report contradictory outcomes
- Preserve external AbortError identity across storage timeouts and cancel streamed readers without returning partial bytes
- Cover credential-free pre-abort, mid-stream cancellation, progress cutoff, and terminal outcome exclusivity
* feat(diagnostics): record preparation outcomes
- Persist completed, cancelled, and failed preparation lifecycles through the validated diagnostics recorder
- Store only operation kind, outcome, cancellation or failure category, terminal phase, and coarse duration bucket
- Exclude document subjects, font families, storage identities, URLs, raw durations, messages, and stack traces
* chore(app): keep browser font tests with typography split
- Remove the browser font transport test inherited from a mixed cancellation commit; the source and coverage remain on the typography branch and safety snapshot
* test(vue): assert injected render suspension
- Exercise shouldSuspendRender instead of removed Core loading state\n- Preserve the contract that rendering resumes without a version change
* test(app): complete atomic preparation contracts
- Acknowledge first presentation in headless file-open tests\n- Assert the cancellable font-loading signature at the Tauri fallback boundary
* fix(app): preserve preparation cancellation
- Stage imported graphs before mutating live tabs and propagate aborts through page, DOM, font, and storage work\n- Use the accessible progress primitive and clamp determinate values\n- Cover fallback-font cancellation and yield pending-open test polling to the task queue
* test(text): await fallback font request cancellation
Start the mocked remote font request before aborting so the test proves that the active request receives the preparation signal.
Adds figma.exposeInstanceSwap(slots, candidates, propertyName), which
exposes one or more nested instances as an instance-swap slot — the
enclosing component (or component set, walking up past intermediate
variant members) gets an INSTANCE_SWAP property offering the given
candidates, and each slot instance is tagged to respond to it. Mirrors
Figma's "Create component property > Instance swap", letting a designer
pick which component fills a slot (e.g. an icon on a button) instead of
the slot being baked-in static content.
Exposed as the expose_instance_swap MCP/CLI/chat tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(app): isolate system clipboard adapters
- Split browser and Tauri clipboard behavior into focused adapters\n- Inject browser clipboard capabilities into unit-testable operations\n- Remove navigator and document mutation from headless clipboard tests
* refactor(app): delegate browser clipboard fallbacks
- Use copy-to-clipboard for modern rich MIME writes and execCommand fallback\n- Keep OpenPencil-specific HTML and plain-text payload construction at the adapter boundary\n- Remove hand-rolled browser capability and selection handling
* test(clipboard): verify rich browser menu round-trip
- Exercise copy from the browser Edit menu under a user gesture\n- Verify text/html and text/plain ClipboardItem formats\n- Paste the system clipboard payload back into the canvas
* refactor(clipboard): reduce adapter surface
- Expose only command dispatch and the injectable system clipboard contract\n- Keep browser and Tauri copy/paste operations private to their adapters\n- Rename the in-memory DataTransfer fallback and share design HTML recognition
* fix(clipboard): harden format and fallback handling
- Require complete OpenPencil or Figma clipboard markers\n- Await browser writes so adapter failures resolve false\n- Write plain-only Tauri payloads as text and reject unrelated clipboard text
* fix(clipboard): reject unrelated current browser data
* fix(clipboard): bind fallbacks to copied selection
- Match cached rich HTML to the current Tauri plain-text fallback\n- Preserve nodes when selection changes during an asynchronous cut\n- Reject unrelated current browser HTML before consulting memory
* fix(clipboard): reuse the shared memory payload type
* fix(clipboard): scan design markers linearly
* fix(clipboard): distinguish unavailable and empty reads
* style(clipboard): use includes for marker closure
* test(clipboard): avoid wall-clock marker assertions
createComponentFromNode copied only a hand-picked list of fields onto the
new COMPONENT node, omitting primaryAxisSizing/counterAxisSizing. A HUG-sized
auto-layout frame silently reverted to FIXED sizing on conversion, so later
padding changes no longer resized the frame (itemSpacing still worked since
it doesn't affect the frame's own size).
Merges the contributor clipboard fallback fix with maintainer follow-ups for browser cut safety and isolated fallback tests. Selections are preserved when clipboard serialization fails, and clipboard fallback tests no longer depend on host APIs.
Merges the contributor fix with maintainer follow-up coverage. MCP results now treat omitted isError as success, scope detection to mcp__ tools, and preserve generic tool error handling.
* feat(app): make crash recovery configurable
- Add an enabled-by-default persisted recovery preference and General settings control
- Stop recovery writes and remove the active document snapshot when disabled
- Suppress startup recovery discovery while the preference is disabled
- Cover disabled persistence and re-enable behavior
* fix(i18n): translate recovery preferences
* fix(app): serialize recovery disable cleanup
- Block re-enabled persistence until pending snapshot removal completes
- Preserve disable generations so stale cleanup cannot reset newer recovery state
- Display runtime-overridden recovery state in Settings
- Deep-clone nested preferences before updating recovery
- Register the Vite-owned MCP child as a worktree-prefixed Portless sibling service\n- Inject HTTPS and WebSocket automation URLs into the browser instead of assuming port 7600\n- Isolate development socket and discovery files while preserving fixed-port non-Portless flows
- Carry normalized tool-name arrays through app and development JSON configuration
- Serialize the legacy CSV format only at child-process environment boundaries
- Bound, validate, trim, and deduplicate development tool policy input
- Keep New tabs provisional so opening or creating a design reuses the active tab
- Separate recent document, storage, menu, worker, and workspace responsibilities
- Add source-aware recents, responsive files UI, loading states, and localized copy
- Preserve native local Open Recent behavior while supporting remote storage history
- Publish explicit effective tool state while retaining disabled tools for Settings
- Classify filesystem writes as side effects and localize category labels
- Stop failed restarts and return precise development control status codes
- Encapsulate app-global runtime state in a testable service
- Serialize health refresh, start, stop, and restart operations
- Validate health metadata and clean state after failures
- Remove duplicated document-access declarations from core tools
- Replace the MCP catalog with typed descriptors, capabilities, and policy
- Emit standard MCP annotations through type-safe registration
- Restart the Vite-managed MCP server with the current authentication, root, and tool settings
- Keep the development control endpoint protected by the local token
- Cover explicit no-auth and configured-root environment propagation
- Collect tool metadata through the existing registration wrapper
- Expose the runtime catalog to Settings without a parallel MCP-only list
- Keep disabled tools discoverable so they can be re-enabled
- Declare inspection or modification access on every canonical tool definition
- Add bulk category controls while preserving individual disabled-tool storage
- Keep runtime availability separate from document access semantics
- Dispose CanvasKit dash effects and render nested frame guides
- Validate imported guide GUIDs and invalidate stale raw guide metadata
- Resolve frame owners through nested hit ancestry and require primary-button ruler drags
- Share guide preview types through a neutral editor module
- Reject combine_as_variants inputs that span different parents\n- Preserve all source components when validation fails\n- Cover the tool-level error path
- Match the Figma parent and insertion-index API contract\n- Reject duplicate component references before graph mutation\n- Share slash-name variant derivation with editor actions\n- Add structural regression coverage and changelog entry
figma.combineAsVariants() was unimplemented in the scripting/automation
layer (figma-api/proxy.ts's compatibility surface), even though the editor
UI already has a full equivalent — createComponentSetFromComponents()
(editor/components.ts) plus wrapSelectionInContainer()
(editor/structure/container-wrap.ts) — wired to the app's own "Create
component set" menu action.
This ports that logic down to FigmaAPI (the class MCP tools, the CLI, and
AI chat scripting all run against): wraps selected COMPONENT nodes sharing
a parent into a COMPONENT_SET, and derives variant property definitions from
"Category/Value" node-name segments, matching the editor behavior exactly.
Drops the undo-stack push and selection-state writes the editor path has,
since neither concept exists at the scripting layer — everything else is
the same primitives (SceneGraph.createNode/insertChildAt/reparentNode),
which FigmaAPI already had direct access to.
Also adds a matching combine_as_variants tool (packages/core/src/tools/
create/components.ts) registered in EXTENDED_TOOLS, so it's exposed
through MCP, the CLI, and AI chat alongside the existing create_component/
create_instance tools.
* Revert "feat(ai): add HarnessAgent sidecar foundation (#560)"
This reverts commit 83a5ea1b42.
* Revert "Revert "feat(ai): add HarnessAgent sidecar foundation (#560)""
This reverts commit 0d8c03515888c62dc47186d4a3b0b7b04e78f8af.
* refactor(ai): ship Harness as optional companion
* fix(ai): support Harness companion on Windows
* test(ai): restore navigator after Harness Windows test