Commit Graph
2691 Commits
Author SHA1 Message Date
Dylan M. Taylor ffb4fc7e3e refactor(ui): simplify app and presentation handlers 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 252d909427 refactor(ui): simplify widgets and dialogs 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 927ceb0638 refactor(ui): simplify panels and menus 2026-10-28 16:00:36 +00:00
Dylan M. Taylor e98b97de2b refactor(engine): simplify session and test helpers 2026-10-28 16:00:36 +00:00
Dylan M. Taylor d87f9effe1 refactor(engine): simplify asset and code generation helpers 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 5e16683963 refactor(engine): simplify canvas and board handlers 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 23f57c8660 refactor(engine): simplify command handling for strict clippy 2026-10-28 16:00:36 +00:00
Dylan M. Taylor bafda992a7 docs: clarify crate lint placement 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 4268bd859f refactor(fig): split mapping and lint test helpers 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 6ebcf6b511 refactor(text): split shaping and layout finishing 2026-10-28 16:00:36 +00:00
Dylan M. Taylor c618e402fa refactor(format): replace wildcard imports 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 99afdd99ab docs: document workspace lint inheritance 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 8591d44909 refactor(doc): declare node property dependencies 2026-10-28 16:00:36 +00:00
Dylan M. Taylor 10bb163626 fix(xtask): apply workspace lint policy 2026-10-28 16:00:35 +00:00
Dylan M. Taylor e1a223bb1f refactor(layout): split flow layout phases 2026-10-28 16:00:35 +00:00
Dylan M. Taylor c420613efa chore(clippy): enforce strict workspace lint policy 2026-10-28 16:00:35 +00:00
watchakorn-18kandClaude Sonnet 5.5 38ab0eaa6b docs(ui): document the right-click label pick and its tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wxxFg3gana4zaUKnyirTC
2026-10-28 22:27:29 +07:00
watchakorn-18kandClaude Sonnet 5.5 df6062d7fb fix(ui): right-click on a frame's name label picks that frame
The context menu looked only at what is under the pointer on the page, so a name label drawn over
its section selected the section. Copy link to selection then gave every frame in that section the
section's link. Labels now win, as they do on a press.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wxxFg3gana4zaUKnyirTC
2026-10-28 22:14:06 +07:00
NetterSilverandClaude Haiku 5.5 877ac1fd5e Reject non-page ids in page.duplicate
Found while testing crates/engine/src/cmd/page.rs:96 (page.duplicate, `let src = id(p, "id").unwrap_or(s.page()?)`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:38:32 +01:00
samhyunandClaude Fable 5.1 3773a60801 M8: the browser's wait after a preview never exceeds the render it may save
With a fixed 120 ms wait, a page whose full renders take 15–100 ms was held coarse for 120 ms
after a key zoom or a page switch, where before it was refined in the next frame. The wait is
now the last full render's time when that is shorter than `SETTLE_MS`: a wait never costs more
than the render it may save, and on a heavy page (seconds a render) it is 120 ms as before.

Also from review: the test that the wait counts from the preview no longer times a preview
render (a margin of a few milliseconds under load); a sleep between the request and the poll
stands in for the render, with the whole settle as margin. `Inline::in_flight` says that a
preview's wait counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-28 22:53:07 +09:00
phonedu 8e6b3bf449 M12: MCP comment feedback and live change events
Comments become the task queue for an attached or headless MCP client: comment.prompt returns a bounded Markdown brief, the Comments panel gains Copy for AI, and the MCP server exposes fix_comments, watch_comments, figcraft://comments and wait_for_changes. Mutating comment.* commands append to a bounded in-memory event log; the control channel's events.poll long-polls it without the UI thread, and a successful batch commits its events only if the whole batch lands. No model, network dependency, asset or file-format change.
2026-10-28 21:42:31 +08:00
samhyunandClaude Fable 5.1 8c951810ab M8: a test for same-zoom requests before a zoom refines; the gaps status line
From the review of #1468: a pan or an edit at the new zoom before its full render has run
renders from scratch too, so it shows a preview and restarts the wait instead of running a
blocking full render between it and the next step; once refined, requests at that zoom build
on the kept image and are refined at once. The new test pins both down. The status line of
`docs/gaps.md` now carries the date of the gap 7 entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-28 22:29:55 +09:00
samhyunandClaude Fable 5.1 f24a8312f7 M8: the browser refines a zoom only once it has paused
The browser renders on the UI thread. A zoom showed its coarse preview and then, in the very
next frame, ran the full render, so every wheel step of a continuous zoom had a blocking full
render between it and the next: on a heavy page each one took seconds, and the zoom stuttered
for as long as it went on.

The browser's worker (`Inline`) now refines a preview only once no newer request has come for
`SETTLE_MS` (120 ms) after the preview was shown: the zoom has paused. Each wheel step still
shows its preview at once, and the worker asks for a repaint when the wait is over. Pans and
edits have no preview and are refined at once, as before. The desktop has a render thread of
its own and is unchanged. A zoom passing through a scale also no longer spends a full render
there.

The wait counts from the preview being handed over, not from the request: rendering the
preview can itself take longer than the wait (the page is blocked meanwhile, so no newer
request could have come), and counted from the request the full render would follow in the
very next frame, as before.

Measured in Chrome on a synthetic page of 20 images, 60 shadowed rectangles and two
background blurs (a full render takes about 3.7 s, a preview about 250 ms), 20 wheel steps in
and 20 out, 50 ms apart, three runs each: the frame interval's p95 went from 3.7 s to 240 ms,
the frames over 100 ms from 79–80 (one full render per step) to 39–41 (one preview per step),
and the zoom took 10–12 s instead of 141 s. The one full render after the pause still takes as
long as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-28 22:10:27 +09:00
NetterSilverandClaude Haiku 5.5 2199dcb2da Reject non-numeric paddingHorizontal/paddingVertical in node.set
Found while testing crates/engine/src/cmd/props.rs:623-634 (the "paddingHorizontal" and "paddingVertical" arms)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 13:04:10 +00:00
hussain-hz 636096fad3 M10: add Arabic draft interface catalog
Translate 2508 interface entries from English, keep count-neutral plurals, and gate the web catalog on Arabic font readiness. Record AI assistance and pending native review.

AI-assisted-by: OpenAI Codex
2026-10-28 15:16:31 +03:00
Skyfuller1303 faa96926f4 M4: keep stroked whole-shape merges and bound region paints per file
Merging every region of one stroked shape is a no-op again: only fill
parts count, so a vector with several painted regions still merges.

The region paint import budget now covers the whole file instead of
resetting for each layer, so instances of a main with large region paint
stacks cannot multiply the work.
2026-10-28 11:59:53 +01:00
Skyfuller1303 c785e96b1c Merge remote-tracking branch 'origin/main' into feat/fig-vector-region-fills
# Conflicts:
#	crates/format/src/fig/export.rs
#	crates/format/src/fig/map.rs
2026-10-28 10:12:00 +01:00
Skyfuller1303 4b13e4e3cf M7: line the Home tab's divider up with the icon rail
The Home tab started at x=8 and was 44 wide, so its divider sat at x=52 while the icon rail below is 56 wide. Without inset window buttons the Home tab now ends at the rail's width (shared RAIL_WIDTH constant), so the divider continues the rail's edge. The inset (macOS) title bar keeps its 44-wide Home tab.
2026-10-28 10:00:02 +01:00
MortenHustedandClaude Opus 5.5 8fadd5d8e5 #1460: file.exportSvg's doc says which of file.export's options it takes
"(as file.export)" read as all of them, but the PDF and XML options are
refused there, so it now says they are file.export's SVG options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:46:27 +01:00
MortenHustedandClaude Opus 5.5 3567bef862 #1460: commands and tools that pass their params on list what they pass
file.placeImage runs image.place's code and file.exportSvg file.export's,
so their docs now list the names those read (width, height, target, mode;
scale or size and the export options). MCP's create_node passes points,
closed and select to node.create, and pointer and key read ctrl, so their
schemas list them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:35:23 +01:00
Skyfuller1303 219a9ea0b1 M4: require FILL styles for vector region fill links 2026-10-28 09:29:22 +01:00
MortenHustedandClaude Opus 5.5 40b9288f70 M1: opening a file that is already open says when it changed on disk, and can read it again
`file.open` on a path already open in a tab switched to it and returned
its index, even when another process had changed the file since, so the
caller kept looking at the old copy with nothing saying so (#997). The
tab's disk stamp from #1330 (`DocState::on_disk`) now tells:

- the result says `stale: true` when the file on disk differs from what
  the tab read or last saved;
- `reload: true` reads the file again into the tab, as Revert to saved
  does (refused while the tab has unsaved changes or is in a live
  session), and the result says `reloaded: true`.

MCP's `open_file` takes `reload` and passes it on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:19:56 +01:00
MortenHustedandClaude Opus 5.5 27c11858e1 M0.9: MCP tools and batch steps refuse names they don't take (#1413)
`run_command {"command": "inspect.code", "args": {...}}` ran the command
without its params, on the selection. MCP tool arguments are now checked
against each tool's input schema (and the objects it describes inside,
such as a batch's steps; open objects like `params` take anything), and
a step key other than `command`, `params` and `as` is refused in
command.batch, figcraft-cli scripts and the MCP batch tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:06:59 +01:00
MortenHustedandClaude Opus 5.5 ff4fe00294 M0.9: commands refuse parameter names they don't take (#1413)
A misspelt parameter name was ignored and the command reported success,
answering as if the parameter hadn't been given (`comment.list
{"filterr": "open"}` listed resolved threads). `Session::execute` now
refuses names the command's params doc doesn't list, naming them and
what the command takes, before the command runs, so every path into a
command (CLI, batches, control channel, MCP, plugins, the UI) gets the
check.

The names come from the existing docs, parsed once with the registry:
each top-level item's first word and the words joined to it by `|`, `/`
or `+`, bracketed text and what follows `:`, `=` or `→` being
description, alternative groups (`{x, y} | {points}`) included. `ids`
and `id` are always taken (`params::targets` reads either); node.set,
whose doc is its props list, keeps its own check.

The junk sweep runs each junk object as given and cut down to the names
a command takes, so the junk values still reach the parsers. Tests that
passed names their command never read (comment.resolve's author,
collab.host's link, node.get's depth) pass only what it takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:06:58 +01:00
MortenHustedandClaude Opus 5.5 64e648ae7e M0.9: command docs list every parameter their commands read
Before commands can refuse names their docs don't list (#1413), the docs
have to list what the code reads. These are the top-level names the run
code (and every helper it hands the params to) reads that its doc left
out, all checked against the code; nothing changes behaviour:

- creation: node.create (characters, points, closed), frame.create,
  shape.*, text.create and vector.create (index, name, select),
  image.place (name)
- aliases the code already took: comment.* (id | link), annotation.add
  and annotation.edit (label | text), text.set (characters | text),
  text.listStyle (style | list), fill.add and stroke.add (paint | color),
  proto.interaction.add/set (durationMs | duration_ms, delayMs | delay_ms)
- options: file.export and export.update (contentsOnly,
  includeBoundingBox, svgOutlineText, svgSimplifyStroke), comment.next
  (frame, resolved), layout.suggest (direction), variable.create
  (description), vector.edit.widthPoint (side), collab.share (key),
  collab.manageFile (access, label, link, name), fill.pattern (visible,
  blend)
- docs whose shape hid a name: proto.interaction.add (a stray `}` closed
  the object before delayMs; dest was only in a trailer),
  proto.interaction.set (patch fields in a second object), collab.join
  (a top-level comma inside the link examples), pref.uiScale (commas in
  the value's description)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:06:33 +01:00
MortenHustedandClaude Opus 5.5 81769cbe74 #1458: test layoutMode beside the keys that tune it too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:51:02 +01:00
MortenHustedandClaude Opus 5.5 f98da27882 #1458: name the key that turns auto layout off
`{"layoutMode": "NONE", "wrap": true}` was refused with a message
naming `layout`, a key the call didn't have. The error now names the
key the call used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:49:34 +01:00
MortenHustedandClaude Opus 5.5 2ff69183d7 M7: CSS system font names draw in macOS's System Font
`system-ui`, `-apple-system`, `BlinkMacSystemFont` and
`.AppleSystemUIFont` join the names `apple_alias` resolves to the
installed System Font, as SF Pro and SF UI already do. SVG import keeps
a family the SVG lists when a font answers to its name, so on a Mac
`font-family="-apple-system, system-ui, sans-serif"` keeps
`-apple-system` and draws in System Font; elsewhere the generic
stand-ins apply as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:35:46 +01:00
MortenHustedandClaude Opus 5.5 b45c41a02c M2: auto layout settings in one call don't depend on how their keys are spelled
`apply_order` gave `layout` the rank of every other key, so the props
map's alphabetical order decided: `gap` applied before an object
`layout`, which then replaced the whole auto layout (the gap went back
to its default), and `padding` applied after `layout: null` and turned
auto layout back on.

`layout`/`layoutMode` now apply right after the geometry keys and
before the keys that tune them, and auto layout keys beside a `layout`
of null or "none" are refused, naming them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:35:33 +01:00
NetterSilverandClaude Opus 5.5 2d02f289ec Keep session joins working when a document has out-of-range ids
Fixes #1067

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 02:16:00 +01:00
Davit MatchakhelidzeandClaude Opus 5.5 a7ea510c65 i18n(ka): მხარდაუჭერელის in "Ignore unsupported"
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-28 05:11:34 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 610f40bd78 i18n(ka): Georgian for 76 strings added since the language landed
Click = დაწკაპეთ in the voting hint, as in the catalog's other 24 click hints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-28 04:00:21 +04:00
aiongg 90054a89c4 Merge pull request #1399 from Jeremy8776/feat/install-scope-policy
Windows installation policy: FigCraft
2026-10-27 17:09:27 -04:00
aiongg 2ed7aee28d ci: use storytold/craft-common-actions at the merged installer-policy commit 2026-10-27 20:51:59 +00:00
Guillermo Bueno fe7872708f M4: draw bounded dots for zero-length round dashes 2026-10-27 21:11:32 +01:00
Guillermo Bueno 54eced7ffb M6: address offline Penpot review regressions 2026-10-27 20:48:08 +01:00
Jeremy 738b218837 fix(installer): check the exact architecture-specific machine folder
The pre-signing check matched any folder named Program Files, so it rejected
a valid MSI when Windows resolves Program Files elsewhere, accepted the 64-bit
root for an x86 package and accepted a different app name. Build the expected
folder from the package architecture's resolved root and the policy name.
2026-10-27 19:08:54 +00:00
Filip Strajnar 6cc466a878 M6: credit Filip Strajnar for the FigJam import fix 2026-10-27 20:04:54 +01:00
Guillermo Bueno 7ea161f33f M6: merge main and isolate offline Penpot import/export 2026-10-27 18:59:49 +01:00
Filip Strajnar 4ea2d29754 M6: preserve FigJam fallback outlines and sticky bodies 2026-10-27 18:47:27 +01:00