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.
A page kept its ruler guides in `NodeKind::Page`, and since frame guides
(#70) every layer also has its own `guides` field. Both are written as
`guides`, and on reading the layer's own field takes the key, so:
- a reopened page's guides landed in the layer field and were not drawn;
- saving that page wrote `guides` twice (the page kind's list, always
written, then the layer's), and the file no longer opened ("duplicate
field `guides`").
Every layer, pages included, now keeps its guides in the one layer field;
the key in the file is unchanged, so existing files open with their guides.
The field reads every `guides` list a layer has, so files already saved
with the key twice open again with both lists, in file order. This also
covers collab `Create` payloads from older peers.
Regressions: JSON round trips of page and frame guides, a page with two
lists, a bad list still refused (doc); save, reopen, save, reopen through
`.figcraft` and a document.json with the key twice (format). Checked in the
app over the control channel: guides drawn after two save/reopen cycles, and
a file with the key twice opens with all its guides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6Dyzof21BCrUsByhx2w
The Scale tool (and `transform.scale`) scales a layer's children whatever
their constraints. After the edit, overrides are recorded by diffing the
changed layers, and a size or place that changed while an ancestor resized
is read as what constraints give, so it isn't an override. For a scaled
instance that left its layers' scaled size and points unrecorded: the next
rebuild of the instance (a layer added to its main, say) put a 14 px
icon's triangle back at the main's 24 px size. Strokes already kept their
scaled weight, as a separate override group.
`scale_node` now marks `size` and `position` overrides on the layers it
scales below the scaled layer when they are layers of an instance and the
value changed. Undo takes them back with the rest of the edit.
The report's own sequence (clearing effects on the component holding the
icon) no longer reproduces on main; this covers the rebuild path that
still did. Regressions: a scaled instance through value edits and a
rebuild of its main; one scaled with the tool, with undo; a scaled icon
nested in a component and in an instance of that component, through edits
of both mains.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6Dyzof21BCrUsByhx2w
- 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>
Open and Revert to saved recorded the file's size and modification time after reading it: a change written in between would be stamped as already loaded and a later save would replace it. Taking the stamp first makes such a change refuse the next save instead (CodeRabbit on #1374).
With a file open in the desktop app, figcraft-cli could change it on disk;
the next Save (or auto-save) wrote the app's copy over it and said
"Saved", losing the CLI's changes.
Each tab now remembers its file's size and modification time when it
opens, reverts or saves it (the Stamp library files already use), and
Save and auto-save refuse while the file on disk differs, pointing to
Revert to saved or Save as. Scripts pass `file.save {overwrite: true}`.
Save as and saves to an explicit path are unchanged.
* 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)