* fix(app): protect unsaved documents when closing
Mark tabs with unsaved content updates and ask whether to save before
closing them. The prompt now covers tab closes, the desktop window close
button, and the application Quit action, which previously discarded work
when autosave had no writable target.
Track a content revision separately from scene and recovery versions so a
save only clears the indicator when it wrote the revision it captured.
Cancelled pickers, failed writes, and edits made during a save keep the
document open. Desktop uses the platform alert; the browser keeps the
styled dialog.
The desktop menu replaces the predefined Quit item so the accelerator and
Dock-independent quit path request confirmation instead of exiting.
* fix(ai): resolve credentials only when used
Opening a document, creating a chat, or browsing chat history connected
the provider and read saved secrets, which triggered system credential
prompts without user intent.
Startup now reads credential status only, migration runs inside the first
explicit resolution, and the chat panel initializes local history without
creating a transport. Stock-photo keys resolve per search instead of at
settings refresh, and credentials still marked legacy count as configured
so upgrading does not appear to lose them.
* refactor(ai): export diagnostics from Settings only
Chat kept its own debug log, copied mixed app-wide usage into a
conversation export, and reported a missing cache rate as zero. Remove
that surface and record AI requests, model steps, and tool activity as
correlated diagnostic events instead.
Settings remains the single export location, usage summaries can now
distinguish unreported telemetry from zero, and transcript or tool
payloads are no longer part of the export.
* fix(ai): clear legacy credentials for real
Clearing a Pexels, Unsplash, or provider key only removed the current
store entry. A value that still lived in legacy storage kept the key
configured, so a later search migrated and used the credential the user
had just removed.
Migrate before mutating so clearing also removes the legacy value, and
share one in-flight migration so the media and provider paths cannot
migrate the same plaintext twice.
* fix(ai): scope credential migration per source
Sharing one migration promise process-wide let a second storage return
the first migration's result, leaving its own legacy keys unmigrated
while reporting success. Track in-flight migrations per storage and
serialize them, because every migration writes to the same store and
concurrent runs could overwrite each other.
* fix(app): destroy the window after a confirmed close
Tauri's onCloseRequested helper destroys the window itself when a handler
returns without preventing the event. Approving a close therefore invoked
plugin:window|destroy, which the capability set did not grant, so the
window stayed open with a permission error after saving.
Always intercept the request and destroy the window explicitly once the
choice is confirmed, and grant core🪟allow-destroy in place of the
now-unused close permission.
* fix(app): show a filled dot for unsaved tabs
The unsaved indicator used a stroked Lucide circle whose fill attribute
kept it an empty outline, reading as a disabled control. Draw the
indicator as a filled accent dot matching the status dots used elsewhere
in the app.
* refactor(app): focus the unsaved prompt with VueUse
Replace the manual watcher, nextTick, and component $el focus with
useFocus, which focuses the Save button when the dialog mounts. Assert the
focus in the close-protection test so the Return-saves behavior stays
covered.
* refactor(app): route Quit through the shared menu channel
The Quit item emitted a bespoke app:request-exit event and the close
module listened for it, while every other native item travels as a
menu-event id dispatched by the shell and editor menu composables.
Emit menu-event "quit" for both the Quit item and the platform exit
request, handle it in useShellMenu beside check-updates, and share one
confirmAppExit so window closes and app exits agree on a single approval.
* refactor(app): generate the macOS app menu entries
The application menu hardcoded its labels and the Quit accelerator in
Rust while every other menu entry is generated from APP_MENU_SCHEMA.
Move the custom app entries (About, Check for Updates, Quit) into
APP_MENU_APP_ITEMS and emit desktop/generated/app-menu.json, keyed by id
so the native builder cannot silently drop a label.
Placement stays in Rust because the OS-predefined items sit between them,
and the menu title now comes from the packaged product name.
* build(tauri-menu): check generated menus against the schema
The generated menu files are committed but nothing verified them, so a
schema edit could silently leave desktop/generated stale until the next
release build regenerated it.
Split the renderers from the write step, register the tool as a workspace
so its dependencies resolve, and compare the committed files with the
schema in a test that runs with the other tool checks.
* fix(app): serialize exit confirmations
The window close handler and the Quit item both call confirmAppExit, and
the per-handler closing flag does not cover the two paths. Both could run
close preparation, so an unsaved document could be prompted twice.
Share one in-flight confirmation and clear it when it settles, so a
cancelled or failed attempt still prompts again on the next request.
Share typed MCP, AI, and WebMCP exclusions across adapters. Preserve the browser tool inventory through explicit exclusions and keep execution support and user permissions independent.
Record property presence for exact undo/redo, including explicit undefined values, and reject instance index mutations. Tighten numeric and operand schemas to match execution contracts.
Define native Valibot inputs and execution/exposure metadata on each tool. Derive effects and default capabilities, consume upstream Standard Schema conversion, and validate finite numeric inputs consistently across adapters.
Move atomic execution to Core and restore failures from Scene Graph checkpoints without relying on a property diff. Preserve topology, collections, indexes and surviving object identities during rollback.
BREAKING CHANGE: custom tools use input schemas and execution metadata instead of params, ParamDef and independently declared mutation flags. Direct tool execution validates inputs before invoking the handler.
- Reject open vector networks before contacting the provider\n- Cover closed and open vector targets explicitly\n- Label the stock photo prompt example code fence
- Reject text, line, structural, and populated container targets before provider requests\n- Preserve closed shape and Boolean geometry support without mutating target bounds\n- Cover accepted and rejected node families with focused tests
- Reject combine_as_variants inputs that span different parents\n- Preserve all source components when validation fails\n- Cover the tool-level error path
- Route bounded selection renders through the optional Vision model
- Return compact text findings without retaining image data in Design chat history
- Bound MCP and AI image exports by their longest pixel edge
- Track loaded font provenance and aggregate page-level face fidelity, including synthesized styles and active Inter substitutions
- Add a localized warning banner with retry and affected-layer actions
- Expose get_font_status through the Figma API and MCP tool registry
- Include stroke color and rounded weight in node and child summaries
- Select the first visible stroke when hidden paints precede it
- Cover both describe output paths with mixed stroke visibility
Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
- Validate inline SVG roots against their supported root props
- Leave descendant SVG markup to the SVG renderer instead of Design JSX validation
- Keep warnings for unsupported root props and cover both paths
Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
- Name every create_shape node type in the tool description
- Direct arbitrary geometry requests to SVG-backed create_vector input
- Keep the prose synchronized with the declared type enum
Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
CodeRabbit found the RotatedPlain control node was created on
instead of , so the unstroked control assertion was not actually
validating the same setup without a stroke.
- Export OverlapScope, AnalyzeOverlapsSummary, and OverlapIntersection
from the analyze barrel so consumers don't deep-import.
- Expand the local rectangle by stroke overflow before transforming
through the world matrix, so stroked rotated nodes get their true
rotated footprint instead of an underestimated canvas-space expansion.
- Add clipPolygon to geometry and preserve the clipped polygon through
the full clip chain in computeNodeBounds, so a later outer clip can't
reintroduce corners an inner clip already removed.
- Cap returned overlaps to an empty list when limit is 0 or negative;
summary totals still reflect the complete result set.
- Trim whitespace from scope and severity inputs so " major " resolves.
- Add geometry and overlap tests covering each fix.
Implement the analyze overlaps CLI command, RPC endpoint, and ToolDef
to enable heuristic detection of visual overlaps, parent overflows, and
overlay patterns. Geometry computation now uses world-matrix visual
bounds to handle nested ancestor rotations and clipping frames correctly.
Update file-backed CLI commands to use Node fs/promises instead of Bun
runtime file APIs. This is required for the published CLI to function
when installed and executed in a standard Node environment. Add the
package.json subpath export to the core package for metadata consumers.
- Add AST-based lint checks for broad unknown object assertions and local JsonObject aliases
- Centralize JsonObject in core and package-local MCP RPC JSON typing
- Replace baseline Record<string, unknown> assertions with named shared/domain types
Replace hand-rolled regex heuristic with proper acorn JS parsing.
The AST correctly identifies ExpressionStatement as the last node
regardless of trailing semicolons, comments, or complex syntax.
Fixes edge cases like '42;' which the regex wrongly treated as a
statement.
Co-authored-by: stiff <v.stiff@gmail.com>
Co-authored-by: Jais Pedersen <jais@pedersens.net>
Stdio handshake:
Server now sends { type: register, token } to WebSocket clients on
connection, so the stdio bridge receives the token immediately instead
of deadlocking while both sides wait to receive.
Eval return values (REPL-style):
Add wrapEvalCode() that detects when the last non-empty line is a value
expression and promotes it to a return statement. Bare expressions like
JSON.stringify(result) now return the value instead of undefined.
Single implementation in packages/core/src/tools/analyze/eval-wrap.ts
used by both the tool and the automation bridge.
Co-authored-by: stiff <v.stiff@gmail.com>
Co-authored-by: Jais Pedersen <jais@pedersens.net>
- Replace simple no-op test callbacks with explicit undefined returns
- Remove no-empty-function exceptions for autosave, stroke picker, font settings, pen, tauri, AI adapter, and undo helper tests
- Validate check plus affected engine and E2E tests
- Replace non-null assertions in modify tool tests
- Use expectDefined and getNodeOrThrow for graph, proxy, and style-run lookups
- Validate check and affected modify tool tests
- Replace low-count E2E non-null assertions with store guards and optional expectations
- Use expectDefined for tool adapter and XPath node lookups
- Validate affected engine and E2E tests
- Replace browser store non-null assertions with explicit initialization guards
- Use expectDefined for optional test resources and tool results
- Clean low-count non-null assertions in font, icon, snap, OKHCL, and visual tests
- Remove stale imports and unused locals from split engine and e2e tests
- Drop the targeted no-unused-vars test allowances
- Keep check and affected test suites warning-free
- Remove the broad test exception for consistent type imports
- Mark test-only core and helper imports as type imports where appropriate
- Keep targeted validation for affected engine tests
- Re-enable duplicate imports, radix, prefer-const, nested ternary, and silent catch checks in tests
- Drop the redundant spec-file override covered by the broader test override
- Fix the remaining test offenders surfaced by the stricter rules
- Configure oxfmt custom import groups for workspace, app, package, and test aliases
- Keep type imports grouped with their matching source category instead of one global tail group
- Expand the format script to cover formatter config, Vite files, and scripts
- Move top-level engine and e2e prefixed test files under domain folders
- Update fixture path helpers after moving render and pen tests
- Add lint coverage to prevent new top-level prefixed test files
- Refresh testing docs for the new fig and layout paths
- Merge structural lint rules into oxlint.json and remove the second config file
- Add #tests/* path aliases and rewrite deep test helper imports
- Ban new deep parent-relative imports through the shared lint config
- Move tools.test.ts into focused registry, create, modify, read, and structure files
- Add shared tool test helpers for graph setup and tool lookup
- Preserve all 38 tool tests after the split