@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
@@ -11,6 +11,8 @@
11
11
  <meta name="apple-mobile-web-app-title" content="COS">
12
12
  <title>COS Control</title>
13
13
  <link rel="icon" href="/control/mark.svg" type="image/svg+xml">
14
+ <!-- iOS "Add to Home Screen" ignores SVG icons: without this, a saved Control Web shortcut is a grey letter. -->
15
+ <link rel="apple-touch-icon" href="/control/apple-touch-icon.png">
14
16
  <link rel="stylesheet" href="/control/app.css">
15
17
  <link rel="stylesheet" href="/control/control.css">
16
18
  <link rel="stylesheet" href="/control/panes/speakers.css">
@@ -22,6 +24,14 @@
22
24
  <link rel="stylesheet" href="/control/panes/memories.css">
23
25
  <link rel="stylesheet" href="/control/panes/today-actions.css">
24
26
  <script src="/control/theme.js"></script>
27
+ <!-- The app's scripts start downloading now, while the page parses; theme.js runs them after the first paint
28
+ (#boot-scripts, the same list). -->
29
+ <link rel="preload" href="/control/vendor/qrcode-encoder.js" as="script">
30
+ <link rel="preload" href="/control/memory-review.js" as="script">
31
+ <link rel="preload" href="/control/app.js" as="script">
32
+ <link rel="preload" href="/control/shell.js" as="script">
33
+ <link rel="preload" href="/control/panes/cos-chat.js" as="script">
34
+ <link rel="preload" href="/control/panes/questions.js" as="script">
25
35
  </head>
26
36
  <body>
27
37
  <svg width="0" height="0" style="position:absolute" aria-hidden="true">
@@ -51,11 +61,21 @@
51
61
  <symbol id="i-link" viewBox="0 0 24 24"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"/></symbol>
52
62
  </svg>
53
63
 
64
+ <!-- 7.0.19: the first paint. Shows at once, before any script or answer from the Mac: the mark on the page's own
65
+ background, true whichever comes next (the sign-in card or the app). Out of the layout (fixed), so nothing moves
66
+ when it goes. app.js removes it; a CSS failsafe hides it after 25 s if the scripts never run. -->
67
+ <div id="first-paint" class="first-paint" aria-hidden="true"><svg class="mark"><use href="#i-mark"/></svg></div>
68
+ <noscript><p class="noscript-note">COS Control Web needs JavaScript. Turn it on for this site, then reload.</p></noscript>
69
+
54
70
  <main id="auth-gate" class="auth-gate hidden" aria-labelledby="auth-title">
55
71
  <div class="auth-card">
56
72
  <svg class="mark" aria-hidden="true"><use href="#i-mark"/></svg>
57
73
  <h1 id="auth-title">Connect this browser</h1>
58
- <p>COS runs on your Mac. Sign in once and this browser stays connected for 7 days.</p>
74
+ <p id="auth-intro">COS runs on your Mac. Sign in once. You stay signed in while you use COS here, for up to 30 days.</p>
75
+ <div id="auth-offline" class="auth-offline hidden" role="status">
76
+ <p id="auth-offline-text">Your Mac may be asleep, or this device may be offline or off Tailscale. This page checks again every 30 seconds.</p>
77
+ <button type="button" class="btn primary block" id="auth-retry">Try again</button>
78
+ </div>
59
79
  <form id="auth-form">
60
80
  <div id="auth-code-block">
61
81
  <label for="auth-code">Sign-in code</label>
@@ -114,6 +134,8 @@
114
134
  </header>
115
135
 
116
136
  <main class="workspace" id="workspace">
137
+ <!-- W1: the Mac not answering, and the sign-in ending soon (app.js "Mac reachability and sign-in lifetime") -->
138
+ <div id="conn-notice" class="conn-notice" hidden></div>
117
139
  <!-- SESSION QUESTIONS (panes/questions.js, 6.99.12): over every pane while one waits -->
118
140
  <div id="sq-host" class="sq-host" aria-live="polite" hidden></div>
119
141
  <!-- TODAY -->
@@ -136,6 +158,10 @@
136
158
  <div class="today-cols">
137
159
  <div class="today-main">
138
160
  <div id="today-story" class="story-skel" aria-live="polite"><div class="skeleton"></div><div class="skeleton" style="width:82%"></div><div class="skeleton" style="width:58%"></div></div>
161
+ <form id="today-ask" class="cc-composer today-ask hidden" autocomplete="off" aria-label="Ask about this summary">
162
+ <textarea class="field cc-input" id="today-ask-input" rows="1" maxlength="2000" placeholder="Ask about this or steer a session" aria-label="Ask about this summary"></textarea>
163
+ <div class="cc-buttons"><button type="submit" class="cx-btn primary cc-ask">Send</button></div>
164
+ </form>
139
165
  <section class="run-panel" aria-labelledby="run-title">
140
166
  <div class="panel-head">
141
167
  <h2 id="run-title" class="panel-title">Running<span id="run-count" class="count"></span></h2>
@@ -162,6 +188,7 @@
162
188
  <div class="panel-head"><h2 id="next-title" class="panel-title">Next up</h2></div>
163
189
  <div id="next-body" class="next-up"></div>
164
190
  <div id="next-actions" class="next-actions"></div>
191
+ <div id="next-first-prep" class="next-first-prep hidden"></div>
165
192
  <div id="next-later" class="next-later hidden" aria-label="Later today"></div>
166
193
  </section>
167
194
  </div>
@@ -184,16 +211,29 @@
184
211
  <div class="cx-controls sx-views" id="sessions-views"></div>
185
212
  <div class="split sx-split" id="sessions-split">
186
213
  <div class="split-list" id="sessions-list-col">
187
- <div class="sx-tools">
188
- <form class="cx-search sx-search" id="sessions-search-form" role="search">
189
- <input id="sessions-q" class="field" type="search" placeholder="Search titles, transcripts…" enterkeyhint="search" autocomplete="off" aria-label="Search titles, transcripts">
190
- <button type="button" class="cx-clear hidden" id="sessions-clear" data-sx="clear" aria-label="Clear search">Clear</button>
191
- </form>
192
- <select id="sessions-recency" class="field sx-select" aria-label="Recency">
193
- <option value="newest">Newest</option>
194
- <option value="oldest">Oldest</option>
195
- <option value="match">Best match</option>
196
- </select>
214
+ <!-- 7.0.23 step 2b: the list's own head (DESIGN_PLAN v2 "The list"): the Sessions menu, Search, one quiet line and the sort menu. -->
215
+ <div class="sx-lhead" id="sessions-lhead">
216
+ <div class="sx-lh-top">
217
+ <button type="button" class="sx-view-btn" id="sessions-view-btn" data-sx="view-menu" aria-haspopup="menu" aria-expanded="false" aria-controls="sessions-view-menu"><span>Sessions</span><span class="caret" aria-hidden="true"></span></button>
218
+ <div class="sx-menu hidden" id="sessions-view-menu" role="menu" aria-label="Sessions">
219
+ <button type="button" role="menuitem" data-sx="view" data-view="sessions">Sessions</button>
220
+ <button type="button" role="menuitem" data-sx="view" data-view="usage">Usage</button>
221
+ <button type="button" role="menuitem" data-sx="view" data-view="cost">COS cost</button>
222
+ <button type="button" role="menuitem" data-sx="refresh">Refresh</button>
223
+ </div>
224
+ </div>
225
+ <div class="sx-tools">
226
+ <form class="cx-search sx-search" id="sessions-search-form" role="search">
227
+ <input id="sessions-q" class="field" type="search" placeholder="Search titles and transcripts" enterkeyhint="search" autocomplete="off" aria-label="Search titles and transcripts">
228
+ <button type="button" class="cx-clear hidden" id="sessions-clear" data-sx="clear" aria-label="Clear search">Clear</button>
229
+ </form>
230
+ </div>
231
+ <div class="sx-tools sx-tools-line">
232
+ <p class="sx-quiet hidden" id="sessions-quiet"></p>
233
+ <button type="button" class="cx-btn quiet sx-new-folder hidden" id="sessions-new-folder" data-sx="new-folder">New folder</button>
234
+ <select id="sessions-sort" class="field sx-select" aria-label="Sort"><option value="updated">Updated</option><option value="opened">Opened</option></select>
235
+ </div>
236
+ <p class="sx-org-note hidden" id="sessions-org-note" role="status"></p>
197
237
  </div>
198
238
  <p class="cx-note sx-hint hidden" id="sessions-hint"></p>
199
239
  <div id="sessions-hooks"></div>
@@ -268,12 +308,9 @@
268
308
 
269
309
  <div id="toast" class="toast hidden" role="status"></div>
270
310
 
271
- <script src="/control/vendor/qrcode-encoder.js"></script>
272
- <script src="/control/memory-review.js"></script>
273
- <script src="/control/app.js"></script>
274
- <script src="/control/shell.js"></script>
275
- <script src="/control/panes/cos-chat.js"></script>
276
- <script src="/control/panes/questions.js"></script>
311
+ <!-- The app's scripts, in the order they run. theme.js adds them one frame after the page is drawn, so the first paint
312
+ (#first-paint) never waits for them, and runs boot only once every one of them has run (W1_first_open_speed.md). -->
313
+ <script type="application/json" id="boot-scripts">["/control/vendor/qrcode-encoder.js", "/control/memory-review.js", "/control/app.js", "/control/shell.js", "/control/panes/cos-chat.js", "/control/panes/questions.js"]</script>
277
314
  <!-- Loaded when first opened, then in the background once the page is idle (app.js ensurePane). -->
278
315
  <script type="application/json" id="lazy-panes">{
279
316
  "messages": "/control/panes/messages.js",
@@ -56,10 +56,12 @@
56
56
  /* ── the chat ─────────────────────────────────────────── */
57
57
  .cc { --cc-you: color-mix(in srgb, var(--gold) 22%, var(--panel)); }
58
58
  .cc-head { display: flex; align-items: center; gap: 12px; padding: 14px 0 12px; border-bottom: 1px solid var(--hairline); flex-wrap: wrap; }
59
- .cc-head-text { min-width: 0; flex: 1; }
59
+ .cc-head-text { min-width: 0; flex: 1 1 16rem; }
60
+ /* W1 (open Duo, narrow chat column): the description was squeezed to four short lines beside All messages, Mark all read
61
+ and New thread. Below 16rem for the words, the links wrap beneath them. */
60
62
  .cc-title { font: 500 24px/1.1 var(--display); color: var(--ink); margin: 0; }
61
63
  .cc-sub { margin: 3px 0 0; font-size: 13px; line-height: 1.35; color: var(--sub); }
62
- .cc-head-acts { display: flex; gap: 14px; }
64
+ .cc-head-acts { display: flex; flex-wrap: wrap; gap: 4px 14px; }
63
65
  .cc-head-acts .link-btn { min-height: 36px; font-size: 14px; font-weight: 500; }
64
66
  .cc-avatar { flex: none; width: 30px; height: 30px; border-radius: 8px; background: #2b211a; color: var(--gold); display: grid; place-items: center; }
65
67
  .cc-avatar .icon { width: 17px; height: 17px; }
@@ -99,12 +101,45 @@
99
101
  .cc-move .cc-choice { font-weight: 500; }
100
102
  .cc-fine { margin: 8px 0 0; font-size: 12.5px; color: var(--tertiary); }
101
103
  .cc-fine.warn { color: var(--hot); }
104
+ /* 7.0.9: a running answer's steps (its trail), newest last; once it ends, a quiet disclosure. A relayed message names the
105
+ session it went to. Every line wraps inside the card: the Duo folded is 382 px wide. */
106
+ .cc-steps-slot:empty { display: none; }
107
+ .cc-steps { list-style: none; margin: 8px 0 0; padding: 0 0 0 10px; border-left: 2px solid var(--hairline); display: grid; gap: 3px; min-width: 0; }
108
+ .cc-step { font-size: 13px; line-height: 1.4; color: var(--sub); overflow-wrap: anywhere; min-width: 0; }
109
+ .cc-step.prose { color: var(--ink); }
110
+ .cc-step.more { color: var(--tertiary); }
111
+ .cc-steps.live .cc-step:last-child { color: var(--ink); }
112
+ .cc-steps-done { margin-top: 8px; min-width: 0; }
113
+ .cc-steps-done > summary { cursor: pointer; min-height: 32px; display: flex; align-items: center; font-size: 13px; color: var(--tertiary); }
114
+ .cc-steps-done > summary::-webkit-details-marker { display: none; }
115
+ .cc-steps-done > summary::before { content: ''; width: 0; height: 0; margin-right: 8px; border-style: solid; border-width: 4px 0 4px 6px; border-color: transparent transparent transparent currentColor; transition: transform 160ms ease; }
116
+ .cc-steps-done[open] > summary::before { transform: rotate(90deg); }
117
+ @media (prefers-reduced-motion: reduce) { .cc-steps-done > summary::before { transition: none; } }
118
+ .cc-relay { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--hairline); min-width: 0; }
119
+ .cc-relay-line { margin: 0; font-size: 14px; line-height: 1.45; color: var(--ink); overflow-wrap: anywhere; }
120
+ .cc-relay-reply { margin: 6px 0 0; font-size: 14px; line-height: 1.45; color: var(--ink); overflow-wrap: anywhere; white-space: pre-wrap; }
121
+ /* 7.0.9: older questions, closed until opened, above the chat. */
122
+ .cc-archive { margin: 0 0 14px; padding: 0 0 10px; border-bottom: 1px solid var(--hairline); min-width: 0; }
123
+ .cc-archive > summary { cursor: pointer; min-height: 36px; display: flex; align-items: center; font-size: 13.5px; color: var(--sub); }
124
+ .cc-archive > summary::-webkit-details-marker { display: none; }
125
+ .cc-archive > summary::before { content: ''; width: 0; height: 0; margin-right: 8px; border-style: solid; border-width: 4px 0 4px 6px; border-color: transparent transparent transparent currentColor; transition: transform 160ms ease; }
126
+ .cc-archive[open] > summary::before { transform: rotate(90deg); }
127
+ @media (prefers-reduced-motion: reduce) { .cc-archive > summary::before { transition: none; } }
128
+ .cc-archive > .cc-fine { margin: 0 0 10px; }
129
+ .cc-relay-who { display: block; font-size: 12.5px; color: var(--tertiary); margin-bottom: 2px; }
102
130
  .cc-steer { display: flex; gap: 8px; margin-top: 10px; }
103
131
  .cc-steer .field { flex: 1; min-width: 0; height: 36px; }
104
132
 
105
133
  /* ── the composer: two visible buttons, no key needed ─── */
106
134
  .cc-composer { position: sticky; bottom: 0; z-index: 5; background: var(--page); border-top: 1px solid var(--hairline); padding: 10px 0 calc(10px + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
107
- display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 10px; align-items: end; }
135
+ display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: flex-end; }
136
+ /* W1 (Miles 2026-11-09, open Duo: "Ask COS, or drop a thought, a task, a ..." wrapped and clipped beside the camera,
137
+ clip, Ask and Drop in the three-column split). The row follows the composer's own width, not the screen: the words need
138
+ 19rem (every composer placeholder is 36 characters or fewer), and when the buttons do not fit beside that they move to
139
+ the row below, at its right. Panes are resizable, so no breakpoint can know this. Same rule as the reader's box. */
140
+ .cc-composer > .cc-input { flex: 1 1 19rem; min-width: 0; }
141
+ .cc-composer > .cc-buttons { flex: none; margin-left: auto; }
142
+ .cc-composer > .cc-chips, .cc-composer > .cc-foot, .cc-composer > .cc-note { flex: 1 1 100%; min-width: 0; }
108
143
  .cc-input { min-height: 44px; max-height: 140px; padding: 11px 12px; font-size: 16px; border-radius: 12px; }
109
144
  .cc-buttons { display: flex; gap: 8px; }
110
145
  .cc-buttons .cx-btn { height: 44px; padding: 0 16px; font-size: 15px; border-radius: 12px; }
@@ -119,8 +154,8 @@
119
154
  .cc-note { grid-column: 1 / -1; margin: 0; font-size: 13px; color: var(--hot); }
120
155
  /* A phone at 390 px: the box on its own row, the buttons under it, the menus under those. */
121
156
  @media (max-width: 480px) {
122
- .cc-composer { grid-template-columns: minmax(0, 1fr); }
123
- .cc-buttons { justify-content: flex-end; }
157
+ .cc-composer > .cc-input { flex-basis: 100%; }
158
+ .cc-composer > .cc-buttons { flex: 1 1 100%; justify-content: flex-end; }
124
159
  .cc-buttons .cx-btn { flex: 1; justify-content: center; }
125
160
  .cc-buttons .cc-attach { flex: none; }
126
161
  }
@@ -249,13 +284,24 @@ html.kb-up .cc-kb .cc-model .field { width: 100%; }
249
284
  their own above the words, Take photo and the clip beside Fork and Send, and the limits in plain words under the chips. */
250
285
  .att-composer { flex-wrap: wrap; }
251
286
  .att-composer .att-files { flex: 1 1 100%; padding: 2px 0 4px; }
287
+ /* W1 (Miles 2026-11-09 21:24, closed Duo screenshot: "Continue this session" wrapped onto two lines, the second
288
+ clipped). The phone's real width did not trip the 430 px rule below, so the row now follows the space the box itself
289
+ has: the words need at least 19rem (the longest placeholder, "Message this session (not an answer)", is about 277 px
290
+ in the kit font), and when the buttons do not fit beside that they move to the row below, at its right. Wide panes
291
+ keep the one-row layout. Measured in WebKit at 382, 440, 520 and 951 px: lanes/W1_evidence/duo_layout_*. */
292
+ .att-composer > textarea { flex: 1 1 19rem; min-width: 0; }
293
+ .att-composer > .composer-actions { margin-left: auto; }
252
294
  /* 6.99.30 (canary): the folded Sessions reader squeezed the box to about 101 px beside Fork, the clip and Send. Under
253
295
  430 px the words get their own full row and the buttons sit on the row below, at its right. */
254
296
  @media (max-width: 429px) {
255
297
  .att-composer > textarea { flex: 1 1 100%; }
256
- .att-composer > .composer-actions { margin-left: auto; }
257
298
  }
258
299
  .att-composer .att-files.hidden { display: none; }
300
+ .phone-approval-packet { flex: 1 1 100%; min-width: 0; }
301
+ .phone-approval-packet label { display: block; font-size: 13px; margin-bottom: 4px; }
302
+ .phone-approval-packet select { width: 100%; max-width: 100%; font: inherit; color: inherit; background: var(--card, transparent); }
303
+ .phone-approval-packet pre { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.4 ui-monospace, monospace; max-height: 11rem; overflow-y: auto; margin: 6px 0; }
304
+ .composer-actions:has([data-phone-offer]) { flex-wrap: wrap; }
259
305
  .att-clip, .ta-clip { display: inline-flex; gap: 6px; }
260
306
  .att-clip:empty { display: none; }
261
307
  .att-clip .cc-attach { height: 38px; width: 42px; border-radius: var(--radius-sm); }