Commit Graph
737 Commits
Author SHA1 Message Date
bflatastic 56196eac5a Merge main into #1409
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/session/undo.rs
#	docs/roadmap.md
2026-10-29 11:27:45 +07:00
bflatastic f154011299 Merge main into #1461 2026-10-29 11:19:05 +07:00
bflatastic 4fcc49aa37 Merge main into #1458 2026-10-29 11:15:01 +07:00
bflatastic f99f01d57b Merge main into #1460 2026-10-29 11:09:30 +07:00
BFlat 60ea32f171 Merge pull request #1465 from phonedu/mcp-agent-feedback
M12: MCP comment feedback and live change events
2026-10-29 10:51:24 +07:00
bflatastic 9202b1032f Merge main into #1460 2026-10-29 10:38:06 +07:00
bflatastic 61c5b1c0b8 Merge main into #1407
# Conflicts:
#	docs/roadmap.md
2026-10-29 10:30:23 +07:00
bflatastic aa5c016518 Merge main into #1419
# Conflicts:
#	docs/roadmap.md
2026-10-29 10:30:12 +07:00
bflatastic 0cc380c228 Merge main into #1466 2026-10-29 10:08:11 +07:00
bflatastic 6abddb0f68 Merge main into #1465 2026-10-29 09:55:55 +07:00
BFlat 7f646aac24 Merge pull request #1468 from samhyun/perf/web-zoom-settle
M8: the browser refines a zoom only once it has paused
2026-10-29 09:49:26 +07:00
BFlat 4a0bd79797 Merge pull request #1488 from ChristopherLinnett/issue/1086-image-validation
fix(collab): image entries must match their hash and sane bounds (#1086)
2026-10-29 09:49:20 +07:00
BFlat 5804c0e2cc Merge pull request #1489 from ChristopherLinnett/issue/857-confirm-quit
fix(app): ask before ⌘Q quits with unsaved changes (#857)
2026-10-29 09:41:19 +07:00
bflatastic e83f9691b3 Merge main into #1466 2026-10-29 09:32:26 +07:00
bflatastic 170f1fdf32 #1466: gaps and ROADMAP list the Arabic draft as shipped 2026-10-29 09:26:15 +07:00
Christopher Linnett 57cf1bec48 fix(collab): keep in-use images and resync clients after filtered history (#1086)
- An image a layer or style still shows can't be removed: removing it and
  putting other bytes under its key would swap the picture that layer
  shows, which giving the key other bytes directly can't. A batch made by
  the diff stops using an image (node changes, then styles) before it
  removes it; an undo keeps the changes that apply, so its image may stay.
- Restore and replay leave out the image entries history written under
  older rules can't apply, using one method shared with the undo history
  (Replica::apply_kept, extracted from history.rs). It is exact for every
  image rule, including removals in use and replacements, and leaves out
  only image entries: any other change that doesn't apply is still an
  error. The image-only filter it replaces is gone.
- A server whose restore or replay left image entries out, or whose saved
  log puts images, catches every resuming client up with its snapshot,
  including one at the same sequence number.
- Tests: an_image_in_use_cannot_be_removed,
  an_image_no_longer_in_use_can_be_removed, undoing_an_image_layer_still_works,
  a_restart_replays_past_a_logged_removal_of_an_image_in_use,
  a_resume_after_history_was_left_out_catches_up_with_the_snapshot,
  a_resume_after_a_clean_restart_catches_up_with_the_log,
  a_snapshot_with_a_broken_layer_change_still_fails_to_open.
- Docs: collab-protocol.md, collaboration-parity.md.
2026-10-29 10:25:16 +08:00
bflatastic 6c18fe916b Merge main into #1466 2026-10-29 09:23:22 +07:00
bflatastic 9d42833008 Merge main into #1465 2026-10-29 09:18:39 +07:00
Christopher Linnett 7cce115ad6 fix(collab): check old images against the state they apply to (#1086)
- without_refused_images judges each image entry against the document it
  applies to and the batch's earlier entries (image_for, shared with
  apply): a logged put of other bytes under a key the replay state holds
  is left out, so a restart no longer stops there and drops the rest of
  the log.
- Server::restore keeps its saved log only when the log puts no images:
  a resuming client may already hold other bytes under a logged key, which
  the restored state can't check, so clients catch up with the checked
  snapshot instead.
- Tests: a_restart_replays_past_a_logged_image_replacement,
  a_client_resuming_past_a_refused_image_in_the_saved_log_catches_up,
  a_client_resuming_past_a_replaced_image_in_the_saved_log_catches_up.
- Docs: collab-protocol.md, collaboration-parity.md.
2026-10-29 09:37:33 +08:00
Christopher Linnett 949d74f7ca fix(collab): image checks keep documents shareable and history replayable (#1086)
- The entry size cap is figcraft_doc::ImageData::MAX_BYTES (256 MiB), the
  cap the .fig, .sketch and REST importers already read images with (now
  one constant), instead of 32 MiB: an imported image larger than 32 MiB
  made the document unjoinable and its server snapshot unrestorable.
- History written before the checks (a restored or joined snapshot, a
  replayed server log, a file published to a server) applies without the
  image entries they refuse (docprops::without_refused_images), instead of
  the file not opening or a restart dropping every edit after the image.
- A key that already holds an image can't be given other bytes: producers
  key images by their bytes (the REST importer's placeholder keys always
  hold the same picture), and merge treats a key's image as never
  changing.
- REST import: an unresolved imageRef shaped like a content hash shares
  the rest-image-missing placeholder (the placeholder's bytes don't hash to
  it); ImageData::claims_content_hash is shared with collab.
- Tests: collab image_tests (4 new, size test uses a small cap), format
  image_tests (1 new).
- Docs: collab-protocol.md, collaboration-parity.md, file-format-parity.md.
2026-10-29 08:55:03 +08:00
Christopher Linnett 0e508fd316 fix(app): a second quit before the window draws counts once (#857)
The app's logic runs while the main window is minimised, without the
frame that acts on a close. Two quits there queued the main window's close
twice: the first let it take over another window, and the second then
quit the app over that window's unsaved changes, as answered.

- close_current queues a window's close only once.
- Test: quitting_twice_before_the_window_draws_closes_it_once.
- docs/hardware-parity.md revision history.
2026-10-29 08:50:00 +08:00
Christopher Linnett 7660877845 fix(present): nothing opens or releases behind an open link notice (#1096)
- finish(): a viewer-fired link opens directly (notice skipped) only while
  no notice shows; any other link that fires while one shows is dropped.
- finish(): raising a notice ends the press under way, so its release
  doesn't fire the prototype's mouse-up actions (a link, a navigation)
  behind the notice, which takes no input while it shows.
- The mail notice says the link opens the mail app to write a message
  (opening a mailto: link doesn't send anything).
- Tests: nothing_opens_while_a_notice_shows_with_the_notice_skipped,
  a_press_that_raised_a_notice_doesnt_release_behind_it.
- docs/prototyping-parity.md row and revision history.
2026-10-29 08:50:00 +08:00
Christopher Linnett f1c2ee81e4 fix(collab): image entries must match their hash and sane bounds (#1086)
Image entries applied bytes to any key with no checks (audit COL-9): a
collaborator could poison the shared content-addressed image store, claim
absurd dimensions, balloon memory, or name an image `../../x`, which a
saved file then wrote as the archive member `images/../../x`.

- Every key must be 1–128 ASCII letters, digits, `-` or `_`:
  `figcraft_doc::ImageData::is_valid_key`, promoted from the format
  crate's private `is_valid_hash` so the file reader and collab share one
  rule. The reader now also takes `-` and `_`, so the REST importer's
  `rest-image-missing` placeholder survives a save and load.
- The REST importer's side of the same attack: an unresolved `imageRef`
  that can't legitimately be an archive member name (path text, an
  over-long ref) shares the `rest-image-missing` placeholder instead of
  being keyed by its raw name
  (`image_tests::a_path_like_rest_image_ref_never_names_an_archive_member`).
- A key with the shape of a content hash (64 hex chars) must be exactly
  the lowercase sha256 of the bytes, the form `image_hash` writes; an
  uppercase look-alike is refused. Other keys (svg FNV-128, REST
  placeholders) claim no content address and only meet the rules above.
- A side is at most 65 535 px; decoded bytes at most 32 MiB, with the
  base64 text capped before decoding. 0×0 stays valid: the format crate
  stores it for sizes it can't probe.
- Refusals are Error::Invalid and reject their batch, so every replica's
  store stays clean; catch-up snapshots apply through the same check.
- Docs: collab-protocol.md, collaboration-parity.md and
  file-format-parity.md (rows, status lines, histories).
- Tests: collab image_tests (8), format image_tests: round-trip and the
  REST placeholder gate.
2026-10-29 08:11:13 +08:00
Christopher Linnett 73398cedf1 fix(app): ask before ⌘Q quits with unsaved changes (#857)
winit's default macOS app menu ends the process through AppKit's
`terminate:` at once, so ⌘Q and Quit from the menu bar never asked about
unsaved changes (the recovery copies were all that survived).

- Install craft-libs' app menu (craft-menu, same rev as the other
  craft-libs pins; macOS only). Its Quit sends the main window a close
  request, the same path as the window's close button, so
  windows::on_close_request cancels the close and the unsaved-changes
  question asks first. Failure to install keeps winit's menu and logs it.
- windows_tests::a_quit_with_unsaved_changes_waits_for_the_answer pins
  the contract the menu relies on: a close request with unsaved changes
  sends CancelClose and asks; Don't Save then closes.
- Like the close button it closes the main window only: with several
  windows open, each quit closes one (documented).
- docs/hardware-parity.md macOS row, status line and revision history.
- Cargo.lock: craft-menu, craft-quit, craft-keymap, muda and their deps;
  cargo's own minimal resolution also moves a few windows-sys/getrandom
  edges to versions already in the lock.
2026-10-29 08:11:13 +08:00
Christopher Linnett 53f47706ec fix(present): prototype links open only behind the notice or a viewer's click (#1096)
An Open link action carries a URL from the document and fires from whatever
trigger the prototype sets, so an unattended presentation could open web links
and mail messages one after another: a mail link bypassed the leaving-the-
prototype notice entirely, and a skipped notice let any trigger open any
number of links.

- finish() routes every openable URL, mail included, through the notice.
- Skipping the notice ("Don't show this again") opens at once only a link the
  viewer's own click, press, drag or key fired, one per frame. A delay, the
  pointer passing over a layer (enter, leave, hover) and a timed mouse
  trigger that fires after the gesture is over still show the notice, which
  then offers no checkbox: it couldn't skip that notice anyway.
- The player tags each queued link with whether the viewer fired it
  (Presenter::fire_from); an OpenUrl outside an interaction defaults to not.
- The notice covers mail links (new locale string, mail wording).
- Engine prefs/command doc text follows the rule; behavior untouched there.
- present_links_tests: shared setup helpers for the link tests; new
  tests for the skip rule (delay, hover, delayed mouse-up, key, one link per
  click, checkbox only for clicked links); the scheme-filter test now clicks
  with the notice skipped so the filter alone keeps those URLs out.
- docs/prototyping-parity.md row, status line and revision history updated.
2026-10-29 08:11:13 +08:00
Brandon ThomasandClaude Opus 5.5 cb227c9715 Web: raise the wasm size gate to 25 MiB - 512 KiB
The 0.12.0 release's Web job failed at 25,319,327 bytes against the 24 MiB (25,165,824-byte) gate. opt-level z, fat LTO and wasm-opt -Oz are already in place and extra wasm-opt passes save only ~20 KB, so the gate moves to 25,690,112 bytes, still under Cloudflare's 25 MiB per-file cap. The wasm grew ~2 MB in three days (egui 0.37, new features); bringing it back down is follow-up work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:36:52 -04: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
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 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
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 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
Guillermo Bueno 54eced7ffb M6: address offline Penpot review regressions 2026-10-27 20:48:08 +01:00
Guillermo Bueno 7ea161f33f M6: merge main and isolate offline Penpot import/export 2026-10-27 18:59:49 +01:00
0d47564737 M3: bind fill and stroke variables directly in layer props (#1000)
node.create and node.set take {"$var": variable id or name} for fill and stroke: an exact id
first, then an unambiguous name or Collection/name. The binding goes through the same code as
variable.bind, is applied after literal values and style links, replaces other bindings of that
paint's colour and clears its style link, all in one undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 12:55:48 +03:00
d21c9d4292 M8: keep undo entries after a failed batch undoes an earlier edit (#1410)
Session::grouped saved only the undo stack's length, so a failed group could drop the entries it
added but not bring back the ones an edit.undo inside it popped. It now keeps each document's undo
entries on entry, as it already did for redo, and restores them when the group fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 12:55:48 +03:00
300a5f2f9d M0.9: let batches continue after failed steps (#1386)
command.batch and the MCP batch tool take onError: "abort" (the default, unchanged) or
"continue". In continue mode each step runs inside a rollback-only checkpoint split out of
Session::grouped: a failed step is rolled back on its own, later steps still run, and the result
is one {step, command, ok, result|error} object per step. What was applied is one undo step; a
reference to a failed step fails too; malformed batches are still rejected before any step runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:54 +03:00
57c1133a34 M0.9: find layer ids without changing the selection (#996)
node.find {root?, name?, type?, text?, component?, limit?} returns {ids, total} for the strict
descendants of root (or the current page) that match every filter given: name, text and main
component name as case-insensitive substrings, type as an exact type name. It changes neither the
selection, the page nor the undo history. Part of #996; fields/outline are left for later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:54 +03:00
8ae76d9b2a M8: bound attached MCP replies to 512 MiB (#1113)
The attached backend read each reply from the app's control port with read_line, which has no
limit. It now reads up to 512 MiB before the newline (an app window capture is at most 256 MiB as
base64) and stops at the first byte past that. An oversized or non-UTF-8 reply drops the
connection and is reported like a lost reply; the next call reconnects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:53 +03:00
叶林橦 5f7aea91f9 ui: rebuild the toolbar pill on a 32 pt grid, without the Resources button
Brings #1210 onto main as it stands after #1019 and #1209, and keeps both:

- #1019's flyout caret keeps its 16 pt target, 2 pt clear of its tool (#988).
- #1209's narrow-window handling stays as it landed: the pill is never wider
  than the canvas less a margin and its buttons scroll inside it under a drag
  or the wheel, the help button lifts above it when they'd overlap, and the
  pill still clips to itself rather than floating over the panels.

The design itself, as #1210 proposed it:
- One grid for every state of the pill: 32 pt cells, 8 pt padding, 8 pt between
  top-level items (the divider included), a 1 pt divider as tall as the content,
  16 pt carets, 14 pt mode marks, and a 32 pt mode strip whose current mode is a
  plain inset with no outline.
- The Resources button goes; the left rail's Assets tab, Shift+I, Alt+2 and
  View > Panels > Assets remain the ways into the Assets panel (and the locale
  key, its test and the parity notes go with it).
- The vector edit and shape builder pills move onto the same grid, so a pill
  never changes its spacing as the tool changes.

main_width is now the same walk the pill draws, and a test walks it for the
design and board toolbars: the last item ends exactly PAD from the right edge.
That caught the board pill asking for 8 pt more than it drew.
2026-10-27 12:28:49 +08:00
叶林橦 27d5d99e56 M7: land Flutter code generation on main's code targets
main split `codegen.rs` into `dev/codegen.rs` (the shell: `CodeLang`,
`code_for`, `Ctx`, the shared helpers) and one module per native target
(`css`, `html`, `swift`, `compose`, `arkui`), and added Vue and ArkUI from
other PRs. Flutter arrives as the tenth target, in its own module:
`dev/codegen/flutter.rs`, wired from `codegen.rs` (`CodeLang::Flutter`, `dart`
as an alias, rem left to the web targets), with `flutter_definition` /
`flutter_type` / `flutter_default` beside main's `arkui_definition`.

Every language list in the tree is a union of the two sides now: the
`inspect.code` and `inspect.componentCode` specs, `codeConnect` (parse, import
line, expression, template, path checks, messages), the playground CSS/HTML
assets, the 21 UI catalogues (main added eight after this branch: bn, fi, it,
ka, nl, pl, th, vi, each with Flutter in its own conjunction) and the docs.
Flutter is an extra code target; nothing main gained is dropped.

The reviewer's absolute-child bug (#1382-1403: a child that ignores its auto
layout parent fell through to `Place::Flow`) is fixed in `flutter_children`:
flow and absolute children are split, the absolute ones go in a `Stack` around
the Row/Column at the parent's own box (so their offsets stay the parent's and
the padding moves onto the line instead of insetting them), the line fills that
Stack (`Positioned.fill`, so `Expanded` still has a bounded width), and only
flow children now decide `CrossAxisAlignment.stretch` and `Expanded` — the
absolute child no longer joins the line's spacing or loses its offsets.

`flutter_lifts_a_child_that_ignores_its_auto_layout_parent_out_of_the_line`
covers both kinds of child in one auto-layout frame: a 50×20 flow child and a
30×30 absolute one asking for `fill` on both axes, in a parent with 4 pt
padding. It checks the offset, that the absolute child is out of the Row, that
no `Expanded`/stretch comes from it, and that the padding insets the line only.

ROADMAP and dev-mode-parity keep main's header (2026-10-26) and take this
branch's history row in date order (2026-10-16).

`cargo test`: engine 1391 pass, ui-egui 814 + 8 + 1 pass; fmt and clippy clean.
The one engine failure, `board_tables_tests::a_double_click…`, fails the same
way on a pristine `origin/main` here: this sandbox has no `craft-fonts`
checkout, so its text doesn't wrap and the row can't grow.
2026-10-27 11:30:27 +08:00
aiongg 60c671da66 docs: update source paths for the layout moves 2026-10-26 20:36:51 +00:00
Skyfuller1303 a4076caaf4 Merge main and preserve vector region paints through path operations
Retain regional paint stacks and coordinate frames through simplify, offset, flatten, bucket normalization and Shape Builder. Preserve legacy fill semantics and synchronize importer warning catalogs. Verified with cargo xtask ci on Windows and Ubuntu/WSL, native UI screenshots, and CLI round-trip/export checks.
2026-10-26 19:50:00 +01:00
bflatastic 5e7cd52f6d Merge main into #827 2026-10-27 01:06:45 +07:00
BFlat cac54fd552 Merge pull request #840 from StrajnarFilip/feat/vector-drawable-export
M6: export as Android Vector Drawable (XML)
2026-10-27 01:06:21 +07:00
BFlat 740cc2a2c6 Merge pull request #726 from ijashuzain/feat/variables-dart-export
M6: export variables as Dart constants for Flutter
2026-10-27 00:53:10 +07:00