M3.9: closing a modified project asks to save it first; the modified mark follows undo

- 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 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-04 23:02:14 +07:00
1 parent 10644a83aa
commit e0ed28f55d
18 files changed
+437 -9

No files matched your search

+8 -1
View File
@@ -134,7 +134,14 @@ build real projects through these interfaces; they are worked examples of everyt
"control":"Title","value":"John Smith"}`); a command that targets an explicit `comp` runs even
when the active comp would disable it (`essential.exportTemplate {"comp":"Lower Third", …}`).
* **History**: Levels of Undo defaults to 32 (`prefs.set {"key":"general.undoLevels","value":99}`);
`history` lists branches and `history {"goto": id}` jumps between them.
`history` lists branches and `history {"goto": id}` jumps between them. A command that changes
nothing (a value set to what it already is) records no undo step. `get_state` reports `dirty`
(unsaved changes); it follows undo, so undoing back to the saved state clears it.
* **Unsaved changes**: commands run through MCP / `engine.execute` never ask to save. In the
desktop app, `ui.menu.invoke` of a command that closes a modified project (`file.open`,
`file.newProject`, `file.closeProject`, `file.revert`…) and the window's close button show the
Save / Don't Save / Cancel prompt (`ui_click` on `dialog.unsaved.save` / `dontSave` / `cancel`;
`dialog.unsaved.revert` for Revert); the control channel's `app.quit {"force": true}` skips it.
### Motion tracking
+10 -1
View File
@@ -531,7 +531,16 @@ What keeps it fast:
app; inline on wasm32).
- **Undo is a pointer swap.** History stores `Arc<Project>` snapshots and comps are `Arc`s, so
undo and redo of any edit are constant time. An edit copies the comps it changes (a one-layer
edit in a 5,000-layer comp copies that comp: ~8 ms).
edit in a 5,000-layer comp copies that comp: ~8 ms). An edit that changes nothing (a value
set to what it already was) records no step: `Project::same_content` compares the comps the
edit left shared by pointer, so the check costs about as much as the copy.
- **Modified follows undo.** The session keeps the snapshot it last opened or saved
(`Session::saved_project`); the project is unmodified whenever the current snapshot is that
one, so undoing (or jumping in the History panel, or rolling back a batch) to the saved state
clears the modified mark. The window title shows the project and `*` while it is modified, and
closing a modified project (Quit, the window's close button, New Project, Open, Open Recent,
Close Project, Revert, the demo project, a Home template) asks Save / Don't Save / Cancel first
(`ui-egui/src/panels/unsaved.rs`). Agents' `engine.execute` calls are never asked.
Numbers (Apple Silicon, 14 cores, release build, best of 3–20 runs; the machine was shared with
other builds, load average 150–220, so treat them as orders of magnitude). "Before" is the
+1 -1
View File
@@ -53,7 +53,7 @@ panel registers an element id; both are enforced by `crates/ui-egui/tests/ui_age
| `ui.playback` | `{action: play\|stop\|toggle\|status}` | `{playing, time, audio, levelsDb, peaksDb}` (audio: an audio preview is running; L/R meter levels and held peaks in dBFS) |
| `ui.resize` | `{width, height}` | Resizes the window (points). |
| `ui.focus` | `{}` | Activates the window and takes keyboard focus. Agents normally avoid this, because the app is started without stealing focus. |
| `app.quit` | `{}` | Closes the app. |
| `app.quit` | `{force?}` | Closes the app. A modified project shows the Save / Don't Save / Cancel prompt first (automation ids `dialog.unsaved.save`, `dialog.unsaved.dontSave`, `dialog.unsaved.cancel`) unless `force: true`. |
## Input
+11
View File
@@ -383,6 +383,17 @@ Browser in the web app (landed in M13.10: browser storage and File System Access
wrong signatures and versions, fuel exhaustion, traps and bit-identical output; script
`Socket` validates ports, and ScriptUI resource strings accept trailing array commas.
## Update: M3.9 unsaved changes
Closing a modified project asks first, as After Effects does: 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 (Save asks for a path when the project is untitled, and
cancelling that keeps the project open); File ▸ Revert asks to discard. The window title shows
the project's path and `*` while it has unsaved changes. The modified mark now follows undo
(undoing, jumping in the History panel or rolling back a batch to the saved state clears it),
and edits that change nothing record no undo step. Agents' `engine.execute` / MCP calls are never
asked; the control channel's `app.quit {force: true}` skips the prompt.
## Update: M13.25 Home template gallery, Simulate Output ▸ My Custom RGB
**Home ▸ Templates** (UI-7; File ▸ New ▸ New Project from Template…): a gallery of eight