Previously the typing indicator only showed during 'submitted' status
(before first token). Once streaming began, gaps between tool calls
had no visual feedback — user saw silence and thought AI stopped.
Now isThinking is true when:
- status is submitted (waiting for first token)
- status is streaming and last part is a completed tool call or
step-start (AI deciding what to do next)
- set_layout: only change spacing/align/padding when explicitly passed.
Previously spacing/align/padding defaulted to 0/MIN, wiping existing
values when AI only wanted to change one property.
- search_icons: client-side slice to enforce limit (API ignores small
limits, returned 31 results for limit=1)
- system-prompt: issues from describe are mandatory fixes, not advisory.
Final describe after set_* calls is required workflow step.
- iconify: convert <circle>, <rect>, <ellipse>, <line>, <polygon>,
<polyline> to path d-strings. Fixes lucide:search (missing circle),
lucide:user (missing head), lucide:mail (missing envelope), etc.
- describe: show justify/items/sizing in layout description, detect
icon role, recurse issues into children at every depth level
- describe-issues: split layout checks into describe-layout-issues.ts
- New checks: fill without flex parent, absolute in flex, nested flex
without fill, duplicate sibling names, stroke color/weight mismatch,
circular frame without clip, excessive nesting, same fill as parent,
image placeholder without content, sibling height inconsistency,
text wrapping detection, alignment issues (between+HUG, between<2,
stretch with all fixed, equal children packed without gap)
- system-prompt: remove justify="evenly" — not supported
- Remove docs/mcp-codegen-plan.md (internal planning doc, not for repo)
- Remove lol.fig test fixture left in repo root
- Fix inline type imports in codegen.ts — use top-level SceneGraph import
- Move get_codegen_prompt out of core/ALL_TOOLS into MCP server only
(core must be browser-safe, node:fs is not available in browser)
- Remove skill from packages/mcp/skills/ — content saved to #97
- Slim down codegen.md overlap note (canonical reference stays there)
Closes review comments. Skill npm publishing tracked in #97.
- Zero icon frame x/y after appendChild to parent — fixes icons appearing
at viewport origin instead of inside their parent container
- Add name→names fallback to insert_icon for backward compatibility with
single-icon calls (name param still works, returns legacy format)
- Add emoji section to system prompt: emoji show as □ tofu on canvas,
use insert_icon for UI iconography, emoji only in user content/reactions
- Strengthen flex requirement in layout docs: every parent with fill
children MUST have flex='col'|'row'
- Track per-run step count, warn AI when 5 steps remain via _warning
field injected into tool results
- Show 'Continue' button in chat when step limit (50) is exhausted
- Add step budget section to system prompt so AI plans ahead
- Reset run step counter on each new agent call via prepareCall
- Add calc tool (recursive descent parser) so AI uses exact math
instead of error-prone mental arithmetic for layout sizing
- Make set_layout direction param optional — keeps current layoutMode
when only changing spacing/padding (eliminates ~50% of NO-OP mutations)
- Add set_layout to SKIP_ARGS in NO-OP detection
- Update system prompt: calc in workflow, mandatory before rendering
into fixed-size containers
AI was ignoring the soft suggestion and generating 130+ element JSX
in a single render call. Now uses explicit skeleton example, numbered
steps, and 🚫 markers to enforce incremental building.
Complex designs must be built layer by layer: skeleton first,
then fill sections one at a time with describe checks between.
Prevents nested containers missing w="fill" and collapsing.
- Mark transport dirty instead of resetting chat on settings change
- Rebuild Chat with new transport while preserving message history
- Watch apiKey and customBaseURL changes in addition to model/provider
- Fix provider select z-index inside settings popover (z-52 + isolate)
- Prevent popover dismiss when interacting with select dropdown
layout.ts: When text fills width (stretch in col parent), MeasureFunc
now uses Yoga's constraint width directly instead of clamping to
the 100px default fixedWidth.
prompt: Emphasize that ALL intermediate flex containers need w="fill",
not just the first level. grow inside HUG = zero width.
Decorative elements (bokeh, glows, gradients, star dots) must use
absolute positioning (x/y), not flex layout. Putting 10+ tiny shapes
in a flex container wastes gap space and squashes real content.
configureTextLeaf for HEIGHT mode was calling yogaChild.setWidth(child.width)
unconditionally, which locked text at the 100px default. This blocked
Yoga's alignSelf:stretch from expanding text to the parent's cross axis.
Now skips setWidth when text has layoutAlignSelf=STRETCH in a vertical
parent (w="fill" case). Same fix for the headless fallback path.
Test: text with w="fill" in flex="col" stretches to 260px (300 - 2×20 pad).
export-jsx: Text with layoutAlignSelf=STRETCH (w="fill" in col parent) or
layoutGrow>0 (w="fill" in row parent) was emitting w={computedPx} instead
of letting the fill/grow detection below handle it. Now skips fixed w
emission when text stretches or grows.
describe: wrap layout without rowGap flagged — wrapped rows stick together.
prompt: wrap always needs rowGap.
Tests: 2 new round-trip tests for text w="fill" in col and row parents.
Prompt:
- Progress bar pattern with grow bar bg + overflow hidden
- Don't mix w={N} and grow={N} on same node
- Don't wrap Text in Frame just for sizing
- Vertical dividers must be inside flex=row with h=fill
- Horizontal dividers in flex=col
Describe:
- w + grow conflict detection
- Child Rectangle overflowing parent without clipping
- Vertical divider in column layout (wrong parent)
- Horizontal divider in row layout (wrong parent)
Prompt:
- Text inside fixed-width containers must have explicit w
- Wrap layout column width formula: count chars × 0.6 × fontSize
- Don't guess widths in wrap grids
Describe:
- Text wider than parent available width flagged with fix suggestion
Prompt — full typography guide:
- Type scale (6-8 sizes, ratio ~1.25)
- Weight hierarchy (max 2-3 per design)
- Color hierarchy for light/dark backgrounds
- Line height rules (tight for headings, relaxed for body)
- Line length (45-75 chars), spacing between text elements
- Uppercase only for small labels/overlines
- Alignment guidelines
Corner radius — nested radius rule:
- inner = outer − padding, 0 if padding ≥ outer
Describe checks:
- Child radius exceeding parent or not matching inner rule
- Uppercase text at sizes > 13px
describe now catches:
- Text without color (invisible — fills is empty)
- Shapes without fill/stroke (invisible)
- Icon-sized frames with no visible content
- Dark text on dark background
- grow inside HUG parent (no effect)
- Children overflowing fixed parent
- HUG container where all children use grow/stretch (collapses)
- Cross-axis overflow
Prompt changes:
- Text always requires color="#hex" — no fill = invisible
- Shapes/icons always need bg or stroke
- Button icons highlighted as #1 invisible element source
Two fixes:
1. layout.ts: Add estimateTextSize() fallback (~0.6 × fontSize per char)
for when MeasureFunc returns null (font not loaded) or CanvasKit is
unavailable. Previously fell back to child.width/child.height which
defaults to 100×100 for new nodes, blowing up every HUG container.
2. ai/tools.ts: Only call computeAllLayouts + requestRender + push undo
for mutating tools (def.mutates). Read-only tools (describe, get_jsx,
node_bounds, etc.) were triggering full layout recompute on every call,
wasting cycles and potentially destabilizing layouts during AI sessions.
- New oxlint rule: open-pencil/no-silent-catch — errors on empty catch blocks
- Replace all 8 empty catch blocks with console.warn() logging
- Add worker timeout (30s) and main-thread fallback for .fig parsing
- Fix null crash in renderer filter/picture cache cleanup
- Buffer copy before worker transfer for safe fallback
- Load all font weights including default family (Inter 500/600/700 were skipped)
- Fix weightToStyle mapping: 400 → Regular, not Medium
- Block render loop during file loading to prevent stale fallback renders
- Prefer Figma textPicture when available over buildParagraph
- Clear textPicture when text properties change
- Await font loading before first render on file open and page switch
* Fix per-character text fill colors and vector region loop direction
* Replace rotation handle with corner rotation zones
Remove the rotation handle circle/stem above the selection bounding box.
Rotation is now triggered from corner zones outside resize handles,
matching Figma's behavior. Cursor uses native CSS directional arrows
(n-resize, ne-resize, etc.) that rotate with the node, avoiding
cross-platform SVG cursor issues.
Drop the OpenPencil clipboard format from the editor — Figma's Kiwi
format is the single clipboard path now. Fix the remaining roundtrip
losses:
- textAutoResize: was hardcoded to WIDTH_AND_HEIGHT, now uses actual value
- autoRename: now serialized (was always defaulting to true on import)
OpenPencil format: zero property differences, compressed under 1MB.
Figma format: node count, clipsContent, constraints, arcData,
layoutAlignSelf all verified against the fixture file.
Browser clipboard has size limits — the 4MB uncompressed OpenPencil
format was being silently truncated, leaving only the lossy Figma
format. Now:
- Deflate-compress the JSON payload (3.94MB → 597KB for gold-preview)
- Put OpenPencil format first in the HTML so it survives truncation
- Parse side tries inflate first, falls back to raw for compat
Properties lost during copy→paste via Figma Kiwi format:
- clipsContent: frameMaskDisabled now set for all node types, not just FRAME/GROUP
- horizontalConstraint/verticalConstraint: now serialized (SCALE, CENTER, etc.)
- arcData: now serialized (startingAngle, endingAngle, innerRadius)
- strokeCap, strokeJoin, strokeMiterLimit, dashPattern: now serialized
- layoutAlignSelf: now serialized (STRETCH was lost, defaulting to AUTO)
* Perf: cache label collection, offload .fig compression to worker
Label cache: collect sections/components once per scene change,
filter by viewport on each frame. Eliminates full tree walk during
pan/zoom (~17ms/frame → <1ms on large files).
Export worker: move fflate compression off the main thread to prevent
frame drops during save (451ms+ → non-blocking).
* Perf: worker-based .fig parsing, instance index, non-blocking font loading
- Offload .fig parsing (unzip + Kiwi decode) to a Web Worker
- Add instance index (componentId → Set<nodeId>) for O(1) getInstances()
- Defer graph event subscription during file open to skip redundant syncs
- Make font loading non-blocking — render immediately, load fonts in background
- Copy image buffers before worker transfer to prevent detached ArrayBuffer crash
- Show toast on font load failure and file open errors
- Cache failed Google Fonts families to avoid repeated network requests
- Fix missing ref import in FillPicker
- Yield to UI between parse and layout for responsive loading spinner
- Replace non-null assertions with guards/optional chaining
- Replace useless spreads with Array.from() where mutation-safe
- Remove unused imports, variables, and functions
- Fix no-base-to-string with proper type narrowing
- Fix sort without comparator, new Array() pattern
- Prefix unused parameter with underscore
Warnings: 76 → 42 (remaining are max-lines, justified any casts, vendored code)