@gotcos/glasses-server 7.0.7 → 7.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.env.example +19 -0
  2. package/CHANGELOG.md +305 -0
  3. package/docs/web-key-verifier-contract.md +378 -0
  4. package/managed-runtime-contract.json +9 -1
  5. package/package.json +10 -3
  6. package/server/bootstrap.ts +10 -0
  7. package/server/index.ts +129 -23
  8. package/server/lib/agent-session-older.ts +218 -0
  9. package/server/lib/agent-session-recent.ts +321 -0
  10. package/server/lib/agent-session-store.ts +102 -20
  11. package/server/lib/api-auth.ts +37 -1
  12. package/server/lib/calendar-meetings.ts +70 -0
  13. package/server/lib/calendar-source.ts +54 -9
  14. package/server/lib/claude-hooks-installer.ts +111 -12
  15. package/server/lib/conversation.ts +56 -28
  16. package/server/lib/cors-reject-log.ts +51 -0
  17. package/server/lib/cos-started-sessions.ts +101 -0
  18. package/server/lib/disk-io-meter.ts +202 -0
  19. package/server/lib/drop-service.ts +47 -12
  20. package/server/lib/drop-store.ts +21 -1
  21. package/server/lib/event-loop-stall.ts +62 -0
  22. package/server/lib/full-hold-engine.ts +114 -0
  23. package/server/lib/full-hold.ts +124 -0
  24. package/server/lib/g2-turn-provenance.ts +128 -9
  25. package/server/lib/list-model-memo.ts +85 -0
  26. package/server/lib/listener-startup.ts +18 -0
  27. package/server/lib/meeting-batch-transcribe.ts +32 -5
  28. package/server/lib/model-efforts.ts +40 -0
  29. package/server/lib/permission-broker.ts +443 -11
  30. package/server/lib/secret-path-guard.ts +4 -1
  31. package/server/lib/session-cache-writer.ts +8 -1
  32. package/server/lib/session-hook-events.ts +23 -1
  33. package/server/lib/session-hooks-runtime.ts +34 -0
  34. package/server/lib/session-log.ts +313 -4
  35. package/server/lib/session-organizer.ts +305 -0
  36. package/server/lib/session-state-derive.ts +36 -0
  37. package/server/lib/session-stream-events.ts +39 -3
  38. package/server/lib/thread-turn-queue.ts +24 -0
  39. package/server/lib/today-brief.ts +522 -58
  40. package/server/lib/turn-end-ask.ts +320 -0
  41. package/server/lib/turn-footer.ts +3 -3
  42. package/server/lib/web-control-integrity.ts +160 -0
  43. package/server/lib/web-control-offnet-page.ts +58 -0
  44. package/server/lib/web-control-static.ts +53 -9
  45. package/server/lib/web-key-authority.ts +372 -0
  46. package/server/lib/web-key-envelope.ts +98 -0
  47. package/server/lib/web-key-handoff.ts +135 -0
  48. package/server/lib/web-key-origin.ts +21 -0
  49. package/server/lib/web-key-passkey.ts +155 -0
  50. package/server/lib/web-key-registry.ts +825 -0
  51. package/server/lib/web-key-signer-app.ts +152 -0
  52. package/server/lib/web-session-auth.ts +133 -15
  53. package/server/lib/whisper-local.ts +93 -7
  54. package/server/lib/whisper-preview.ts +5 -3
  55. package/server/lib/work-orchestrator/snapshot.ts +17 -3
  56. package/server/lib/work-review-runtime.ts +17 -4
  57. package/server/routes/agent-session-bindings.ts +16 -1
  58. package/server/routes/agent-session-stream.ts +7 -0
  59. package/server/routes/agent-sessions.ts +106 -2
  60. package/server/routes/drops.ts +7 -2
  61. package/server/routes/g2-turns.ts +35 -0
  62. package/server/routes/health.ts +14 -1
  63. package/server/routes/live-events.ts +6 -1
  64. package/server/routes/permission-broker.ts +26 -0
  65. package/server/routes/query-jobs.ts +3 -0
  66. package/server/routes/session-organizer.ts +149 -0
  67. package/server/routes/sessions.ts +15 -24
  68. package/server/routes/thread-turn-queue.ts +50 -14
  69. package/server/routes/today-brief.ts +91 -36
  70. package/server/routes/web-control.ts +37 -4
  71. package/server/routes/web-key.ts +284 -0
  72. package/server/routes/welcome-context.ts +6 -2
  73. package/server/routes/work-orchestrator.ts +12 -7
  74. package/server/scripts/write-web-control-manifest.mjs +43 -0
  75. package/server/web-control/.manifest.json +44 -0
  76. package/server/web-control/app.css +45 -0
  77. package/server/web-control/app.js +1680 -108
  78. package/server/web-control/apple-touch-icon.png +0 -0
  79. package/server/web-control/index.html +54 -17
  80. package/server/web-control/panes/cos-chat.css +52 -6
  81. package/server/web-control/panes/cos-chat.js +364 -32
  82. package/server/web-control/panes/questions.js +135 -14
  83. package/server/web-control/panes/sessions.css +280 -0
  84. package/server/web-control/panes/settings.js +1 -1
  85. package/server/web-control/panes/today-actions.css +35 -0
  86. package/server/web-control/panes/today-actions.js +121 -6
  87. package/server/web-control/panes/work.css +3 -0
  88. package/server/web-control/panes/work.js +185 -38
  89. package/server/web-control/theme.js +33 -0
  90. package/server/web-signer/.manifest.json +11 -0
  91. package/server/web-signer/index.html +74 -0
  92. package/server/web-signer/signer-proof.js +348 -0
  93. package/server/web-signer/signer-store.js +39 -0
  94. package/server/web-signer/signer.css +23 -0
  95. package/server/web-signer/signer.js +341 -0
  96. package/shared/web-approval-packets.golden.json +124 -0
  97. package/shared/web-turn-envelope.ts +271 -0
package/.env.example CHANGED
@@ -209,6 +209,25 @@ BIND_HOST=0.0.0.0
209
209
  # Per-hook rollback without reinstall: echo 0 > ~/.cos-glasses/hook-ask-hold
210
210
  # COS_PERMISSION_BROKER_DESK_IDLE_S=90 # never below 30
211
211
  # COS_PERMISSION_BROKER_TIMEOUT_S=110 # clamped to 5..120 (under the hook's 125 s)
212
+ # 7.0.16: a waiting session question's first 80 characters show on Today and the session rows
213
+ # (glasses, phone, COS Control, Control Web). Question text only, never options or answers. It is
214
+ # also written to the session-hook ledger on disk. Default ON; 0 restores 7.0.13's empty line.
215
+ # COS_WAITING_QUESTION_WORDS=0
216
+ # 7.0.16, both OFF by default: hold a session question (QUESTION) or a tool approval (APPROVAL) for
217
+ # the whole hook wait, about an hour, so the glasses, the phone or Control Web can answer it even if
218
+ # none was connected when it was asked. Only on engines proven to keep their own dialog up meanwhile
219
+ # (Claude Code CLI 2.1.321+ in a terminal, Claude Desktop 2.1.317+); the Mac's dialog still shows at
220
+ # once and the first answer wins. To turn on: set it here, restart the server, then COS Control >
221
+ # Settings > Reinstall hooks. To turn off: remove it and restart (every held item goes back to the Mac).
222
+ # Keep APPROVAL off for now: a signed-in Control Web browser could allow a tool for about an hour.
223
+ # COS_QUESTION_FULL_HOLD=1
224
+ # COS_APPROVAL_FULL_HOLD=1
225
+ # 7.0.18, default ON (named by Miles 11/10): a Claude desktop session that ends its turn with a
226
+ # question and reply buttons shows as waiting on the glasses, phone, Control Web and COS Control once
227
+ # the desk has been idle 90 s. Only an ask with reply buttons (or marked blocked); gone on any reply,
228
+ # a new turn, the session ending, or after 8 hours. Never print runs, COS-started runs or sub-agents.
229
+ # Words follow COS_WAITING_QUESTION_WORDS. 0 turns it off (restart the server); nothing to reinstall.
230
+ # COS_TURN_END_ASKS=0
212
231
 
213
232
  # ── THE LLM (chat) ──────────────────────────────────────────────────────
214
233
  # Chat runs through your LOCAL agent CLI — NOT an API key:
package/CHANGELOG.md CHANGED
@@ -1,3 +1,308 @@
1
+ ## 7.0.27 - Control Web: the Sessions list fits its column at every width
2
+
3
+ 2026-11-11. 7.0.26 plus one Control Web layout fix.
4
+
5
+ - **The list no longer scrolls sideways when its column is narrow.** With the divider dragged to its narrowest (240 px), the list was 32 px wider than its column, so the column panned sideways and cut its left edge ("ssions", "earch titles and transcripts", half the row icons). The reach line ("Last 30 days. 582 older through search.") shared a row with New folder and the sort menu and was squeezed to one word a line, and with the keyboard up the sort menu and search field were cut at the right. Now the reach line has its own full-width row, New folder and the sort menu share the next one and wrap under each other if they must, and a long folder name ends in an ellipsis like a title. Nothing is hidden to make it fit: the content fits the column.
6
+ - Tested in a browser by dragging the divider from its narrowest to its widest at both open-phone sizes, keyboard up and down, with and without a session open: 7.0.26 fails, this build passes. The reply area and the reader are unchanged.
7
+
8
+ **Default-on for every install:** yes, the layout fix; there is no switch.
9
+
10
+ ### If you roll back
11
+
12
+ - To 7.0.26: the narrow list scrolls sideways again. Nothing is stored by this change.
13
+ - To 7.0.18: as in 7.0.26 below.
14
+
15
+ ## 7.0.26 - 7.0.25, with kept sessions only where they were asked for: the Mac app and the glasses keep their list
16
+
17
+ 2026-11-11. 7.0.25 plus one server change and one Control Web line.
18
+
19
+ - **Kept sessions only for a client that asks.** In 7.0.25 every client received the kept sessions (COS pins, folders, sessions a person started through COS) on top of its usual list, so after a server update the Mac app and the glasses would have shown up to 40 more rows with no app update. Now the server adds them only when the request asks (`kept=1`). The Mac app 0.5.389 and the glasses 6.11.681 do not ask, so they get the same list as 7.0.18. Control Web asks, so Sessions in Control Web shows them as in 7.0.25.
20
+ - Nothing else changes: `/older` and the folder, pin and rename routes are as in 7.0.25.
21
+
22
+ **Default-on for every install:** everything in 7.0.25 below; the one difference is that kept sessions reach Control Web only.
23
+
24
+ ### If you roll back
25
+
26
+ - To 7.0.25: the Mac app and the glasses list kept sessions again. Nothing is stored by this change.
27
+ - To 7.0.18: as in 7.0.25 below.
28
+
29
+ ## 7.0.25 - Control Web: the Sessions list in groups, 30 days, folders, COS pins and rename; the server keeps them; two 7.0.23 faults fixed; a queued note is no longer stuck
30
+
31
+ 2026-11-11. 7.0.23 plus fixes to it (below), the server's session store, the queue fixes and the grouped list. 7.0.24 was never released.
32
+
33
+ **A fault in 7.0.23, fixed here: Send could sit below the screen once files were attached.** In the new Sessions frame the reply area could grow (the chips of attached files wrap to more rows) until Send was past the bottom of the screen with nothing to scroll: 2 files on the open Duo, 1 with the keyboard up on the folded phone. 7.0.22 did not do this (its page scrolls). Now the reply area follows one rule: it never grows past the visible height, its inside scrolls, and the reply box with Send stays pinned at its bottom, always in view. The chip row and a long draft scroll inside their own capped rows; the busy note, the queue and Not sent sit above the reply box and scroll. Checked in WebKit at five sizes with and without the keyboard, with 0 to 5 files, 0 to 3 queued notes, Not sent, the busy and "another app" notes and a long draft. Also: on the open Duo, Copy session and the rail's Settings reach 44 px for touch, and on a short screen with the keyboard up the reply area drops its extra spacing.
34
+
35
+ **A second fault in 7.0.23, fixed here:** when the web sign-in lapsed while Sessions was open, the sign-in screen came up but kept the Sessions frame (the page fixed at one screen, no scrolling). With the keyboard up the Connect button sat below the screen and could not be reached; only quitting and reopening cleared it. 7.0.22 did not do this. Now the frame comes off whenever the sign-in screen shows, and comes back with the app. Checked in WebKit with a real scroll and a real tap on Connect at four sizes (7.0.23 fails, this build passes).
36
+
37
+ **A queued note is no longer stuck (server):** a follow-up sent while a session was busy could wait long after the session's turn had really ended: COS's reading of newer Claude Code transcripts took a copied tool request for an open turn, and while the session kept writing, the wait kept renewing. One note waited over two hours this way. Now that reading ignores records that are not the user's or the assistant's own turns, so a copied tool request no longer reopens a turn. Each waiting note also carries why it waits, and the queued-note line in Control Web says it in words, for example "It has files, so it waits for the current turn to end."
38
+
39
+ **What changes in the Sessions list, for everyone who opens it in Control Web:**
40
+ - **Groups, top to bottom:** Needs you, Working now, Pinned, your folders, then Today, Yesterday, Earlier this week, Last week, Earlier this month. A session shows once. Needs you uses the same rule as Today, so the two agree.
41
+ - **Rows move only when the list is at rest.** A session that starts waiting or running moves to its group 2 s after the list was last pressed, scrolled or focused, never under your finger. That includes a change the live stream sends. The list keeps its place when rows are added above.
42
+ - **30 days instead of 7:** Last week and Earlier this month are folded groups of compact rows, with Show older for more. One quiet line says how far it reaches, for example "Last 30 days. 271 older through search."
43
+ - **Folders:** New folder, rename, delete, and Move to folder from a row's menu. Deleting a folder releases its sessions; it never deletes a session.
44
+ - **COS pins and rename:** Pin and Unpin from a row's menu, and Rename, which search matches. An agent's own pin shows as "Pinned in Claude" and only the agent can undo it; a COS pin under it shows Unpin in COS.
45
+ - **The Sessions menu** (Sessions, Usage, COS cost, Refresh) and one sort menu (Updated or Opened; Best match while searching) head the list.
46
+
47
+ **What changes on the server, for every client:**
48
+ - **Kept sessions (changed in 7.0.26: Control Web only).** In 7.0.26 the server adds them only when a client asks (`kept=1`); Control Web asks, and the Mac app 0.5.389 and the glasses 6.11.681 get the same list as 7.0.18. What a kept session is: a session pinned in COS or put in a folder stays in the list at any age and past the per-agent cap of 20; a session a person started through COS stays past that cap for 7 days. At most 40 kept rows per response, across agents; the rest are counted (`dropped.kept`) and come back as short rows through `/older`, so none is lost. About +150 ms and +27 KB per list read when all 40 are in use.
49
+ - **Sessions a person started through COS (Ask, Work, Continue, tasks) stay in the list for 7 days past the cap of 20, with no action from anyone** (from 7.0.26, in Control Web). COS routines and maintenance jobs are left out. A job counts only when a person started it: no origin or a task origin, and no maintenance workflow, so a new kind of background job is left out by default.
50
+ - `/api/models` gains each agent's efforts. List rows gain `folder_id`, `cos_pinned`, `title_override` and the reported model where known. Older clients ignore unknown keys.
51
+ - **New disk reads:** a model warm after each list read (Claude and Codex, at most 4 at a time, each file at most once a minute), and the query-job journal for session ids and times only (each day file at most every 30 s). No prompts or answers are read.
52
+
53
+ **What is stored and where:** one new file, `session-organizer.json` in the data folder (`~/.cos-glasses/data/` on a default install; it follows `COS_DATA_DIR`). It is created on the first change and holds folder names, COS pins, renamed titles and the session ids they belong to, as plain text.
54
+
55
+ **Tested** in a browser at phone and desktop sizes (the list, folders and frame checks pass) and by the full test suite.
56
+
57
+ **Default-on for every install:** yes: the grouped list, the 30 days, the kept sessions (Control Web only from 7.0.26), the store, the reply area rule, the sign-in screen fix, the queue fix and the reason line. There is no switch.
58
+
59
+ ### If you roll back
60
+
61
+ - To 7.0.23: Control Web shows the frame with the old flat 7-day list, both 7.0.23 faults above come back, and a queued note can wait after a turn has ended again. The other clients drop back to today's list (on 7.0.26 they never received kept sessions). `session-organizer.json` stays on disk untouched and is ignored; reinstalling 7.0.25 picks it up again. Nothing else is stored by this release.
62
+ - To 7.0.18 (the public build before this one): everything from 7.0.19 to 7.0.25 in Control Web goes back to the 7.0.18 page, and a queued note can wait after a turn has ended again. `session-organizer.json` stays on disk and 7.0.18 ignores it (it reads no such file); the queued-note fields this release adds (the hold reason and its time) are ignored too. Nothing is rewritten.
63
+
64
+ ## 7.0.23 - Control Web: a visible height under 80 px is no reading, and Sessions is one screen (layout, not a proven fix)
65
+
66
+ 2026-11-11. 7.0.22 (dca6a1a) plus two changes to Control Web, on the lead's go (00:17). Candidate only: nothing is installed or published by this commit. Neither change is claimed to fix his screenshots until his phone shows it.
67
+
68
+ - **What his phone reported:** on the unfolded Duo (the open, 1119 wide layout), after a keyboard closed, the visible height read 0 for minutes at a time (19:55 to 20:01, 20:13 to 20:27 on 11/10), while the page height was back within 0.3 s. Never on the folded phone.
69
+ - **What the code now does with that:** a visible height under 80 px, or none, counts as no reading. The page then uses the whole page height from the top, so the reply box, Today's panel, the question card and the image viewer are laid out on the real screen, not on a height of 0. Before, only exactly 0 fell back. Work's chat box reads the same corrected height. Each run of bad readings is counted, and every layout report carries the counts (vz, vzr).
70
+ - **Sessions is one screen (the frame):** while Sessions shows its list and a session, the page never scrolls as a whole. The rail, the list and the conversation each scroll by themselves, and the reply area sits at the bottom of the session.
71
+ - A session opens at its newest turn and stays there as turns arrive, until you scroll away.
72
+ - The reply area takes at most 60 percent of the session (three quarters with the keyboard up). What it is doing now gives way first; the reply box never shrinks.
73
+ - The session's question cards end the conversation. Scrolled away from them, a Go to the question button sits over the end of the conversation and takes you back.
74
+ - Full width reads Hide list in Sessions. Usage and COS cost keep the page's own scroll.
75
+ - One position report on open and after a keyboard closes (at most 8 a page load): the visible box, and whether the rail, the list and the reply box are inside it. Numbers and yes or no only.
76
+ - **Checked on this Mac** (WebKit, touch on, scale 1, six sizes from 449 to 1440 wide): 227 checks pass, with real wheel events, and two keyboard stand-ins whose limits are stated with the checks. Only his phone shows a real keyboard, real touch scrolling and the 0.85 scale.
77
+
78
+ **Default-on for every install:** yes: both. The frame is a layout change for everyone who opens Sessions in Control Web. There is no switch.
79
+
80
+ ### If you roll back
81
+
82
+ - To 7.0.22: the guard and the frame go; Sessions scrolls as one page again. Nothing is stored by this release.
83
+
84
+ ## 7.0.22 - Control Web reports two more layout readings from the phone; nothing else changes
85
+
86
+ 2026-11-10. 7.0.21 (be6b117) plus two layout reports in Control Web, on the lead's go (20:52). It fixes nothing. Miles saw the Today panel move with the page under it, and the rail and list leave the screen when he squeezed a divider on his Duo. Neither happens on this Mac, so these reports let his phone say what the page did. Private on Miles's Mac first.
87
+
88
+ - **The Today panel report:** while the panel is open, the first time the page scrolls or the visible area moves (up, down or sideways), one report with those positions at open and now, and where the panel and the rail sit. Once per open.
89
+ - **The divider report:** when a drag on any divider ends, one report with the column widths, the page width and scroll width, how far the visible area has moved, and where the rail, the list and the reader sit. At most six a page load.
90
+ - **Numbers only,** plus the panel kind and the divider name. No text, titles or ids. Both go through the existing layout report limits.
91
+ - **Not proven off the device:** what his phone reports. On this Mac (WebKit, scale 1, 951 and 1119 wide) neither problem reproduces.
92
+
93
+ **Default-on for every install:** yes: both reports. There is no switch.
94
+
95
+ ### If you roll back
96
+
97
+ - To 7.0.21: the two reports stop. Nothing else changes and nothing is stored by this release.
98
+
99
+ ## 7.0.21 - Control Web shows the COS mark first on every reopen, and the phone reports where its open time goes
100
+
101
+ 2026-11-10. 7.0.19 (1a7c322) plus one change to Control Web, on the lead's go (14:55) after Miles's first 7.0.19 open on his phone: the mark showed only 72 ms before the app (first paint 520 ms, the app 592 ms). Not 7.0.20: that number stays with the phone approvals work, which is on hold. Private on Miles's Mac first.
102
+
103
+ - **Why the mark came late:** the page's six scripts (about 735 KB) sat at the end of the page, and the browser often drew nothing until all of them had run. On the phone that is most of the wait.
104
+ - **Now the scripts run one frame after the page is drawn,** in the same order as before, and the app starts only once every one of them has run, as before. They still start downloading while the page arrives, so a first open does not wait longer for them.
105
+ - **Measured in WebKit on this Mac,** with each script slowed to stand in for the phone and 150 ms added to every request: on a reopen the mark showed at about 185 ms every time (before: 194 ms on one run, 476 ms on another), and the app showed at the same time as before. A first-ever open still varies from run to run, before and after; the app is no slower.
106
+ - **The open timing report gains three numbers:** when the page finished arriving, when the scripts had run, and how long the sign-in check took. So the next slow open says where its time went.
107
+ - **The phone's layout reports gain the page width:** the layout width, the width the screen shows at 100%, the scroll width, and the element that sticks out past the edge (none when nothing does). This is to settle why the Duo shows Control Web at 85%: our page, or a phone or browser setting. Numbers and a short element name only, no text.
108
+ - **Not proven off the device:** only the phone shows its own first paint, a first-ever open, and the Duo's widths.
109
+
110
+ **Default-on for every install:** yes: the script loading, and the extra numbers in the layout reports. There is no switch.
111
+
112
+ ### If you roll back
113
+
114
+ - To 7.0.19: the scripts run as part of the page again, so the mark can come late; the reports lose the new numbers. Nothing is stored by this change.
115
+
116
+ ## 7.0.19 - Control Web shows the COS mark at once on open, instead of a blank page
117
+
118
+ 2026-11-10. 7.0.18 (a7e0317) plus one change to Control Web, on Miles's go (12:07, "Let's build that for the next publish for today. Ensure no regressions."). Private on Miles's Mac first.
119
+
120
+ - **The problem (Miles, 11:56):** opening the web app for the first time was a little slow. Measured: the page was blank until the Mac had answered twice, once to re-check the page and once to say whether this browser is signed in. Caching and compression were already right.
121
+ - **Now the COS mark shows at once,** on the page's own background in your theme, right after the small theme script and before the app's scripts run or the Mac answers. It goes the moment the sign-in card or the app shows. It shows neither one's content, so it is true whichever comes next.
122
+ - **Nothing else moves:** the mark sits above the page, so the app and the sign-in card appear exactly where they did. It takes no taps, asks the Mac for nothing and loads no new file.
123
+ - **If the page's scripts never run,** the mark hides itself after 25 seconds, longer than the 20 second sign-in check. A Mac that does not answer still shows "COS on your Mac is not answering" as before.
124
+ - **With JavaScript off,** the page now says "COS Control Web needs JavaScript. Turn it on for this site, then reload." It was blank.
125
+ - **A timing report on open:** one layout report gives the milliseconds from opening to the first paint and to the app or sign-in card showing, inside the existing report limits, with no text. So the next slow open is measured on the phone itself.
126
+ - **Measured in WebKit on this Mac** with 150 ms added to every request: on a reopen the first paint came at 189 ms instead of 358 ms, and the app at the same time as before. Only the phone shows its own numbers, and the Home Screen app.
127
+ - **Notes:** the 7.0.18 entry below now also says what the glasses show for a turn-end question and how to answer by voice. That line was left out of the 7.0.18 package.
128
+
129
+ **Default-on for every install:** yes: the first paint, the JavaScript-off line and the open timing report. There is no switch.
130
+
131
+ ### If you roll back
132
+
133
+ - To 7.0.18: the page is blank again until the Mac answers. Nothing is stored by this change.
134
+
135
+ ## 7.0.18 - A question a session ends its turn with reaches the glasses, Control Web and the desktop
136
+
137
+ 2026-11-10. 7.0.17 (7313e50) plus X1's server half (claude/server-7018-turn-end-ask) and W1's Control Web card (claude/server-7018-web-turn-end), on Miles's go (07:14, "fold this in to the latest update so I can respond from glasses, web app and desktop"). Pairs with COS Control 0.5.389. Private on Miles's Mac first.
138
+
139
+ - **The problem (Miles, 07:02):** a Claude session in the desktop app that ended its turn with a question and reply buttons read as idle. It never asked through AskUserQuestion, so nothing on the glasses or the web said a session was waiting on him.
140
+ - **The server now sees it:** the session's last next-step call (`mcp__ccd_turn__wrap_up`) carries the question and its reply buttons. Once that turn has stopped, the session's row reads as waiting on a question (`waiting_source: 'turn_end'`), with the question (80 characters) and up to two reply labels (40 each). Only a question with at least one reply button, or one marked blocked, counts. App buttons such as commit or create PR are never offered as choices.
141
+ - **Quiet while you are at the Mac:** the row shows only after the Mac has been idle for the same threshold as held questions (never under 30 seconds). Once shown, it stays shown until it clears. At the Mac, the Claude app's own box is the answer.
142
+ - **It clears itself** on any reply, a new turn, the session ending, or after 8 hours.
143
+ - **The words** follow the existing switch for question words (`COS_WAITING_QUESTION_WORDS`). With it off, the row still says a question waits, with no words and no choices.
144
+ - **Control Web:** Today's row reads "<session> asked: <question>". The session reader and Today's sheet show the question, a button per choice, and one line: "A choice or a reply here reaches the session as a message from COS, not typed in the session, and the session may ask you to confirm." The reply box stays for your own answer.
145
+ - **On the glasses (public app 6.11.681, no new glasses build):** within about a minute while Home or the Sessions list is on the lens, the header shows `◎Mac` and Home reads "Mac waiting on you since" the time it started waiting. Swipe down to the Menu and choose `Mac waiting · 1` to see the session and the question. That page says "Answer on the Mac.", which is wrong for this case until the next glasses build: you can answer by voice from the glasses with Sessions, the session, then Continue. Your words arrive as a verified G2 turn. The choices are not offered on the glasses yet.
146
+ - **What an answer sends:** a tap, or a typed reply, goes through the session's existing Continue as "Answer sent from COS Control Web through COS, not typed in this session: <your choice>". It never reads as typed at the desk, so the session may ask you to confirm. If the question was answered on another screen first, nothing is sent and it says so.
147
+ - **Logs and counts:** the session ledger adds only the question, its labels and the blocked mark on that one tool's rows; never the session's recap. The authenticated question status adds counts (armed, shown, cleared, aged out), never words.
148
+ - **Not proven off the device:** tests and WebKit show each rule; only the glasses, the phone and the desktop app can show the full loop.
149
+
150
+ **Default-on for every install:** yes: turn-end questions (`COS_TURN_END_ASKS`, on unless set to 0 and restarted), their words where question words are already on, and the Control Web card.
151
+
152
+ ### If you roll back
153
+
154
+ - To 7.0.17: sessions that end their turn with a question read as idle again. The extra ledger key (`turn_ask`) is carried in the row but 7.0.17 never reads it. Nothing else is stored.
155
+
156
+ ## 7.0.17 - Control Web stops zooming in and out on the phone
157
+
158
+ 2026-11-10. 7.0.16 (d2de7ee) plus one change to Control Web, on Miles's go (06:25, "Yes, start it"). Private on Miles's Mac first.
159
+
160
+ - **The problem (Miles, 06:17):** in the Control Web app on the Duo, the page zoomed in and out around the keyboard and the layout stayed wrong. His phone's own layout report showed the page at 2.12x with the keyboard down on the unfolded Duo: most likely a double-tap zoom fitting a column (a pinch reads the same in that report). Every field was already 16 px, so it was not the focus zoom.
161
+ - **Double-tap zoom is off:** the page root says `touch-action: manipulation`. Scrolling still works. The Sessions splitter and the charts keep their stricter touch rules.
162
+ - **On iPhone and iPad only,** the page's viewport is capped at its normal scale (`maximum-scale=1`), once, as it loads. Never while the page is already zoomed, so the zoom never changes under your fingers. Android, the Mac and other browsers keep the viewport as it was. The cost: in the Control Web app added to the Home Screen, pinch to zoom no longer works at all; in a Safari tab it still does (Apple keeps pinch there). Elsewhere pinch is unchanged.
163
+ - **The 16 px floor** on touch screens also covers any element that types without being a field (none exists today).
164
+ - **A layout report** when the zoom crosses 1.01 either way: the scale, the pane, the focused element's kind (never its text) and how long since the last touch, inside the existing report limits, so the next one is diagnosed from numbers.
165
+ - **Not proven off the device:** the tests and WebKit show each rule is in place; only the phone can show the zoom has stopped.
166
+
167
+ **Default-on for every install:** yes: double-tap zoom off in Control Web everywhere, the iOS viewport cap, and the zoom layout report.
168
+
169
+ ### If you roll back
170
+
171
+ - To 7.0.16: the page zooms as before. Nothing is stored by this change.
172
+
173
+ ## 7.0.16 - Questions held for the whole wait (off by default), question words on rows, and a Today summary that holds still
174
+
175
+ 2026-11-09. 7.0.15 (ffcb6f9) plus X1's full hold (claude/server-question-hold 053d01c), T1's Today cadence (claude/server-today-stable 18b4031) and W1's web follow-ups (claude/server-web-hardening ed32418). Each reviewed by Q1. Not installed: this build changes the permission path, and its private install waits for Miles's own go. Does not contain 7.0.14 (the tailnet fix) or f4f309f.
176
+
177
+ ### Named default-on changes
178
+
179
+ - **Question words on rows (Miles 22:10, "Yes, show by default").** A session waiting on a question now says the first 80 characters of its first question, everywhere a waiting row shows: the glasses, the phone app, COS Control and any signed-in Control Web browser. Only the first question's text: never an option, a description, a header, a second question or an answer. Control and format characters are replaced, whitespace collapsed, cut at 80 with an ellipsis. Stored in the session-hook ledger on disk (`session-hook-events.jsonl`, rotated once at 10 MB; the last 6 hours replay at boot) and in `waiting_detail` until the question settles. Never in the broker's logs. `COS_WAITING_QUESTION_WORDS=0` restores 7.0.15 exactly.
180
+ - **The Today summary is rewritten less (T1).** A material change (a session opened or closed, a message typed into one, Needs you, Work cards, meetings, the read state, a recording) rewrites after the existing 10 minutes; anything else (a reply, a state flip, sub-agents, durations) only once the shown summary is 30 minutes old. The count sentence before the paragraph changes only after a new count holds 5 minutes. An ask that could not read the session list never replaces today's summary. Replay over two busy hours: 12 model calls down to 4, 44 shown-text changes down to 4. Model, daily cap, switch, breaker, forced Refresh and the reconnect exception are unchanged.
181
+
182
+ ### Package
183
+
184
+ - The npm package no longer carries `server/scripts/compact-session-log.ts` (the compaction tool, not ready) or `server/scripts/full-hold-mutation-gate.mjs` (a test gate).
185
+
186
+ ### Off by default (X1)
187
+
188
+ - **`COS_QUESTION_FULL_HOLD`:** a session question is held for the whole hook wait (3590 s after Reinstall hooks), whether or not a COS device is live when it is asked, on an engine proven to keep its own dialog up while the hook waits (CLI 2.1.321 and later in a terminal, not a print run; the desktop app 2.1.317 and later). The Mac's dialog is up at once; the first answer wins, from the Mac, the glasses, the phone app, COS Control or Control Web. A question shows on devices once the desk has been idle 90 s; before that it is not answerable by id (409 `not_shown`).
189
+ - **`COS_APPROVAL_FULL_HOLD`:** the same for a tool approval, only while the desk stays idle, never ExitPlanMode. A web answer needs a touch within 60 s and a server-issued confirm for that item and decision. Keep it off until the phone key lands.
190
+ - The three switches are listed in `.env.example` and the managed-runtime contract, and work on a COS Control install through `~/.cos-glasses/.env` (final review S6).
191
+ - With both switches off, admission, deadlines, hook output, the questions response (key for key), `~/.claude/settings.json` and the hook block equal 7.0.15; `/confirm` answers 404. Turning a switch on: `~/.cos-glasses/.env`, restart, then Reinstall hooks in Control. There is no Control switch for them.
192
+
193
+ ### Control Web (W1)
194
+
195
+ - **Open streams end with the sign-in (final review S1).** A live stream a browser opened (a session's view, the live events, a COS answer) now ends at its next heartbeat once that browser's sign-in no longer verifies: Sign out every browser, the cookie's expiry, or the 30-day cap. The browser reconnects with the cookie it holds now. Streams the API token opened (the glasses, the phone app, COS Control) are unchanged.
196
+ - **Sign out every browser also ends sign-in links and codes (final review V7).** A sign-in link or code made before the sign-out no longer signs a browser in after it; one made after it works as before.
197
+ - **Display-stream tickets only for the token door (final review S8).** `/api/models` hands a display-stream ticket only to a caller the API token let in (the glasses, the phone app, COS Control), never to a browser signed in by cookie. Control Web never used it.
198
+ - **Meaning search** says where the Jev and SubQ keys are now: search Settings in COS Control for Jev key or SubQ key.
199
+ - **Today's summary holds still:** it stays through a pending or failed session read (the age line names what could not be read; the Ask box stays), keeps the tallest height it has had and shrinks only on a new day, a width change, Refresh or the switch toggled; unchanged words are not written again.
200
+ - **Question block on X1's final contract:** reads `webAnswer`; with `confirmApprovals` an approval is confirmed first and answered once; a 409 `not_shown` keeps the card with "Not sent: this question is not on your devices yet. Answer it at the Mac, or try again in a minute." With both hold switches off the server sends neither, and nothing changes.
201
+ - **Question refusals in words:** when an answer is refused, the card now says what happened: "Already answered on another device.", "Already answered at the Mac, or the session moved on.", "Left for the Mac. Answer it there.", "This question timed out here. Answer it at the Mac." or "This question is no longer waiting." Before, a refusal that carried a reason (answered at the Mac, a drain, an invalid answer id) kept the card and showed a raw code with a retry. Present since 6.99.x, including 7.0.15.
202
+
203
+ **Default-on for every install:** yes: the question words on rows, the Today summary cadence, on the web the Today summary hold, browser streams ending when their sign-in does, Sign out every browser also ending sign-in links and codes, and no display-stream ticket for a signed-in browser. The full hold and its web rules are off unless a hold switch is on.
204
+
205
+ ### If you roll back
206
+
207
+ - A browser whose sign-in was renewed by 7.0.15 or later holds the four-part cookie, and a server rolled back to 7.0.13 or earlier, including 7.0.7, makes that browser sign in again. Nothing is lost.
208
+ - Back on 7.0.7, questions older than 7 days count as waiting again (7.0.9 had stopped counting them).
209
+ - The inbox: the first start of 7.0.9 or later marks every unread inbox item read, once. 7.0.7 drops that marker when it writes, so after a rollback to 7.0.7 and a later update, everything that arrived unread under 7.0.7 is marked read again.
210
+
211
+ ## 7.0.15 - Control Web for the phone, and server hardening from runtime evidence
212
+
213
+ 2026-11-09. 7.0.13 (97402b4) plus the production hardening pass, web lane (W1) and server lane (S1). Private on Miles's Mac first. Does not contain 7.0.14 (the tailnet fix), X1's question hold or T1's Today work; those ride a later build.
214
+
215
+ ### Control Web (W1)
216
+
217
+ - **Off Tailscale:** a browser that reaches the server from outside the tailnet gets a readable help page, "Open COS over Tailscale" (status still 403).
218
+ - **Sign-in that lasts while in use:** a signed-in browser is renewed in the last 2 days of its 7, up to 30 days from first sign-in. Renewed cookies are a new 4-part shape; status and login add `renewUntil`, renewed answers carry `X-COS-Web-Session`. A notice shows in the last 24 hours, and the sign-in card says why a sign-in lapsed.
219
+ - **Sign-in links:** a refused link on a browser that is still signed in opens the app with an "old link" note. The server log names why a link failed (used, expired, evicted or unknown; the link is never logged). A failed link no longer counts toward the 8-failure lock on that device; wrong codes and tokens still do.
220
+ - **When the Mac does not answer:** every pane says so, with the time and both likely causes; while the page is visible it checks `/api/health` every 30 s. A cold open with the Mac unreachable shows that card instead of the sign-in form, and retries every 30 s.
221
+ - **Today:** Running shows the desktop's COS job rows (no Stop on the web). Start work defaults to Claude Opus from the Mac's model catalog; a saved destination wins. A Work card with no session of its own preselects Start a new session.
222
+ - **Meaning search** asks consent per browser before anything is sent (off until Turn on).
223
+ - **Sort** no longer shows Keep, Drop or Undo (no server routes behind them).
224
+ - **A question waiting at the Mac's prompt:** Today's card and the session reader say it once, with the words when the server has them and an honest "COS cannot read this question's words from here" line when it does not, and offer nothing that pretends to answer. A Claude session waiting on a question reads "Waiting for your answer".
225
+ - **Question block (X1 contract v1):** the web is ready for a later server's question list (`waiting`); 7.0.15's server sends none, so nothing changes with this server. With a server that sends it, the web draws held, at the Mac (words and options, one card per question) and answered (2 minutes). Answers send how recently the page was touched; an approval the server wants confirmed is confirmed and sent once more; the three new refusals keep the card with a retry line. The cards sit above the reader's Send box, which reads "Message this session (not an answer)" while a question is open. With no `waiting` key nothing changes.
226
+ - **Duo layout:** every composer keeps its placeholder on one line at any width. The session reply box, COS chat, Today ask and a thread reply put their buttons on the row below when the words have less than 19rem; the Ask sheet's buttons wrap under its field; the COS chat header's links wrap under its description. Two placeholders are shorter: "Ask COS or drop a thought" (Drop it on) and "Ask about this or steer a session" (Today). Measured in WebKit at 382, 440, 520 and 951 px wide and at the narrowest chat column; not yet checked on the phone.
227
+ - **Copy:** the web names the Mac's Settings page "Glasses, phone and web" (Control 0.5.387).
228
+
229
+ ### Server (S1)
230
+
231
+ - **Session ledger:** an unchanged session is not appended again (adds `data/session-log-state.json`, tied to the ledger's inode); one re-append per session on the first boot after the update. The ledger reader streams, opens with `O_NONBLOCK` and reads only a regular file. `/api/sessions/lookup` answers from history again on a large ledger (old range rule); its first call per boot streams the whole ledger. The compaction tool is not ready and is not offered.
232
+ - **whisper-server and the preview sidecar** are stopped with SIGKILL plus a bounded wait on shutdown, and the preview sidecar also when it restarts, ending the crash reports at restart.
233
+ - **Work reviews** are saved only when something besides `updatedAt` changed.
234
+ - **Instrumentation:** an event-loop stall monitor (a line only on a stall over 2 s, at most 1 per 10 s); `[cors] blocked origin=...` once per origin per hour; the stop log names what was running; extended HQ timeout lines.
235
+ - **No ledger configured:** one "said once" line per process instead of per-session lines; the session-cache warning is said once.
236
+ - **Secret guard** refuses previews of `key-secrets/` and `provider-keys.json`.
237
+ - **Opt-in, off by default:** progressive HQ no longer retries a timed-out checkpoint (`COS_MEETING_PROGRESSIVE_HQ=1`); a per-path disk I/O meter (`COS_DISK_IO_METER=1`).
238
+ - **Tests:** two flaky tests made deterministic, six bounded.
239
+
240
+ **Default-on for every install:** yes. Web: the off-Tailscale page, sign-in renewal and its notices, the not-answering card and its 30 s health check, Today re-reading `/api/query-jobs/active` every 15 s while COS jobs run, the at-Mac question copy, Running rows, Start work defaults, meaning-search consent, the Sort cuts, the Duo layout and the two placeholders. Server: ledger dedupe and its state file, the lookup rule, SIGKILL on shutdown, the work-review save rule, the stall monitor and the new log lines, the secret-guard paths. The question block draws nothing new with this server.
241
+
242
+ ### If you roll back
243
+
244
+ - A browser whose sign-in was renewed by 7.0.15 or later holds the four-part cookie, and a server rolled back to 7.0.13 or earlier, including 7.0.7, makes that browser sign in again. Nothing is lost.
245
+ - Back on 7.0.7, questions older than 7 days count as waiting again (7.0.9 had stopped counting them).
246
+ - The inbox: the first start of 7.0.9 or later marks every unread inbox item read, once. 7.0.7 drops that marker when it writes, so after a rollback to 7.0.7 and a later update, everything that arrived unread under 7.0.7 is marked read again.
247
+
248
+ ## 7.0.13 - Work chat Plan no longer fails on a short session id
249
+
250
+ 2026-11-09. 7.0.12 (2d54a4a) plus one fix. Private on Miles's Mac first.
251
+
252
+ - **The bug.** Before every Work chat plan, COS Control pushes a snapshot of the sessions it sees. A Claude CLI session the server knows only by its short 8 character id reached that snapshot as `claude:<8 hex>`, and the server rejected the WHOLE snapshot (400 `invalid_snapshot`, `invalid_live_row`). While that one session ran, Plan failed on every card with "could not build this task's session list".
253
+ - **The fix.** A live row with an invalid id, or text past its bounds, is skipped and counted instead. `/api/work/orchestrator/input` and `/snapshot` report `droppedLiveRows` (only when not zero) and log it. Links and receipts stay strict: a bad one is still a 400.
254
+ - **Default-on for every install:** yes (a parse that used to refuse now accepts and skips). A clean snapshot answers exactly as before.
255
+
256
+ ## 7.0.12 - Prep any meeting on Today, with your own context
257
+
258
+ 2026-11-09. 7.0.11 (1beefbd) plus one change. Private on Miles's Mac first.
259
+
260
+ - **Prep on every Next up row (Control Web).** Each meeting row has a quiet Prep; the first row keeps Prep me and Open meeting and gains Add context. Prep opens a panel under that row: a box for goals, people and what you want out of it, Add files, Cancel and Start prep. One panel at a time; the words survive the page's refresh; Escape keeps them, Cancel drops them.
261
+ - **Start prep** uses that meeting's own Prep draft (the same one Prep me opens): typed context is added after any notes already there, files upload first, and a file that does not go in stops the run with one line saying so. With Prep off but Ask on, it asks "Prep me for <title> at <time>" with the context as quoted data. With neither, no Prep is offered.
262
+ - **Holds get no Prep.** New `CALENDAR_HOLD_WORDS` / `isCalendarHold` (Blocked, Busy, Hold, Focus, Travel, Personal, OOO, Lunch and similar, whole word at the start, unless the title says "with"). `/api/calendar/today` rows and welcome-context `nextEvent` carry `hold` (additive).
263
+ - **Default-on for every install:** yes, the Prep actions on Today (web). Desktop follows in Control 0.5.385.
264
+
265
+ ## 7.0.11 - Today knows your calendar, shows more of it, and takes questions
266
+
267
+ 2026-11-09. 7.0.10 (004df50) plus Today round 2. Private on Miles's Mac first.
268
+
269
+ - **The summary sees the calendar.** The meeting in progress, the next 4 with how long until each, and meetings that ended in the last 2 hours. An upcoming meeting is worked into an existing sentence ("...before your 10:00 forecast call"), never a sentence of its own; still at most three sentences. A session whose recent words name a meeting that already ended is marked so its prep reads as past.
270
+ - **No more "can wait until tomorrow".** The prompt no longer asks for it; the opener already says what can wait.
271
+ - **Calendar API: `/api/calendar/today`.** `next` holds up to 5 meetings (was 3). Duplicate invites (same title and start) and canceled events are dropped before `next`, `remainingCount` and the new `endedRecently` are counted.
272
+ - **Control Web Next up.** The current or next meeting plus up to 4 more, then "+N more today", which opens Meetings.
273
+ - **Ask about this.** A box under the Today summary asks COS about it or tells a session what to do. It sends the typed words plus the summary and one line per session (title, provider, state, last activity, last ask) as quoted data, and COS can relay an instruction to a Claude session by title. Hidden without durable Ask jobs, offline, or before a model summary exists.
274
+ - **The brief view** (GET and POST `/api/today/brief`) gains an optional `sessions` list (at most 12, scrubbed) stored with each summary.
275
+ - **Default-on for every install:** yes: the summary inputs and prompt, Next up rows, and the Ask box. Model, interval, cap, switch and breaker unchanged.
276
+
277
+ ## 7.0.10 - Today summary describes what is happening now
278
+
279
+ 2026-11-09. 7.0.9 (1b2a8f2) plus one change. Private on Miles's Mac first.
280
+
281
+ - **Today summary, recent activity first.** The summary used to see each session through one 180 character line made of its FIRST prompt and the latest reply, so a session's opening ask from weeks ago led the summary. It now reads, on the server, each session's last 1 to 3 messages from the past 24 hours (with how long ago), the start of the latest reply, and when the session last moved. Titles are treated as history.
282
+ - **Sub-agents:** only running ones, or ones finished within 2 hours, with their task. Older finished ones become a count. A session with a running sub-agent counts as working.
283
+ - **Collapsing:** sessions quiet for over a day become one count line (a session waiting on the person always shows). Work cards that have not moved in 48 hours become one line.
284
+ - **Messages relayed in from another screen or session** are labelled as such and never attributed to the person. Agent hand-backs and background-task notices are not counted as asks.
285
+ - **Privacy:** more of each session's recent text goes to the same model through the same isolated runner. Every field is scrubbed for secrets, long opaque tokens and short commit ids before it is sent; only the summary is cached.
286
+ - **Default-on for every install:** yes, this changes the Today summary's inputs and prompt for everyone. Model, interval, daily cap, switch and breaker are unchanged (Sonnet).
287
+
288
+ ## 7.0.9 - 7.0.8 plus phone approvals (dormant) and the anchor bootstrap switch
289
+
290
+ 2026-11-08. 7.0.8 (d8bc2a7) merged with the phone approvals server (6ed40cd), plus one new switch. Both switches below are OFF by default, so with neither set this build behaves as 7.0.8 for every install. Not device-qualified; private on Miles's Mac first.
291
+
292
+ - **Messages on Control Web (Miles, 2026-11-08).** The rail badge counts only new messages you have not seen, cleared on any device once seen (on the first start of this server every unread inbox item is marked read once, seen or not, and never counts again; corrected in 7.0.16, final review S2). The pinned COS row says how many are new and how many wait on your reply; Work's your move no longer counts there. A finished Ask never shows "COS is answering": it fetches its words, or says COS finished without a written answer. A running Ask shows its step trail (the same steps the glasses show, the newest six, then an "N steps" disclosure when done). An Ask that relayed to another session says which session, links to it, and shows that session's reply when it comes. A question in the COS chat that has waited 7 days since it was asked stops counting and moves to older questions above the chat, where you can still read and answer it.
293
+ - **Phone approvals, dormant.** The enrolled phone key server (the `/api/web-key/*` routes, the signer page on port 3144, served-asset integrity for it, and verified-turn authority for a phone-signed Control Web turn) is in the package behind `COS_WEB_KEY_AUTHORITY`, default off. Off, none of those routes exists (each answers 404 as on 7.0.8), port 3144 is never opened, `/api/health` has no `webKeySigner`, and a web turn is recorded exactly as on 7.0.8: never vouched. `verify_g2_turn.py` keeps refusing web authority either way.
294
+ - **New: `COS_WEB_KEY_ANCHOR_BOOTSTRAP`, default off.** On, with `COS_WEB_KEY_AUTHORITY` off, the server answers exactly two routes so COS Control can register this Mac's Touch ID key and start its 24 hour clock: `POST /api/web-key/anchor` (loopback only, the API token and the key's own signature, as before) and `GET /api/web-key/status` (API token only). The status reply in this mode is `{ ok: true, enabled: false, mode: "bootstrap", anchorRegistered, anchorMature, anchorSetAt, aud, origin, epoch }`; `enabled` stays false so an older COS Control hides phone approvals. Everything else stays off exactly as with both switches off: phone enrollment, the key list, revoke, nonces, approval packets, the handoff, the consume route, port 3144, the signer integrity check, the extra cookie port, `webKeySigner` in health, and verified-turn authority. Read like every switch: `1`, `true` or `on` is on, `0`, `false` or `off` is off, anything else the default. Listed in `managed-runtime-contract.json`.
295
+ - **With `COS_WEB_KEY_AUTHORITY` on, nothing changes** and the bootstrap switch does nothing: the status reply is byte for byte the 6.99.31 one (no `mode` field). The anchor registered in bootstrap mode is the same record the authority reads, so turning the authority on later needs no second registration and keeps its `setAt`.
296
+ - **Known, unchanged: registering the same key again restarts its 24 hour clock.** The server sets `setAt` to now on every registration, the same key included (no keys are revoked and the epoch stays). Do not re-register to check that the key persisted.
297
+ - Default-on for every install: none.
298
+
299
+ ## 7.0.8 - Control Web home screen icon
300
+
301
+ 2026-11-08. 7.0.7 (689a41b, the public candidate) plus one change. Control Web only. Private on Miles's Mac first.
302
+
303
+ - **Control Web has a home screen icon.** Saving Control Web to an iPhone home screen showed a grey letter, because iOS ignores the SVG favicon. The page now names a 180 px PNG of the COS mark on its cream background (`apple-touch-icon.png`). Remove an old shortcut and add it again from Safari's Share menu to pick it up.
304
+ - No other change from 7.0.7. Default-on for every install: this icon only.
305
+
1
306
  ## 7.0.7 - Answer lists never change what Markdown draws
2
307
 
3
308
  2026-11-08. 7.0.6 (f98dbbd, installed privately on Miles's Mac) plus four final QA fixes. Server and Control Web. This is the public candidate, paired with COS Control 0.5.380.