diff --git a/.devbook/arc42/06-runtime-view.md b/.devbook/arc42/06-runtime-view.md index 39ed52c4b..30306cb7f 100644 --- a/.devbook/arc42/06-runtime-view.md +++ b/.devbook/arc42/06-runtime-view.md @@ -496,3 +496,31 @@ sequenceDiagram - **A missed nudge costs only time.** Desktop A runs a full exchange on every nudge and one to catch up when its socket opens. While a question is open and the socket is down, it polls for replies every two minutes. - **The answer survives a restart.** Desktop A stores the accepted answer in SQLite before calling the source's handler and replays it to a handler that was not running. The handler must therefore accept the same answer twice. - **A late reply is rejected with its reason.** A reply to a question already answered or closed is rejected, and the phone shows which answer won. + +## Main Window Close, Hide and Quit + +```meta +related: [".devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md#5-each-window-has-its-own-view-state-closing-the-main-window-hides-it-to-the-tray", ".devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md#1-the-server-lives-in-the-desktop-process-over-streamable-http-on-loopback", ".devbook/design/interaction-guidelines.md#save-timing-and-debounce"] +``` + +Closing the desktop app's main window hides it to a notification-area icon, so the process +and its MCP listener keep running. Local ADR 0043 §5 records the decision. It is not built yet. + +```mermaid +stateDiagram-v2 + [*] --> Shown: App starts, MCP listener binds + [*] --> Hidden: Start at sign-in + Shown --> Hidden: Close with the tray setting on + Shown --> Exiting: Close with the tray setting off, or Quit + Hidden --> Shown: Open Backlog, double-click, second launch, link, toast, Show in main window + Hidden --> Exiting: Quit, sign-out, shutdown, MSIX update + Shown --> Exiting: Sign-out, shutdown, MSIX update + Exiting --> [*]: Flush, close every window, remove the icon +``` + +- **A hide flushes as a close does.** The navigation and window-bounds stores are written before the window hides, so a hidden app that is later ended loses nothing. +- **Hidden is not idle.** The MCP listener answers and `open_window` opens pop-outs. Popped-out windows stay open, and a jump to the main window re-shows it. +- **A second launch finds the running app.** A plain launch is redirected to the running instance, as a `backlog://` link already is, so two apps never compete for the MCP port. +- **The first hide says where the app went.** A one-time toast says Backlog is still running in the tray. +- **Start at sign-in enters Hidden directly.** The MSIX `StartupTask` starts the app with no window, so the tools are up before the first session. The setting is off by default and works only in the installed app. +- **Exiting is the only way out.** Quit, sign-out, shutdown and an MSIX update all flush, close every window and remove the icon. With the tray setting off, a close goes there too. diff --git a/.devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md b/.devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md index 34851855c..6338c0c8a 100644 --- a/.devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md +++ b/.devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md @@ -1,7 +1,7 @@ # ADR 0012: Backlog is an MCP server hosted inside the running desktop application ```meta -date: 2026-09-22 +date: 2026-11-10 related: [".devbook/arc42/03-context-and-scope.md#access-channels-scope", ".devbook/arc42/05-building-block-view.md#desktop-app", ".devbook/arc42/07-deployment-view.md#local-deployment-desktop", ".devbook/arc42/08-crosscutting-concepts.md#feature-enablement", ".devbook/arc42/08-crosscutting-concepts.md#authentication-and-authorization", ".devbook/arc42/adr/0002-backlog-module-owns-the-entry-text-language.md", ".devbook/arc42/adr/0003-sqlite-is-the-canonical-local-task-store.md", ".devbook/arc42/adr/0004-knowledge-index-is-a-generated-local-database.md", ".devbook/arc42/adr/0008-knowledge-reads-from-a-branch-snapshot-when-there-is-no-clone.md", ".devbook/arc42/adr/0011-devbook-annotations-are-a-third-replica-container.md", ".devbook/arc42/adr/guidelines/0007-minimal-apis-over-controllers.md", ".devbook/domain/tasks/domain.md#task", ".devbook/domain/devbook/features.md#remarks-on-a-chapter", ".devbook/domain/dev-pc-management/features.md#configuration-and-tool-version-tracking", ".devbook/arc42/adr/0044-devbook-subject-state-lives-in-backlogs-own-store.md"] c4: [backlog.desktop] c4-touches: [backlog.mobile, backlog.desktop.backlogModule, backlog.desktop.sessions] @@ -141,7 +141,8 @@ that is accepted.** The client registers an HTTP server; when nothing listens, the client reports the server unavailable and the session continues without its tools. The skills that call them are written for that already — the pasted plan item "is the whole input for now" — and opening Backlog is the -whole of the repair. +whole of the repair. The listener runs as long as the process does, and closing the main +window hides it to the tray rather than ending the process (local ADR 0043 §5). ### 2. The host code: a framework reference in `Backlog.Desktop` only, started the way `TaskSyncWorker` is @@ -398,3 +399,10 @@ day this record was accepted. A row marked **Built** is one the plan has closed. annotation tools belong to two switchable areas (`backlog`, `repository-devbook`) that a person turns on separately, and a tool for an area that is off would be a surface for a capability that has none. + +## History + +| Date | Change | Why | +| --- | --- | --- | +| 2026-11-10 | The MCP listener now outlives the main window (local ADR 0043 §5). It stops only on Quit, sign-out, shutdown or an MSIX update. While **Keep running in the tray when closed** is on, closing the window no longer removes the tools from sessions. Not built yet. | Plan `tray-keep-alive`: closing the window was the most common reason a session ran without the `backlog` tools. | +| 2026-09-22 | Accepted: Backlog is an MCP server hosted in the running desktop app, over Streamable HTTP on loopback, written ahead of the code. | The `backlog-mcp-server` plan needed a decision to build against. | diff --git a/.devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md b/.devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md index 683880be7..fb7797c27 100644 --- a/.devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md +++ b/.devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md @@ -1,8 +1,8 @@ # ADR 0043: A surface can open in its own window, and modules register the surfaces the shell shows ```meta -date: 2026-11-04 -related: [".devbook/arc42/adr/0022-the-shell-shows-one-main-view-picked-by-a-view-switch.md#amendment", ".devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md#1-the-server-lives-in-the-desktop-process-over-streamable-http-on-loopback", ".devbook/arc42/adr/0027-the-brain-is-a-scope-beside-the-repositories.md", ".devbook/arc42/adr/0038-backlog-queues-session-requests-and-a-manager-session-dispatches-them.md", ".devbook/arc42/adr/guidelines/0005-modular-monolith-structure.md#deviations-and-gaps", ".devbook/design/interaction-guidelines.md#shell-header", ".devbook/design/interaction-guidelines.md#workspace-panes"] +date: 2026-11-10 +related: [".devbook/arc42/adr/0022-the-shell-shows-one-main-view-picked-by-a-view-switch.md#amendment", ".devbook/arc42/06-runtime-view.md#main-window-close-hide-and-quit", ".devbook/design/interaction-guidelines.md#save-timing-and-debounce", ".devbook/arc42/adr/0012-backlog-is-an-mcp-server-inside-the-desktop-app.md#1-the-server-lives-in-the-desktop-process-over-streamable-http-on-loopback", ".devbook/arc42/adr/0027-the-brain-is-a-scope-beside-the-repositories.md", ".devbook/arc42/adr/0038-backlog-queues-session-requests-and-a-manager-session-dispatches-them.md", ".devbook/arc42/adr/guidelines/0005-modular-monolith-structure.md#deviations-and-gaps", ".devbook/design/interaction-guidelines.md#shell-header", ".devbook/design/interaction-guidelines.md#workspace-panes"] ``` Each module puts its screen in the desktop shell by registering a **`ShellSurface`** from @@ -25,6 +25,9 @@ decisions were settled with the repository owner before the plan was imported, a this record writes them down. Built: §1 to §7, step by step, as the History records. Amended 2026-11-04: §5 lets a person remember a popped-out window, one window at a time. The repository owner decided it, and it is built, as the History records. +Amended 2026-11-10: §5 closes the main window to the notification area, so the app and +its MCP listener outlive it. The repository owner decided it under plan +`tray-keep-alive`, and it is not built yet. A **local** decision, numbered in the local sequence. Every bare ADR number below means the local one. It keeps ADR 0022's rule of one main view per window and replaces ADR @@ -157,7 +160,7 @@ no window key, such as a web harness tab or a loopback page, draws it nowhere. T look and placement are the design chapter's ([shell header](../../design/interaction-guidelines.md#shell-header)). -### 5. Each window has its own view state; the main window owns the app's life +### 5. Each window has its own view state; closing the main window hides it to the tray ```meta ``` @@ -166,8 +169,30 @@ Each window has its own view state. `WindowStateLifetime` becomes `Scoped` in th head, the same as the web harness. Data and change signals stay process-wide singletons, so a task saved in one window reloads in the others. -Closing the main window quits the app and closes every popped-out window. By default -nothing reopens on the next start. A person opts in one window at a time: the +Closing the main window with ✕ or Alt+F4 flushes the navigation and window-bounds stores +and hides the window to a notification-area icon. The process and the MCP listener keep +running, and popped-out windows stay open. The +[runtime view](../06-runtime-view.md#main-window-close-hide-and-quit) draws the states. + +- **The tray icon's menu** has **Open Backlog** and **Quit**. Double-clicking the icon + restores the main window. +- **Quit** flushes the stores, closes every window and ends the app. Sign-out, shutdown + and an MSIX update end it too. +- **A hidden main window comes back** on a plain second launch, a `backlog://` link, a + toast press, or **Show in main window**. +- **The first hide** shows a one-time toast saying Backlog is still running. + +Two per-machine settings sit on the MCP server settings page: + +- **Keep running in the tray when closed** is on by default. Turned off, closing the main + window quits the app again. +- **Start Backlog when I sign in** is off by default. It uses an MSIX `StartupTask`, and a + start at sign-in opens straight to the tray with no window. An unpackaged build (Debug + and Aspire) disables the switch with the note "Available in the installed app". When + Windows reports the task disabled by the person or by policy, the switch is disabled + and says to change it in Task Manager's Startup apps. + +By default nothing reopens on the next start. A person opts in one window at a time: the **Remember this window** toggle in a popped-out window's header (§4) marks that window **remembered**. @@ -175,8 +200,8 @@ A remembered window reopens on the next start, after the main window. It shows t surface and query it showed when the app quit, after any jump or scope change. It opens at its own last bounds, maximised if it was, moved onto a connected monitor. -- **Quitting the app keeps it remembered.** Closing the main window is not a choice - about the other windows. +- **Quitting the app keeps it remembered.** Quit is not a choice about the other + windows. - **Closing that window forgets it.** So does **Show in main window**, and so does pressing **Remember this window** again. - **A surface the catalog no longer offers is skipped,** and its remembered window is @@ -262,6 +287,9 @@ Negative: - **Places that assume one window must change**: the single notification-centre listener, and the `FirstOrDefault()` window lookups behind app notifications and the Brain folder picker. +- **The app outlives its main window.** A session keeps the `backlog` tools after the + window closes, and the head owns a tray icon, a second-launch redirect and a startup + task that the plan builds and has to test by hand. - **A remembered window needs an identity that outlasts the run.** It keeps its window key across starts, and it reports every jump and scope change to the store, so it reopens on what it showed. @@ -308,6 +336,7 @@ Neutral: | Date | Change | Why | | --- | --- | --- | +| 2026-11-10 | Amended §5, renamed from "the main window owns the app's life": closing the main window (✕ or Alt+F4) flushes the navigation and window-bounds stores and hides it to a notification-area icon. The process and the MCP listener keep running, and popped-out windows stay open. The tray menu has **Open Backlog** and **Quit**, and a double-click restores the window. Quit keeps the flush, closes every window and quits. A plain second launch, a `backlog://` link, a toast press and **Show in main window** re-show a hidden main window, and the first hide shows a one-time toast. Two per-machine switches join the MCP server settings page: **Keep running in the tray when closed**, on by default, and **Start Backlog when I sign in**, off by default, an MSIX `StartupTask` that starts to the tray. That switch is disabled in an unpackaged build and when Windows reports the task disabled by the person or by policy. The runtime view gains the scenario "Main window close, hide and quit". Not built yet. | Plan `tray-keep-alive`: closing the window stopped the MCP listener, so every session lost the `backlog` tools until Backlog was opened again. | | 2026-11-04 | Both §4 toggles kept side by side after `main` landed **Keep on top**: the header reads title, scope chips, **Keep on top**, **Remember this window**, **Show in main window**. **Keep on top** keeps the pin; **Remember this window** wears Lucide's `bookmark` (`BookmarkIcon`). Both sit in `surface-window__actions`, so below 40rem they stay on the title's row. `IShellWindows` answers `CanKeepOnTop` and `CanRemember` apart, and `shell-windows.json` keeps `onTop` and `remembered` as sections of their own beside `surfaces`. | Two toggles on one glyph would read as one control, and the pin already meant on top. | | 2026-11-04 | §5's remembered windows built. `ShellWindowStore` keeps a `remembered` list (key, surface, query, bounds) beside `surfaces`, written only while one is remembered. A query value stored as null is left out on read. `ShellWindowReopenPlan` sorts the remembered windows into those to open and those whose surface `ISurfaceCatalog` no longer offers, which are forgotten. `ShellWindows.ReopenRemembered` runs from `App` once the main window is created. It reopens each window under its old key at its own bounds, clamped to a connected monitor, and one that throws is forgotten without stopping the rest. `SurfaceWindow` draws the **Remember this window** toggle through `IShellWindows.CanRemember`, which only the head's `ShellWindows` answers true. Each jump and chip press is reported through `ReportShowing`. `BookmarkIcon` joins the component library. Quitting, closing the window, and bounds after a restart have no unit test, because they live in the head; they are checked by hand. | The amendment's own row, below, built in the same pull request. | | 2026-11-04 | Amended §5: a person can remember a popped-out window, one window at a time. §4's header gains an icon-only toggle, **Remember this window**, in the desktop head, and none where a window has no key. A remembered window reopens on the next start, after the main window, on the surface and query it showed at quit, at its own last bounds, moved onto a connected monitor. Quitting keeps it; closing it, **Show in main window** or pressing the toggle again forgets it. `shell-windows.json` gains a section for remembered windows beside `surfaces`, which is unchanged. The default stays off. Rejected keeps the automatic layout restore out, now beside the opt-in that replaces it, and adds a remembered window that outlives its own close. The Consequences follow. Not built yet. | The repository owner wants chosen windows back after a restart without the app guessing a whole layout. | diff --git a/.devbook/design/interaction-guidelines.md b/.devbook/design/interaction-guidelines.md index a251d9906..fdd54ee1e 100644 --- a/.devbook/design/interaction-guidelines.md +++ b/.devbook/design/interaction-guidelines.md @@ -47,7 +47,7 @@ the product has no save affordance. | Rule | Requirement | |---|---| | Text debounce | Continuous text edits MUST auto-save **500 ms–1000 ms** after the last keystroke (recommended default 750 ms). | -| Idle/blur flush | A pending save MUST be flushed immediately on field/editor **blur**, on navigation away, and on app background/close. | +| Idle/blur flush | A pending save MUST be flushed immediately on field/editor **blur**, on navigation away, on app background or close, and when the main window hides to the tray, which flushes the same way a close does. | | Discrete actions | Discrete changes (reorder, toggle, checkbox, metadata edit, drop) MUST save **immediately** (no debounce). | | Max in-flight latency | If a save has no acknowledgement within ~5 s, the indicator MUST move from `Saving` toward `Offline`/retry. | | Local-first ordering | Update UI (optimistic) → write local canonical Markdown → background sync; editing MUST NOT wait on sync. |