Branches of a file in a live session: create, edit, review, update from
main and merge while the file is in a session. The branch stays with its
author and makes ids in their own client space far above the main file's;
merging is one synced edit and one undo step; peers' edits merge against
the branch's own base and conflicts resolve as offline; a view link still
refuses merging; a branch made outside the session that adds layers is
refused with a reason. Branch history entries for creation and updates
from main. Fuzz and junk tests extended to export settings, guides and the
live case. Docs: gap 17, collaboration parity, ROADMAP, mcp, protocol.
* 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)
* 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.
* 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)
* M12: boards dot grid, re-attaching connector ends, curved connectors
Board pages draw a dot grid in the canvas render, after the page colour
and under the layers (figcraft_render::Dots), thinned out when zoomed out
and coloured from the theme's ink against the page. A selected connector
shows its ends as handles; dragging one onto an object re-attaches it,
onto empty canvas leaves it free (board.reattach, one undo step, also
over MCP). Connectors can be curved, and elbows go around the objects
they join when the short way would cross them.
* M12: boards dot grid: blending a dot in a function of its own (Sonar)
Existing text keeps the named instance's optical size, so documents render as before; the
Sketch import stores the size as the text's opsz value instead.
Two gaps CodeRabbit found in the guard:
- `same_file` compared paths, plain and resolved. A second hard link to
the file is a different path to the same data, so `convert`,
`run --out` and Save As to the link passed the guard and truncated
the file. It now asks the file system (same-file: device and inode on
Unix, volume and file index on Windows). same-file was already in the
lock through walkdir.
- `run --in X --out X` checked the tab the script left active. A script
that ends on `file.duplicate` or `file.new` leaves a tab with no count
of images left in X, so X was written over. The count is now taken
when X is opened and checked against `--out` whatever tab is active;
the active tab's own file is still checked as before.
The script check is a separate function so it can be tested without a
file past the 4 GiB budget.
A native file whose images together pass IMAGE_BUDGET (4 GiB, or 20,000
images) opens without the images past it. They are intact in the file, but
saving it again wrote the archive from the images in memory, so every
save, autosave or `convert` onto itself deleted them for good. A 7.6 GiB
`.fig` converted to a 7.3 GiB `.figcraft` reopened with 1,312 of its 2,337
images: one save would have dropped the other 1,025.
Loading now tells images left out for the budget apart from damaged ones
(`Loaded::images_past_budget`). A tab opened or reverted from such a file
remembers the file, and saving over it is refused with a message that says
how many images would be lost and to save under another name; saving a copy
elsewhere works, and the original keeps its images. Auto-save skips the
tab instead of retrying and failing every minute. `figcraft-cli convert`
and `run --in X --out X` refuse to write over their input the same way.
The same file is matched by its resolved path, so `./a.figcraft` and
`a.figcraft` count as one (`file_access::same_file`).
The note for each image left out for the budget now says it stays in the
file, which can't be saved over without losing it. Damaged and missing
images keep the old wording: they can't be kept, and saving drops them as
before. The doc comment on report_lost_images said saving always drops
the images it reports, which is no longer true for budget ones, so it went.
This stops the data loss; it doesn't make such files editable in place.
Loading images on demand would, and is discussed in the issue this links.
Tests: the count and the note for both budgets; saving over the file
refused with the file unchanged, Save As then working, and saving back over
the original refused again; a snapshot encoded in the background refused
too; a tab without images left out saving as before. Checked by hand on the
7.3 GiB file with file.save, file.saveAs, convert and run --out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* E21: require release font coverage and stabilize capture deadline tests
* E21: separate capture, IME and field-save regression phases
* M0: correct the migration qualification reference
* M-egui037: document input lifecycle and UI regression contracts
* M0: correct egui 0.37 status docs and stabilize join-code regression
---------
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
Co-authored-by: Codebuff <noreply@codebuff.com>