@gotcos/glasses-server 6.98.1 → 6.99.12
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.
- package/CHANGELOG.md +149 -0
- package/package.json +2 -1
- package/server/index.ts +147 -9
- package/server/lib/api-auth.ts +31 -14
- package/server/lib/claude-bridge.ts +21 -2
- package/server/lib/codex-bridge.ts +21 -2
- package/server/lib/display-bus.ts +3 -0
- package/server/lib/drop-destinations.ts +134 -0
- package/server/lib/drop-history-search.ts +97 -0
- package/server/lib/drop-p2-boot.ts +60 -0
- package/server/lib/drop-p2-runtime.ts +237 -0
- package/server/lib/drop-p2-settings.ts +174 -0
- package/server/lib/drop-resolution.ts +168 -0
- package/server/lib/drop-router.ts +325 -0
- package/server/lib/drop-service.ts +739 -0
- package/server/lib/drop-settings.ts +45 -0
- package/server/lib/drop-store.ts +428 -0
- package/server/lib/drop-when.ts +265 -0
- package/server/lib/glasses-pairing.ts +15 -0
- package/server/lib/learning-needs-you.ts +111 -0
- package/server/lib/live-cues-v2-settings.ts +2 -1
- package/server/lib/live-cues-v2.ts +6 -4
- package/server/lib/meeting-consolidation-run.ts +169 -0
- package/server/lib/meeting-store.ts +61 -0
- package/server/lib/meeting-title-steer.ts +200 -0
- package/server/lib/network-policy.ts +84 -6
- package/server/lib/permission-broker.ts +11 -0
- package/server/lib/ping-budget.ts +149 -0
- package/server/lib/ping-engine.ts +245 -0
- package/server/lib/ping-feed-sink.ts +33 -0
- package/server/lib/ping-sources.ts +192 -0
- package/server/lib/query-job-coordinator.ts +5 -2
- package/server/lib/query-job-runtime.ts +45 -4
- package/server/lib/query-job-types.ts +10 -1
- package/server/lib/reminder-scheduler.ts +153 -0
- package/server/lib/tailnet-self-name.ts +103 -0
- package/server/lib/tailscale-nudge.ts +40 -2
- package/server/lib/task-store.ts +32 -0
- package/server/lib/web-ask-admission.ts +56 -0
- package/server/lib/web-live-chunks.ts +273 -0
- package/server/lib/web-memories-bridge.ts +323 -0
- package/server/lib/web-session-auth.ts +492 -0
- package/server/lib/web-usage-bridge.ts +154 -0
- package/server/lib/work-search.ts +14 -1
- package/server/lib/your-move-count.ts +58 -0
- package/server/routes/display.ts +11 -1
- package/server/routes/drops.ts +260 -0
- package/server/routes/health.ts +16 -0
- package/server/routes/live-cues.ts +5 -4
- package/server/routes/meeting.ts +61 -5
- package/server/routes/permission-broker.ts +23 -3
- package/server/routes/transcribe-stream.ts +34 -3
- package/server/routes/web-control.ts +569 -0
- package/server/routes/welcome-context.ts +2 -1
- package/server/web-control/app.css +519 -0
- package/server/web-control/app.js +3643 -0
- package/server/web-control/control.css +312 -0
- package/server/web-control/fonts/DMSans.ttf +0 -0
- package/server/web-control/fonts/Fraunces.ttf +0 -0
- package/server/web-control/fonts/JetBrainsMono.ttf +0 -0
- package/server/web-control/index.html +275 -0
- package/server/web-control/mark.svg +1 -0
- package/server/web-control/memories/d3.min.js +2 -0
- package/server/web-control/memories/graph-explorer.css +200 -0
- package/server/web-control/memories/graph-explorer.js +748 -0
- package/server/web-control/memories/memories-app.js +1561 -0
- package/server/web-control/memories/memories-theme.css +73 -0
- package/server/web-control/memories/memories.html +72 -0
- package/server/web-control/memories/memory-stewardship.js +63 -0
- package/server/web-control/memories/memory-workspace.js +216 -0
- package/server/web-control/panes/cos-chat.css +157 -0
- package/server/web-control/panes/cos-chat.js +973 -0
- package/server/web-control/panes/dictation.js +173 -0
- package/server/web-control/panes/meetings.css +257 -0
- package/server/web-control/panes/meetings.js +2264 -0
- package/server/web-control/panes/messages.js +711 -0
- package/server/web-control/panes/questions.css +32 -0
- package/server/web-control/panes/questions.js +275 -0
- package/server/web-control/panes/sessions.css +452 -0
- package/server/web-control/panes/settings.js +292 -0
- package/server/web-control/panes/speakers.css +269 -0
- package/server/web-control/panes/speakers.js +2755 -0
- package/server/web-control/panes/threads.js +200 -0
- package/server/web-control/panes/work.css +261 -0
- package/server/web-control/panes/work.js +1667 -0
- package/server/web-control/shell.js +294 -0
- package/server/web-control/theme.js +25 -0
- package/server/web-control/vendor/qrcode-encoder.js +10 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/* COS Control Web: Settings in Control's hub categories (SettingsHubCatalog.swift 25-146 and its
|
|
2
|
+
groups). Rows the server owns work here: Decision models, Live cues, Fireflies import status,
|
|
3
|
+
Morning brief, Task folders, Paused threads and Server status. Everything Control keeps on the
|
|
4
|
+
Mac (preferences, the pet, dictation, keys, pairing, permissions) is named under its category
|
|
5
|
+
as "On your Mac", so nothing looks missing and nothing security-related is offered here.
|
|
6
|
+
"This browser" is the web's own group: appearance and browser sign-in.
|
|
7
|
+
Keeps the ids and data attributes app.js already handles: data-theme-choice, #device-code-btn,
|
|
8
|
+
#device-code-panel, #copy-link, #tailnet-url, #sign-out, data-action="revoke-all", #settings-status. */
|
|
9
|
+
(function () {
|
|
10
|
+
'use strict';
|
|
11
|
+
var W = window.COSWeb;
|
|
12
|
+
var $ = W.$, esc = W.esc, api = W.api;
|
|
13
|
+
var pane = $('pane-settings');
|
|
14
|
+
var S = { data: null, state: 'idle' };
|
|
15
|
+
|
|
16
|
+
var CATEGORIES = [
|
|
17
|
+
{ id: 'general', title: 'General', summary: 'How COS Control opens, its shortcut, Today and the setup guide.', mac: ['App', 'Activity home', 'Setup guide'] },
|
|
18
|
+
{ id: 'ai', title: 'AI and accounts', summary: 'The AI apps COS works through, your keys and the models behind Work.', mac: ['Your AI apps', 'Local model', 'Your own key', 'Search'] },
|
|
19
|
+
{ id: 'glasses', title: 'Glasses and iPhone', summary: 'Pair your glasses and phone, and how videos reach this Mac.', mac: ['Devices', 'Video uploads'] },
|
|
20
|
+
{ id: 'meetings', title: 'Meetings', summary: 'Recording, the meeting assistant, live cues and your meeting library.', mac: ['Recording', 'Meeting assistant', 'Library and sync'] },
|
|
21
|
+
{ id: 'dictation', title: 'Dictation', summary: 'Type by voice into any Mac app: shortcuts, sounds, text and AI polish.', mac: ['Dictation', 'Microphone', 'Shortcuts', 'Sounds and audio', 'Text', 'AI polish', 'History'] },
|
|
22
|
+
{ id: 'voice', title: 'Transcription and voice', summary: 'The engines and models that turn speech into text on this Mac.', mac: ['Voice setup', 'Models', 'Performance'] },
|
|
23
|
+
{ id: 'work', title: 'Work and agents', summary: 'Sessions, Work chat, background jobs, folders and the morning brief.', mac: ['Sessions', 'Work chat', 'Background', 'Folders and notes'] },
|
|
24
|
+
// 6.99.11 (Drop it P2): shown only when the server has the P2 settings and Drop it is on.
|
|
25
|
+
{ id: 'reach', title: 'COS reaching out', summary: 'What COS tells you about on its own, when it may interrupt, and your reminders.', mac: [], p2: true },
|
|
26
|
+
{ id: 'privacy', title: 'Privacy and permissions', summary: 'What COS can use on this Mac. Each permission turns on one feature.', mac: ['Permissions'] },
|
|
27
|
+
{ id: 'pet', title: 'Pet', summary: 'The session pet: its character, size and motion.', mac: ['Session pet', 'Characters'] },
|
|
28
|
+
{ id: 'system', title: 'System and updates', summary: 'The COS server on this Mac, app updates, and checks when something is off.', mac: ['App updates', 'Troubleshooting'] }
|
|
29
|
+
];
|
|
30
|
+
var DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
31
|
+
|
|
32
|
+
function soft(p) { return p.then(function (d) { return d; }, function () { return null; }); }
|
|
33
|
+
function load(force) {
|
|
34
|
+
if (S.state === 'loading' || (S.data && !force)) return;
|
|
35
|
+
S.state = 'loading';
|
|
36
|
+
Promise.all([
|
|
37
|
+
soft(api('/api/decision-provider')), soft(api('/api/live-cues/v2/settings')), soft(api('/api/morning-brief')),
|
|
38
|
+
soft(api('/api/domains')), soft(api('/api/agent-sessions/fences')), soft(api('/api/meeting-import/fireflies/status')),
|
|
39
|
+
soft(api('/api/health')), soft(api('/api/drops/settings/p2'))
|
|
40
|
+
]).then(function (r) {
|
|
41
|
+
S.data = { dp: r[0], cues: r[1], mb: r[2], domains: r[3], fences: r[4], ff: r[5], health: r[6], p2: r[7] };
|
|
42
|
+
S.state = 'ready';
|
|
43
|
+
render();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function row(name, desc, control) {
|
|
48
|
+
return '<div class="cx-set-row"><div class="cx-set-text"><span class="cx-set-name">' + esc(name) + '</span>' +
|
|
49
|
+
(desc ? '<span class="cx-set-desc">' + esc(desc) + '</span>' : '') + '</div>' + (control || '') + '</div>';
|
|
50
|
+
}
|
|
51
|
+
function value(text) { return '<span class="cx-set-value">' + esc(text) + '</span>'; }
|
|
52
|
+
function toggle(on, attr, label) { return '<button type="button" class="switch" role="switch" aria-checked="' + !!on + '" aria-label="' + esc(label) + '" ' + attr + '></button>'; }
|
|
53
|
+
function group(title, blurb, rows) {
|
|
54
|
+
return '<div class="cx-set-group"><h3 class="cx-set-title">' + esc(title) + '</h3>' + (blurb ? '<p class="cx-set-blurb">' + esc(blurb) + '</p>' : '') +
|
|
55
|
+
'<div class="cx-list">' + rows + '</div></div>';
|
|
56
|
+
}
|
|
57
|
+
function onMac(names) {
|
|
58
|
+
return '<p class="cx-set-mac">On your Mac: ' + esc(names.join(', ')) + '. Change these in COS Control.</p>';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function browserGroup() {
|
|
62
|
+
var choice = window.CosTheme ? window.CosTheme.choice() : 'system';
|
|
63
|
+
var themeBtn = function (id, label) {
|
|
64
|
+
var on = choice === id;
|
|
65
|
+
return '<button type="button" class="cx-set-row cx-set-choice" data-theme-choice="' + id + '" aria-pressed="' + on + '"><span class="cx-set-name">' + esc(label) + '</span><span class="value">' + (on ? '<span class="check">✓</span>' : '') + '</span></button>';
|
|
66
|
+
};
|
|
67
|
+
return '<section class="cx-set-cat"><h2 class="cx-col-title">This browser</h2>' +
|
|
68
|
+
group('Appearance', '', themeBtn('system', 'Match this device') + themeBtn('light', 'Light') + themeBtn('dark', 'Dark')) +
|
|
69
|
+
group('Sign-in', '',
|
|
70
|
+
'<button type="button" class="cx-set-row" id="device-code-btn"><span class="cx-set-text"><span class="cx-set-name">Sign in another device</span><span class="cx-set-desc">Shows a QR for the phone camera, and a code to type.</span></span><span class="chev"></span></button>' +
|
|
71
|
+
'<div id="device-code-panel" class="device-code hidden" aria-live="polite"></div>' +
|
|
72
|
+
'<button type="button" class="cx-set-row" id="copy-link"><span class="cx-set-text"><span class="cx-set-name">Copy link for your phone</span><span class="cx-set-desc" id="tailnet-url">' + esc(W.state.tailnetUrl || 'Checking Tailscale') + '</span></span></button>' +
|
|
73
|
+
'<button type="button" class="cx-set-row" id="sign-out"><span class="cx-set-text"><span class="cx-set-name">Sign out of this browser</span></span></button>' +
|
|
74
|
+
'<button type="button" class="cx-set-row" data-action="revoke-all"><span class="cx-set-text"><span class="grow cx-set-name">Sign out every browser</span><span class="cx-set-desc">Ends every signed-in browser, copies included. Glasses and the phone app stay paired.</span></span></button>') +
|
|
75
|
+
'</section>';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── COS reaching out (6.99.11) ──────────────────────────
|
|
79
|
+
function p2On() {
|
|
80
|
+
var d = S.data || {}, cap = d.health && d.health.capabilities && d.health.capabilities.dropIt;
|
|
81
|
+
return !!(cap && cap.enabled && cap.p2Settings && d.p2 && d.p2.settings);
|
|
82
|
+
}
|
|
83
|
+
function p2Get(path) { return path.split('.').reduce(function (o, k) { return o == null ? undefined : o[k]; }, S.data.p2.settings); }
|
|
84
|
+
function p2Switch(path, label) { return toggle(p2Get(path), 'data-set="p2" data-p2="' + path + '"', label); }
|
|
85
|
+
function p2Num(path, min, max, label) {
|
|
86
|
+
return '<input type="number" class="field cx-set-num" inputmode="numeric" data-p2="' + path + '" min="' + min + '" max="' + max + '" value="' + esc(String(p2Get(path))) + '" aria-label="' + esc(label) + '">';
|
|
87
|
+
}
|
|
88
|
+
function p2Time(path, label) { return '<input type="time" class="field cx-set-time" data-p2="' + path + '" value="' + esc(String(p2Get(path))) + '" aria-label="' + esc(label) + '">'; }
|
|
89
|
+
function p2Text(path, label, hint) {
|
|
90
|
+
return '<textarea class="field cx-set-steer" rows="2" maxlength="300" data-p2="' + path + '" aria-label="' + esc(label) + '" placeholder="' + esc(hint) + '">' + esc(String(p2Get(path) || '')) + '</textarea>';
|
|
91
|
+
}
|
|
92
|
+
var FULL_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
93
|
+
function reachBody() {
|
|
94
|
+
var day = p2Get('checkin.weekday');
|
|
95
|
+
return group('Pings', 'COS posts these in the COS chat. They interrupt you only within these limits.',
|
|
96
|
+
row('Pings', 'Turn off to keep COS from posting on its own. Reminders and follow-ups on your drops still come.', p2Switch('pings.on', 'Pings')) +
|
|
97
|
+
row('Interruptions a day', 'On a weekday. Reminders do not count.', p2Num('pings.dailyCap', 0, 20, 'Interruptions a day')) +
|
|
98
|
+
row('From one kind a day', '', p2Num('pings.perSource', 1, 10, 'Interruptions from one kind a day')) +
|
|
99
|
+
row('Minutes between', 'The shortest gap between two interruptions.', p2Num('pings.gapMinutes', 0, 240, 'Minutes between interruptions')) +
|
|
100
|
+
row('Quiet from', 'No interruption and no new work ping from this time.', p2Time('pings.quietStart', 'Quiet from')) +
|
|
101
|
+
row('Quiet until', 'What waited posts as one item after this.', p2Time('pings.quietEnd', 'Quiet until')) +
|
|
102
|
+
row('Weekends are quiet', '', p2Switch('pings.weekendsQuiet', 'Weekends are quiet')) +
|
|
103
|
+
row('First interruption', 'The earliest a weekday interruption comes.', p2Time('pings.firstBanner', 'First interruption'))) +
|
|
104
|
+
group('What COS pings about', 'Each lands in the COS chat.',
|
|
105
|
+
row('Meeting prep', '15 minutes before your next meeting.', p2Switch('pings.sources.prep', 'Meeting prep')) +
|
|
106
|
+
row('Agents finishing', 'When a session you sent work to ends its turn.', p2Switch('pings.sources.agent', 'Agents finishing')) +
|
|
107
|
+
row('Follow-ups ready', 'When a meeting leaves follow-ups to review.', p2Switch('pings.sources.followups', 'Follow-ups ready')) +
|
|
108
|
+
row('Threads gone quiet', 'At most once a week for each thread.', p2Switch('pings.sources.stale', 'Threads gone quiet')) +
|
|
109
|
+
row('Red signals', 'A signal the system marks red.', p2Switch('pings.sources.signals', 'Red signals'))) +
|
|
110
|
+
group('Steer', 'Words, matched whole. COS reads no more than this into them.',
|
|
111
|
+
row('Ping me about', 'A ping that mentions one of these may interrupt you, even when its kind stays in the chat.', p2Text('steer.about', 'Ping me about', 'JCK, Rain, pricing')) +
|
|
112
|
+
row('Leave alone', 'A ping that mentions one of these is not posted. Your reminders and follow-ups always are.', p2Text('steer.leaveAlone', 'Leave alone', 'HubDB, standup'))) +
|
|
113
|
+
group('Follow-ups on your drops', 'One line under the drop when what it became resolves.',
|
|
114
|
+
row('Follow-ups', '', p2Switch('followUps.on', 'Follow-ups')) +
|
|
115
|
+
row('Work cards', 'At QA, complete or archived.', p2Switch('followUps.kinds.card', 'Work cards')) +
|
|
116
|
+
row('Handoffs', 'Finished, failed or refused.', p2Switch('followUps.kinds.handoff', 'Handoffs')) +
|
|
117
|
+
row('Sessions', 'Replied or stopped.', p2Switch('followUps.kinds.session', 'Sessions')) +
|
|
118
|
+
row('Threads', 'Resolved or gone quiet.', p2Switch('followUps.kinds.thread', 'Threads'))) +
|
|
119
|
+
group('Weekly check-in', 'COS asks what you do not want to forget this week. Your reply becomes a reminder.',
|
|
120
|
+
row('Check-in', '', p2Switch('checkin.on', 'Weekly check-in')) +
|
|
121
|
+
row('Day', '', '<select class="field cx-set-day" data-p2="checkin.weekday" aria-label="Check-in day">' + FULL_DAYS.map(function (n, i) {
|
|
122
|
+
return '<option value="' + i + '"' + (i === day ? ' selected' : '') + '>' + n + '</option>';
|
|
123
|
+
}).join('') + '</select>') +
|
|
124
|
+
row('Time', '', p2Time('checkin.time', 'Check-in time'))) +
|
|
125
|
+
group('Reminders', 'The ones you set by saying remind me.',
|
|
126
|
+
row('Default time', 'What a day alone means, and the time for Tomorrow and Monday.', p2Time('reminders.defaultTime', 'Default reminder time')) +
|
|
127
|
+
row('Snooze for', 'Minutes.', p2Num('reminders.snoozeMinutes', 5, 1440, 'Snooze minutes')) +
|
|
128
|
+
row('Notify me', 'A reminder notifies you at any hour.', p2Switch('reminders.banners', 'Reminder notifications')) +
|
|
129
|
+
row('Show on the glasses', 'For the glasses build that shows COS on the lens.', p2Switch('reminders.lensPush', 'Show reminders on the glasses'))) +
|
|
130
|
+
group('Which may interrupt', 'The rest land in the COS chat without a notification.',
|
|
131
|
+
row('Follow-ups on your drops', '', p2Switch('banners.resolution', 'Follow-ups on your drops may interrupt')) +
|
|
132
|
+
row('Meeting prep', '', p2Switch('banners.prep', 'Meeting prep may interrupt')) +
|
|
133
|
+
row('Agents finishing', '', p2Switch('banners.agent', 'Agents finishing may interrupt')) +
|
|
134
|
+
row('Follow-ups ready', '', p2Switch('banners.followups', 'Follow-ups ready may interrupt')) +
|
|
135
|
+
row('Threads and signals', '', p2Switch('banners.stale', 'Threads and signals may interrupt')) +
|
|
136
|
+
row('Weekly check-in', '', p2Switch('banners.checkin', 'Weekly check-in may interrupt')));
|
|
137
|
+
}
|
|
138
|
+
/** One field as the partial PUT the server merges: "pings.sources.prep" becomes { pings: { sources: { prep: v } } }. */
|
|
139
|
+
function p2Patch(path, v) {
|
|
140
|
+
var keys = path.split('.'), out = {}, at = out;
|
|
141
|
+
for (var i = 0; i < keys.length - 1; i++) { at[keys[i]] = {}; at = at[keys[i]]; }
|
|
142
|
+
at[keys[keys.length - 1]] = v;
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
function p2Save(path, v, undo) {
|
|
146
|
+
return api('/api/drops/settings/p2', { method: 'PUT', body: p2Patch(path, v) }).then(function (ans) {
|
|
147
|
+
if (ans && ans.settings) S.data.p2.settings = ans.settings;
|
|
148
|
+
W.toast('Saved');
|
|
149
|
+
return ans;
|
|
150
|
+
}, function (err) { if (undo) undo(); say((err && err.message) || 'That did not save.', true); });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function categoryBody(cat) {
|
|
154
|
+
var d = S.data || {};
|
|
155
|
+
var out = '';
|
|
156
|
+
if (cat.id === 'reach') return reachBody();
|
|
157
|
+
if (cat.id === 'ai' && d.dp) {
|
|
158
|
+
var p = d.dp.provider || '';
|
|
159
|
+
var name = function (x) { return x === 'subq' ? 'SubQ' : x === 'jev' ? 'Jev' : x || 'Not set'; };
|
|
160
|
+
var eff = d.dp.effective || {};
|
|
161
|
+
out += group('Decision models', 'Jev or SubQ, for Work suggestions, done checks, Work search and ⌘K.',
|
|
162
|
+
['jev', 'subq'].map(function (x) {
|
|
163
|
+
var on = p === x;
|
|
164
|
+
return '<button type="button" class="cx-set-row cx-set-choice" data-set="provider" data-provider="' + x + '" aria-pressed="' + on + '"><span class="cx-set-text"><span class="cx-set-name">Use ' + name(x) + '</span><span class="cx-set-desc">' +
|
|
165
|
+
(x === 'jev' ? 'Fast and cheap.' : 'Asks a yes or no question per match.') + '</span></span><span class="value">' + (on ? '<span class="check">✓</span>' : '') + '</span></button>';
|
|
166
|
+
}).join('') +
|
|
167
|
+
row('In use', 'Work suggestions, done checks and search.', value(['session', 'completion', 'search'].map(function (k) { return name(eff[k]); }).filter(function (v, i, a) { return a.indexOf(v) === i; }).join(', '))));
|
|
168
|
+
}
|
|
169
|
+
if (cat.id === 'meetings') {
|
|
170
|
+
if (d.cues) {
|
|
171
|
+
var cap = d.cues.capability || {};
|
|
172
|
+
out += group('Live cues', 'Short prompts on the glasses during a meeting, and their limits.',
|
|
173
|
+
row('Live cues', d.cues.source === 'env' ? 'Set on the Mac by the server environment.' : 'Quiet suggestions on your glasses during meetings.', toggle(d.cues.enabled, 'data-set="cues"' + (d.cues.source === 'env' ? ' disabled' : ''), 'Live cues')) +
|
|
174
|
+
(cap.model ? row('Model', '', value(String(cap.model))) : ''));
|
|
175
|
+
}
|
|
176
|
+
if (d.ff) {
|
|
177
|
+
var imported = d.ff.counts && typeof d.ff.counts.imported === 'number' ? d.ff.counts.imported : null;
|
|
178
|
+
out += group('Fireflies import', 'Bring Fireflies meetings into your library.',
|
|
179
|
+
row('Keep importing', d.ff.keyConfigured ? 'New Fireflies meetings come in on their own.' : 'Add a Fireflies key on the Mac to import.', value(d.ff.keepImporting ? 'On' : 'Off')) +
|
|
180
|
+
(imported != null ? row('Imported', 'Last ' + (d.ff.windowDays || 30) + ' days.', value(String(imported))) : ''));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (cat.id === 'dictation') {
|
|
184
|
+
out += group('Dictionary', 'One dictionary for G2 meetings, Mac recordings and dictation.',
|
|
185
|
+
'<button type="button" class="cx-set-row" data-go="dictation/dictionary"><span class="cx-set-text"><span class="cx-set-name">Open the dictionary</span><span class="cx-set-desc">Words and corrections, shared with the Mac.</span></span><span class="chev"></span></button>');
|
|
186
|
+
}
|
|
187
|
+
if (cat.id === 'work') {
|
|
188
|
+
if (d.mb && d.mb.config) {
|
|
189
|
+
var c = d.mb.config, st = d.mb.status || {};
|
|
190
|
+
var days = (c.days || []).map(function (x) { return DAYS[x] || ''; }).filter(Boolean).join(' ');
|
|
191
|
+
var next = st.nextRunAt ? new Date(st.nextRunAt) : null;
|
|
192
|
+
var last = st.lastRun && st.lastRun.firedAt ? new Date(st.lastRun.firedAt) : null;
|
|
193
|
+
out += group('Morning brief', 'A daily brief in your inbox.',
|
|
194
|
+
row('Morning brief', 'Runs at ' + (c.time || '07:00') + (days ? ' on ' + days : '') + (c.timezone ? ', ' + c.timezone : '') + '.', toggle(c.enabled !== false, 'data-set="brief"', 'Morning brief')) +
|
|
195
|
+
(next && !isNaN(next) ? row('Next run', '', value(next.toLocaleString(undefined, { weekday: 'short', hour: 'numeric', minute: '2-digit' }))) : '') +
|
|
196
|
+
(last && !isNaN(last) ? row('Last run', st.lastRun.status ? st.lastRun.status : '', value(last.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }))) : ''));
|
|
197
|
+
}
|
|
198
|
+
if (d.domains && d.domains.domains) {
|
|
199
|
+
out += group('Task folders', 'The areas your tasks are grouped by.', d.domains.domains.map(function (x) { return row(x.label || x.name, x.name, value(x.abbr || '')); }).join(''));
|
|
200
|
+
}
|
|
201
|
+
if (d.fences && d.fences.fences && d.fences.fences.length) {
|
|
202
|
+
out += group('Paused threads', 'Threads COS stopped writing to. Shows only when one exists.', d.fences.fences.map(function (f) {
|
|
203
|
+
return row(f.title || f.target || 'Thread', [f.provider, f.adapterReason].filter(Boolean).join(' · '), '<button type="button" class="cx-btn quiet" data-set="release" data-target="' + esc(f.target || '') + '">Release</button>');
|
|
204
|
+
}).join(''));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (cat.id === 'system' && d.health) {
|
|
208
|
+
var h = d.health, up = Number(h.uptime_seconds) || 0;
|
|
209
|
+
out += group('Server', 'Status, restart and updates for the COS server.',
|
|
210
|
+
row('Status', '', value(h.status === 'ok' ? 'Running' : String(h.status || 'Unknown'))) +
|
|
211
|
+
row('Version', '', value(h.server_version || '')) +
|
|
212
|
+
(up ? row('Up for', '', value(up >= 86400 ? Math.floor(up / 86400) + ' d ' + Math.floor((up % 86400) / 3600) + ' h' : Math.floor(up / 3600) + ' h ' + Math.floor((up % 3600) / 60) + ' min')) : '') +
|
|
213
|
+
(h.claude ? row('Claude Code', '', value(String(h.claude).replace(/\s*\(.*\)$/, ''))) : '') +
|
|
214
|
+
(h.codex ? row('Codex', '', value(String(h.codex))) : ''));
|
|
215
|
+
}
|
|
216
|
+
return out + (cat.mac.length ? onMac(cat.mac) : '');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function render() {
|
|
220
|
+
if (W.parseRoute().pane !== 'settings') return;
|
|
221
|
+
var host = String(W.state.serverName || 'your Mac').replace(/-/g, ' ');
|
|
222
|
+
pane.innerHTML = '<div class="cx">' +
|
|
223
|
+
'<header class="cx-hero"><span class="cx-glyph"><svg class="icon"><use href="#i-settings"/></svg></span>' +
|
|
224
|
+
'<div class="cx-hero-text"><h1 class="cx-title">Settings</h1><p class="cx-detail-line">COS on ' + esc(host) + '. Keys, permissions and glasses pairing change only on the Mac.</p></div>' +
|
|
225
|
+
'<div class="cx-actions"><button type="button" class="cx-btn quiet" data-set="refresh"><svg class="icon"><use href="#i-refresh"/></svg>Refresh</button></div></header>' +
|
|
226
|
+
browserGroup() +
|
|
227
|
+
(S.state !== 'ready' ? '<div class="cx-list" style="margin-top:18px">' + W.skeletonRows(4) + '</div>' :
|
|
228
|
+
CATEGORIES.filter(function (cat) { return !cat.p2 || p2On(); }).map(function (cat) {
|
|
229
|
+
return '<section class="cx-set-cat"><h2 class="cx-col-title">' + esc(cat.title) + '</h2><p class="cx-set-blurb">' + esc(cat.summary) + '</p>' + categoryBody(cat) + '</section>';
|
|
230
|
+
}).join('')) +
|
|
231
|
+
'<p class="status-line" id="settings-status"></p></div>';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function say(text, isError) { var el = $('settings-status'); if (el) { el.textContent = text; el.classList.toggle('error', !!isError); } }
|
|
235
|
+
|
|
236
|
+
pane.addEventListener('click', function (e) {
|
|
237
|
+
var go = e.target.closest('[data-go]');
|
|
238
|
+
if (go) { var parts = go.getAttribute('data-go').split('/'); return W.go(parts[0], parts.slice(1)); }
|
|
239
|
+
var t = e.target.closest('[data-set]');
|
|
240
|
+
if (!t || t.disabled) return;
|
|
241
|
+
var act = t.getAttribute('data-set');
|
|
242
|
+
if (act === 'refresh') { S.data = null; S.state = 'idle'; load(true); return render(); }
|
|
243
|
+
if (act === 'provider') {
|
|
244
|
+
api('/api/decision-provider', { method: 'PATCH', body: { provider: t.getAttribute('data-provider') } })
|
|
245
|
+
.then(function () { W.toast('Saved'); S.data = null; S.state = 'idle'; load(true); }, function (err) { say(err.message, true); });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (act === 'cues') {
|
|
249
|
+
var on = t.getAttribute('aria-checked') === 'true';
|
|
250
|
+
t.setAttribute('aria-checked', String(!on));
|
|
251
|
+
api('/api/live-cues/v2/settings', { method: 'POST', body: { enabled: !on } })
|
|
252
|
+
.then(function () { W.toast(!on ? 'Live cues on' : 'Live cues off'); }, function (err) { t.setAttribute('aria-checked', String(on)); say(err.message, true); });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (act === 'brief') {
|
|
256
|
+
var bon = t.getAttribute('aria-checked') === 'true';
|
|
257
|
+
t.setAttribute('aria-checked', String(!bon));
|
|
258
|
+
api('/api/morning-brief', { method: 'PUT', body: { enabled: !bon } })
|
|
259
|
+
.then(function () { W.toast(!bon ? 'Morning brief on' : 'Morning brief paused'); }, function (err) { t.setAttribute('aria-checked', String(bon)); say(err.message, true); });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (act === 'p2') {
|
|
263
|
+
var path = t.getAttribute('data-p2'), was = t.getAttribute('aria-checked') === 'true';
|
|
264
|
+
t.setAttribute('aria-checked', String(!was));
|
|
265
|
+
p2Save(path, !was, function () { t.setAttribute('aria-checked', String(was)); });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (act === 'release') {
|
|
269
|
+
if (t.getAttribute('data-armed') !== '1') { t.setAttribute('data-armed', '1'); t.textContent = 'Tap again to release'; return; }
|
|
270
|
+
// The server fails closed without confirm; the second tap is the confirmation.
|
|
271
|
+
api('/api/agent-sessions/fences/release', { method: 'POST', body: { target: t.getAttribute('data-target'), confirm: true } })
|
|
272
|
+
.then(function () { W.toast('Released'); S.data = null; S.state = 'idle'; load(true); }, function (err) { say(err.message, true); });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Number, time, day and steer fields save when they change (a number is clamped by the server, then shown as saved).
|
|
277
|
+
pane.addEventListener('change', function (e) {
|
|
278
|
+
var f = e.target && e.target.closest ? e.target.closest('[data-p2]') : null;
|
|
279
|
+
if (!f || f.getAttribute('data-set') === 'p2' || !S.data || !S.data.p2) return;
|
|
280
|
+
var path = f.getAttribute('data-p2'), raw = f.value;
|
|
281
|
+
var v = f.type === 'number' || f.tagName === 'SELECT' ? Number(raw) : raw;
|
|
282
|
+
if (typeof v === 'number' && !isFinite(v)) return;
|
|
283
|
+
p2Save(path, v).then(function (ans) {
|
|
284
|
+
var saved = ans && ans.settings ? p2Get(path) : undefined;
|
|
285
|
+
if (saved !== undefined && String(saved) !== String(f.value)) f.value = String(saved);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
W.registerPane('settings', {
|
|
290
|
+
load: function () { load(false); render(); }
|
|
291
|
+
});
|
|
292
|
+
})();
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/* COS Control Web: speakers pane styles, on top of control.css. Phone first (closed Duo, about 382 wide), then the
|
|
2
|
+
open Duo (about 890) and the desktop from 1100. No fixed or bottom layers: cards sit under the row they concern. */
|
|
3
|
+
|
|
4
|
+
#pane-speakers { --sp-line: color-mix(in srgb, var(--ink) 13%, transparent); --sp-danger: var(--rec); }
|
|
5
|
+
#pane-speakers .sp-grow { flex: 1 1 auto; }
|
|
6
|
+
#pane-speakers .sp-muted { color: var(--sub); }
|
|
7
|
+
#pane-speakers .sp-tert { color: var(--tertiary); font-size: 12.5px; line-height: 1.4; margin: 4px 0 0; }
|
|
8
|
+
#pane-speakers .sp-small { font-size: 12.5px; line-height: 1.4; }
|
|
9
|
+
#pane-speakers .sp-strong { font-weight: 600; }
|
|
10
|
+
#pane-speakers .sp-accent { color: var(--accent); font-size: 12.5px; }
|
|
11
|
+
#pane-speakers .sp-ink { color: var(--ink); font-weight: 600; }
|
|
12
|
+
#pane-speakers .sp-err { color: var(--rec); font-size: 13px; margin: 6px 0 0; }
|
|
13
|
+
#pane-speakers .sp-mono { font: 400 11.5px/1.45 var(--mono); color: var(--sub); margin: 3px 0 0; overflow-wrap: anywhere; }
|
|
14
|
+
#pane-speakers .sp-mono-s { font: 400 10.5px/1.4 var(--mono); color: var(--sub); font-variant-numeric: tabular-nums; }
|
|
15
|
+
#pane-speakers .sp-caps { font: 600 9.5px/1.3 var(--mono); letter-spacing: 0.07em; color: var(--tertiary); }
|
|
16
|
+
#pane-speakers .sp-orange { color: #c26b1e; }
|
|
17
|
+
#pane-speakers .sp-green { color: var(--green); }
|
|
18
|
+
#pane-speakers .sp-amber-t { color: var(--amber); }
|
|
19
|
+
#pane-speakers .sp-one { -webkit-line-clamp: 1; }
|
|
20
|
+
#pane-speakers .sp-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
21
|
+
#pane-speakers p { margin: 6px 0 0; }
|
|
22
|
+
|
|
23
|
+
/* Text buttons and the destructive pair. */
|
|
24
|
+
#pane-speakers .sp-text { min-height: 32px; padding: 0 4px; color: var(--accent); font: 600 13.5px/1 var(--body); background: none; border: 0; }
|
|
25
|
+
#pane-speakers .sp-text:disabled { opacity: 0.45; }
|
|
26
|
+
#pane-speakers .sp-danger-text { color: var(--sp-danger); }
|
|
27
|
+
#pane-speakers .cx-btn.sp-danger { color: var(--sp-danger); border-color: color-mix(in srgb, var(--sp-danger) 45%, transparent); }
|
|
28
|
+
#pane-speakers .cx-btn .icon { flex: none; }
|
|
29
|
+
|
|
30
|
+
/* A quiet working ring; still under reduced motion. */
|
|
31
|
+
.sp-spin { display: inline-block; width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--sp-line); border-top-color: var(--accent); animation: sp-turn 0.8s linear infinite; vertical-align: -2px; margin-right: 6px; flex: none; }
|
|
32
|
+
@keyframes sp-turn { to { transform: rotate(360deg); } }
|
|
33
|
+
@media (prefers-reduced-motion: reduce) { .sp-spin { animation: none; border-top-color: var(--sp-line); border-right-color: var(--accent); } }
|
|
34
|
+
#pane-speakers .sp-loading { display: flex; align-items: center; justify-content: center; padding: 28px 0; font-size: 13.5px; color: var(--sub); }
|
|
35
|
+
|
|
36
|
+
/* ── toolbar ─────────────────────────────────────────── */
|
|
37
|
+
#pane-speakers .sp-controls { justify-content: flex-start; min-height: 36px; gap: 10px 22px; }
|
|
38
|
+
#pane-speakers .sp-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; flex: 1 1 360px; }
|
|
39
|
+
#pane-speakers .sp-check { margin-left: 0; }
|
|
40
|
+
.sp-select { position: relative; display: inline-flex; align-items: center; }
|
|
41
|
+
.sp-select select.field { min-height: 32px; width: auto; padding: 0 30px 0 11px; font-size: 13px; border-radius: 8px; background-position: calc(100% - 16px) 13px, calc(100% - 11px) 13px; }
|
|
42
|
+
#pane-speakers .sp-search { flex: 1 1 200px; max-width: 280px; }
|
|
43
|
+
#pane-speakers .sp-search .field { min-height: 32px; font-size: 13.5px; }
|
|
44
|
+
|
|
45
|
+
/* ── Meetings to review ──────────────────────────────── */
|
|
46
|
+
#pane-speakers .sp-mrow { grid-template-columns: 32px minmax(0, 1fr) auto 12px; align-items: center; }
|
|
47
|
+
#pane-speakers .sp-mrow .chev { margin-top: 0; }
|
|
48
|
+
#pane-speakers .sp-counts { display: block; margin-top: 2px; font-size: 12px; color: var(--tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
49
|
+
#pane-speakers .sp-mrow .sp-mono { display: block; margin-top: 3px; font-size: 11px; }
|
|
50
|
+
.sp-pills { display: inline-flex; flex-wrap: wrap; gap: 5px; }
|
|
51
|
+
.sp-pills-in { margin-top: 6px; }
|
|
52
|
+
.sp-pills-side:empty { display: none; }
|
|
53
|
+
.sp-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px; padding: 34px 16px; color: var(--sub); font-size: 14px; line-height: 1.45; }
|
|
54
|
+
.sp-empty p { max-width: 400px; margin: 0; }
|
|
55
|
+
.sp-empty .cx-glyph { color: var(--tint, var(--accent)); }
|
|
56
|
+
|
|
57
|
+
/* ── Samples to review ───────────────────────────────── */
|
|
58
|
+
.sp-card { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; padding: 14px 16px; max-width: 980px; }
|
|
59
|
+
.sp-card-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
|
|
60
|
+
.sp-add-glyph { width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; background: var(--gold-bg); color: var(--accent); flex: none; }
|
|
61
|
+
.sp-add-glyph .icon { width: 17px; height: 17px; }
|
|
62
|
+
.sp-card-title { font: 500 18px/1.2 var(--display); margin: 0; color: var(--ink); }
|
|
63
|
+
.sp-notice { display: flex; gap: 8px; align-items: flex-start; padding: 8px 10px; border-radius: 8px; background: var(--qopen); font-size: 13px; color: var(--ink); margin: 8px 0; }
|
|
64
|
+
.sp-i { flex: none; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--accent); color: var(--accent); display: grid; place-items: center; font: 700 10px/1 var(--body); margin-top: 1px; }
|
|
65
|
+
.sp-status { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 8px 0; }
|
|
66
|
+
.sp-lead { font-size: 13.5px; color: var(--sub); line-height: 1.45; }
|
|
67
|
+
.sp-held-list { margin-top: 8px; }
|
|
68
|
+
.sp-held-row { display: grid; grid-template-columns: 136px minmax(0, 1fr) auto; gap: 8px 14px; align-items: center; padding: 12px 0; border-top: 1px solid var(--sp-line); }
|
|
69
|
+
.sp-held-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; font-size: 13px; }
|
|
70
|
+
.sp-held-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
|
71
|
+
.sp-sug { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
|
+
.sp-sug b { color: var(--accent); font-weight: 600; }
|
|
73
|
+
.sp-held-acts { justify-content: flex-end; }
|
|
74
|
+
.sp-held-name { display: flex; flex-direction: column; gap: 6px; }
|
|
75
|
+
.sp-hint { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--sub); }
|
|
76
|
+
.sp-hint:empty { display: none; }
|
|
77
|
+
.sp-chips { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
|
78
|
+
.sp-chip { height: 28px; font-size: 12.5px; }
|
|
79
|
+
#pane-speakers .sp-field { min-height: 34px; padding: 0 10px; font-size: 14px; border-radius: 8px; width: 210px; max-width: 100%; }
|
|
80
|
+
.sp-more { margin: 12px auto 2px; display: flex; }
|
|
81
|
+
.sp-listen { display: flex; align-items: center; gap: 6px; }
|
|
82
|
+
.sp-play { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--btn-border); background: var(--btn-bg); color: var(--ink); display: grid; place-items: center; flex: none; }
|
|
83
|
+
.sp-play.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
|
84
|
+
.sp-step { width: 22px; height: 22px; border-radius: 6px; color: var(--sub); font: 600 17px/1 var(--body); display: grid; place-items: center; }
|
|
85
|
+
.sp-step:disabled { opacity: 0.35; }
|
|
86
|
+
.sp-pos { font: 400 10.5px/1 var(--mono); color: var(--sub); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
87
|
+
.sp-play-glyph { width: 0; height: 0; border-left: 8px solid currentColor; border-top: 5px solid transparent; border-bottom: 5px solid transparent; margin-left: 2px; }
|
|
88
|
+
.sp-stop-glyph { width: 8px; height: 8px; background: currentColor; border-radius: 1.5px; }
|
|
89
|
+
.sp-confirm { border: 1px solid var(--panel-border); border-radius: 10px; padding: 12px; margin: 8px 0; background: var(--qopen); }
|
|
90
|
+
.sp-sheet { border-top: 1px solid var(--sp-line); margin-top: 6px; padding-top: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
91
|
+
.sp-sheet p { margin: 0; }
|
|
92
|
+
.sp-sheet-title { font: 500 22px/1.2 var(--display); margin: 2px 0 2px; color: var(--ink); }
|
|
93
|
+
.sp-sheet-foot { border-top: 1px solid var(--sp-line); padding-top: 10px; margin-top: 4px; }
|
|
94
|
+
.sp-toggle { margin: 0; font-size: 13.5px; align-items: flex-start; }
|
|
95
|
+
.sp-toggle input { margin-top: 1px; flex: none; }
|
|
96
|
+
.sp-nm, .sp-batch { border: 1px solid var(--panel-border); border-radius: 9px; padding: 11px 13px; display: flex; flex-direction: column; gap: 5px; background: var(--panel); }
|
|
97
|
+
.sp-batch { background: var(--qopen); }
|
|
98
|
+
.sp-nm-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 13px; }
|
|
99
|
+
.sp-fold summary { cursor: pointer; font-size: 13px; font-weight: 600; padding: 6px 0; }
|
|
100
|
+
.sp-fold[open] summary { margin-bottom: 4px; }
|
|
101
|
+
.sp-outcome { padding: 6px 0; border-top: 1px solid var(--sp-line); }
|
|
102
|
+
|
|
103
|
+
/* ── Voices (the directory) ──────────────────────────── */
|
|
104
|
+
.sp-dnotice { padding: 9px 12px; border-radius: 9px; font-size: 13px; color: var(--sub); background: color-mix(in srgb, var(--accent) 7%, transparent); margin: 0 0 8px; }
|
|
105
|
+
.sp-dnotice.stale { background: var(--amber-bg); }
|
|
106
|
+
.sp-dir { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 14px; overflow: hidden; }
|
|
107
|
+
.sp-dhead, .sp-vrow { display: grid; grid-template-columns: 38px minmax(0, 1fr) 64px 86px 70px 70px 88px 12px; gap: 12px; align-items: center; padding: 0 14px; }
|
|
108
|
+
.sp-dhead { padding-top: 10px; padding-bottom: 10px; font: 600 9px/1 var(--mono); letter-spacing: 0.06em; color: var(--tertiary); border-bottom: 1px solid var(--sp-line); }
|
|
109
|
+
.sp-dhead .sp-vname { grid-column: 1 / 3; }
|
|
110
|
+
.sp-dhead > span:not(.sp-vname) { text-align: right; }
|
|
111
|
+
.sp-vrow { width: 100%; padding-top: 12px; padding-bottom: 12px; text-align: left; color: inherit; border-top: 1px solid var(--sp-line); }
|
|
112
|
+
.sp-dhead + .sp-vrow { border-top: 0; }
|
|
113
|
+
.sp-vrow:hover { background: var(--qopen); }
|
|
114
|
+
.sp-vglyph { width: 38px; height: 38px; }
|
|
115
|
+
.sp-vname { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
116
|
+
.sp-vtitle { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
117
|
+
.sp-vtitle b { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
118
|
+
.sp-vsrc { font-size: 12px; color: var(--sub); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
119
|
+
.sp-vstack { display: none; font: 400 11px/1.4 var(--mono); color: var(--sub); }
|
|
120
|
+
.sp-metric { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; min-width: 0; }
|
|
121
|
+
.sp-metric b { font: 600 12px/1.2 var(--mono); color: var(--ink); white-space: nowrap; }
|
|
122
|
+
.sp-metric small { font-size: 10px; color: var(--sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
123
|
+
.sp-nomatch { padding: 24px 16px; text-align: center; color: var(--sub); font-size: 13.5px; }
|
|
124
|
+
.sp-voice { max-width: 980px; }
|
|
125
|
+
.sp-vhead { display: flex; gap: 14px; align-items: flex-start; padding: 14px 0 6px; }
|
|
126
|
+
.sp-vhead .cx-glyph { width: 46px; height: 46px; }
|
|
127
|
+
.sp-vh-title { font: 600 22px/1.2 var(--body); margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; overflow-wrap: anywhere; }
|
|
128
|
+
.sp-mcards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin: 14px 0 12px; }
|
|
129
|
+
.sp-mcard { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; padding: 13px; min-height: 76px; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
|
130
|
+
.sp-mcard b { font: 600 19px/1.1 var(--body); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
131
|
+
.sp-mcard small { font-size: 11px; color: var(--sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
132
|
+
.sp-icards { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 18px; }
|
|
133
|
+
.sp-icard { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 13px; padding: 15px; display: flex; flex-direction: column; gap: 6px; font-size: 12.5px; color: var(--sub); min-height: 94px; }
|
|
134
|
+
.sp-icard b { font-size: 14px; color: var(--ink); font-weight: 600; }
|
|
135
|
+
.sp-warnline { color: var(--ink); font-size: 12px; margin-top: 2px; }
|
|
136
|
+
.sp-hist-label { display: block; margin: 0 0 8px; letter-spacing: 0.11em; color: var(--sub); }
|
|
137
|
+
#pane-speakers .sp-arow { grid-template-columns: 32px minmax(0, 1fr) auto 12px; align-items: center; }
|
|
138
|
+
#pane-speakers .sp-arow .chev { margin-top: 0; }
|
|
139
|
+
#pane-speakers .sp-arow .sp-mono { display: block; font-size: 11px; }
|
|
140
|
+
.sp-amber { color: var(--amber); background: var(--amber-bg); }
|
|
141
|
+
.sp-match { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
|
|
142
|
+
.sp-match b { font: 600 12px/1 var(--mono); }
|
|
143
|
+
.sp-match small { font-size: 10.5px; color: var(--sub); }
|
|
144
|
+
|
|
145
|
+
/* ── the review ──────────────────────────────────────── */
|
|
146
|
+
.sp-review { max-width: 980px; }
|
|
147
|
+
.sp-rhead { align-items: flex-start; }
|
|
148
|
+
.sp-rtitle { min-width: 0; flex: 1 1 320px; }
|
|
149
|
+
.sp-display { font: 500 22px/1.2 var(--display) !important; overflow-wrap: anywhere; }
|
|
150
|
+
.sp-racts { justify-content: flex-end; }
|
|
151
|
+
.sp-policy { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; padding: 10px 14px 12px; margin-bottom: 12px; }
|
|
152
|
+
.sp-policy .field { min-height: 32px; width: auto; font-size: 13px; padding: 0 30px 0 10px; border-radius: 8px; background-position: calc(100% - 16px) 13px, calc(100% - 11px) 13px; }
|
|
153
|
+
.sp-tl { padding: 4px 0 14px; }
|
|
154
|
+
.sp-tl-label { display: block; letter-spacing: 0.14em; margin-bottom: 6px; }
|
|
155
|
+
.sp-bar { display: flex; height: 34px; border-radius: 3px; overflow: hidden; touch-action: pan-y; cursor: crosshair; }
|
|
156
|
+
.sp-bar span { flex: 1 1 0; min-width: 0; }
|
|
157
|
+
.sp-bar span.dim { opacity: 0.4; }
|
|
158
|
+
.sp-tl-read { min-height: 20px; margin-top: 5px; font-size: 13px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
|
|
159
|
+
.sp-legend { display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 3px 8px; margin-top: 4px; }
|
|
160
|
+
.sp-leg { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--sub); text-align: left; min-height: 24px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
161
|
+
.sp-leg.dim { color: var(--tertiary); }
|
|
162
|
+
.sp-leg i { width: 9px; height: 9px; border-radius: 2px; flex: none; }
|
|
163
|
+
.sp-pad { padding: 0 0 12px; }
|
|
164
|
+
.sp-sbar { border-top: 1px solid var(--sp-line); padding: 10px 0; }
|
|
165
|
+
.sp-voices { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 14px; overflow: hidden; margin-bottom: 14px; }
|
|
166
|
+
.sp-voice-row { padding: 10px 16px; display: flex; flex-direction: column; gap: 6px; }
|
|
167
|
+
.sp-voices > .sp-voice-row + .sp-voice-row, .sp-voices > .sp-zone + .sp-voice-row, .sp-voices > .sp-fold { border-top: 1px solid var(--sp-line); }
|
|
168
|
+
.sp-vr-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
|
|
169
|
+
.sp-vr-head b { font-size: 14.5px; font-weight: 600; overflow-wrap: anywhere; }
|
|
170
|
+
.sp-dimname { color: var(--sub); }
|
|
171
|
+
.sp-you { font: 400 10px/1 var(--mono); padding: 2px 6px; border-radius: 8px; background: var(--green-bg); color: var(--green); }
|
|
172
|
+
.sp-ramp { display: inline-flex; align-items: center; gap: 5px; }
|
|
173
|
+
.sp-ramp-bars { display: inline-flex; gap: 1.5px; }
|
|
174
|
+
.sp-ramp-bars i { width: 6px; height: 7px; border-radius: 1px; background: var(--sp-line); }
|
|
175
|
+
.sp-ramp-bars i.lit { background: var(--amber); }
|
|
176
|
+
.sp-phrases { display: flex; flex-direction: column; gap: 4px; padding-left: 9px; border-left: 2px solid var(--sp-line); }
|
|
177
|
+
.sp-phrases.loose { border-left-style: dashed; }
|
|
178
|
+
.sp-phrase { display: flex; align-items: center; gap: 8px; }
|
|
179
|
+
.sp-ptext { flex: 1 1 auto; font-size: 13.5px; line-height: 1.4; color: var(--ink); overflow-wrap: anywhere; }
|
|
180
|
+
.sp-pplay { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; color: var(--sub); flex: none; }
|
|
181
|
+
.sp-pplay .sp-play-glyph { border-left-width: 7px; border-top-width: 4.5px; border-bottom-width: 4.5px; }
|
|
182
|
+
.sp-pplay.on { color: var(--accent); }
|
|
183
|
+
.sp-pplay-space { width: 24px; flex: none; }
|
|
184
|
+
.sp-closest { display: flex; flex-direction: column; gap: 2px; font-size: 12.5px; color: var(--sub); }
|
|
185
|
+
.sp-closest .sp-tert { margin: 0; font-size: 12px; }
|
|
186
|
+
.sp-naming { display: flex; flex-direction: column; gap: 6px; }
|
|
187
|
+
.sp-naming .sp-field { width: 100%; max-width: 360px; }
|
|
188
|
+
.sp-scope { gap: 16px; }
|
|
189
|
+
.sp-opts { display: flex; flex-direction: column; gap: 4px; max-width: 360px; }
|
|
190
|
+
.sp-opts:empty { display: none; }
|
|
191
|
+
.sp-opt { display: flex; justify-content: space-between; align-items: center; gap: 10px; width: 100%; min-height: 30px; padding: 3px 8px; border-radius: 6px; background: var(--qopen); border: 1px solid var(--panel-border); font-size: 13.5px; color: var(--ink); text-align: left; }
|
|
192
|
+
.sp-opt:disabled { opacity: 0.55; }
|
|
193
|
+
.sp-vacts .cx-btn { height: 30px; font-size: 13px; }
|
|
194
|
+
.sp-clip { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
|
|
195
|
+
.sp-suggest { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; font-size: 13px; }
|
|
196
|
+
.sp-suggest .sp-strong { flex: 1 1 160px; }
|
|
197
|
+
.sp-merged .sp-mhead { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; color: inherit; min-height: 28px; }
|
|
198
|
+
.sp-merged .sp-mhead b { font-size: 14.5px; font-weight: 600; }
|
|
199
|
+
.sp-disc { width: 0; height: 0; border-left: 6px solid var(--sub); border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform 0.15s; flex: none; }
|
|
200
|
+
.sp-disc.open { transform: rotate(90deg); }
|
|
201
|
+
.sp-members { margin-left: 4px; border-left: 1px solid var(--sp-line); }
|
|
202
|
+
.sp-members .sp-voice-row { padding-left: 12px; padding-right: 0; }
|
|
203
|
+
.sp-members .sp-voice-row + .sp-voice-row { border-top: 1px solid var(--sp-line); }
|
|
204
|
+
.sp-short { padding: 6px 16px; }
|
|
205
|
+
.sp-short .sp-voice-row { padding-left: 0; padding-right: 0; border-top: 1px solid var(--sp-line); }
|
|
206
|
+
.sp-zone { padding: 0 16px 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
207
|
+
.sp-review > .sp-zone { padding: 0 0 12px; }
|
|
208
|
+
.sp-banner { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 9px 11px; border-radius: 9px; background: var(--amber-bg); font-size: 13px; color: var(--ink); }
|
|
209
|
+
.sp-progress { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--sub); }
|
|
210
|
+
.sp-ccard { border: 1px solid var(--panel-border); border-radius: 11px; padding: 12px 14px; background: var(--qopen); display: flex; flex-direction: column; gap: 4px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 14%, transparent); }
|
|
211
|
+
.sp-ccard > b { font-size: 14px; }
|
|
212
|
+
.sp-ccard p { margin: 2px 0; }
|
|
213
|
+
.sp-ccard .sp-inline { margin-top: 6px; }
|
|
214
|
+
.sp-writeup { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 20px; }
|
|
215
|
+
.sp-sec-body { white-space: pre-wrap; font-size: 13.5px; line-height: 1.5; color: var(--ink); margin-top: 3px; overflow-wrap: anywhere; }
|
|
216
|
+
.sp-writeup-note { font-size: 13px; padding: 4px 2px 18px; }
|
|
217
|
+
|
|
218
|
+
/* ── phone (closed Duo) ──────────────────────────────── */
|
|
219
|
+
@media (max-width: 599px) {
|
|
220
|
+
.sp-pills-side { display: none !important; }
|
|
221
|
+
#pane-speakers .sp-mrow { grid-template-columns: 32px minmax(0, 1fr) 12px; }
|
|
222
|
+
#pane-speakers .sp-tools { width: 100%; }
|
|
223
|
+
#pane-speakers .sp-search { max-width: none; flex-basis: 100%; }
|
|
224
|
+
#pane-speakers .sp-search .field { min-height: 40px; font-size: 16px; }
|
|
225
|
+
.sp-select select.field { min-height: 38px; font-size: 14px; background-position: calc(100% - 16px) 16px, calc(100% - 11px) 16px; }
|
|
226
|
+
#pane-speakers .sp-text { min-height: 36px; font-size: 14.5px; }
|
|
227
|
+
#pane-speakers .sp-field { min-height: 40px; font-size: 16px; width: 100%; }
|
|
228
|
+
#pane-speakers .cx-switch { gap: 12px; flex-wrap: wrap; }
|
|
229
|
+
.sp-card { padding: 12px; border-radius: 12px; }
|
|
230
|
+
.sp-held-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
|
231
|
+
.sp-held-acts { justify-content: flex-start; }
|
|
232
|
+
.sp-play { width: 36px; height: 36px; }
|
|
233
|
+
.sp-step { width: 36px; height: 36px; font-size: 20px; }
|
|
234
|
+
.sp-dhead { display: none; }
|
|
235
|
+
.sp-vrow { grid-template-columns: 38px minmax(0, 1fr) 12px; }
|
|
236
|
+
.sp-vrow .sp-metric { display: none; }
|
|
237
|
+
.sp-vstack { display: block; }
|
|
238
|
+
.sp-mcards { grid-template-columns: 1fr 1fr; }
|
|
239
|
+
.sp-icards { grid-template-columns: 1fr; }
|
|
240
|
+
.sp-voice-row { padding: 10px 12px; }
|
|
241
|
+
.sp-zone { padding: 0 12px 12px; }
|
|
242
|
+
.sp-pplay { width: 36px; height: 36px; }
|
|
243
|
+
.sp-pplay-space { width: 36px; }
|
|
244
|
+
.sp-opt { min-height: 40px; font-size: 15px; }
|
|
245
|
+
.sp-vacts .cx-btn { height: 36px; font-size: 14px; }
|
|
246
|
+
.sp-leg { min-height: 30px; }
|
|
247
|
+
.sp-racts { justify-content: flex-start; }
|
|
248
|
+
.sp-rhead .cx-btn { height: 36px; }
|
|
249
|
+
.sp-policy .field { min-height: 38px; font-size: 14px; background-position: calc(100% - 16px) 16px, calc(100% - 11px) 16px; }
|
|
250
|
+
}
|
|
251
|
+
@media (min-width: 600px) {
|
|
252
|
+
.sp-pills-in { display: none !important; }
|
|
253
|
+
}
|
|
254
|
+
/* The open Duo has room for the table, but not for every metric column at full width. */
|
|
255
|
+
@media (min-width: 600px) and (max-width: 899px) {
|
|
256
|
+
.sp-dhead, .sp-vrow { grid-template-columns: 34px minmax(0, 1fr) 54px 78px 58px 62px 78px 10px; gap: 8px; padding-left: 10px; padding-right: 10px; }
|
|
257
|
+
.sp-dhead .c-count, .sp-dhead .c-samples, .sp-dhead .c-conf, .sp-dhead .c-seen { font-size: 8.5px; }
|
|
258
|
+
}
|
|
259
|
+
.sp-voice-row > .cx-btn, .sp-voice-row > .sp-text, .sp-naming > .sp-text { align-self: flex-start; }
|
|
260
|
+
@media (max-width: 599px) {
|
|
261
|
+
#pane-speakers .sp-controls > .cx-switch { flex-wrap: nowrap; gap: 12px; max-width: 100%; overflow-x: auto; scrollbar-width: none; }
|
|
262
|
+
#pane-speakers .sp-controls > .cx-switch::-webkit-scrollbar { display: none; }
|
|
263
|
+
#pane-speakers .sp-controls > .cx-switch .cx-switch-label { display: none; }
|
|
264
|
+
#pane-speakers .sp-controls > .cx-switch button { font-size: 14px; white-space: nowrap; flex: none; }
|
|
265
|
+
.sp-rtitle { flex-basis: 100%; }
|
|
266
|
+
#pane-speakers .sp-rtitle .sp-mono { font-size: 10.5px; }
|
|
267
|
+
}
|
|
268
|
+
.sp-ccard > p, .sp-pad p, .sp-sheet > p, .sp-confirm p { font-size: 13px; line-height: 1.45; }
|
|
269
|
+
.sp-parts { margin: 0 0 12px; }
|