M12: session restore: windows and tabs come back as they were left

The interface keeps a saved session with the preferences, written whenever
it changes (at once, then at most once a second; nothing while idle): each
window's place, size, maximized or full-screen state and screen, its panels,
its tabs in order with the one in front, and per tab the saved file or your
online file (server and id, never a token), the page and the view.

At the next start the windows and tabs open again (`session.restore`): a
window off every screen moves onto one, files that are gone are skipped
with one message, online files are joined again as their owner and take
their place when they arrive, and a file or invite link from the command
line opens in front. Unsaved documents stay the recovery copies' job.

A mark written before restoring and cleared once the windows have settled
keeps a crash while restoring from repeating: the next run starts without
it and the home screen offers Restore last session. Preferences ▸ Reopen
windows and tabs from last time (`pref.restoreSession`) and `--fresh`
start without it. `window.list` reports each window's rectangle and state;
`ui.inspect` reports the kept session.

The saved-state reader takes junk, cut-off, huge and wrongly typed input
without failing; tests cover the round trip, order and front tab, missing
files, an online file through a scripted link, off-screen rectangles,
the crash guard, the preference, a command-line file and several windows.
This commit is contained in:
bflatastic
2026-10-13 13:38:00 +07:00
parent e56856f401
commit 264aa2a703
33 changed files with 2105 additions and 76 deletions
+26 -3
View File
@@ -241,9 +241,12 @@ for menus, the Actions palette and `engine.execute`.
### `window.list {}`
Every window: `[{id, main, title, files, active, focused}]` (`files` are its tabs' names, `active`
the one it shows, `focused` the window requests go to). `ui.inspect` reports it as `windows`, plus
`window` (the window it ran in).
Every window: `[{id, main, title, files, active, focused, rect, maximized, fullscreen, monitor}]`
(`files` are its tabs' names, `active` the one it shows, `focused` the window requests go to;
`rect` is `[x, y, width, height]` in screen points, its outer position and inner size while it is
neither maximized nor full screen, so a maximized window reports the place it goes back to;
`monitor` is the screen showing most of it, `[x, y, width, height]`, or `null` when none is known).
`ui.inspect` reports it as `windows`, plus `window` (the window it ran in).
### `window.focus {id}`
@@ -257,6 +260,26 @@ closing while others are open takes over one of them, and closing the last windo
Close the app (every window, without asking about unsaved changes).
### Session restore
The desktop app keeps its windows and tabs with the preferences as they change and opens them
again at the next start (see [ui-parity.md](ui-parity.md)); `figcraft --fresh` starts without
them. `ui.inspect` reports it as `session`:
- `saved`: the session kept now, `{windows: [{rect, maximized, fullscreen, monitor, tabs: [{path}
| {server, file}, page, view: [cx, cy, zoom]], front, leftTab, rightTab, leftCollapsed,
panelWidths}]}`, the main window first. Tabs are saved files and your online files (their
server and id, never a token); documents never saved aren't in it.
- `restore`: the preference (`pref.restoreSession {on?}`).
- `offered`: this run started without the last run's session (the preference, `--fresh`, or the
last run never got through restoring), and `session.restore` brings it back.
- `joining`: restored online files still on their way from their servers.
- `monitors`: the screens the app knows, `[[x, y, width, height]]`.
`engine.execute {command: "session.restore", params: {session?}}` restores the session on offer
(else the one kept), or the saved session given: it returns `{windows, opened, joining, missing}`
and the windows are placed at the main window's next frame (`window.list` shows them).
## Example session (shell)
```sh