@gotcos/glasses-server 6.98.1 → 6.99.16

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 (98) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/package.json +2 -1
  3. package/server/index.ts +170 -11
  4. package/server/lib/api-auth.ts +31 -14
  5. package/server/lib/claude-bridge.ts +21 -2
  6. package/server/lib/codex-bridge.ts +21 -2
  7. package/server/lib/cos-thread.ts +582 -0
  8. package/server/lib/display-bus.ts +3 -0
  9. package/server/lib/drop-attachments.ts +55 -0
  10. package/server/lib/drop-destinations.ts +134 -0
  11. package/server/lib/drop-history-search.ts +97 -0
  12. package/server/lib/drop-p2-boot.ts +60 -0
  13. package/server/lib/drop-p2-runtime.ts +237 -0
  14. package/server/lib/drop-p2-settings.ts +174 -0
  15. package/server/lib/drop-resolution.ts +168 -0
  16. package/server/lib/drop-router.ts +325 -0
  17. package/server/lib/drop-service.ts +830 -0
  18. package/server/lib/drop-settings.ts +56 -0
  19. package/server/lib/drop-store.ts +450 -0
  20. package/server/lib/drop-when.ts +265 -0
  21. package/server/lib/glasses-pairing.ts +15 -0
  22. package/server/lib/learning-needs-you.ts +111 -0
  23. package/server/lib/live-cues-v2-settings.ts +2 -1
  24. package/server/lib/live-cues-v2.ts +6 -4
  25. package/server/lib/media-store.ts +80 -0
  26. package/server/lib/meeting-batch-transcribe.ts +15 -2
  27. package/server/lib/meeting-consolidation-run.ts +169 -0
  28. package/server/lib/meeting-store.ts +61 -0
  29. package/server/lib/meeting-title-steer.ts +200 -0
  30. package/server/lib/network-policy.ts +84 -6
  31. package/server/lib/permission-broker.ts +13 -0
  32. package/server/lib/phone-recording.ts +1 -1
  33. package/server/lib/ping-budget.ts +149 -0
  34. package/server/lib/ping-engine.ts +245 -0
  35. package/server/lib/ping-feed-sink.ts +33 -0
  36. package/server/lib/ping-sources.ts +192 -0
  37. package/server/lib/query-job-coordinator.ts +5 -2
  38. package/server/lib/query-job-runtime.ts +45 -4
  39. package/server/lib/query-job-types.ts +10 -1
  40. package/server/lib/reminder-scheduler.ts +153 -0
  41. package/server/lib/tailnet-self-name.ts +103 -0
  42. package/server/lib/tailscale-nudge.ts +40 -2
  43. package/server/lib/task-store.ts +32 -0
  44. package/server/lib/web-ask-admission.ts +56 -0
  45. package/server/lib/web-live-chunks.ts +273 -0
  46. package/server/lib/web-memories-bridge.ts +323 -0
  47. package/server/lib/web-session-auth.ts +492 -0
  48. package/server/lib/web-usage-bridge.ts +154 -0
  49. package/server/lib/work-search.ts +14 -1
  50. package/server/lib/your-move-count.ts +58 -0
  51. package/server/routes/cos-thread.ts +79 -0
  52. package/server/routes/display.ts +11 -1
  53. package/server/routes/drops.ts +262 -0
  54. package/server/routes/health.ts +16 -0
  55. package/server/routes/live-cues.ts +5 -4
  56. package/server/routes/meeting.ts +61 -5
  57. package/server/routes/permission-broker.ts +23 -3
  58. package/server/routes/transcribe-stream.ts +43 -4
  59. package/server/routes/web-control.ts +569 -0
  60. package/server/routes/welcome-context.ts +2 -1
  61. package/server/scripts/web-live-cls/cdp.mjs +57 -0
  62. package/server/scripts/web-live-cls/measure.mjs +80 -0
  63. package/server/scripts/web-live-cls/mock.mjs +97 -0
  64. package/server/scripts/web-live-cls/render.mjs +26 -0
  65. package/server/web-control/app.css +519 -0
  66. package/server/web-control/app.js +3643 -0
  67. package/server/web-control/control.css +325 -0
  68. package/server/web-control/fonts/DMSans.ttf +0 -0
  69. package/server/web-control/fonts/Fraunces.ttf +0 -0
  70. package/server/web-control/fonts/JetBrainsMono.ttf +0 -0
  71. package/server/web-control/index.html +275 -0
  72. package/server/web-control/mark.svg +1 -0
  73. package/server/web-control/memories/d3.min.js +2 -0
  74. package/server/web-control/memories/graph-explorer.css +200 -0
  75. package/server/web-control/memories/graph-explorer.js +748 -0
  76. package/server/web-control/memories/memories-app.js +1561 -0
  77. package/server/web-control/memories/memories-theme.css +73 -0
  78. package/server/web-control/memories/memories.html +72 -0
  79. package/server/web-control/memories/memory-stewardship.js +63 -0
  80. package/server/web-control/memories/memory-workspace.js +216 -0
  81. package/server/web-control/panes/cos-chat.css +174 -0
  82. package/server/web-control/panes/cos-chat.js +1118 -0
  83. package/server/web-control/panes/dictation.js +173 -0
  84. package/server/web-control/panes/meetings.css +314 -0
  85. package/server/web-control/panes/meetings.js +2568 -0
  86. package/server/web-control/panes/messages.js +718 -0
  87. package/server/web-control/panes/questions.css +32 -0
  88. package/server/web-control/panes/questions.js +275 -0
  89. package/server/web-control/panes/sessions.css +452 -0
  90. package/server/web-control/panes/settings.js +292 -0
  91. package/server/web-control/panes/speakers.css +269 -0
  92. package/server/web-control/panes/speakers.js +2755 -0
  93. package/server/web-control/panes/threads.js +200 -0
  94. package/server/web-control/panes/work.css +261 -0
  95. package/server/web-control/panes/work.js +1667 -0
  96. package/server/web-control/shell.js +294 -0
  97. package/server/web-control/theme.js +25 -0
  98. package/server/web-control/vendor/qrcode-encoder.js +10 -0
@@ -0,0 +1,200 @@
1
+ /* COS Control Web: Threads, the same pane as Control's (ActivityWindow.swift contextList(kind: "thread")
2
+ 4841-4897, sectionHeader 5065-5123, contextRow 5007-5034; ContextDetailPane in Views.swift 6112-6189;
3
+ Models.swift thread() and threadBody() 620-690). Read only, as in Control.
4
+ Data: GET /api/threads?limit=50, /api/threads/:id, /api/threads/search?q=&limit=20, /api/context/status.
5
+ Routes: #/threads, #/threads/<id>. */
6
+ (function () {
7
+ 'use strict';
8
+ var W = window.COSWeb;
9
+ var $ = W.$, esc = W.esc, api = W.api;
10
+ var pane = $('pane-threads');
11
+
12
+ var S = { rows: null, state: 'idle', error: '', status: null, q: '', hits: null, searching: false, ranFor: '', semantic: null, details: {} };
13
+
14
+ function trimmed(v) { var s = typeof v === 'string' ? v.trim() : ''; return s || null; }
15
+ function subtitle(t) {
16
+ var meetings = Number(t.meeting_count) || 0;
17
+ var topics = Array.isArray(t.topics) ? t.topics.filter(function (x) { return typeof x === 'string'; }) : [];
18
+ return [meetings > 0 ? meetings + ' meeting' + (meetings === 1 ? '' : 's') : null,
19
+ topics.length ? topics.slice(0, 3).join(', ') : null,
20
+ t.is_resolved === true ? 'resolved' : null].filter(Boolean).join(' · ');
21
+ }
22
+ // threadBody: notes, then facts, then Stakeholders / Meetings / Milestones / Sources.
23
+ function body(t) {
24
+ var sections = [];
25
+ var notes = (t.manual_updates || []).map(function (u) { return trimmed(u && u.content); }).filter(Boolean);
26
+ if (notes.length) sections.push(notes.join('\n\n'));
27
+ var facts = [];
28
+ var topics = (t.topics || []).filter(function (x) { return typeof x === 'string'; });
29
+ if (trimmed(t.target_date)) facts.push('Target: ' + trimmed(t.target_date));
30
+ if (trimmed(t.serves_goal)) facts.push('Goal: ' + trimmed(t.serves_goal));
31
+ var first = trimmed(t.first_seen), last = trimmed(t.last_seen);
32
+ if (first || last) facts.push('Seen: ' + (first || '?') + ' to ' + (last || '?'));
33
+ if (topics.length) facts.push('Topics: ' + topics.join(', '));
34
+ if (facts.length) sections.push(facts.join('\n'));
35
+ function section(title, items, total) {
36
+ if (!items.length) return;
37
+ var count = total > items.length ? items.length + ' of ' + total : String(items.length);
38
+ sections.push(title + ' (' + count + ')\n' + items.map(function (i) { return '- ' + i; }).join('\n'));
39
+ }
40
+ function strings(key) { return (t[key] || []).map(function (x) { return trimmed(x); }).filter(Boolean); }
41
+ section('Stakeholders', strings('stakeholders'));
42
+ var meetings = (t.meetings || []).map(function (m) {
43
+ var name = trimmed(m && m.name); if (!name) return null;
44
+ var date = trimmed(m.date); return date ? date + ' ' + name : name;
45
+ }).filter(Boolean);
46
+ section('Meetings', meetings, Number(t.meeting_count) || 0);
47
+ section('Milestones', strings('milestones'));
48
+ section('Sources', strings('sources'));
49
+ return sections.join('\n\n');
50
+ }
51
+ function copyFormat(t) {
52
+ var text = body(t) || t.name || '';
53
+ return 'Thread ' + t.id + ' (' + subtitle(t) + ')\n\n"""\n' + text + '\n"""';
54
+ }
55
+ function matchLabel(m) { return m === 'both' ? 'Keyword + meaning' : m === 'semantic' || m === 'meaning' ? 'Meaning' : 'Keyword'; }
56
+
57
+ function load(force) {
58
+ if (S.state === 'loading' || (S.rows && !force)) return;
59
+ S.state = 'loading';
60
+ render();
61
+ Promise.all([
62
+ api('/api/threads?limit=50'),
63
+ api('/api/context/status').then(function (d) { return d; }, function () { return null; })
64
+ ]).then(function (res) {
65
+ S.rows = (res[0] && res[0].threads) || [];
66
+ S.status = res[1] && res[1].threads ? res[1].threads : null;
67
+ S.state = 'ready';
68
+ render();
69
+ }, function (err) {
70
+ S.state = 'error';
71
+ S.error = err && err.status === 404 ? 'Update the server to search threads from this panel.' : (err.message || 'Threads could not be loaded.');
72
+ render();
73
+ });
74
+ }
75
+ function search() {
76
+ var q = S.q.trim();
77
+ if (q.length < 2) { S.hits = null; S.ranFor = ''; render(); return; }
78
+ S.searching = true;
79
+ render();
80
+ api('/api/threads/search?q=' + encodeURIComponent(q) + '&limit=20').then(function (d) {
81
+ if (S.q.trim() !== q) return;
82
+ S.searching = false; S.ranFor = q;
83
+ S.hits = (d && d.hits) || [];
84
+ S.semantic = d ? d.semanticAvailable === true : null;
85
+ render();
86
+ }, function (err) {
87
+ S.searching = false; S.ranFor = q; S.hits = [];
88
+ S.error = err.message || 'The lookup failed.';
89
+ render();
90
+ });
91
+ }
92
+
93
+ function hero() {
94
+ var q = S.q.trim();
95
+ var active = S.status && S.status.available ? Number(S.status.active) || 0 : null;
96
+ var detail;
97
+ if (q.length >= 2) detail = S.searching ? 'Looking up…' : 'Lookup across stored threads';
98
+ else if (S.rows && active != null) detail = S.rows.length + ' shown · ' + active + ' active';
99
+ else detail = 'Follow work that develops across meetings and time.';
100
+ var stats = S.status && S.status.available
101
+ ? '<div class="cx-stats"><span class="cx-stat"><b>' + (Number(S.status.total) || 0).toLocaleString() + '</b><span>TRACKED</span></span>' +
102
+ '<span class="cx-stat"><b>' + (S.rows ? S.rows.length : 0).toLocaleString() + '</b><span>SHOWN</span></span></div>'
103
+ : '';
104
+ return '<header class="cx-hero" data-tint="threads"><span class="cx-glyph"><svg class="icon"><use href="#i-threads"/></svg></span>' +
105
+ '<div class="cx-hero-text"><h1 class="cx-title">Review threads</h1><p class="cx-detail-line">' + esc(detail) + '</p></div>' +
106
+ '<div class="cx-actions"><button type="button" class="cx-btn quiet" data-th="refresh"><svg class="icon"><use href="#i-refresh"/></svg>Refresh</button></div>' +
107
+ stats + '</header>';
108
+ }
109
+ function searchBar() {
110
+ var q = S.q.trim();
111
+ var note = q.length >= 2 && S.ranFor === q && S.semantic === false ? '<p class="cx-note" style="margin:-4px 2px 10px">Keyword only. Threads have no meaning index.</p>' : '';
112
+ return '<div class="cx-controls"><form class="cx-search" data-th-form role="search" style="max-width:420px">' +
113
+ '<input class="field" type="search" id="th-q" placeholder="Search topics, ideas…" value="' + esc(S.q) + '" autocomplete="off" aria-label="Search topics, ideas">' +
114
+ (S.q ? '<button type="button" class="cx-clear" data-th="clear">Clear</button>' : '') + '</form></div>' + note;
115
+ }
116
+ function row(t) {
117
+ return '<button type="button" class="cx-row" data-th="open" data-id="' + esc(t.id) + '">' +
118
+ '<span class="cx-row-glyph"><svg class="icon"><use href="#i-threads"/></svg></span><span class="cx-row-body">' +
119
+ '<span class="cx-row-title strong">' + esc(t.name || '(untitled)') + '</span>' +
120
+ (subtitle(t) ? '<span class="cx-row-sub">' + esc(subtitle(t)) + '</span>' : '') +
121
+ '<span class="cx-id">' + esc(t.id) + '</span></span><span class="chev"></span></button>';
122
+ }
123
+ function hitRow(h) {
124
+ return '<button type="button" class="cx-row" data-th="open" data-id="' + esc(h.id) + '">' +
125
+ '<span class="cx-row-glyph"><svg class="icon"><use href="#i-threads"/></svg></span><span class="cx-row-body">' +
126
+ '<span class="cx-row-title strong">' + esc(h.title || h.name || '(untitled)') + '</span>' +
127
+ (h.snippet ? '<span class="cx-row-sub">' + esc(h.snippet) + '</span>' : '') +
128
+ '<span class="cx-meta" style="margin-top:4px"><span class="cx-id" style="margin:0">' + esc(h.id) + '</span><span class="cx-cap">' + esc(matchLabel(h.match)) + '</span></span>' +
129
+ '</span><span class="chev"></span></button>';
130
+ }
131
+ function list() {
132
+ var q = S.q.trim();
133
+ if (q.length >= 2 && S.ranFor === q) {
134
+ if (!S.hits.length) return '<div class="cx-list"><p class="cx-empty">No threads match that lookup.</p></div>';
135
+ return '<div class="cx-list">' + S.hits.map(hitRow).join('') + '</div>';
136
+ }
137
+ if (S.state === 'error') return '<div class="cx-notice warn">' + esc(S.error) + '</div>';
138
+ if (!S.rows) return '<p class="cx-note">Loading threads…</p>';
139
+ if (!S.rows.length) return '<div class="cx-list"><p class="cx-empty">No threads yet. Threads are markdown files in threads/, or tracked automatically with a bridge.</p></div>';
140
+ return '<div class="cx-list">' + S.rows.map(row).join('') + '</div>';
141
+ }
142
+ function renderList() {
143
+ var focus = document.activeElement && document.activeElement.id === 'th-q';
144
+ var caret = focus ? document.activeElement.selectionStart : null;
145
+ pane.innerHTML = '<div class="cx" data-tint="threads">' + hero() + searchBar() + list() + '</div>';
146
+ if (focus) { var input = $('th-q'); if (input) { input.focus(); try { input.setSelectionRange(caret, caret); } catch (_e) {} } }
147
+ }
148
+ function renderDetail(id) {
149
+ var cached = S.details[id] || (S.rows || []).filter(function (t) { return t.id === id; })[0];
150
+ if (cached) W.setCrumb(cached.name || id);
151
+ pane.innerHTML = '<div class="cx">' + W.skeletonRows(4) + '</div>';
152
+ api('/api/threads/' + encodeURIComponent(id)).then(function (t) {
153
+ var rt = W.parseRoute(); if (rt.pane !== 'threads' || rt.args[0] !== id) return;
154
+ t = t && t.thread ? t.thread : t;
155
+ S.details[id] = t;
156
+ W.setCrumb(t.name || id);
157
+ var text = body(t);
158
+ pane.innerHTML = '<div class="cx" data-tint="threads"><div class="cx-detail-head"><div style="min-width:0">' +
159
+ '<h2 class="cx-detail-title" style="font:500 22px/1.2 var(--display)">' + esc(t.name || '(untitled)') + '</h2>' +
160
+ (subtitle(t) ? '<p class="cx-detail-meta">' + esc(subtitle(t)) + '</p>' : '') +
161
+ '<p class="cx-id" style="user-select:all">' + esc(t.id) + '</p></div>' +
162
+ '<div class="cx-actions"><button type="button" class="cx-btn primary" data-th="copy">Copy as Context</button></div></div>' +
163
+ '<div class="cx-card"><div class="cx-card-body md">' + (text ? W.md(text) : '<p style="color:var(--sub)">(no stored body)</p>') + '</div></div></div>';
164
+ }, function (err) {
165
+ var r = W.parseRoute(); if (r.pane !== 'threads' || r.args[0] !== id) return;
166
+ pane.innerHTML = '<div class="cx"><div class="cx-notice warn">' + esc(err && err.status === 404 ? 'That thread is no longer stored.' : (err.message || 'The thread could not be read.')) + '</div>' +
167
+ (err && err.status === 404 ? '' : '<button type="button" class="cx-btn quiet" data-th="retry">Try again</button>') + '</div>';
168
+ });
169
+ }
170
+ function render() { var a = W.parseRoute(); if (a.pane === 'threads' && !a.args.length) renderList(); }
171
+
172
+ pane.addEventListener('click', function (e) {
173
+ var t = e.target.closest('[data-th]');
174
+ if (!t) return;
175
+ var act = t.getAttribute('data-th');
176
+ if (act === 'refresh') { S.rows = null; S.state = 'idle'; load(true); if (S.q.trim().length >= 2) search(); return; }
177
+ if (act === 'clear') { S.q = ''; S.hits = null; S.ranFor = ''; return renderList(); }
178
+ if (act === 'open') return W.go('threads', [t.getAttribute('data-id')]);
179
+ if (act === 'retry') { var cur = W.parseRoute().args[0]; if (cur) renderDetail(cur); return; }
180
+ if (act === 'copy') {
181
+ var id = W.parseRoute().args[0], th = S.details[id];
182
+ if (th) W.copyText(copyFormat(th)).then(function () { W.toast('Copied as grounded context'); }, function (err) { W.toast(err.message); });
183
+ }
184
+ });
185
+ pane.addEventListener('input', function (e) {
186
+ if (e.target.id !== 'th-q') return;
187
+ S.q = e.target.value;
188
+ clearTimeout(pane._t);
189
+ if (S.q.trim().length < 2) { S.hits = null; S.ranFor = ''; renderList(); return; }
190
+ pane._t = setTimeout(search, 350);
191
+ });
192
+ pane.addEventListener('submit', function (e) { if (e.target.matches('[data-th-form]')) { e.preventDefault(); clearTimeout(pane._t); search(); } });
193
+
194
+ W.registerPane('threads', {
195
+ load: function (args) {
196
+ if (args && args.length) renderDetail(args[0]);
197
+ else { load(false); renderList(); }
198
+ }
199
+ });
200
+ })();
@@ -0,0 +1,261 @@
1
+ /* COS Control Web: Work pane styles, on top of control.css. Control's board: columns 234pt wide on a raised ground,
2
+ cards on the panel with a 7pt radius, a gold rule for the chosen text switch, amber for a card asking for you,
3
+ green for one running. Below 600px the stages stack, one under another, with no sideways page scroll. */
4
+
5
+ #pane-work { max-width: 1400px; }
6
+ .wk { --danger: var(--rec, #c8452f); }
7
+ .wk [data-tone='green'] { --tone: var(--green); }
8
+ .wk [data-tone='amber'] { --tone: var(--amber); }
9
+ .wk [data-tone='danger'] { --tone: var(--danger); }
10
+ .wk [data-tone='accent'] { --tone: var(--accent); }
11
+ .wk [data-tone='muted'] { --tone: var(--sub); }
12
+
13
+ .wk-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
14
+ .wk-row.wk-top { align-items: flex-start; }
15
+ .wk-wrap { flex-wrap: wrap; }
16
+ .wk-grow { flex: 1; min-width: 0; }
17
+ .wk-push { margin-left: auto; }
18
+ .wk-muted { color: var(--sub); font-size: 13px; line-height: 1.4; overflow-wrap: anywhere; }
19
+ .wk-small { font-size: 12.5px; line-height: 1.45; overflow-wrap: anywhere; }
20
+ .wk-fine { font-size: 11.5px; line-height: 1.45; color: var(--tertiary); margin: 10px 0 0; }
21
+ .wk-mono { font: 400 11px/1.3 var(--mono); color: var(--sub); white-space: nowrap; }
22
+ .wk-error { color: var(--danger); font-size: 13px; margin: 6px 0; overflow-wrap: anywhere; }
23
+ .wk-pad { padding: 12px; margin: 0; }
24
+ .wk-pad-x { padding: 0 2px; margin: 0 0 10px; }
25
+ .wk-clamp2, .wk-clamp3 { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
26
+ .wk-clamp2 { -webkit-line-clamp: 2; }
27
+ .wk-clamp3 { -webkit-line-clamp: 3; }
28
+ .wk-h2 { font: 500 22px/1.2 var(--display); margin: 0; color: var(--ink); }
29
+ .wk code { font: 400 0.92em/1.3 var(--mono); background: var(--qopen); padding: 0 3px; border-radius: 4px; }
30
+ .wk-u { text-decoration: underline; text-decoration-color: var(--gold); text-decoration-thickness: 2px; text-underline-offset: 2px; }
31
+
32
+ .wk-text-btn { display: inline-flex; align-items: center; min-height: 30px; padding: 0 2px; font: 600 13px/1.2 var(--body); color: var(--accent); white-space: nowrap; }
33
+ .wk-text-btn:disabled { opacity: 0.45; }
34
+ .wk-link { text-align: left; color: var(--accent); font: 600 14px/1.35 var(--body); padding: 0; }
35
+ .wk-decision-title { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
36
+ @media (max-width: 599px) { .wk-decision-title { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } }
37
+ .wk-icon-btn { width: 36px; justify-content: center; padding: 0; }
38
+ .wk-caret { display: inline-block; width: 0; height: 0; margin-left: 6px; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid currentColor; vertical-align: 2px; opacity: 0.7; }
39
+ .wk-note { justify-content: space-between; align-items: center; }
40
+
41
+ /* ── tabs and filters ───────────────────────────────────── */
42
+ .wk-controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 18px; margin: 12px 0 14px; }
43
+ .wk-tabs { flex-wrap: wrap; row-gap: 0; }
44
+ .wk-more { position: relative; display: inline-flex; }
45
+ .wk-menu { position: absolute; top: calc(100% + 6px); right: 0; left: auto; max-width: calc(100vw - 32px); z-index: 30; min-width: 200px; padding: 6px; border-radius: 10px; background: var(--panel); border: 1px solid var(--panel-border); box-shadow: var(--shadow); display: flex; flex-direction: column; }
46
+ .wk-tabs .wk-menu button { padding: 10px 10px; text-align: left; border-radius: 7px; font: 500 14px/1.2 var(--body); color: var(--ink); }
47
+ .wk-tabs .wk-menu button::after { display: none; }
48
+ .wk-menu button:hover { background: var(--qopen); }
49
+ .wk-filters { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px 12px; min-width: 0; flex: 1 1 auto; }
50
+ .wk-filters .cx-search { flex: 1 1 200px; max-width: 280px; }
51
+ .wk-pick { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--sub); min-width: 0; }
52
+ .wk-pick select.field, .wk-pick input.field { width: auto; min-height: 34px; padding: 0 30px 0 10px; font-size: 13.5px; border-radius: 8px; color: var(--ink); background-position: calc(100% - 16px) 15px, calc(100% - 11px) 15px; }
53
+ .wk-pick input.field { padding: 0 10px; }
54
+ .wk-pick-wide { display: flex; flex-direction: column; align-items: stretch; gap: 6px; margin: 10px 0; }
55
+ .wk-pick-wide span { font: 600 12px/1.2 var(--body); color: var(--sub); }
56
+ .wk-pick-wide select.field { width: 100%; max-width: 520px; min-height: 40px; }
57
+ @media (max-width: 599px) {
58
+ .wk-controls { margin: 10px 0 12px; }
59
+ .wk-filters { width: 100%; justify-content: flex-start; }
60
+ .wk-filters .cx-search { max-width: none; flex-basis: 100%; }
61
+ .wk-pick { flex: 1 1 140px; flex-direction: column; align-items: stretch; gap: 4px; font-size: 11.5px; }
62
+ .wk-pick select.field { min-height: 38px; font-size: 15px; width: 100%; background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px; }
63
+ .wk-text-btn { min-height: 36px; font-size: 14px; }
64
+ }
65
+
66
+ /* ── capture ────────────────────────────────────────────── */
67
+ .wk-capture { margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
68
+ .wk-capture .field { min-height: 40px; font-size: 15px; padding: 8px 12px; border-radius: 9px; }
69
+ .wk-capture-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: flex-end; }
70
+ .wk-capture-row select.field { width: auto; min-width: 180px; padding-right: 32px; background-position: calc(100% - 17px) 18px, calc(100% - 12px) 18px; }
71
+
72
+ /* ── Your move ──────────────────────────────────────────── */
73
+ .wk-queue { padding-top: 6px; }
74
+ .wk-queue-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin-bottom: 4px; }
75
+ .wk-decision { display: flex; gap: 12px; align-items: flex-start; padding: 16px 0; border-bottom: 1px solid var(--hairline); }
76
+ .wk-decision-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
77
+ .wk-decision-main p { margin: 0; }
78
+ .wk-decision-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: none; }
79
+ .wk-state { margin: 0; font: 500 12.5px/1.35 var(--body); color: var(--tone, var(--sub)); }
80
+ @media (max-width: 599px) {
81
+ .wk-decision { flex-direction: column; gap: 8px; }
82
+ .wk-decision-actions { flex-direction: row; align-items: center; gap: 12px; }
83
+ }
84
+
85
+ /* ── board ──────────────────────────────────────────────── */
86
+ .wk-board-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 16px; margin: 4px 0 12px; }
87
+ .wk-board-head .wk-muted { margin: 4px 0 0; font-size: 12.5px; }
88
+ .wk-results { margin: 0 0 12px; display: flex; flex-direction: column; gap: 8px; }
89
+ .wk-band { border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
90
+ .wk-band-row { display: grid; grid-template-columns: 70px minmax(0, 1fr) auto; gap: 12px; align-items: baseline; width: 100%; padding: 9px 6px; text-align: left; }
91
+ .wk-band-row + .wk-band-row { border-top: 1px solid var(--hairline); }
92
+ .wk-band-row:hover { background: var(--gold-bg); }
93
+ .wk-band-stage { font: 400 10.5px/1.3 var(--mono); color: var(--sub); }
94
+ .wk-band-title { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
95
+ .wk-band-why { font-size: 12px; color: var(--accent); white-space: nowrap; }
96
+ .wk-band-why.meaning { color: var(--gold); font-style: italic; }
97
+ @media (max-width: 599px) { .wk-band-row { grid-template-columns: minmax(0, 1fr); gap: 2px; } .wk-band-title { white-space: normal; } }
98
+
99
+ .wk-mentioned { margin: 0 0 12px; }
100
+ .wk-menu-label { display: inline-flex; align-items: center; min-height: 32px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--panel-border); background: var(--btn-bg); font: 500 13px/1 var(--body); color: var(--ink); }
101
+ .wk-mentioned-list { margin-top: 8px; max-height: 420px; overflow-y: auto; }
102
+ .wk-mrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; padding: 10px 14px; text-align: left; font-size: 13.5px; line-height: 1.35; border-top: 1px solid var(--hairline); }
103
+ .wk-mrow:first-child { border-top: 0; }
104
+ .wk-mrow span:first-child { min-width: 0; overflow-wrap: anywhere; }
105
+ .wk-more-row { display: block; width: 100%; padding: 11px 14px; text-align: left; font: 600 13px/1.2 var(--body); color: var(--accent); }
106
+
107
+ /* Columns: stacked on the closed Duo; side by side from 600px, scrolling sideways inside the board as Control's does. */
108
+ .wk-columns { display: flex; flex-direction: column; gap: 14px; }
109
+ .wk-col { background: color-mix(in srgb, var(--qopen) 70%, transparent); border: 1px solid var(--panel-border); border-radius: 9px; min-width: 0; }
110
+ .wk-col-head { padding: 12px 12px 11px; border-bottom: 1px solid var(--panel-border); }
111
+ .wk-col-head h3 { margin: 0; font: 600 14px/1.2 var(--body); color: var(--ink); }
112
+ .wk-col-head .wk-row { justify-content: space-between; }
113
+ .wk-col-head p { margin: 5px 0 0; font-size: 11.5px; color: var(--sub); }
114
+ .wk-count { font: 400 11.5px/1 var(--mono); color: var(--sub); }
115
+ .wk-col-body { padding: 10px; display: flex; flex-direction: column; gap: 10px; }
116
+ .wk-col-body > * { flex: none; }
117
+ @media (min-width: 600px) {
118
+ .wk-columns { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(206px, 1fr); overflow-x: auto; gap: 12px; padding-bottom: 6px; scroll-snap-type: x proximity; overscroll-behavior-x: contain; }
119
+ .wk-col { scroll-snap-align: start; display: flex; flex-direction: column; max-height: calc(100dvh - 150px); }
120
+ .wk-col-body { overflow-y: auto; flex: 1; min-height: 120px; }
121
+ }
122
+
123
+ .wk-card { position: relative; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 7px; overflow: hidden; }
124
+ .wk-card[data-tone='asking'] { border-color: color-mix(in srgb, var(--amber) 60%, transparent); }
125
+ .wk-card[data-tone='running'] { border-color: color-mix(in srgb, var(--green) 60%, transparent); }
126
+ .wk-card[data-tone='matched'] { border-color: color-mix(in srgb, var(--gold) 50%, transparent); }
127
+ .wk-card[data-tone='asking']::before, .wk-card[data-tone='running']::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; border-radius: 2px; background: var(--amber); }
128
+ .wk-card[data-tone='running']::before { background: var(--green); }
129
+ .wk-card-tap { display: flex; flex-direction: column; gap: 9px; width: 100%; padding: 12px; text-align: left; color: inherit; }
130
+ .wk-card-tap:hover { background: var(--qopen); }
131
+ .wk-card-tap:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
132
+ .wk-card-title { font: 500 13.5px/1.38 var(--body); color: var(--ink); display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
133
+ .wk-card-source { font-size: 11.5px; line-height: 1.35; color: var(--sub); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
134
+ .wk-status { display: flex; align-items: baseline; gap: 6px; margin: 0; font: 600 11.5px/1.35 var(--body); color: var(--tone, var(--accent)); overflow-wrap: anywhere; }
135
+ .wk-status .icon { width: 12px; height: 12px; align-self: center; }
136
+ .wk-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: currentColor; transform: translateY(-1px); }
137
+ .wk-dot.hollow { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }
138
+ .wk-dot.live { animation: wk-pulse 1.6s ease-in-out infinite; }
139
+ @keyframes wk-pulse { 50% { opacity: 0.35; } }
140
+ @media (prefers-reduced-motion: reduce) { .wk-dot.live { animation: none; } }
141
+ .wk-quote { margin: 0; font-size: 12px; font-style: italic; line-height: 1.4; color: var(--ink); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
142
+ .wk-meeting { display: flex; align-items: flex-start; gap: 6px; width: 100%; padding: 0 12px 10px; text-align: left; font-size: 11.5px; line-height: 1.35; color: var(--accent); }
143
+ .wk-meeting .icon { width: 13px; height: 13px; flex: none; margin-top: 1px; }
144
+ .wk-meeting span { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
145
+ .wk-date { margin: -4px 0 0; padding: 0 12px 9px; text-align: right; font: 400 10.5px/1.3 var(--mono); color: var(--sub); }
146
+ .wk-card-foot { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-top: 1px solid var(--hairline); }
147
+ .wk-card-foot .wk-text-btn { flex: none; }
148
+ .wk-stage-lg { max-width: 200px; }
149
+ .wk-stage { flex: 0 1 auto; width: auto; max-width: 132px; field-sizing: content; min-height: 28px; padding: 0 24px 0 9px; border-radius: 7px; border: 1px solid var(--panel-border); background: var(--btn-bg); color: var(--ink); font: 500 12.5px/1 var(--body);
150
+ appearance: none; -webkit-appearance: none; cursor: pointer;
151
+ background-image: linear-gradient(45deg, transparent 50%, var(--sub) 50%), linear-gradient(135deg, var(--sub) 50%, transparent 50%);
152
+ background-position: calc(100% - 13px) 12px, calc(100% - 9px) 12px; background-size: 4px 4px; background-repeat: no-repeat; }
153
+ .wk-stage:disabled { opacity: 0.5; cursor: default; }
154
+ .wk-stage-lg { min-height: 32px; font-size: 13.5px; background-position: calc(100% - 13px) 14px, calc(100% - 9px) 14px; }
155
+ .wk-confirm { margin: 8px 10px 10px; padding: 10px 12px; border-radius: 8px; background: var(--gold-bg); font-size: 13px; }
156
+ .wk-confirm p { margin: 0 0 8px; overflow-wrap: anywhere; }
157
+ @media (max-width: 599px) {
158
+ .wk-stage { min-height: 36px; font-size: 14px; background-position: calc(100% - 13px) 16px, calc(100% - 9px) 16px; }
159
+ .wk-card-title { font-size: 15px; }
160
+ .wk-card-source, .wk-meeting { font-size: 12.5px; }
161
+ .wk-status { font-size: 12.5px; }
162
+ .wk-meeting { min-height: 28px; }
163
+ }
164
+
165
+ /* ── Focus ──────────────────────────────────────────────── */
166
+ .wk-focus-counts { margin: 0 0 10px; gap: 6px 16px; }
167
+ .wk-count-link { display: inline-flex; align-items: center; gap: 5px; min-height: 30px; font: 500 13px/1.2 var(--body); color: var(--accent); border-bottom: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent); }
168
+ .wk-count-link .chev { width: 6px; height: 6px; }
169
+ .wk-count-link:disabled { color: var(--sub); border-bottom-color: transparent; }
170
+ .wk-frow { display: flex; flex-direction: column; gap: 5px; width: 100%; padding: 14px 16px; text-align: left; border-top: 1px solid var(--hairline); }
171
+ .wk-frow:first-child { border-top: 0; }
172
+ .wk-frow:hover { background: var(--qopen); }
173
+ .wk-frow-title { font: 500 14px/1.38 var(--body); color: var(--ink); overflow-wrap: anywhere; }
174
+
175
+ /* ── Sort ───────────────────────────────────────────────── */
176
+ .wk-sort { display: flex; flex-direction: column; gap: 10px; }
177
+ .wk-sort-head { gap: 6px 12px; }
178
+ .wk-sort-head .wk-muted { font-size: 12.5px; }
179
+ .wk-sort-body { display: flex; flex-direction: column; gap: 14px; border-top: 1px solid var(--hairline); padding-top: 14px; }
180
+ .wk-sort-group h3 { margin: 0; font: 600 15px/1.3 var(--body); min-width: 0; overflow-wrap: anywhere; }
181
+ .wk-keys { margin: -6px 0 0; }
182
+ .wk-sort-note { margin: -8px 0 0; }
183
+ @media (hover: none) { .wk-keys { display: none; } }
184
+ @media (max-width: 599px) { .wk-sort-head .wk-muted { order: 3; flex-basis: 100%; } }
185
+ .wk-sort-card { padding: 12px; border-radius: 9px; background: var(--qopen); border: 1px solid var(--panel-border); display: flex; flex-direction: column; gap: 7px; }
186
+ .wk-sort-card p { margin: 0; }
187
+ .wk-items h4 { margin: 0; font: 600 14px/1.2 var(--body); }
188
+ .wk-items > .wk-muted { margin: 4px 0 8px; font-size: 12.5px; }
189
+ .wk-items-list { padding: 0 12px; border-radius: 9px; background: color-mix(in srgb, var(--qopen) 60%, transparent); border: 1px solid var(--panel-border); }
190
+ .wk-intake-row { padding: 12px 0; display: flex; flex-direction: column; gap: 6px; }
191
+ .wk-intake-row + .wk-intake-row { border-top: 1px solid var(--hairline); }
192
+ .wk-intake-text { margin: 0; font: 500 14px/1.42 var(--body); color: var(--ink); overflow-wrap: anywhere; }
193
+ .wk-intake-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; }
194
+ .wk-intake-meta { display: flex; align-items: baseline; gap: 4px; min-width: 0; flex: 1 1 220px; }
195
+ .wk-intake-meta .wk-meeting { padding: 0; width: auto; color: var(--sub); }
196
+ .wk-intake-meta .wk-muted { font-size: 11.5px; white-space: nowrap; }
197
+ .wk-evidence summary { cursor: pointer; font-size: 12.5px; color: var(--accent); min-height: 28px; }
198
+ .wk-evidence p { white-space: pre-wrap; font-size: 12px; color: var(--sub); margin: 6px 0 0; }
199
+ .wk-sort-nav { gap: 18px; padding-top: 4px; }
200
+ .wk-undo { justify-content: flex-end; }
201
+
202
+ /* ── detail ─────────────────────────────────────────────── */
203
+ .wk-backbar { margin: 4px 0 10px; }
204
+ .wk-backbar .icon { width: 14px; height: 14px; }
205
+ @media (max-width: 599px) { .wk-backbar { display: none; } }
206
+ .wk-detail { display: flex; flex-direction: column; gap: 18px; }
207
+ .wk-detail-main, .wk-detail-side { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
208
+ @media (min-width: 900px) {
209
+ .wk-detail { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 400px); gap: 22px; align-items: start; }
210
+ .wk-detail-side { padding: 16px; border-radius: 12px; background: color-mix(in srgb, var(--qopen) 60%, transparent); border: 1px solid var(--panel-border); }
211
+ }
212
+ @media (min-width: 1300px) { .wk-detail { grid-template-columns: minmax(0, 1fr) 452px; } }
213
+ .wk-detail-head { display: flex; flex-direction: column; gap: 10px; padding-bottom: 14px; border-bottom: 1px solid var(--hairline); }
214
+ .wk-detail-head p { margin: 0; }
215
+ .wk-detail-title { font: 500 24px/1.22 var(--display); margin: 0; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
216
+ .wk-detail-head .wk-detail-title { flex: 1 1 300px; }
217
+ .wk-actions { flex: none; }
218
+ .wk-section { display: flex; flex-direction: column; gap: 10px; }
219
+ .wk-section > p { margin: 0; }
220
+ .wk-fact h3 { margin: 0 0 6px; font: 600 12px/1.2 var(--body); color: var(--sub); }
221
+ .wk-fact .cx-card-body { font-size: 14.5px; }
222
+ .wk-contrib { padding: 12px; border-radius: 8px; background: var(--panel); border: 1px solid var(--panel-border); display: flex; flex-direction: column; gap: 6px; }
223
+ .wk-contrib p { margin: 0; }
224
+ .wk-contrib strong { font-size: 13.5px; }
225
+ .wk-sub { display: flex; flex-direction: column; gap: 8px; padding-top: 6px; }
226
+ .wk-sub .wk-meeting { padding: 8px 10px; border-radius: 8px; border: 1px solid var(--panel-border); background: var(--btn-bg); font-size: 13px; }
227
+ .wk-sub .wk-muted, .wk-sub p { margin: 0; }
228
+ .wk-handoff { padding: 12px; border-radius: 9px; background: var(--panel); border: 1px solid color-mix(in srgb, var(--tone, var(--panel-border)) 45%, transparent); display: flex; flex-direction: column; gap: 6px; }
229
+ .wk-handoff p { margin: 0; }
230
+ .wk-handoff .wk-state { color: var(--tone); }
231
+ .wk-section .cx-btn.primary { align-self: flex-start; }
232
+ .wk-section > .cx-btn { align-self: flex-start; }
233
+ .wk-reply { display: flex; flex-direction: column; gap: 8px; }
234
+ .wk-reply p { margin: 0; }
235
+ .wk-reply .field { min-height: 80px; font-size: 15px; padding: 9px 12px; border-radius: 9px; }
236
+ .wk-missing { padding: 20px 0; }
237
+
238
+ /* ── Start work and Edit task ───────────────────────────── */
239
+ .wk-start, .wk-edit { max-width: 640px; display: flex; flex-direction: column; gap: 12px; }
240
+ .wk-start p, .wk-edit p { margin: 0; }
241
+ .wk-start > *, .wk-edit > *, .wk-section > *, .wk-sort > * { flex: none; }
242
+ .wk-start-task { font-size: 15px; line-height: 1.45; overflow-wrap: anywhere; }
243
+ .wk-field { display: flex; flex-direction: column; gap: 6px; }
244
+ .wk-label { font: 600 12px/1.2 var(--body); color: var(--sub); }
245
+ .wk-field .field { min-height: 42px; font-size: 15px; padding: 9px 12px; border-radius: 9px; }
246
+ .wk-choices { border: 1px solid var(--panel-border); border-radius: 9px; overflow: hidden; background: var(--panel); }
247
+ .wk-choice { display: flex; gap: 10px; align-items: flex-start; padding: 12px; cursor: pointer; }
248
+ .wk-choice + .wk-choice { border-top: 1px solid var(--hairline); }
249
+ .wk-choice input { margin-top: 3px; accent-color: var(--gold); width: 16px; height: 16px; flex: none; }
250
+ .wk-choice > span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
251
+ .wk-choice strong { font-size: 14px; }
252
+ .wk-choice.off { opacity: 0.5; cursor: default; }
253
+ .wk-lines { display: flex; flex-direction: column; gap: 4px; }
254
+ .wk-line { display: grid; grid-template-columns: 70px minmax(0, 1fr); gap: 8px; font-size: 12.5px; }
255
+ .wk-line span:first-child { color: var(--sub); }
256
+ .wk-line span:last-child { overflow-wrap: anywhere; }
257
+ .wk-more-actions { border-top: 1px solid var(--hairline); padding-top: 10px; }
258
+ .wk-more-actions summary { cursor: pointer; font: 600 13px/1.2 var(--body); min-height: 32px; display: flex; align-items: center; }
259
+ .wk-more-body { display: flex; flex-direction: column; gap: 10px; padding-top: 8px; }
260
+ .wk-edit-foot { display: flex; flex-direction: column; gap: 8px; padding-top: 12px; border-top: 1px solid var(--hairline); }
261
+ .wk-edit-foot .wk-confirm { margin: 0; }