Commit Graph
3465 Commits
Author SHA1 Message Date
Trevor BrownandClaude Fable 5.1 453e0b9af8 M8: a whole-turn hue is 0, a negative hue counts back, and calc() is refused (tests)
Review asks on #1773: `css_hue_wraps_a_whole_turn_and_calc_is_not_a_number` checks that 360, 1turn,
400grad, 2 pi rad and -1turn all read as hue 0, that -90deg, -0.25turn, -100grad and 630 all read as
270, the same in oklch() and lch(), and that `calc(NaN)`, `calc(infinity)`, `calc(1)` and
`calc(1turn)` are refused as non-numbers rather than read as 0 or clamped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 23:48:36 -05:00
Dylan M. Taylor ee4edc7364 perf(engine): replace linear scans that made document-sized inputs quadratic (#1921)
* perf(engine): replace linear scans that made document-sized inputs quadratic

enum_of re-classified the whole string for every character; widget names,
comment mentions and devcompare's common siblings each searched a growing
Vec; data.fill's export names and codegen's class names retried every
suffix from 2 against a list, cubic in the number of copies of one name;
and a batch rename template had no length cap although it is expanded
again for every match.

* address review: report the pattern in its own length error
2026-11-03 23:48:20 -05:00
Dylan M. Taylor ddb8bfa665 fix(format): cap the bytes copied to fill a variable's missing modes 2026-11-04 04:44:24 +00:00
Dylan M. Taylor ad9894a90f fix(doc): give render bounds a visit budget so a repeating child list ends 2026-11-04 04:31:52 +00:00
Dylan M. Taylor 44bec70a47 fix(pathops): cap an arrowhead's weight like a stroke's width 2026-11-04 04:25:42 +00:00
Dylan M. Taylor 972ccc6a17 fix(engine): count each id Shape Builder is given once (#1974) 2026-11-03 23:01:44 -05:00
Dylan M. Taylor 519ffd4987 fix(format): import a Sketch page once however often the file lists it (#1973) 2026-11-03 23:01:19 -05:00
Dylan M. Taylor 5f022aa8a9 perf(engine): index variables once when following a library's alias chains (#1968)
* perf(engine): index variables once when following a library's alias chains

* address review: return before indexing variables for an empty seed set
2026-11-03 23:00:54 -05:00
Dylan M. Taylor 898a08b0ce perf(pdf): collect each text style's outlines and decorations in one pass (#1965)
* perf(pdf): collect each text style's outlines and decorations in one pass

* address review: build each style's paths inside the style loop
2026-11-03 23:00:28 -05:00
Dylan M. Taylor 3c7bae6a04 perf(vectordrawable): resume each repeated name's suffix search where it stopped (#1964)
* perf(vectordrawable): resume each repeated name's suffix search where it stopped

* address review: continue from a longer stem when the name suffix counter is exhausted
2026-11-03 22:59:53 -05:00
Dylan M. Taylor 975544b895 perf(collab): memoise layer depths in diff and skip the sibling scan for a new child (#1961)
* perf(collab): memoise layer depths in diff and skip the sibling scan for a new child

* address review: bound the depth walk by the document's node count
2026-11-03 22:59:27 -05:00
Dylan M. Taylor c1dfa9ecee fix(ui): write a plugin's offered file through the confined file helper (#1960) 2026-11-03 22:59:02 -05:00
Dylan M. Taylor bcc611b3a9 fix(engine): hold a plugin's text.preview to the permissions of the command it previews (#1959)
* fix(engine): hold a plugin's text.preview to the permissions of the command it previews

* address review: check PREVIEWABLE before recursing into a preview's inner command
2026-11-03 22:58:27 -05:00
Dylan M. Taylor 1be869793f fix(doc): bound the string bytes an expression holds and stop concatenating at the cap (#1950)
* fix(doc): bound the string bytes an expression holds and stop concatenating at the cap

* address review: count only appended characters; keep validating operands past the cap
2026-11-03 22:58:02 -05:00
Dylan M. Taylor 16a79570f1 perf(svg): hash an embedded image's bytes once per import (#1945)
Every shape painted with a raster pattern hashed the pattern's whole
encoded image again to find its document key, so one embedded image reused
by many shapes multiplied the hashing work without adding any image data.
The key is kept per buffer for the import.
2026-11-03 22:57:28 -05:00
Dylan M. Taylor 32b658fe71 perf(engine): list a few step names in a missing-reference error (#1941)
Every missing $ref rendered the batch's whole name table into its error,
and with onError: continue each such error was kept, so a compact batch
cost its name count per failed step. The hint names twelve steps and
counts the rest.
2026-11-03 22:57:03 -05:00
Dylan M. Taylor b9b64a8755 fix(render): check the nesting limit for mask content and bound GIF frame buffers (#1925)
* fix(render): check the nesting limit for mask content and bound GIF frame buffers

A mask's content is drawn straight from mask_pixels, not through
draw_layer, so masks nested inside masks recursed past the 256-level limit
and down the stack. GIF decoding budgeted frames by the logical canvas but
left the decoder's allocation limits off, so one frame descriptor could ask
for a gigabyte before the frame came back to be checked.

* address review: give the GIF decoder one frame of working room beyond the animation cap
2026-11-03 22:56:28 -05:00
Dylan M. Taylor f54e099a48 fix(mcp): render prompt placeholders in one pass over the template (#1922)
Each argument was substituted into the already rendered text, so a value
holding placeholders was expanded again by every later replacement and a
short request could grow the prompt without bound. The template is now
scanned once and argument text is never read as syntax.
2026-11-03 22:27:10 -05:00
Dylan M. Taylor 6da4c30cae fix(svg): refuse a document compressed twice and charge a refused embedded one (#1924)
One gzip layer is unpacked within the import budget, but usvg gunzips what
it is handed again with no limit, so a doubly compressed document or
embedded image expanded outside the budget. A compressed embedded image
that failed its budget also left the shared allowance untouched, so every
sibling image could repeat the work; a refusal now spends what was left.
2026-11-03 22:26:21 -05:00
Dylan M. Taylor 9475a815da fix(layout): end the fill distribution loop when no child can freeze (#1926)
Each iteration froze the children whose clamping passed a 1e-9 tolerance,
but it compared the aggregate violation against the same tolerance: several
clampings each under it could add up past it, so no child froze, the
pending set never shrank and the loop never ended. An iteration that
freezes nothing now settles the remaining children as clamped.
2026-11-03 22:25:33 -05:00
Dylan M. Taylor 3b4de34efe fix(doc): list a page once however often a file lists it (#1927)
Loading accepted a page id repeated in the pages list and repair kept the
repeats, so import, Fig and Penpot export and every page walk visited and
retained that page's subtree once per listing. validate now reports a
repeated page and repair drops the repeats.
2026-11-03 22:24:44 -05:00
Dylan M. Taylor 417ad412a2 perf(engine): keep each events.poll topic once (#1935)
The topic list was validated but not deduplicated, and it is scanned for
every retained event while the log's lock is held, so a request repeating
one topic thousands of times multiplied that work. Repeats collapse and a
list naming all is just all.
2026-11-03 22:23:52 -05:00
Dylan M. Taylor df07ac2d09 fix(engine): never let document text close the generated HTML style element (#1955) 2026-11-03 22:23:03 -05:00
Dylan M. Taylor 88851996a0 fix(format): a saved file replaces its predecessor with the same permissions (#1957) 2026-11-03 22:22:03 -05:00
Trevor BrownandClaude Fable 5.1 7be7c054eb M12: a connector joined to a layer inside a moving section is no snap target; the docs say which connectors share a run (#1916)
Review: a section moving carries its stickies and their connectors, so those connectors and
their labels are no snap targets for the section either (an end counts as carried when its node
or any ancestor is moving). The parity rows, the END_GAP doc and the test's doc no longer say
connectors from one side run together: elbows share their first run along the normal, curves
leave tangent to it, straight ones fan out from the same point.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 21:46:34 -05:00
Trevor BrownandClaude Fable 5.1 2dc7bed2a0 M12: connectors attached to a side start 4 pt outside the border (#1916)
A connector end attached to a side was routed from the middle of that side, so the stroke's
centre line started on the border and half its width plus the round cap lay on the fill, and
several connectors leaving one side piled up into a knot sticking into the card. FigJam starts
the line a few pixels outside the border and connectors leaving one side run together along the
side's normal before they fan out (the issue's screenshots). `board_route::END_GAP` (4 pt): an end
attached to a side (`RouteEnd::Box`) starts that far outside it along the side's normal; an end
placed by hand on the object (`RouteEnd::On`) keeps its exact point; a free end is unchanged.
The pinned route coordinates in the engine, format and UI tests move with it; the overlay's
attach points stay on the border. Docs: the Connectors rows, status line and history of
boards-parity.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 21:08:19 -05:00
Dylan M. Taylor 9aad731693 address review: use the request-reply timeout for the reply deadline 2026-11-04 01:29:49 +00:00
Trevor Brown 2c998f783f Merge remote-tracking branch 'upstream/main' into fix/pencil-dark-background 2026-11-03 20:28:23 -05:00
Trevor Brown 94c4370c98 Merge remote-tracking branch 'upstream/main' into fix/page-background-opacity 2026-11-03 20:24:36 -05:00
Trevor Brown 35d64c7478 Merge remote-tracking branch 'upstream/main' into fix/web-events-clock 2026-11-03 20:20:08 -05:00
Trevor Brown 685a3ddae0 Merge remote-tracking branch 'upstream/main' into fix/svg-import-stretch-and-dash-offset 2026-11-03 20:18:56 -05:00
Trevor Brown dfac369df0 Merge remote-tracking branch 'upstream/main' into fix/css-color-none-and-units 2026-11-03 20:17:35 -05:00
Trevor Brown 876abb6b89 Merge remote-tracking branch 'upstream/main' into fix/variables-header-gap 2026-11-03 20:12:15 -05:00
Dylan M. Taylor 4cd7313ca2 address review: give plugins a fixed message for library failures 2026-11-04 00:20:16 +00:00
Dylan M. Taylor 4bb35d5266 perf(collab): check a replaced list's entries against sets, not a scan per entry (#1962) 2026-11-03 17:58:51 -05:00
Dylan M. Taylor 3265cbca40 perf(engine): look up a component's asset root once per usage report, and skip mode migration without instances (#1966) 2026-11-03 17:58:39 -05:00
Dylan M. Taylor fb4d0b5035 perf(doc,engine): rebuild a widget by removing its children in one pass (#1967) 2026-11-03 17:58:27 -05:00
Dylan M. Taylor cfb29a4a96 perf(text): compute each glyph's cluster end once per run (#1963) 2026-11-03 17:58:09 -05:00
Trevor BrownandClaude Fable 5.1 5eb28b310a M12: the bar above the selection stays away while a quick-create handle is dragged (#1919)
Review: letting a handle go over the selection bar made the next shape behind it. The bar
hides while a "+" is dragged, as it does for any drag (board_connect::dragging), so there
is nothing to drop behind; the drop makes the next one where the bar was, and the bar is
back once typing in it ends. Test the_bar_stays_away_while_a_handle_is_dragged; the docs
rows say the toolbar and the bar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 14:47:46 -05:00
Dylan M. Taylor 53ebf1f39c fix(pathops): sanitize a brush's width profile once before sampling (#1948) 2026-11-03 14:40:54 -05:00
MortenHustedandClaude Opus 5.5 024b7fe452 M3: importing a file keeps its own variables and styles when their ids are already taken
`file.import` of a FigCraft file kept the document's style, variable or
collection for every id both files used, and the pasted layers kept
those ids. Ids are numbered per file, so two files made apart share
them, and the imported layers were bound to the document's unrelated
variables, modes and styles.

`append_pages` now gives the incoming file new ids first (`Renames`):
- an incoming style, variable or collection is shared, as before, only
  when the document has the same under its id and everything it names
  is shared too (the same file imported again); a collection and its
  variables are shared only together; library copies keep their ids;
- every other id the document already uses gets a new one from its
  allocators, which now take ids to avoid (the incoming file's own);
- layers' style links, bindings, explicit modes and prototype actions,
  styles' bindings, variables' collections, aliases, calculations and
  extensions, and the renamed collections' mode ids follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:14:20 +01:00
Dylan M. Taylor a23c9df9d6 fix(engine): escape Vue attribute values that follow literal text in a Code Connect template 2026-11-03 19:03:55 +00:00
Trevor BrownandClaude Fable 5.1 96ef28cc67 M12: quick-create drag ends when the selection moves; no drop on the toolbar (#1919)
Review: a drag whose object is no longer the selected one is dropped from egui's data at once
(it lingered when the selection moved to another sticky or shape and could still act once the
first was selected again); a handle let go over the floating toolbar, which lies inside the canvas
rectangle, makes nothing, like one let go over a panel.

Tests: a_drag_ends_when_the_selection_moves_to_another_shape, letting_a_handle_go_over_the_toolbar_makes_nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 13:42:03 -05:00
Dylan M. Taylor 4682c9dd75 fix(cli,ui): escape control characters in document and plugin text sent to the terminal 2026-11-03 18:03:47 +00:00
Trevor BrownandClaude Fable 5.1 246a89733c M12: quick-create drag: stickies and shapes in sections, parent coordinates, the handle's side, no drop off the canvas (#1919)
Review: the drop target is the topmost sticky or shape under the pointer, sections and frames
looked into (board::sticky_or_shape_at, instead of the stamp's any-layer hit test on the page's
children); the drop point is placed in the parent's coordinates; the connector leaves by the
handle's side (as the preview draws it) and arrives by the facing side; a handle let go off the
canvas makes nothing; the parity rows on quick-create handles agree.

Tests: dropping_a_handle_on_a_shape_inside_a_frame_joins_it, letting_a_handle_go_over_a_panel_makes_nothing,
the_next_one_goes_where_it_is_dropped (frame case).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 12:44:59 -05:00
Trevor BrownandClaude Fable 5.1 d48aa59ec7 M12: drag a "+" handle to drop the next shape anywhere or join another object (#1919)
The quick-create handles only took a click. As the help says ("drag the preview to any location
on your board"), a handle can now be dragged: the preview follows the pointer and, let go on empty
board, the next sticky or shape is made there, centred on the pointer, joined by a connector whose
ends take the sides facing each other (board.addConnected gains at: [x, y]); let go on another
sticky or shape, a connector joins the two instead (board.connector), that object outlined while
over it. figcraft_engine::board::object_at exposes the hit test a stamp uses. The click is as
before.

Tests: board_connect_tests (two), board_connected_tests::the_next_one_goes_where_it_is_dropped.
Docs: boards-parity row, status line and revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 11:52:41 -05:00
Davit MatchakhelidzeandClaude Sonnet 5.5 ff564b1e16 i18n(ka): add 6 text-wrap strings
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-11-03 19:58:08 +04:00
Dylan M. Taylor d744379e60 fix(engine): refuse document URLs with shell syntax or a malformed authority
webbrowser on WSL hands an address to cmd.exe /c start, escaping only &
and ^, so a document link holding | (or < > " `) ran what followed it
with the viewer's authority; and an address its parser can't read is
reopened as a file path. Characters never valid in a URL unencoded are now
refused, and an http(s) authority must be made of host characters.
2026-11-03 15:46:13 +00:00
Dylan M. Taylor 02db900cc8 fix(engine): keep plugins out of branches, saved versions and library links
A read-only plugin could archive, merge, create or update a branch: branch
state lives beside the document, so neither the command's history flag nor
the document Arc check saw the write. Any plugin could read a saved version
through dev.compare's version parameter although version.* is denied, and
library.list handed plugins each library's path, which for a server library
is the share link with its token.
2026-11-03 15:46:13 +00:00
Dylan M. Taylor c673996fe7 fix(engine): install a directory plugin only with the permissions that were shown
The confirmation showed the directory entry's access and commands, but the
downloaded bundle's manifest was registered as is, so a directory could
show a read-only plugin and install one that edits the document. A manifest
asking for more than the entry showed is refused before it is saved.
2026-11-03 15:46:01 +00:00