`Project::same_content` ignored `next_id`, so an edit that only took an id was dropped as a
no-op and the id could be handed out again (an edit may keep it beyond the project).
`guard::tests::the_session_survives_a_panicking_edit`, merged after M3.9, failed on main for
this reason. Ids now count; setting a value to what it already is still records nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `Project::to_file_json` writes `savedBy` (the EffectCraft version) ahead of the project and
checks that the text reads back before anything is written: JSON has no NaN or infinity, so
such a value made a file that saved but could never be opened. Save now fails with a message
and the file on disk is left as it was.
- Opening a project saved by a newer EffectCraft warns that settings this version doesn't know
are lost on saving; `file.open` reports `savedBy`. A file that isn't a project names itself in
the error.
- Increment and Save keeps the extension (`.ecprojx` stays XML); Save a Copy writes XML for
`.ecprojx` / `.xml` paths, like Save As.
- Docs: architecture (document model), parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Quit and the window's close button, File ▸ New Project, Open Project, Open Recent, Close
Project, the demo project and a Home template show Save / Don't Save / Cancel when the
project has unsaved changes (Save asks for a path when untitled; cancelling that keeps the
project open). File ▸ Revert asks to discard. Before, all of them dropped unsaved work
without asking.
- The window title shows the project's path and `*` while it is modified.
- The modified mark follows undo: the session keeps the snapshot it last opened or saved
(`Session::saved_project`), so undoing, jumping in the History panel or rolling back a batch
to the saved state clears it.
- An edit that changes nothing (a value set to what it already is) records no undo step and
doesn't mark the project modified (`Project::same_content`, comparing untouched comps by
pointer).
- Agents' `engine.execute` / MCP calls are never asked; `ui.menu.invoke` shows the prompt like a
click (automation ids `dialog.unsaved.save` / `dontSave` / `revert` / `cancel`), and the
control channel's `app.quit {force: true}` skips it.
- Docs: architecture, agents, control protocol, parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>