The browser's requests to a server now open library and plugin directory
connections, so the panels work there as on the desktop. A plugin installed in
the browser lasts as long as the page.
* M3: library analytics list how each style and variable is used
A file reports how many of its layers use each of a team library's styles and
variables, and Show usage lists them by a Type choice with their layers and
files. Layers in instances count for their component. Also ignores plugins/target.
* M3: the style analytics test waits for the instance report too
Found while testing crates/color/src/lib.rs:247 (css_args, used by the rgb/rgba and hsl/hsla arms of parse_css at :159 and :178)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A widget description can have a line, 100 long and black and 1 thick unless told
otherwise, and an SVG that comes in as locked layers, scaled to fit a size and
within the widget's 256 layers. The Counter example uses both.
Both reports were checked by running them and are already fixed on main. Pins a rounded rectangle or frame stroking only the side asked for in every alignment, and SVG font families and ids leaving out characters XML forbids.
The desktop shell swallows a plain Cmd/Ctrl+V, press and release, and sends an
image paste event; FigCraft ignored it and waited for a release that no longer
comes. The browser's paste event gives eframe only text, so the page now reads
the image from it, under the same limits.
Bound variables with an iOS or Android code syntax are named in generated SwiftUI and Compose in place of their value: paints, stroke weight, sizes, corner radius, gaps, padding, opacity and text. A variable with none for the platform keeps the value.
Choosing Image shows a checkered placeholder and Upload from computer instead of a dialog at once; an image paint shows its asset preview. A file dropped on the open picker replaces the image, keeping the fill mode, crop, position and adjustments (image.replace, one undo step; paths capped as image.place's). The browser's file picker reaches the tab too. Also fixes a race in library_detach_tests.
Found while testing crates/render/src/draw.rs:190-240 (stroke_area, the Inside/Outside ring fast path)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Gap 10: a file counts the library instances it places, creates, pastes or duplicates and reports them like its detaches. The server counts what a file's totals grow past their highest in the current week and keeps a year of weeks (actions.json). library.usage {days} and Show usage give insertions and detachments over the past 30 days, 90 days or year. New variants are appended; an older server refuses them and Show usage says so.
A request line can carry timeoutMs beside id and method, capped at 600,000 ms; without it the wait stays 60 s and older apps ignore the field. figcraft-cli mcp --attach PORT --timeout SECONDS sends it with every request and reads the answers that much longer. A timeout's message says how long the request waited.
* M11: a widget opens its plugin's panel (plugin API 5)
Gap 6: a widget callback's ui_show opens the plugin's panel and keeps the module loaded; the panel's callbacks get the widget's id and state and redraw it in the same undo step. Older plugins' ui_show from a widget still opens nothing. The Counter example sets its count from a panel.
* M11: open_panel takes its opener as one value
Gap 14: variable.extendCollection and Extend collection on a collection's row in the Variables editor. Extensions follow edits of the collection they extend at once (normalising keeps them in step whenever variables change), extensions extend again, and a loop of extensions from a hostile file comes apart into plain collections.
* M4: the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks
* M4: the Create from color window's collection picker is its own function
An odd-length pattern is drawn repeated ([4, 2, 1] as [4, 2, 1, 4, 2, 1]),
so editing its Dash as three values changed the second gap. The pattern is
written out repeated first and only the edited entry changes.
library.check refused nothing while automation roots confined it: it
counted libraries it never asked about and remembered them as yours. It
now refuses before remembering any. library.updates and library.list
answered a refused check with the team library cached earlier, as if the
server had nothing newer; the refusal now reaches them instead of
falling back like an offline server. The MCP and control protocol docs
say that a root on a network share still reaches that file server.
The UI starts a request through its gate (a new Running state) just before running it and never runs one the transport already cancelled. At the timeout the reply says it was dropped and not applied, or that it is still running and will take effect; a reply that just made it is delivered. Only screenshots and input blame the window.
* M12: an ended vote's results say who voted for each object
* M12: a board keeps earlier votes' results, listed in Results and deleted one at a time
* M12: the vote Results popover is drawn by small helpers, and its stepper, rows and Clear are tested
Fills every key the Finnish catalog was missing against the current
English catalog and command set (missing_translations_report: fi 0 / 0 / 0),
following the catalog's existing terms (sisämarginaali, täyttö,
reunaviiva, tehoste, ilmentymä, yhdistin, taulu) and its space before %.
No existing row is changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stroke settings show the first dash and gap only, and editing either
wrote back [dash, gap], so an imported pattern like [4, 2, 1, 2] collapsed
on the first edit. Each field now changes its own value and keeps the
rest of the pattern.
The roots confined files only: a confined agent could still publish the
open document to any server, host it on the network or join a session.
The engine reaches the network only through the live link, which now
refuses a call made while the roots confine it (hosting, joining,
publishing, sharing, team libraries, plugin directories), and skips the
background team library checks. The MCP and control protocol docs say so.
The timing assertion could fail under load. finite_check_candidates is now its own function, and the test checks that a rename among 200,000 layers looks at only a handful of them.
check_finite_geometry went over every layer of the document after every
command, so each edit cost time in proportion to the file, not the edit.
It now walks Document::changed_ids against the version the command
started from; the imbl diff skips what the two versions share.
100 renames among 200,000 layers: ~0.60 s before, ~0.02 s after.