A log whose generation isn't its snapshot's was skipped as a leftover of a
compaction cut short, but neither generation is under a checksum: one flipped
bit made the server serve the older document, report nothing and overwrite the
log. Replay such a log over the snapshot instead: replay skips what the
snapshot already holds, so a genuine leftover changes nothing, while edits the
snapshot lacks are recovered and the damage is reported.
`figcraft-cli run` ran each step of a script on its own, so a script's `{"$step": n}` reached
`parent` unresolved and the layer landed on the page while every step reported success — the same
commands through `command.batch` (the MCP `batch` tool, the control channel, plugins) put it inside
the frame. One `edit.undo` took back one step of a script rather than the script, and a failing step
left the earlier ones applied.
`Session::batch_step` is now the one path both take — one step of a batch, its `$step` references
resolved against the results before it — and `run_script` wraps its steps in `Session::grouped`: the
whole script is one undo step, and a step that fails takes the script back. The result lines come out
once the script is through, so none of them reports work a later failure took back; a failure prints
one line with `rolledBack: true` and exits 1. `docs/cli.md` documents the reference and the output.
`cargo xtask ci` was red on a clean main on macOS: the test's last shutdown
runs after the server has answered an invalid request and closed the
connection, and `shutdown(2)` on a socket whose peer has already closed is
ENOTCONN on macOS and the BSDs where Linux returns success.
The test is right about what it checks; it checked a return value that is not
portable. Closing the client is what the worker thread waits for, and dropping
the socket does that whether or not shutdown reported anything. The file's
other six shutdowns run while the peer is still open, which is well defined on
both platforms, so they stay checked.
With no CI running the tests on pull requests, the local gate is the only
signal a contributor gets, and it could not be green on macOS.
The control server answered a line that wasn't JSON with an error and read
on. A web page can make the browser send an HTTP request to the loopback
port; its request and header lines were skipped that way and the JSON lines
of its body ran as commands. The first line that isn't JSON now closes the
connection before anything after it is read. Clients that send JSON lines
(MCP --attach, scripts) are unaffected.
The message now comes from the UI crate (dialogs::window_failed) through
tr() and trf(), with entries in every catalog, and the app picks the
language from the preferences before the event loop starts, so the box
reads in it even though no frame was ever drawn. The commands to type stay
as written.
The web build's Services never wrote to the clipboard, so Cmd+V after
Cmd+C got the paste event with whatever text the clipboard held before
and made a text layer of it instead of pasting the copied layers.
WebServices now writes copies (text and PNG) through egui's output,
which eframe hands to the browser clipboard during the copy event.