Commit Graph
94 Commits
Author SHA1 Message Date
BFlat af2df345bc M9: the Join dialog shows a join until its file is ready, with Cancel and Try again (#1381) (#1553) 2026-10-30 00:51:46 +07:00
BFlat f6ccef0c5d M9: a per-address budget for server admin requests, and join codes found by hash (#1075) (#1552)
* M9: a per-address budget for server admin requests, and join codes found by hash (#1075)

* M9: the server tests share one snapshot and address helper (#1075)
2026-10-30 00:20:57 +07:00
BFlat 2e3d9b6f07 M8: SVG pattern tiles drawn once per scale, within the layer budget (#1080) (#1551) 2026-10-29 23:57:49 +07:00
BFlat f702105ed0 M4: a letter and its combining marks shape with one font (#1478) (#1532)
* M4: a letter and its combining marks shape with one font (#1478)

* M4: the stacked-mark test checks positions only in fonts whose anchors stack them (#1478)

* M4: the cluster face rule is tested with stand-in faces, so it runs without system fonts (#1478)

* M4: the #1478 tests use stand-in faces only, so they run everywhere
2026-10-29 22:25:40 +07:00
BFlat 3af5d8a534 M9: comment.list reports the names each message mentions (#1415) (#1534)
Every message (the first and each reply) gets `mentions`: {name, known,
you} for each @mention, by the rule the thread view highlights with
(figcraft_doc::mentions, now shared with mention_ranges). Names resolve
against the file's people and you; unknown names are kept with
known: false; a@b.com mentions nobody.
2026-10-29 22:07:06 +07:00
BFlat 96eed15993 M8: mask groups draw over their own area, nested layers share one memory budget (#1061) (#1535)
A mask group's pixmaps cover only where its mask or content can paint within the view, rounded out to whole pixels, drawing the same pixels (a 40x40 mask on a 3000x2000 view: 96 MB -> 61.5 kB). Isolated layers share a 2 GiB per-thread budget; a layer past it is left out with a warning.
2026-10-29 21:33:46 +07:00
BFlat 0d9c2c3f73 M3: components that would contain themselves are refused, and loops in files are repaired (#1084) (#1540)
* M2: components can't contain their own instances (#1084)

Edits that would make a component hold an instance of itself, directly, through other components or through another variant of its set, are refused with the loop named. Files holding such a loop open with one instance per loop detached and a note; a rebuild never expands an instance inside a copy of itself, so a loop merged in a live session stops growing.

* M2: a variant may hold a sibling variant's instance; only true loops are refused (#1084)

Each variant counts as its own component: refusals and open-time repairs apply only to a component leading back to itself through instances (an instance-swap value included), so files with nested sibling variants open unchanged. sync_instance_depth is split into match_layers and copy_layers.
2026-10-29 21:05:32 +07:00
BFlat 3108d5bee7 M8: edit.duplicate returns an id map with map: true (#995) (#1528)
With map: true, edit.duplicate also returns map: each original layer's id, descendants included (nested frames, groups, instances and their sublayers), paired with its copy's id, so a batch can edit inside a copy through a step reference instead of inspecting it. Without map the result is unchanged.
2026-10-29 20:34:30 +07:00
BFlat 436cfa1b96 M9: the replay log is bounded in bytes by default (#1069) (#1524)
The batches a session keeps for apps that reconnect were bounded only by count on a desktop host
and a private figcraft-server (`max_log_bytes` defaulted to usize::MAX). It now defaults to
64 MiB, and figcraft-server takes it from a new `max_log_mb` setting (default 64; open to the
public, also at most a quarter of `max_file_mb` as before). An app behind the trimmed log catches
up with a snapshot, as it already did past the count limits. The protocol is unchanged.

Nine 8 MiB renames with default limits: all 75.5 MB kept before, 58.7 MB after.

Fixes #1069
2026-10-29 20:02:46 +07:00
BFlat 714bbfcdaa M8: MCP and control calls name the document they act on (#998) (#1533)
Tools that act on a document, and the control methods engine.execute,
document.inspect, node.get and ui.render, take an optional `document`
(an open file's id from doc.list / document.inspect, or its path). The
call acts on that file and the tab in front stays in front, so an
attached agent keeps editing its own file when the user switches tabs.
2026-10-29 19:47:41 +07:00
BFlat b2421885d0 M9: sibling placement in live sessions by binary search (#1065) (#1523)
Every replica placed a created, moved or reordered layer by counting each sibling that sorts
before it, so a batch of many layers under one parent took quadratic time, on the server's
single hub thread too. `Replica::slot` now binary-searches the `(key, id)` order on each side of
the layer's old place (a position change has already given it its new key). The order is
unchanged, ties and fractional positions included.

20,000 creations and 10,000 moves under two parents: 20.05 s before, 0.33 s after (debug build).

Fixes #1065
2026-10-29 19:32:30 +07:00
BFlat 72b4269383 M8: an AI agent attached over MCP comments under its own name (#1414) (#1529)
Every command an MCP client runs acts as the client's name from initialize (clientInfo.title, else .name) with ' (agent)' after it, or 'AI agent' without one, carried to the app as engine.execute {agent}. Its messages, reactions and read state are its own, and its messages are marked (agent: true in the file and comment.list, an Agent label in the thread). The user's name and comments made in the app are unchanged; in live sessions the name and marker travel with the thread.
2026-10-29 19:15:19 +07:00
BFlat 3efb2699b6 M3: the instance-swap menus have a search field that filters as you type (#1338) (#1527) 2026-10-29 19:00:15 +07:00
BFlat 08173cc51e M9: a refused batch's fixes in linear time (#1063) (#1522)
The server's fixes for a refused batch walked every deleted layer's whole subtree again and
compared each document-level fix with all the earlier ones, so a batch deleting a deep chain took
quadratic time on the hub thread. Each subtree walk now stops at a layer already restored, and
document-level fixes are looked up and sent once per target. The fixes are unchanged.

A refused batch deleting a 20,000-deep chain plus 40,000 comment puts: 27.2 s before, 0.14 s after
(debug build).

Fixes #1063
2026-10-29 18:44:03 +07:00
BFlat d4e6ad5d81 M12: the connector tool previews the connector it will make, ends snapping to attach points (#1387) (#1526) 2026-10-29 18:29:48 +07:00
BFlat e32f8f2e07 M8: large images on the canvas draw from a smaller copy (#1483) (#1518)
* M8: large images on the canvas draw from a smaller copy (#1483)

An image shown smaller than its pixels draws on the canvas from a copy scaled down to 4096 or 8192 px on its longer side, cached beside the image in the same budget. Two 16000 x 10000 images (640 MB each) evicted each other on every frame that drew both; as copies they take 80 MB together zoomed out. Moving one over the other: about 2.1 s a frame before, 11 ms at fit and 15 ms at 30% after. Exports, thumbnails and views at 100% and closer draw from the image itself. Tests in large_image_tests; timings in large_image_bench_tests (ignored).

* M8: drop the ignored large-image benchmark; its numbers are in the PR (#1483)
2026-10-29 18:14:18 +07:00
BFlat 0971d096db M8: the progressive JPEG check guards file thumbnails too (#1517)
The header check from #1491 moves to figcraft_format::jpeg so the format crate and the renderer share it, and decode_thumbnail now refuses a progressive JPEG that samples a component more finely than its first (zune-jpeg 0.5.15 asserts on it; in the browser a panic aborts the page). A crafted file's thumbnail on the home screen and in recent files shows no picture instead of crashing. Regression test through decode_thumbnail and archive_preview.
2026-10-29 17:42:53 +07:00
BFlat a492cca415 M6: .fig import resolves library text styles linked by asset key and orders restated override rows by level (#1234) (#1515)
Style links that name a library style by its asset key and version resolve to the file's copy of the style, as variable links already did. The linked text style beats the copies of its values below the row that links it; rows above that one still win. Override rows that restate a nested level (overrideLevel) apply at that level instead of in wire order.
2026-10-29 17:28:55 +07:00
BFlat 07681afebe M6: .fig import keeps the file's saved line breaks while the text is unedited (#1236) (#1519)
* M6: .fig import keeps the file's saved line breaks while the text and its box are unedited (#1236)

The line breaker already keeps trailing spaces and the last glyph's letter spacing out of the fit test and doesn't round box widths; imported text wrapped a word early or mid-word because local fonts measure slightly differently from the file's. The import keeps the saved line starts (derivedTextData) on the text with the box width and a stable key of its layout content; layout follows them while both match and no line overflows its box by more than 3% (+1 px).

* M6: local check that imported text wraps at the line breaks its file saved (#1236)

Runs only when FIGCRAFT_FIG_SAMPLES names a folder of .fig files: each fixed-width text with saved breaks wraps there once imported, or falls back by the overflow slack; prints the counts.
2026-10-29 17:04:29 +07:00
BFlat 6ef899cdbb M7: download a missing free font from the Missing fonts window (#754) (#1510)
A missing family the suite's font store has (Google Fonts, Noto) gets a
Download button. The first one asks the user to allow downloading fonts,
then craft-libs' craft-font-store installs pinned, checksum-verified files
into the Craft apps' shared font folder on a thread of its own. The font
scan reads that folder; once installed the text fits the real font.

font.download / font.downloads drive the store as its agent: refused while
downloading is off, never able to turn it on. Desktop only.
2026-10-29 16:41:31 +07:00
BFlat 3d5185c2ab M0.9: web wasm back under the 24 MiB gate (#1472) (#1511)
The browser build's wasm was 25,982,540 bytes, past the gate raised for 0.12.1. It is now
23,915,213 bytes (22.8 MiB), 1.2 MiB under the restored 24 MiB gate:

- Catalog rows keep their plural forms behind a reference: Text is two words, not room for
  four strings, across ~52,000 baked-in rows (-454 KB).
- The browser build compiles with wasm SIMD (apps/figcraft-web/.cargo/config.toml, wasm-opt
  --enable-simd), so egui's glyph rasterizer leaves out its scalar SIMD fallback (-1.6 MB).
  The plugin workspace is built from the repository root and stays without SIMD.
- Revert the gate raise (cb227c97); the hosting guide names the WebAssembly SIMD requirement.
2026-10-29 16:14:40 +07:00
BFlat 1df9190d0c M3: shadows of images with transparent pixels follow their alpha (#1289) (#1509)
* M3: shadows of images with transparent pixels follow their alpha (#1289)

A drop shadow on an image fill was hidden behind the layer's whole box, so a
transparent PNG's shadow only showed outside its bounding box. Fills that
paint their own alpha (patterns; images with transparent pixels or that leave
part of the box empty) now cast and hide drop shadows, and shade inner
shadows, by the layer's pixels, like groups. Opaque pictures keep their
outline. Each image is scanned for transparency once. Canvas, PNG, SVG and PDF.

* M3: the image shadow tests import their names explicitly (#1289)
2026-10-29 15:43:08 +07:00
BFlat 7ba22fb2b6 M1: Crop image from the Object menu, the right-click menu and the image fill row (#1464) (#1501)
* M1: Crop image from the Object menu, the right-click menu and the image fill row (#1464)

* M1: crop mode shows the image dimmed outside the layer and heavier crop marks (#1464)

* M1: the handle overlay draws gradients and crops in their own helpers (#1464)
2026-10-29 15:07:21 +07:00
BFlat 7c9184b023 docs: rows from the round-4 community PRs (#858, #1409, #1397, #1389, #1143, #1427, #1421) (#1504) 2026-10-29 14:48:37 +07:00
bflatastic c6c11471db Merge main into #1408
# Conflicts:
#	crates/engine/src/cmd/edit.rs
#	docs/roadmap.md
2026-10-29 12:23:48 +07:00
bflatastic 49a41cddd4 Merge main into #1408
# Conflicts:
#	docs/roadmap.md
2026-10-29 11:40:48 +07:00
bflatastic 1505681a54 Merge main into #1407 2026-10-29 11:39:50 +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
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
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
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
bflatastic 502098e02a #840: the status lines without the merge's conflict markers 2026-10-27 00:32:35 +07:00
bflatastic 2aae395310 Merge main into #840 2026-10-27 00:31:32 +07:00
bflatastic 632f2cef4b Merge main into #840
# Conflicts:
#	docs/roadmap.md
2026-10-27 00:12:27 +07:00
bflatastic 15c6834114 Merge main into #1384 2026-10-27 00:10:34 +07:00
bflatastic e469cc8135 Merge main into #840
# Conflicts:
#	apps/figcraft-cli/src/files.rs
#	apps/figcraft-cli/src/main.rs
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/locales/en.toml
#	crates/ui-egui/locales/es.toml
#	crates/ui-egui/src/export_panel.rs
#	crates/ui-egui/src/export_preview_tests.rs
#	docs/file-format-parity.md
#	docs/mcp.md
#	docs/roadmap.md
#	docs/ui-parity.md
2026-10-26 23:23:07 +07:00
Muamer MustaficandCursor 2136ec20b1 M1: a selected unfilled shape drags over a filled layer stacked below it (#1370)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 14:02:23 +01:00
Muamer MustaficandCursor b6d3e002df M4: truncated text fills its last line, keeps truncating when resized, and auto height takes max lines (#1375, #1376, #1377)
- The last line shown runs on into the word wrapped after it, so the ellipsis
  fills the line ("Lorem consec…") instead of following the last whole word.
- An explicit height (node.set, transform.resize, a resize handle, fixed
  sizing) on truncating text keeps it truncating (TextData::fix_size).
- Max lines on auto-height text hugs up to that many lines and ends in an
  ellipsis; text.maxLines keeps auto height, Type settings show it as
  truncating, .fig export writes textTruncation and import keeps auto height.
- node.get reports maxLines; text.fit measures every line.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 13:14:51 +01:00
BFlat 8d60360173 M11: widgets take text input and a property menu (plugin API 4) (#1365)
* M8: widgets take text input and a property menu

Plugin API 4 (gap 6). A widget's text layer can be an editable field:
a double-click edits it like any text, and when editing ends the value
(cleaned, at most 1,000 characters) goes into the widget's state under
the field's name and its plugin redraws, as one undo step. A widget can
also carry a property menu of up to 8 actions, toggles and colour
choices, shown in a small bar over it while it's selected.

- widget.setText and widget.menu drive the same paths from scripts,
  the CLI and MCP; widget.insert/list also report fields and menu
- descriptions, values and stored menus are validated as untrusted;
  an API 3 plugin drawing a field or menu is refused, older hosts
  refuse API 4 manifests; no new host imports
- SDK reads the new events; the Counter example gains a label field
  and a menu (reset, steps of ten, a colour)
- tests for fields, menus, canvas editing, rollback, forged data and
  live sync; junk sweep extended; labels in every command catalog
- docs: plugins, mcp, gaps, target-app-parity, ROADMAP, roadmap

* M8: widget property menu on egui 0.37

Id::new and Ui::id are deprecated in egui 0.37: the widget bar's area uses Id::unique and its buttons' interaction ids come from ui.scope_id().

* M11: a double-click a board object or a widget field takes, in a function of its own (Sonar)
2026-10-26 13:31:53 +07:00
BFlat d3782fdc43 M12: board templates and tables (#1363)
* M12: board templates and tables

Five built-in board templates (retrospective, brainstorm, weekly planner,
flow chart, kanban board) placed as ordinary board objects in one undo step
(board.template, board.templates) from a toolbar picker with pictures, and
board tables made of auto layout frames (board.table, board.tableEdit; up to
50 x 20), typed into with a double-click and edited by row and column from
the bar over the selection. Docs: boards-parity, gap 24, ROADMAP, mcp.

* M12: board templates picker on egui 0.37

Id::new and Ui::id are deprecated in egui 0.37: the templates picker's area uses Id::unique, its buttons' interaction ids come from ui.scope_id(), and its test reads the board bar's area by Id::unique.
2026-10-26 12:52:39 +07:00
BFlat 4fad29a66e M12: board timer and voting sessions (#1361)
* M12: board timer and voting

A countdown everyone on a board sees (board.timer, board.timerState), synced as its end time or the
time left while paused, and voting sessions (board.vote, board.voteResults): votes per person, on
everything, stickies or the selection; clicks vote, your badge takes a vote back; your own votes
show while it runs, everyone's with a ranked list once it ends. Timer and vote sync as document
properties and ballots as one entry per voter; neither is an undo step. A pill at the top right
of the board, new Timer, Pause and Vote icons, labels and strings in every catalog, docs.

* M12: board timer and voting on egui 0.37

Id::new is deprecated in egui 0.37: the timer and voting area and its memory ids use Id::unique.

* M12: board.vote's start and ballot changes in functions of their own (Sonar)
2026-10-26 12:28:59 +07:00
bflatastic 5ee983ef63 Merge main into #1294
# Conflicts:
#	ROADMAP.md
#	docs/roadmap.md
2026-10-26 11:12:25 +07:00
bflatastic 7177f1c042 Merge main into #1021
# Conflicts:
#	ROADMAP.md
#	docs/roadmap.md
2026-10-26 10:29:24 +07:00
Jindřich ŠaršonandClaude Opus 5.5 87db9522b4 M6: Sketch import: merge main (Boards M12 and community fixes)
Conflicts resolved keeping both sides: `.sketch` stays in `is_document`
(now via `OsStr::to_str`); ROADMAP and the parity, roadmap and
file-format docs keep main's headers and rows with the `.sketch` notes
and history rows added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:13:03 +02:00
AganzeFelicite 86aad2bb6a M7: offer the installed styles of an embedded font family (#1246)
Release builds embed some families with only a few styles (Poppins
Regular, Italic and Bold), and the first font scan dropped every
installed face of such a family, so an installed Poppins SemiBold or
Medium was never offered. The first scan now keeps the embedded faces,
which still win for the styles they cover, and offers the installed
styles they lack beside them, as a rescan already did.

Closes #1246
2026-10-24 18:39:44 +02:00
AganzeFelicite c743f8ec1e Merge remote-tracking branch 'upstream/main' into fix/adobe-fonts-macos-298
# Conflicts:
#	ROADMAP.md
#	crates/text/src/fontdb.rs
#	docs/roadmap.md
2026-10-24 18:20:00 +02:00
BFlat 8167f13a6a M12: Boards: a collaborative whiteboard file kind (#1229)
Board files (File > New board) with sticky notes, shapes with text, connectors that follow what they join (straight or elbow, arrowheads, labels), marker, highlighter and eraser, and stamps; a board toolbar and a contextual bar; the Design panel hidden on boards, Prototype and Dev Mode refused there. Boards are ordinary FigCraft files built from ordinary layers, so saving, undo, export, MCP and live sessions work unchanged. File tabs, recent files and Open files show a pen nib for design files and a note for boards (original icons drawn in code).
2026-10-24 04:18:55 +07:00
BFlat d6e8a6e874 Docs: roadmap entries for the community PRs in #507, #510, #529 and #530 (#587)
Roadmap, milestone and parity rows for the 93 community PRs landed in batches #507, #510, #529 and #530.
2026-10-24 02:02:19 +07:00