New and Open open the document in a window of its own (an egui viewport,
craft-window's window list and driver), focused and cascaded; Open replaces
an untouched blank document in its window and brings an already-open file's
window forward. Each window keeps its own interface state and draws its own
document. Close and a window's close button close that window, asking first;
the root viewport adopts another window when its own closes; the last window
stays. Quitting asks about each document in its window. Requests for another
window's document go to that window. Control input, screenshots and the saved
geometry follow the active window. The web app keeps one window.
WindowUi holds what one window shows and is doing (canvas, dialogs,
menus, status, ribbon tab, Backstage, keytips, file dialogs...) for
windows not being drawn; WordApp::swap_window exchanges it with the drawn
window's fields by moves. The swap destructures WordApp and UiState
exhaustively, so every new field must be classified as per window,
per document or app-wide. WordApp::new takes its window defaults from
WindowUi::new.
One recovery worker and store serve every open document. Each document
keeps its slot (record id, revision, timer) and is ticked in place, parked
or active. Closing a document deletes its copy, Don't Save when quitting
does too, and a clean exit covers every open document. File > Recover
lists only copies no open document keeps; Restore opens a copy beside the
open documents or in place of an untouched blank one.
The desktop window follows the session's active document. What the window
keeps per document (AutoSave's file, comment drafts) is parked while
another document shows and comes back with it; everything about the page
that was shown (dialogs, menus, drags, composition, status, caches keyed
by revision) starts afresh. The page's scroll area is per document, so
switching back finds the page where it was left; zoom is in the session.
- View › Window › Switch Windows lists the open documents (active marked,
unsaved dotted) and shows the one chosen (view.switchWindows).
- New, Open, the mailings and personal templates open beside the open
documents and no longer ask about unsaved changes.
- File › Close shows the next open document; after the last one the
window shows no page, as before.
- Quitting asks Save / Don't Save / Cancel about each document with
unsaved work, showing it; Cancel keeps the app open.
- UI requests tagged for a document that isn't shown never act on the
shown one: theme changes apply, Close closes that document when it has
nothing unsaved, the rest are dropped.
- Read Aloud highlights and follows only the document being read.
- A late Save As answer shows and saves the document it was for.
- Untitled documents are numbered in the interface language.
- MCP: execute, batch, type_text, select_text, get_text, inspect_document,
render_page and save_document take an optional doc (an open document's
id); the headless server runs them through Session::with_document and
the bridge passes doc to the app. New list_documents tool; new_document
replies with the new document's id. Tool descriptions and the server
instructions say that file.new and file.open open another document.
- Control channel: engine.execute, document.inspect and ui.render take
doc and work on that document while the window keeps the active one;
file.close on an addressed document refuses unsaved changes unless
forced; ui.* commands need the document on screen. ui.inspect reports
document and documents. Unknown or malformed ids are errors.
- CLI run starts with one open document; help and docs describe how
file.new, file.open and view.switchWindows work in a run.
- docs/mcp.md and docs/control-protocol.md describe document ids and doc.
One session holds several open documents. The active document stays in
the Session's own fields, so commands and the UI keep working on it; the
others are parked as DocState values and swapped in field by field
(std::mem::swap, no clones or relayout). Session and DocState are
destructured exhaustively, so a new field must be classified as per
document or shared before it builds.
- Session::documents, activate, add_document, close_document,
is_untouched_blank, with_document, find_open, is_open; DocInfo.
- Document ids come from a process-wide counter.
- file.new opens beside the open documents; file.open replaces an
untouched blank document, opens beside the others otherwise, and
activates a file that is already open ("alreadyOpen": true).
- file.closeDocument {id?, force?} closes a document without a window
(refuses unsaved changes unless forced); file.close is unchanged.
- view.switchWindows {id?} lists the open documents or activates one.
- Envelopes, Labels, Finish & Merge and New from Template open their
result as a new document; the main document stays open, with no undo
step for the command.
- Each document keeps its own recovery worker and copy; Read Aloud
readings name their document; with_document tags UI requests with the
document they came from.
#3201 makes entering an empty header scroll to it; Esc then scrolls back to the caret, landing 0.5 px from the start (17291.0 vs 17290.5). The test checks the view is back within a pixel.
every_tab_and_command_is_translated needs Format Shape Pane, Full Screen View, Personal Templates and Underline Color, added by other merged PRs. Rows carry the English text until translated.
polish_covers_every_key_of_every_other_catalog expects Italian to have only "Style: APA" beyond Polish; #2041 translated 14 newer strings into Italian. Rows carry the English text until translated.
Main (#1500) moved VML fill reading into shape_style.rs, where filled="False" still counted as on; #2244's test vml_shape_wrap_side_and_anchor_case_insensitivity expects no fill.
crates/ui-egui/src/backstage.rs: #3125's t.nav fill kept, without the recovery black. Docs: status entries prepended to main's lines; the recovery paragraph placed after the status line.
Its complete-catalog test needs every key of every other catalog; the merged PRs added 72 since it was written. Rows carry the English text until translated.
i18n: registry holds 27 languages (sl after nl, da, ro); audit.rs and the Polish test list sl beside pl and ro; tests: both sides kept. Docs: language counts and lists name ro, sl, nl and da; status entries prepended to main's lines.
ROADMAP.md: the PR's status entry prepended to main's first status line. docs/parity-checklist.md: main's side, regenerated after the merges. ko.tsv: union.
crates/ui-egui/src/backstage.rs: t03's bidi_display on the Recent names over t09's borrowed name/path. people.toml: union. Tests: both tracks' tests kept whole. Docs: status lines combined, rows unioned or word-merged.
docs/gaps.md row 4: one row (t06 had kept a second, older copy of it); t05's #1840 and t06's #2964 clauses both in, main's #2963 clause kept. docs/recovery.md: t06's retry sentence with t01's base64 size text. Test files: both tracks' tests kept whole.
crates/pdf/src/lib.rs: t01's vector_text kept before t05's watermark (box area signature). Test files and canvas.rs tests: both tracks' tests kept whole. Docs: status lines combined, rows unioned or word-merged.