@gotcos/glasses-server 6.99.29 → 7.0.7
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/.env.example +18 -0
- package/CHANGELOG.md +475 -0
- package/README.md +3 -1
- package/managed-runtime-contract.json +3 -0
- package/package.json +1 -1
- package/server/index.ts +29 -7
- package/server/lib/agent-session-output-images.ts +627 -49
- package/server/lib/agent-session-store.ts +24 -11
- package/server/lib/agent-session-tool-images.ts +224 -0
- package/server/lib/agent-session-turns.ts +19 -2
- package/server/lib/api-auth.ts +6 -0
- package/server/lib/ask-work-plan.ts +3 -1
- package/server/lib/calendar-source.ts +491 -0
- package/server/lib/codex-bridge.ts +4 -1
- package/server/lib/codex-run-ledger.ts +29 -0
- package/server/lib/context-builder.ts +2 -1
- package/server/lib/drop-p2-runtime.ts +1 -1
- package/server/lib/drop-store.ts +10 -4
- package/server/lib/learning-needs-you.ts +24 -7
- package/server/lib/live-events.ts +533 -0
- package/server/lib/local-path-redact.ts +272 -0
- package/server/lib/meeting-ask-orientation.ts +40 -6
- package/server/lib/meeting-ask-runner.ts +30 -10
- package/server/lib/morning-brief-runtime.ts +4 -2
- package/server/lib/native-mac-caller.ts +29 -0
- package/server/lib/ping-engine.ts +1 -1
- package/server/lib/ping-sources.ts +11 -6
- package/server/lib/preview-content.ts +172 -0
- package/server/lib/preview-error-code.ts +10 -0
- package/server/lib/preview-folders-setting.ts +133 -0
- package/server/lib/preview-log-throttle.ts +18 -0
- package/server/lib/preview-thumbs.ts +139 -0
- package/server/lib/secret-path-guard.ts +80 -0
- package/server/lib/session-files-setting.ts +88 -0
- package/server/lib/session-links.ts +280 -0
- package/server/lib/thread-turn-queue.ts +10 -0
- package/server/lib/today-brief.ts +119 -28
- package/server/lib/web-memories-bridge.ts +3 -1
- package/server/lib/work-activity.ts +56 -6
- package/server/lib/work-session-activity.ts +260 -0
- package/server/lib/your-move-count.ts +9 -3
- package/server/routes/agent-session-bindings.ts +13 -0
- package/server/routes/agent-session-stream.ts +16 -0
- package/server/routes/agent-sessions.ts +146 -11
- package/server/routes/ask-work-plan.ts +4 -1
- package/server/routes/claude-sessions.ts +5 -1
- package/server/routes/health.ts +35 -0
- package/server/routes/live-events.ts +329 -0
- package/server/routes/permission-broker.ts +69 -5
- package/server/routes/session-preview.ts +200 -0
- package/server/routes/thread-turn-queue.ts +207 -3
- package/server/routes/today-brief.ts +44 -9
- package/server/routes/welcome-context.ts +23 -3
- package/server/routes/work-board.ts +9 -2
- package/server/web-control/app.css +79 -9
- package/server/web-control/app.js +1543 -65
- package/server/web-control/control.css +37 -6
- package/server/web-control/index.html +8 -2
- package/server/web-control/memory-review.js +100 -0
- package/server/web-control/panes/cos-chat.css +51 -6
- package/server/web-control/panes/cos-chat.js +268 -16
- package/server/web-control/panes/meetings.css +23 -4
- package/server/web-control/panes/meetings.js +25 -9
- package/server/web-control/panes/memories.css +18 -3
- package/server/web-control/panes/memories.js +130 -50
- package/server/web-control/panes/messages.js +21 -2
- package/server/web-control/panes/preview.js +318 -0
- package/server/web-control/panes/questions.css +20 -4
- package/server/web-control/panes/questions.js +127 -26
- package/server/web-control/panes/reader-media.js +409 -23
- package/server/web-control/panes/sessions.css +50 -10
- package/server/web-control/panes/settings.js +96 -5
- package/server/web-control/panes/speakers.css +3 -5
- package/server/web-control/panes/speakers.js +7 -4
- package/server/web-control/panes/threads.js +3 -2
- package/server/web-control/panes/today-actions.css +49 -18
- package/server/web-control/panes/today-actions.js +217 -50
- package/server/web-control/panes/work.css +11 -1
- package/server/web-control/panes/work.js +10 -2
- package/server/web-control/shell.js +20 -14
|
@@ -47,9 +47,11 @@
|
|
|
47
47
|
stopped: false,
|
|
48
48
|
inflight: false, // a poll is on the wire
|
|
49
49
|
again: false, // something asked for a poll while one was out: one more as soon as it lands
|
|
50
|
-
lastPollAt: 0
|
|
50
|
+
lastPollAt: 0, // when the last poll went out (Date.now)
|
|
51
|
+
load: 'unread' // W13: 'unread' (no read yet), 'ok' (the last read was the server's answer) or 'failed' (it was not)
|
|
51
52
|
};
|
|
52
53
|
var DEFAULT_POLL_MS = 10000;
|
|
54
|
+
var POLL_TIMEOUT_MS = 15000;
|
|
53
55
|
// 6.99.27 (Miles 11/07 09:29, "a big delay to see the questions on the homepage"): while this tab is visible on
|
|
54
56
|
// Today it reads every 3 s, whatever the server's idle pace (10 s). Before, a question parked just after an idle read
|
|
55
57
|
// waited up to 10 s plus the round trip for the next one. Hidden, the page reads nothing (visibilitychange below).
|
|
@@ -133,6 +135,19 @@
|
|
|
133
135
|
// QA 6.99.27 (N4): the read asked for while this one was out goes through nudge, so it waits out the 1 s gap too.
|
|
134
136
|
if (Q.again) { Q.again = false; nudge(); }
|
|
135
137
|
}
|
|
138
|
+
// W13 (QA week of 11/01-11/08): whether the last read was the server's answer. A failed read leaves no cards, and Today's
|
|
139
|
+
// sheet read "no card" as "at the Mac's prompt" (a Claude session) with no way to read again. Each change tells the
|
|
140
|
+
// mounted sheets (their onChange), never on a read that changed nothing.
|
|
141
|
+
function noteLoad(state) {
|
|
142
|
+
if (Q.load === state) return;
|
|
143
|
+
Q.load = state;
|
|
144
|
+
tellMounts();
|
|
145
|
+
}
|
|
146
|
+
function tellMounts() {
|
|
147
|
+
mounts.forEach(function (m) {
|
|
148
|
+
if (m.onChange) { try { m.onChange(itemsFor(m.sessionId).length, atMacFor(m.sessionId).length); } catch (_e) {} }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
136
151
|
function poll() {
|
|
137
152
|
clearTimeout(Q.timer);
|
|
138
153
|
if (Q.stopped || document.hidden || !signedIn()) return;
|
|
@@ -140,20 +155,44 @@
|
|
|
140
155
|
if (Q.inflight) { Q.again = true; return; }
|
|
141
156
|
Q.inflight = true; Q.again = false; Q.lastPollAt = Date.now();
|
|
142
157
|
capForPoll().then(function (cap) {
|
|
143
|
-
if (!usable(cap)) { Q.again = false; landed(); clearCards(); schedule(60000); return; }
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
if (!usable(cap)) { Q.again = false; landed(); clearCards(); schedule(60000); noteLoad(cap ? 'ok' : 'failed'); return; }
|
|
159
|
+
// 6.99.32 (QA 6.99.30 canary W1): a Mac that hangs (asleep, a stale Tailscale route) never answered and never
|
|
160
|
+
// refused, so this read stayed in flight forever: no more polls, and the Mac never marked down. It gives up after
|
|
161
|
+
// POLL_TIMEOUT_MS, says the Mac is not answering, and polls again. The limit covers the body too (QA 6.99.32
|
|
162
|
+
// Skeptic W-1): a Mac that sleeps between the headers and the body otherwise held the poll for good.
|
|
163
|
+
var ctrl = typeof AbortController === 'function' ? new AbortController() : null;
|
|
164
|
+
var limit = ctrl ? setTimeout(function () { ctrl.abort(); }, POLL_TIMEOUT_MS) : null;
|
|
165
|
+
return fetch(pollUrl(), { headers: { 'X-COS-Web': '1' }, credentials: 'same-origin', cache: 'no-store', signal: ctrl ? ctrl.signal : undefined }).then(function (res) {
|
|
166
|
+
// QA 6.99.30 (Skeptic): this 3 s read is what Sessions and Meetings pages keep running, so it tells app.js the Mac
|
|
167
|
+
// answered (any status) or did not (below); otherwise a "down" mark stranded Submit, Allow and Deny for minutes.
|
|
168
|
+
if (W.noteServer) W.noteServer(true);
|
|
169
|
+
if (res.status === 401) { clearTimeout(limit); Q.again = false; landed(); Q.stopped = true; clearCards(); return; }
|
|
170
|
+
if (!res.ok) { clearTimeout(limit); schedule(DEFAULT_POLL_MS); landed(); noteLoad('failed'); return; }
|
|
147
171
|
return res.json().then(function (d) {
|
|
148
|
-
|
|
172
|
+
clearTimeout(limit);
|
|
173
|
+
if (!d || d.enabled === false) { Q.again = false; landed(); clearCards(); schedule(60000); noteLoad('ok'); return; }
|
|
149
174
|
// Painted as soon as the read lands: take() draws the cards and repaints Today's Needs you and the pet.
|
|
175
|
+
// The state first, so a redraw take() makes already reads 'ok'; a read that changed no card redraws nothing, so
|
|
176
|
+
// the sheets still hear that the failure is over.
|
|
177
|
+
var was = Q.load;
|
|
178
|
+
Q.load = 'ok';
|
|
150
179
|
take(Array.isArray(d.items) ? d.items : [], Array.isArray(d.settled) ? d.settled : []);
|
|
180
|
+
if (was !== 'ok') tellMounts();
|
|
151
181
|
var every = Number(d.pollIntervalMs) > 0 ? Number(d.pollIntervalMs) : DEFAULT_POLL_MS;
|
|
152
182
|
schedule(nextWait(every));
|
|
153
183
|
landed();
|
|
184
|
+
}, function () {
|
|
185
|
+
// The body never finished (cut off, or the limit fired mid-body). The Mac did answer the headers, so it is not
|
|
186
|
+
// marked down; the poll is freed and goes again.
|
|
187
|
+
clearTimeout(limit); schedule(DEFAULT_POLL_MS); landed(); noteLoad('failed');
|
|
154
188
|
});
|
|
155
|
-
}, function () {
|
|
156
|
-
|
|
189
|
+
}, function () {
|
|
190
|
+
// No answer at all: refused, unreachable, or the limit fired before the headers (the only abort this read has).
|
|
191
|
+
clearTimeout(limit);
|
|
192
|
+
if (W.noteServer) W.noteServer(false);
|
|
193
|
+
schedule(DEFAULT_POLL_MS); landed(); noteLoad('failed');
|
|
194
|
+
});
|
|
195
|
+
}).catch(function () { schedule(DEFAULT_POLL_MS); landed(); noteLoad('failed'); });
|
|
157
196
|
}
|
|
158
197
|
// Read now, unless a read went out under NUDGE_GAP_MS ago (then at the end of that gap) or one is on the wire (then
|
|
159
198
|
// once more when it lands). Today calls this when a session goes to waiting, so its question shows at once.
|
|
@@ -190,8 +229,9 @@
|
|
|
190
229
|
var ids = {}; pending.forEach(function (i) { ids[i.id] = 1; });
|
|
191
230
|
Object.keys(Q.picks).forEach(function (id) { if (!ids[id]) { delete Q.picks[id]; delete Q.armed[id]; delete Q.notes[id]; delete Q.answerIds[id]; delete Q.missing[id]; } });
|
|
192
231
|
pending.forEach(function (i) {
|
|
193
|
-
if (!Q.picks[i.id]) Q.picks[i.id] = (i.questions || []).map(function () { return { labels: [], other: '' }; });
|
|
232
|
+
if (!Q.picks[i.id]) Q.picks[i.id] = keptPicks(i) || (i.questions || []).map(function () { return { labels: [], other: '' }; });
|
|
194
233
|
});
|
|
234
|
+
forgetPicks(ids);
|
|
195
235
|
Q.items = pending;
|
|
196
236
|
// A card that is already drawn is never redrawn by a poll: a half-typed Other answer stays as it is.
|
|
197
237
|
if (before !== after) { render(); changed(); }
|
|
@@ -295,8 +335,9 @@
|
|
|
295
335
|
// always answers: it never greys out for a missing answer, it goes to it.
|
|
296
336
|
'<div class="sq-foot" data-sq-foot="' + esc(item.id) + '">' +
|
|
297
337
|
'<p class="sq-progress" data-sq-progress="' + esc(item.id) + '" role="status"' + (Q.missing[item.id] != null && !complete(item) ? ' data-alert="1"' : '') + '>' + esc(progressText(item)) + '</p>' +
|
|
298
|
-
'<div class="sq-acts"><button type="button" class="cx-btn primary sq-send" data-sq="send" data-sq-id="' + esc(item.id) + '"' + (sending ? ' disabled' : '') + '>' + sendText(item.id) + '</button>' +
|
|
299
|
-
'<button type="button" class="link-btn sq-leave" data-sq="leave" data-sq-id="' + esc(item.id) + '"' + (sending ? ' disabled' : '') + '>Leave for the Mac</button></div>' +
|
|
338
|
+
'<div class="sq-acts"><button type="button" class="cx-btn primary sq-send" data-sq="send" data-sq-id="' + esc(item.id) + '"' + (sending || offline() ? ' disabled' : '') + '>' + sendText(item.id) + '</button>' +
|
|
339
|
+
'<button type="button" class="link-btn sq-leave" data-sq="leave" data-sq-id="' + esc(item.id) + '"' + (sending || offline() ? ' disabled' : '') + '>Leave for the Mac</button></div>' +
|
|
340
|
+
(offline() ? '<p class="sq-offline" role="status">' + esc(OFFLINE_LINE) + '</p>' : '') +
|
|
300
341
|
noteHtml(item) + '</div></article>';
|
|
301
342
|
}
|
|
302
343
|
function approvalCard(item) {
|
|
@@ -307,9 +348,10 @@
|
|
|
307
348
|
'<header class="sq-head"><h2 class="sq-title">' + esc(engine(item)) + ' wants to use ' + esc(a.tool || item.tool || 'a tool') + '</h2><span class="sq-secs" data-sq-secs="' + esc(item.id) + '">' + esc(timeLeft(secondsLeft(item))) + '</span></header>' +
|
|
308
349
|
'<pre class="sq-command">' + esc(a.summary || 'No details were sent with this request.') + '</pre>' +
|
|
309
350
|
(a.detail && a.detail !== a.summary ? '<details class="sq-details"><summary>Full command</summary><pre class="sq-command">' + esc(a.detail) + '</pre></details>' : '') +
|
|
310
|
-
'<div class="sq-acts"><button type="button" class="cx-btn primary" data-sq="allow" data-sq-id="' + esc(item.id) + '"' + (sending ? ' disabled' : '') + '>' + (armed === 'allow' ? 'Tap again to allow' : 'Allow once') + '</button>' +
|
|
311
|
-
'<button type="button" class="cx-btn sq-deny" data-sq="deny" data-sq-id="' + esc(item.id) + '"' + (sending ? ' disabled' : '') + '>' + (armed === 'deny' ? 'Tap again to deny' : 'Deny') + '</button>' +
|
|
312
|
-
'<button type="button" class="link-btn sq-leave" data-sq="leave" data-sq-id="' + esc(item.id) + '"' + (sending ? ' disabled' : '') + '>Leave for the Mac</button></div>' +
|
|
351
|
+
'<div class="sq-acts"><button type="button" class="cx-btn primary" data-sq="allow" data-sq-id="' + esc(item.id) + '"' + (sending || offline() ? ' disabled' : '') + '>' + (armed === 'allow' ? 'Tap again to allow' : 'Allow once') + '</button>' +
|
|
352
|
+
'<button type="button" class="cx-btn sq-deny" data-sq="deny" data-sq-id="' + esc(item.id) + '"' + (sending || offline() ? ' disabled' : '') + '>' + (armed === 'deny' ? 'Tap again to deny' : 'Deny') + '</button>' +
|
|
353
|
+
'<button type="button" class="link-btn sq-leave" data-sq="leave" data-sq-id="' + esc(item.id) + '"' + (sending || offline() ? ' disabled' : '') + '>Leave for the Mac</button></div>' +
|
|
354
|
+
(offline() ? '<p class="sq-offline" role="status">' + esc(OFFLINE_LINE) + '</p>' : '') +
|
|
313
355
|
noteHtml(item) + '</article>';
|
|
314
356
|
}
|
|
315
357
|
// A question or an approval the Mac's own prompt waits on: what it asks when this page saw it held, and where to answer.
|
|
@@ -356,7 +398,11 @@
|
|
|
356
398
|
var active = document.activeElement;
|
|
357
399
|
var focus = active && active.hasAttribute && active.hasAttribute('data-sq-other') && m.el.contains && m.el.contains(active) ? { id: active.getAttribute('data-sq-id'), q: active.getAttribute('data-q'), start: active.selectionStart, end: active.selectionEnd } : null;
|
|
358
400
|
var list = itemsFor(m.sessionId), mac = atMacFor(m.sessionId);
|
|
359
|
-
|
|
401
|
+
var cards = list.map(function (i) { return i.kind === 'approval' ? approvalCard(i) : questionCard(i, m.scope); }).join('') + mac.map(macCard).join('');
|
|
402
|
+
// The page's own host (over every pane): folded it is one line until tapped (questions.css .sq-banner, 6.99.30).
|
|
403
|
+
if (m.el === host && list.length) cards = bannerHtml(list) + '<div class="sq-cards' + (Q.bannerOpen ? ' open' : '') + '" id="sq-cards">' + cards + '</div>';
|
|
404
|
+
else if (m.el === host) Q.bannerOpen = false;
|
|
405
|
+
m.el.innerHTML = cards;
|
|
360
406
|
m.el.hidden = !list.length && !mac.length;
|
|
361
407
|
if (focus) {
|
|
362
408
|
var box = m.el.querySelector('[data-sq-other][data-sq-id="' + focus.id + '"][data-q="' + focus.q + '"]');
|
|
@@ -364,6 +410,17 @@
|
|
|
364
410
|
}
|
|
365
411
|
if (m.onChange) { try { m.onChange(list.length, mac.length); } catch (_e) {} }
|
|
366
412
|
}
|
|
413
|
+
// 6.99.30 (canary): "1 question for you. Answer", one line over the pane on a folded phone; a tap opens the cards.
|
|
414
|
+
function bannerHtml(list) {
|
|
415
|
+
var qs = list.filter(function (i) { return i.kind !== 'approval'; }).length, n = list.length;
|
|
416
|
+
var noun = qs === n ? (n === 1 ? 'question' : 'questions') : qs === 0 ? (n === 1 ? 'approval' : 'approvals') : (n === 1 ? 'request' : 'requests');
|
|
417
|
+
return '<button type="button" class="sq-banner" data-sq="banner" aria-expanded="' + (Q.bannerOpen ? 'true' : 'false') + '" aria-controls="sq-cards">' +
|
|
418
|
+
'<span class="sq-banner-text">' + esc(n + ' ' + noun + ' for you.') + '</span><span class="sq-banner-act">' + (Q.bannerOpen ? 'Hide' : 'Answer') + '</span></button>';
|
|
419
|
+
}
|
|
420
|
+
// 6.99.30 (canary): with the Mac not answering (app.js noteServer), Submit, Allow and Deny wait and say why, instead of
|
|
421
|
+
// offering a tap that cannot land.
|
|
422
|
+
function offline() { return !!(W.serverUp && W.serverUp() === false); }
|
|
423
|
+
var OFFLINE_LINE = 'Your Mac is not answering. You can answer here once it is back.';
|
|
367
424
|
function render() {
|
|
368
425
|
hostsOf().forEach(drawInto);
|
|
369
426
|
clearInterval(Q.tick);
|
|
@@ -384,7 +441,7 @@
|
|
|
384
441
|
}
|
|
385
442
|
function patchOne(card, id, item) {
|
|
386
443
|
var send = card.querySelector('[data-sq="send"]');
|
|
387
|
-
if (send) { send.disabled = !!Q.sending[id]; send.textContent = sendText(id); }
|
|
444
|
+
if (send) { send.disabled = !!Q.sending[id] || offline(); send.textContent = sendText(id); }
|
|
388
445
|
var progress = card.querySelector('[data-sq-progress]');
|
|
389
446
|
if (progress && item.kind === 'question') {
|
|
390
447
|
progress.textContent = progressText(item);
|
|
@@ -487,6 +544,7 @@
|
|
|
487
544
|
function onClick(e) {
|
|
488
545
|
var b = e.target.closest('[data-sq]');
|
|
489
546
|
if (!b || b.disabled) return;
|
|
547
|
+
if (b.getAttribute('data-sq') === 'banner') { Q.bannerOpen = !Q.bannerOpen; drawInto(hostsOf()[0]); return; }
|
|
490
548
|
var id = b.getAttribute('data-sq-id'), what = b.getAttribute('data-sq'), item = itemOf(id);
|
|
491
549
|
if (!item) return;
|
|
492
550
|
var card = b.closest ? b.closest('.sq-card') : null;
|
|
@@ -551,9 +609,36 @@
|
|
|
551
609
|
// A change after Submit was armed takes the arm back: review, then submit.
|
|
552
610
|
Q.armed[id] = '';
|
|
553
611
|
if (Q.missing[id] != null) delete Q.missing[id];
|
|
612
|
+
keepPicks(id);
|
|
554
613
|
patchCard(id);
|
|
555
614
|
return true;
|
|
556
615
|
}
|
|
616
|
+
// 6.99.30 (QA): a reload mid-answer keeps the picks and the typed Other words, per question id, for this tab only
|
|
617
|
+
// (sessionStorage). Restored only onto the same question (the same number of parts, and labels it still offers);
|
|
618
|
+
// dropped once the question is answered or gone.
|
|
619
|
+
var PICKS_KEY = 'cos.web.questions.picks';
|
|
620
|
+
function picksStore() { try { var v = JSON.parse(window.sessionStorage.getItem(PICKS_KEY) || '{}'); return v && typeof v === 'object' ? v : {}; } catch (_e) { return {}; } }
|
|
621
|
+
function writePicks(all) { try { window.sessionStorage.setItem(PICKS_KEY, JSON.stringify(all)); } catch (_e) {} }
|
|
622
|
+
function keepPicks(id) {
|
|
623
|
+
var all = picksStore(), p = Q.picks[id];
|
|
624
|
+
if (!p) return;
|
|
625
|
+
all[id] = p.map(function (x) { return { labels: x.labels.slice(0, 20), other: String(x.other || '').slice(0, 2000) }; });
|
|
626
|
+
writePicks(all);
|
|
627
|
+
}
|
|
628
|
+
function keptPicks(item) {
|
|
629
|
+
var kept = picksStore()[item.id], qs = item.questions || [];
|
|
630
|
+
if (!Array.isArray(kept) || kept.length !== qs.length) return null;
|
|
631
|
+
return qs.map(function (q, qi) {
|
|
632
|
+
var k = kept[qi] || {}, labels = (q.options || []).map(function (o) { return o.label; });
|
|
633
|
+
return { labels: (Array.isArray(k.labels) ? k.labels : []).filter(function (l) { return labels.indexOf(l) >= 0; }), other: typeof k.other === 'string' ? k.other : '' };
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function forgetPicks(live) {
|
|
637
|
+
var all = picksStore(), gone = Object.keys(all).filter(function (id) { return !live[id]; });
|
|
638
|
+
if (!gone.length) return;
|
|
639
|
+
gone.forEach(function (id) { delete all[id]; });
|
|
640
|
+
writePicks(all);
|
|
641
|
+
}
|
|
557
642
|
// Keys 1 to 9 while a question has focus, as in the terminal: the option with that number, or the last number to type
|
|
558
643
|
// your own. Never while typing in Other, and never with a modifier held.
|
|
559
644
|
function onKey(e) {
|
|
@@ -584,6 +669,10 @@
|
|
|
584
669
|
var id = t.getAttribute('data-sq-id'), item = itemOf(id); if (!item) return;
|
|
585
670
|
var qi = Number(t.getAttribute('data-q')), q = item.questions[qi], p = Q.picks[id][qi];
|
|
586
671
|
p.other = t.value;
|
|
672
|
+
// 6.99.30 (QA): the same card drawn on Today and in the sheet keeps one answer: the words go into every copy.
|
|
673
|
+
hostsOf().forEach(function (m) {
|
|
674
|
+
m.el.querySelectorAll('[data-sq-other][data-sq-id="' + id + '"][data-q="' + qi + '"]').forEach(function (box) { if (box !== t) box.value = t.value; });
|
|
675
|
+
});
|
|
587
676
|
// Single choice: typing your own answer clears the picked option, as on the phone.
|
|
588
677
|
if (!q.multiSelect && t.value.trim() && p.labels.length) {
|
|
589
678
|
p.labels = [];
|
|
@@ -591,6 +680,7 @@
|
|
|
591
680
|
}
|
|
592
681
|
Q.armed[id] = '';
|
|
593
682
|
if (Q.missing[id] != null) delete Q.missing[id];
|
|
683
|
+
keepPicks(id);
|
|
594
684
|
patchCard(id);
|
|
595
685
|
}
|
|
596
686
|
function listen(el) { el.addEventListener('click', onClick); el.addEventListener('change', onChange); el.addEventListener('input', onInput); el.addEventListener('keydown', onKey); }
|
|
@@ -604,20 +694,30 @@
|
|
|
604
694
|
return { count: function () { return itemsFor(m.sessionId).length; }, unmount: function () { mounts = mounts.filter(function (x) { return x !== m; }); } };
|
|
605
695
|
}
|
|
606
696
|
|
|
607
|
-
// QA 6.99.27 (User 2): Today's own card (#sq-host) has a sticky footer, and
|
|
608
|
-
// viewport, so with Other focused the footer sat under it.
|
|
609
|
-
//
|
|
610
|
-
//
|
|
697
|
+
// QA 6.99.27 (User 2): Today's own card (#sq-host) has a sticky footer, and the keyboard covers the bottom of the
|
|
698
|
+
// layout viewport, so with Other focused the footer sat under it. What covers the bottom goes on the host as --sq-kb
|
|
699
|
+
// and questions.css lifts the footer by it. 6.99.30: read the one way Control Web reads the visible viewport (app.js
|
|
700
|
+
// COSWeb.viewportFit, which Today's sheet uses too): the layout viewport under the visible box, in layout px, so a
|
|
701
|
+
// pinch zoom alone lifts nothing and a page the browser panned to the field lifts by what is really under it.
|
|
702
|
+
// The card in Today's sheet (.ta-q) needs none of this: the sheet itself is the visible box (today-actions.js fit).
|
|
611
703
|
function keyboardInset() {
|
|
612
|
-
var
|
|
613
|
-
|
|
614
|
-
var inset = Math.round((window.innerHeight || 0) - (vv.height + vv.offsetTop));
|
|
615
|
-
return inset > 0 ? inset : 0;
|
|
704
|
+
var v = W.viewportFit ? W.viewportFit() : null;
|
|
705
|
+
return v && v.vv ? v.below : 0;
|
|
616
706
|
}
|
|
617
707
|
function fitKeyboard() {
|
|
618
|
-
|
|
708
|
+
var inset = keyboardInset();
|
|
709
|
+
if (host.style && host.style.setProperty) host.style.setProperty('--sq-kb', inset + 'px');
|
|
710
|
+
// A keyboard up (120 px or more, or any strip under a field of the card): the footer stops sticking (questions.css).
|
|
711
|
+
var v = W.viewportFit ? W.viewportFit() : null, a = document.activeElement;
|
|
712
|
+
var typing = !!(a && host.contains && host.contains(a) && a.hasAttribute && a.hasAttribute('data-sq-other'));
|
|
713
|
+
if (host.classList) host.classList.toggle('sq-kb-up', !!(v && v.vv && (v.inset >= 120 || (typing && v.inset > 0))));
|
|
619
714
|
}
|
|
620
715
|
if (window.visualViewport) { window.visualViewport.addEventListener('resize', fitKeyboard); window.visualViewport.addEventListener('scroll', fitKeyboard); }
|
|
716
|
+
window.addEventListener('resize', fitKeyboard); window.addEventListener('orientationchange', fitKeyboard);
|
|
717
|
+
window.addEventListener('pageshow', fitKeyboard);
|
|
718
|
+
host.addEventListener('focusin', fitKeyboard); host.addEventListener('focusout', function () { setTimeout(fitKeyboard, 0); });
|
|
719
|
+
// The Mac going away or coming back redraws the cards at once (their buttons wait while it is away).
|
|
720
|
+
if (W.onServer) W.onServer(function () { render(); });
|
|
621
721
|
|
|
622
722
|
// Hidden: no reads at all (a phone in a pocket spends nothing). Visible again: one read at once (within the 1 s gap).
|
|
623
723
|
document.addEventListener('visibilitychange', function () { if (document.hidden) stop(); else if (signedIn()) start(); });
|
|
@@ -626,5 +726,6 @@
|
|
|
626
726
|
if (W.onRoute) W.onRoute(function (r) { if (r && r.pane === 'today') nudge(); });
|
|
627
727
|
|
|
628
728
|
W.sessionQuestions = { _state: Q, _take: take, _render: render, _presenceAge: presenceAge, _pollUrl: pollUrl, _key: onKey, _advanceMs: ADVANCE_MS, _todayPollMs: TODAY_POLL_MS, _fitKeyboard: fitKeyboard, poll: poll, nudge: nudge, mount: mount,
|
|
629
|
-
itemsFor: itemsFor, atMacFor: atMacFor, answeredFor: answeredFor, wordsOf: wordsOf, macLine: MAC_LINE, answeredLine: ANSWERED_LINE
|
|
729
|
+
itemsFor: itemsFor, atMacFor: atMacFor, answeredFor: answeredFor, wordsOf: wordsOf, macLine: MAC_LINE, answeredLine: ANSWERED_LINE,
|
|
730
|
+
loadState: function () { return Q.load; }, retry: function () { nudge(); } };
|
|
630
731
|
})();
|