Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .devbook/arc42/06-runtime-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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. |
43 changes: 36 additions & 7 deletions .devbook/arc42/adr/0043-a-surface-can-open-in-its-own-window.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
```
Expand All @@ -166,17 +169,39 @@ 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**.

A remembered window reopens on the next start, after the main window. It shows the
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion .devbook/design/interaction-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
Loading