@gotcos/glasses-server 7.0.31 → 7.0.34
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 +10 -8
- package/CHANGELOG.md +161 -0
- package/bin/hooks/cos-session-hook +3 -2
- package/docs/pairing-fixture.json +1 -1
- package/package.json +3 -1
- package/server/index.ts +4 -2
- package/server/lib/api-auth.ts +3 -2
- package/server/lib/archive.ts +4 -2
- package/server/lib/claude-hooks-installer.ts +101 -15
- package/server/lib/codex-hooks-installer.ts +4 -1
- package/server/lib/full-hold.ts +61 -5
- package/server/lib/glasses-pairing.ts +2 -1
- package/server/lib/permission-broker.ts +130 -14
- package/server/lib/question-hold-setting.ts +38 -0
- package/server/lib/question-queue.ts +111 -0
- package/server/lib/session-hooks-runtime.ts +28 -3
- package/server/lib/session-title-cache.ts +40 -0
- package/server/lib/turn-end-ask.ts +51 -1
- package/server/lib/web-session-auth.ts +17 -0
- package/server/routes/agent-sessions.ts +3 -0
- package/server/routes/permission-broker.ts +31 -2
- package/server/routes/session-hooks.ts +23 -0
- package/server/routes/sessions.ts +3 -0
- package/server/web-control/.manifest.json +9 -9
- package/server/web-control/app.css +26 -3
- package/server/web-control/app.js +234 -35
- package/server/web-control/control.css +5 -1
- package/server/web-control/index.html +7 -5
- package/server/web-control/panes/memories.js +18 -9
- package/server/web-control/panes/messages.js +15 -3
- package/server/web-control/panes/questions.js +6 -1
- package/server/web-control/panes/settings.js +18 -10
- package/server/web-control/shell.js +8 -13
|
@@ -294,6 +294,26 @@
|
|
|
294
294
|
return function () { if (!once) { once = true; WRITES.n = Math.max(0, WRITES.n - 1); } };
|
|
295
295
|
}
|
|
296
296
|
if (typeof window === 'object' && window) window.CosBusy = function () { return WRITES.n > 0 || !!(typeof state === 'object' && state && state.reading && state.reading.sending); };
|
|
297
|
+
// 7.0.33 A8 (Anand round 2, slide 19): an answer that is only a machine code ("cos_pipeline_not_configured") is never
|
|
298
|
+
// put on screen as is. Known codes get plain words that say what to do; any other bare code gets one plain sentence.
|
|
299
|
+
// err.code keeps the code, so a pane can still tell them apart.
|
|
300
|
+
var NOT_SET_UP = 'This part of COS is not set up on this Mac, so there is nothing to show here. Nothing is broken.';
|
|
301
|
+
// When trying again does not help, the next step: COS Control's menu restarts the server.
|
|
302
|
+
var STILL = ' If it keeps happening, restart COS from the COS Control menu on your Mac.';
|
|
303
|
+
var PLAIN_CODES = {
|
|
304
|
+
cos_pipeline_not_configured: NOT_SET_UP, pipeline_missing: NOT_SET_UP,
|
|
305
|
+
// Q1: a scripts folder is set (COS_SCRIPTS_DIR) but COS cannot run it: a broken setup, not a missing feature.
|
|
306
|
+
bridge_missing: 'COS on your Mac is set to use a scripts folder it cannot run, so this cannot be read. Check the COS_SCRIPTS_DIR setting on your Mac, then restart COS from the COS Control menu.',
|
|
307
|
+
task_runtime_unavailable: 'Work is not set up on this Mac yet. Open COS Control on your Mac to finish setting it up.',
|
|
308
|
+
unknown_command: 'COS on your Mac is older than this page. Update it from COS Control on your Mac.',
|
|
309
|
+
bridge_error: 'COS on your Mac could not read this just now. Try again in a moment.' + STILL
|
|
310
|
+
};
|
|
311
|
+
function plainMessage(msg) {
|
|
312
|
+
var m = String(msg || '').trim();
|
|
313
|
+
if (!/^[a-z0-9]+(?:_[a-z0-9]+)+$/.test(m)) return msg;
|
|
314
|
+
return PLAIN_CODES[m] || 'COS on your Mac could not do that just now. Try again in a moment.' + STILL;
|
|
315
|
+
}
|
|
316
|
+
function notSetUp(code) { return code === 'cos_pipeline_not_configured' || code === 'pipeline_missing'; }
|
|
297
317
|
function api(path, opts) {
|
|
298
318
|
var o = opts || {};
|
|
299
319
|
var headers = { 'X-COS-Web': '1' };
|
|
@@ -322,7 +342,7 @@
|
|
|
322
342
|
try { data = text ? JSON.parse(text) : {}; } catch (_e) { data = {}; }
|
|
323
343
|
if (!res.ok) {
|
|
324
344
|
var msg = (data && (data.message || (data.error && data.error.message) || data.error)) || ('The server answered ' + res.status + '.');
|
|
325
|
-
var err = new Error(typeof msg === 'string' ? msg : 'The server answered ' + res.status + '.');
|
|
345
|
+
var err = new Error((typeof plainMessage === 'function' ? plainMessage : String)(typeof msg === 'string' ? msg : 'The server answered ' + res.status + '.'));
|
|
326
346
|
err.status = res.status;
|
|
327
347
|
// The machine-readable reason, so a pane can say the right thing for a refusal whose
|
|
328
348
|
// body carries a code and no sentence (for example work-intake's {error: {code}}).
|
|
@@ -597,6 +617,10 @@
|
|
|
597
617
|
else pos = NAV.replace ? NAV.replace.pos : history.length - 1;
|
|
598
618
|
var back = null;
|
|
599
619
|
if (!st && !NAV.replace && NAV.last && typeof NAV.last.pos === 'number' && pos === NAV.last.pos + 1) back = NAV.last.hash;
|
|
620
|
+
// 7.0.33 A11 (Anand round 2, slides 5 and 28): a place picked from the menu (the rail, the pane menu, Ask's Go to)
|
|
621
|
+
// starts a new trail. That page has no path behind it, so Back goes up from it, as after a deep link.
|
|
622
|
+
if (NAV.fresh && Date.now() - NAV.fresh < 2000) back = null;
|
|
623
|
+
NAV.fresh = 0;
|
|
600
624
|
NAV.replace = null;
|
|
601
625
|
// 7.0.30, Miles's pick B: the steps before this one ride in the same mark, the last of them being where Back goes.
|
|
602
626
|
var trail = back ? (NAV.last.trail || []).concat([{ h: NAV.last.hash, t: NAV.last.label || '' }]).slice(-NAV_TRAIL_MAX) : null;
|
|
@@ -621,15 +645,22 @@
|
|
|
621
645
|
var t = st.cosNav.trail;
|
|
622
646
|
return t.map(function (s, i) { return { label: trailName(s.h, s.t), hash: s.h, back: t.length - i }; });
|
|
623
647
|
}
|
|
624
|
-
//
|
|
625
|
-
//
|
|
648
|
+
// 7.0.33 A11: a step is named by what it shows, its title (a meeting's, a session's, "Message #2", a day's date), cut
|
|
649
|
+
// to TRAIL_NAME_MAX characters with an ellipsis; a list is its pane. Only while the title is not loaded does the kind
|
|
650
|
+
// stand in ("Meeting", "Session", "Naming voices"). The same rule as COS Control's trail on the Mac (D4, 0.5.394).
|
|
651
|
+
var TRAIL_NAME_MAX = 28;
|
|
652
|
+
function trailCap(text) { var t = String(text || '').replace(/\s+/g, ' ').trim(); return t.length > TRAIL_NAME_MAX ? t.slice(0, TRAIL_NAME_MAX - 1).replace(/\s+$/, '') + '\u2026' : t; }
|
|
626
653
|
function trailName(hash, crumb) {
|
|
627
654
|
var a = String(hash || '').replace(/^#\/?/, '').split('/').filter(function (x) { return x !== ''; }), p = ROOT_PANES.indexOf(a[0]) >= 0 ? a[0] : 'today';
|
|
628
655
|
if (a.length < 2 || isTabRoute(a.slice(1))) return TITLES[p] || 'Today';
|
|
656
|
+
// Naming voices is a page of its own about a meeting; its crumb is the meeting's title, so by title it read the same
|
|
657
|
+
// as the meeting after it ("Speakers / Pricing review / Pricing review"). It keeps its name.
|
|
658
|
+
if (p === 'speakers' && a[1] === 'review') return 'Naming voices';
|
|
659
|
+
if (crumb) return trailCap(crumb);
|
|
629
660
|
if (p === 'meetings') return 'Meeting';
|
|
630
661
|
if (p === 'sessions') return 'Session';
|
|
631
662
|
if (p === 'speakers' && a[1] === 'review') return 'Naming voices';
|
|
632
|
-
return
|
|
663
|
+
return TITLES[p] || 'Today';
|
|
633
664
|
}
|
|
634
665
|
// One line that never wraps. When it is too wide, steps leave from the middle (the one after the first, then the next),
|
|
635
666
|
// never the first, the one before this page, or this page; "…" stands where they were. widths: each step's, then this
|
|
@@ -807,8 +838,12 @@
|
|
|
807
838
|
if (typeof paintTrail === 'function') paintTrail(r);
|
|
808
839
|
closeMenu();
|
|
809
840
|
if (changedPane) window.scrollTo(0, 0);
|
|
810
|
-
|
|
841
|
+
// 7.0.33 A12 (Q1): through barShow, so a bar sliding back after a page change catches its first tap too.
|
|
842
|
+
barShow($('topbar'));
|
|
811
843
|
onScroll();
|
|
844
|
+
// 7.0.33 A13: the pet in the top bar stays on every page; its card closes when the page changes.
|
|
845
|
+
if (typeof petCardClose === 'function') petCardClose();
|
|
846
|
+
if (typeof petInBar === 'function' && petInBar() && state.sessions) { placePet(); if (changedPane && $('today-pet').classList.contains('hidden')) renderPet(state.sessions); }
|
|
812
847
|
|
|
813
848
|
if (!PANES[r.pane] && LAZY()[r.pane]) {
|
|
814
849
|
var lazyPane = r.pane, lazyEl = $('pane-' + lazyPane), wasEmpty = lazyEl && !lazyEl.getAttribute('data-lazy-ready');
|
|
@@ -861,16 +896,44 @@
|
|
|
861
896
|
var lastY = 0;
|
|
862
897
|
function onScroll() {
|
|
863
898
|
var y = Math.max(0, window.scrollY || 0);
|
|
864
|
-
// On a reader the bar stays (its Back is there).
|
|
865
|
-
//
|
|
866
|
-
var bar = $('topbar'), back = $('topbar-back'), onReader = !!(back && !back.classList.contains('hidden'))
|
|
899
|
+
// On a reader the bar stays (its Back is there). 7.0.33 A12: on the folded Duo too, where the bar now shows a named
|
|
900
|
+
// Back (app.css); before, the bar tucked there and a tap meant for it landed on the page.
|
|
901
|
+
var bar = $('topbar'), back = $('topbar-back'), onReader = !!(back && !back.classList.contains('hidden'));
|
|
867
902
|
bar.classList.toggle('scrolled', y > 8);
|
|
868
|
-
if (y < 60 || y < lastY - 4 || onReader) bar
|
|
903
|
+
if (y < 60 || y < lastY - 4 || onReader) barShow(bar);
|
|
869
904
|
else if (y > lastY + 4 && isPhone()) bar.classList.add('tucked');
|
|
870
905
|
lastY = y;
|
|
871
906
|
}
|
|
872
907
|
function openMenu() { if (sideRail()) return; $('section-menu').classList.remove('hidden'); var cur = document.querySelector('#section-menu [aria-current="page"]') || document.querySelector('#section-menu [data-goto]'); if (cur) cur.focus(); }
|
|
873
908
|
function closeMenu() { $('section-menu').classList.add('hidden'); }
|
|
909
|
+
// 7.0.33 A12 (Anand round 2, slide 27): no tap falls through a hidden top bar. Once the bar has slid away its band shows
|
|
910
|
+
// the page, and a tap meant for the bar (Ask, Back) landed on whatever was there; on Settings that armed, then fired,
|
|
911
|
+
// "Sign out every browser". Now, while the bar is away or still sliding back, the first tap in its band only brings
|
|
912
|
+
// the bar back. Every page and size: the class is the test, so wherever the bar tucks, its band catches.
|
|
913
|
+
var barBackAt = 0, barCatch = null;
|
|
914
|
+
function barShow(bar) { if (bar.classList.contains('tucked')) { bar.classList.remove('tucked'); barBackAt = Date.now(); } }
|
|
915
|
+
// Inside the bar's own band only: its height, and its own left and right (Q1: on the folded Duo the rail's top icon
|
|
916
|
+
// sits beside the bar, and a tap on it while the bar was away was swallowed). The bar only moves up and down, so its
|
|
917
|
+
// left and right hold while it is away.
|
|
918
|
+
function barCatches(x, y) {
|
|
919
|
+
var bar = $('topbar');
|
|
920
|
+
if (!bar || !(y >= 0) || y >= bar.offsetHeight) return false;
|
|
921
|
+
var r = bar.getBoundingClientRect ? bar.getBoundingClientRect() : null;
|
|
922
|
+
if (r && !(x >= r.left && x < r.right)) return false;
|
|
923
|
+
return bar.classList.contains('tucked') || Date.now() - barBackAt < BAR_SLIDE_MS;
|
|
924
|
+
}
|
|
925
|
+
var BAR_SLIDE_MS = 320; // app.css: the bar's transform takes 220 ms; a little more for the finger
|
|
926
|
+
document.addEventListener('pointerdown', function (e) {
|
|
927
|
+
barCatch = null;
|
|
928
|
+
if (!barCatches(e.clientX, e.clientY)) return;
|
|
929
|
+
barCatch = { y: e.clientY, until: Date.now() + 1500 };
|
|
930
|
+
barShow($('topbar')); lastY = Math.max(0, window.scrollY || 0);
|
|
931
|
+
}, true);
|
|
932
|
+
document.addEventListener('click', function (e) {
|
|
933
|
+
var c = barCatch; barCatch = null;
|
|
934
|
+
if (!c || Date.now() > c.until || Math.abs(e.clientY - c.y) > 24) return;
|
|
935
|
+
e.preventDefault(); e.stopPropagation();
|
|
936
|
+
}, true);
|
|
874
937
|
|
|
875
938
|
// ── auth ────────────────────────────────────────────────
|
|
876
939
|
// 7.0.19: the first paint (index.html #first-paint) ends the moment the sign-in card or the app shows, whichever
|
|
@@ -1433,17 +1496,19 @@
|
|
|
1433
1496
|
function petPrefs() {
|
|
1434
1497
|
var p = readStore(PET_KEY);
|
|
1435
1498
|
var pet = typeof p.pet === 'string' && /^(mac|(shipped|gallery):[a-z0-9][a-z0-9-]{0,80})$/.test(p.pet) ? p.pet : 'mac';
|
|
1436
|
-
|
|
1499
|
+
// 7.0.33 A13 (Anand round 2, slide 9): where it shows. 'today' as before, or 'bar': small in the top bar on a phone.
|
|
1500
|
+
return { pet: pet, show: p.show !== false, place: p.place === 'bar' ? 'bar' : 'today' };
|
|
1437
1501
|
}
|
|
1438
1502
|
function setPetPrefs(next) {
|
|
1439
1503
|
var cur = petPrefs();
|
|
1440
|
-
var merged = { pet: next.pet != null ? next.pet : cur.pet, show: next.show != null ? !!next.show : cur.show };
|
|
1504
|
+
var merged = { pet: next.pet != null ? next.pet : cur.pet, show: next.show != null ? !!next.show : cur.show, place: next.place != null ? (next.place === 'bar' ? 'bar' : 'today') : cur.place };
|
|
1441
1505
|
writeStore(PET_KEY, merged);
|
|
1442
1506
|
// Forget the old pet's manifest and loop; Today repaints with the new choice next time it shows.
|
|
1443
1507
|
PET.manifest = null; PET.fetchedAt = 0; PET.pose = null; PET.token++; petStop();
|
|
1444
1508
|
var box = $('today-pet');
|
|
1445
1509
|
if (box) { box.innerHTML = ''; box.classList.add('hidden'); }
|
|
1446
|
-
|
|
1510
|
+
placePet();
|
|
1511
|
+
if ((state.pane === 'today' || petInBar()) && state.sessions) renderPet(state.sessions);
|
|
1447
1512
|
return merged;
|
|
1448
1513
|
}
|
|
1449
1514
|
var PET_HEIGHT = 140;
|
|
@@ -1560,13 +1625,72 @@
|
|
|
1560
1625
|
// Folded (one column) the pet sits above the story. Unfolded (two columns, the same 760px line as
|
|
1561
1626
|
// .today-cols in app.css) it moves to the top of the right-hand column, above Needs you (Miles 11/06).
|
|
1562
1627
|
var PET_WIDE = window.matchMedia ? window.matchMedia('(min-width: 760px)') : null;
|
|
1628
|
+
// 7.0.33 A13 (Anand round 2, slide 9; Miles 12:43, the three choices and Anand's top bar mock): "In the top bar" puts the
|
|
1629
|
+
// same pet, small and round, in a phone's top bar left of Ask, on every page; a tap opens a card under it (the pet, its
|
|
1630
|
+
// line, Change pet and Hide). The folded and the open Duo have the rail instead of that bar: there it stays on Today
|
|
1631
|
+
// until its place there is chosen (render sent to the lead). The default stays On Today.
|
|
1632
|
+
var PET_BAR_MQ = window.matchMedia ? window.matchMedia('(min-width: 600px)') : null;
|
|
1633
|
+
function petInBar() { var p = petPrefs(); return p.show && p.place === 'bar' && !(PET_BAR_MQ && PET_BAR_MQ.matches) && !(typeof sideRail === 'function' && sideRail()); }
|
|
1563
1634
|
function placePet() {
|
|
1564
1635
|
var box = $('today-pet'), side = document.querySelector('#pane-today .today-side'), rec = $('today-rec');
|
|
1565
1636
|
if (!box || !side || !rec) return;
|
|
1637
|
+
var bar = $('topbar'), ask = bar && bar.querySelector('[data-open-ask]');
|
|
1638
|
+
if (petInBar() && ask) { if (ask.previousElementSibling !== box) bar.insertBefore(box, ask); box.classList.add('in-bar'); return; }
|
|
1639
|
+
box.classList.remove('in-bar'); petCardClose();
|
|
1566
1640
|
if (PET_WIDE && PET_WIDE.matches) { if (side.firstElementChild !== box) side.insertBefore(box, side.firstElementChild); }
|
|
1567
1641
|
else if (rec.previousElementSibling !== box) rec.parentNode.insertBefore(box, rec);
|
|
1568
1642
|
}
|
|
1569
|
-
|
|
1643
|
+
function petReplace() { placePet(); if (petInBar() && state.sessions) renderPet(state.sessions); }
|
|
1644
|
+
[PET_WIDE, PET_BAR_MQ].forEach(function (mq) { if (mq) { if (mq.addEventListener) mq.addEventListener('change', petReplace); else if (mq.addListener) mq.addListener(petReplace); } });
|
|
1645
|
+
// The card under the small pet, as in Anand's mock: the pet, its line (petCaption, the words Today shows under it), one
|
|
1646
|
+
// more line when nothing runs, then Change pet and Hide. With something to open, the pet and its line open it, as the
|
|
1647
|
+
// pet on Today does (petTap). Hide turns the pet off and says where it comes back.
|
|
1648
|
+
var PET_IDLE_LINE = 'I\u2019ll wave when an agent starts.';
|
|
1649
|
+
function petCardToggle() {
|
|
1650
|
+
var box = $('today-pet'); if (!box) return;
|
|
1651
|
+
if (box.querySelector('.pet-card')) return petCardClose();
|
|
1652
|
+
var cap = box.querySelector('.pet-caption'), txt = cap ? cap.textContent : '', btn = box.querySelector('.pet-btn');
|
|
1653
|
+
var live = btn && btn.getAttribute('aria-disabled') === 'false';
|
|
1654
|
+
box.insertAdjacentHTML('beforeend', '<div class="pet-card" role="dialog" aria-label="Your pet">' +
|
|
1655
|
+
'<button type="button" class="pet-card-head" data-today="pet-open"' + (live ? '' : ' aria-disabled="true"') + '><span class="pet-card-pet" aria-hidden="true"></span>' +
|
|
1656
|
+
'<span class="pet-card-text"><span class="pet-card-title"></span>' + (txt === 'Nothing running' ? '<span class="pet-card-sub">' + esc(PET_IDLE_LINE) + '</span>' : '') + '</span></button>' +
|
|
1657
|
+
'<div class="pet-card-acts"><button type="button" class="cx-btn quiet" data-goto="settings/pet">Change pet</button><button type="button" class="cx-btn quiet" data-action="pet-hide">Hide</button></div></div>');
|
|
1658
|
+
box.querySelector('.pet-card-title').textContent = txt;
|
|
1659
|
+
var art = box.querySelector('.pet-btn .pet-still, .pet-btn .pet-sprite');
|
|
1660
|
+
if (art) box.querySelector('.pet-card-pet').appendChild(art.cloneNode(true));
|
|
1661
|
+
if (btn) btn.setAttribute('aria-expanded', 'true');
|
|
1662
|
+
petCardPlace(box.querySelector('.pet-card'));
|
|
1663
|
+
}
|
|
1664
|
+
// The card hangs under the pet, but never over a banner's own buttons (the sign-in notice's Hide, the question banner's
|
|
1665
|
+
// Answer, lead 12:5x): if it would cover one, it sits just below the lowest such banner instead.
|
|
1666
|
+
function petCardPlace(card) {
|
|
1667
|
+
if (!card || !card.getBoundingClientRect) return;
|
|
1668
|
+
var c = card.getBoundingClientRect(), drop = 0;
|
|
1669
|
+
document.querySelectorAll('#conn-notice:not([hidden]) button, #sq-host:not([hidden]) button, #sq-host:not([hidden]) a, .rec-banner').forEach(function (b) {
|
|
1670
|
+
var r = b.getBoundingClientRect();
|
|
1671
|
+
if (r.width && r.height && r.left < c.right && r.right > c.left && r.top < c.bottom && r.bottom > c.top) {
|
|
1672
|
+
var host = b.closest('#conn-notice, #sq-host'), h = host ? host.getBoundingClientRect() : r; // the recording banner is its own button
|
|
1673
|
+
drop = Math.max(drop, h.bottom + 8 - c.top);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
if (drop > 0) card.style.top = 'calc(100% + 8px + ' + Math.ceil(drop) + 'px)';
|
|
1677
|
+
}
|
|
1678
|
+
function petHide() {
|
|
1679
|
+
petCardClose(); setPetPrefs({ show: false });
|
|
1680
|
+
toast('Pet hidden. Turn it back on in Settings, Pet.');
|
|
1681
|
+
}
|
|
1682
|
+
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
1683
|
+
document.addEventListener('click', function (e) {
|
|
1684
|
+
var box = $('today-pet');
|
|
1685
|
+
if (box && box.querySelector('.pet-card') && !box.contains(e.target)) petCardClose();
|
|
1686
|
+
}, true);
|
|
1687
|
+
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') petCardClose(); });
|
|
1688
|
+
}
|
|
1689
|
+
function petCardClose() {
|
|
1690
|
+
var box = $('today-pet'), card = box && box.querySelector('.pet-card');
|
|
1691
|
+
if (card) card.remove();
|
|
1692
|
+
var btn = box && box.querySelector('.pet-btn'); if (btn) btn.removeAttribute('aria-expanded');
|
|
1693
|
+
}
|
|
1570
1694
|
var PET_CHANGE = '<button type="button" class="pet-change" data-goto="settings/pet">Change pet</button>';
|
|
1571
1695
|
// A still pet (a gallery pick, Cloud Puff, or a single image on the Mac): one picture for every state.
|
|
1572
1696
|
function renderPetStill(box, m, sessions) {
|
|
@@ -1626,9 +1750,11 @@
|
|
|
1626
1750
|
if (still || spec.frames < 2) return;
|
|
1627
1751
|
var step = function () {
|
|
1628
1752
|
// Runs only while Today is on screen: leaving the pane or hiding the page stops the loop.
|
|
1629
|
-
if (token !== PET.token || document.hidden || state.pane !== 'today') { PET.timer = null; return; }
|
|
1753
|
+
if (token !== PET.token || document.hidden || (state.pane !== 'today' && !petInBar())) { PET.timer = null; return; }
|
|
1630
1754
|
PET.frame = (PET.frame + 1) % spec.frames;
|
|
1631
1755
|
petPaint(el, spec, PET.frame, scale);
|
|
1756
|
+
// 7.0.33 A13: the card's copy of the pet moves with it.
|
|
1757
|
+
var cardArt = box.querySelector('.pet-card .pet-sprite'); if (cardArt) petPaint(cardArt, spec, PET.frame, scale);
|
|
1632
1758
|
PET.timer = setTimeout(step, Math.max(20, spec.interval * 1000));
|
|
1633
1759
|
};
|
|
1634
1760
|
PET.timer = setTimeout(step, Math.max(20, spec.interval * 1000));
|
|
@@ -1862,7 +1988,10 @@
|
|
|
1862
1988
|
var write = typeof storyWrite === 'function' ? storyWrite : function (e, kind, text, when) {
|
|
1863
1989
|
if (kind === 'brief') e.innerHTML = esc(text) + '<span class="story-when">' + esc(when) + '</span>'; else e.textContent = text;
|
|
1864
1990
|
};
|
|
1865
|
-
|
|
1991
|
+
// 7.0.33 A6 (Anand round 2, slide 20): what the sentence says, Needs you and Running do not say again. TODAY.storySaid
|
|
1992
|
+
// is the list of failures this sentence named ('offline' when it says the Mac is not answering); renderNeeds reads it.
|
|
1993
|
+
var said = function (v, brief) { var was = JSON.stringify([TODAY.storySaid, TODAY.storyBrief]); TODAY.storySaid = v; TODAY.storyBrief = !!brief; if (was !== JSON.stringify([v, !!brief]) && typeof renderNeeds === 'function' && $('needs-list')) renderNeeds(m); };
|
|
1994
|
+
if (TODAY.offline) { askBox(null); write(el, 'offline', 'COS on ' + state.serverName + ' is not answering right now. Today fills in when it does.'); said('offline'); return; }
|
|
1866
1995
|
// The model's summary when there is one (it never blocks: the counts line below shows until it lands). The test
|
|
1867
1996
|
// harnesses that run this block alone have no requestBrief, so it is looked up, never assumed.
|
|
1868
1997
|
askBox(null);
|
|
@@ -1870,7 +1999,7 @@
|
|
|
1870
1999
|
requestBrief(m);
|
|
1871
2000
|
var brief = briefShown();
|
|
1872
2001
|
askBox(brief);
|
|
1873
|
-
if (brief) { write(el, 'brief', brief.text, briefWhen(brief, m)); return; }
|
|
2002
|
+
if (brief) { write(el, 'brief', brief.text, briefWhen(brief, m)); said([], true); return; }
|
|
1874
2003
|
}
|
|
1875
2004
|
var first = '';
|
|
1876
2005
|
if (m.sessionsWaiting > 0) first = countWord(m.sessionsWaiting) + ' ' + plural(m.sessionsWaiting, 'session is', 'sessions are') + ' waiting on you';
|
|
@@ -1891,7 +2020,11 @@
|
|
|
1891
2020
|
else second = 'No agents are running';
|
|
1892
2021
|
if (TODAY.recording) second += (unread || working > 0 || open > 0 ? ', and ' : '. ') + 'COS is recording a meeting';
|
|
1893
2022
|
var out = (first ? first + '. ' : '') + second + '.';
|
|
1894
|
-
|
|
2023
|
+
// 7.0.33 A6: more than two sources unread is the Mac not answering, said once, here (it was Needs you's row).
|
|
2024
|
+
if (m.failed.length > 2) { said(m.failed.slice()); write(el, 'counts', (first ? first + '. ' : '') + 'Your Mac is not answering, so this may not be everything.'); return; }
|
|
2025
|
+
var others = m.failed.filter(function (n) { return n !== 'sessions'; }), named = unread ? ['sessions'] : [];
|
|
2026
|
+
if (!m.settled && !m.now.length && !m.later.length && (unread ? others : m.failed).length) named = named.concat(unread ? others : m.failed);
|
|
2027
|
+
said(named);
|
|
1895
2028
|
if (!m.settled && !m.now.length && !m.later.length) out += (unread ? others : m.failed).length ? ' Couldn\u2019t read ' + listWords(unread ? others : m.failed) + ', so this may not be everything.' : unread ? ' This may not be everything that needs you.' : ' Still reading what needs you.';
|
|
1896
2029
|
else if (unread) out += ' This may not be everything that needs you.';
|
|
1897
2030
|
write(el, 'counts', out);
|
|
@@ -1921,6 +2054,10 @@
|
|
|
1921
2054
|
$('run-count').textContent = working.length + jobs.n ? String(working.length + jobs.n) : '';
|
|
1922
2055
|
var list = $('run-list');
|
|
1923
2056
|
// Never "Nothing running" about a list that was not read.
|
|
2057
|
+
// 7.0.33 A6: when nothing runs, the sentence ("No agents are running", or that sessions could not be read) is all
|
|
2058
|
+
// Today says about it; the panel shows again the moment a session or a COS job runs.
|
|
2059
|
+
var panel = list.closest ? list.closest('.run-panel') : null;
|
|
2060
|
+
if (panel && panel.classList) panel.classList.toggle('hidden', !jobs.n && (TODAY.sessionsState === 'failed' || (TODAY.sessionsState === 'answered' && !working.length && !open.length)));
|
|
1924
2061
|
if (TODAY.sessionsState === 'failed') { list.innerHTML = jobs.html + '<button type="button" class="run-more run-failed" data-today-needs="retry">Couldn\u2019t read sessions. Try again</button>'; return; }
|
|
1925
2062
|
var shown = working.slice(0, 3);
|
|
1926
2063
|
var html = jobs.html + shown.map(function (s) { return runRow(s, true); }).join('');
|
|
@@ -2426,12 +2563,18 @@
|
|
|
2426
2563
|
if (m.later.length && !TODAY.showLater) html += '<button type="button" class="need-more" data-today-needs="later">' + esc(m.later.length + (m.later.length === 1 ? ' thing can' : ' things can') + ' wait until tomorrow. Show') + '</button>';
|
|
2427
2564
|
// 6.99.30 (design QA): no empty line above a failed read (it left a 45 px gap), and one retry row, never one per
|
|
2428
2565
|
// source: with the Mac not answering (sessions and Work both unread, or more than two sources) it says so once.
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2566
|
+
// 7.0.33 A6: a failure the sentence above already names is not said again here (Refresh at the top tries again), and
|
|
2567
|
+
// with nothing to show (nothing waiting, which the sentence says, or only what it said) the card steps aside.
|
|
2568
|
+
var said = TODAY.storySaid || [], left = said === 'offline' ? [] : m.failed.filter(function (n) { return said.indexOf(n) < 0; });
|
|
2569
|
+
// A model summary does not say "Nothing is waiting on you", so under one the card still does.
|
|
2570
|
+
if (!m.rows.length) html = m.failed.length || (m.settled && !TODAY.storyBrief) ? '' : '<p class="card-empty">' + esc(m.settled ? 'Nothing is waiting on you.' : 'Reading sessions, Work, voices and memories.') + '</p>';
|
|
2571
|
+
if (left.length) {
|
|
2572
|
+
var silent = TODAY.offline || left.length > 2;
|
|
2573
|
+
html += '<button type="button" class="need-more need-failed" data-today-needs="retry">' + esc(silent ? 'Your Mac is not answering. Try again.' : 'Couldn’t read ' + listWords(left) + '. Try again') + '</button>';
|
|
2433
2574
|
}
|
|
2434
2575
|
list.innerHTML = html;
|
|
2576
|
+
var card = list.closest ? list.closest('.card') : null;
|
|
2577
|
+
if (card && card.classList) card.classList.toggle('hidden', !html);
|
|
2435
2578
|
}
|
|
2436
2579
|
// Weather sits under the date at the top left (Miles, 11/07), where the eye starts, not inside Next up.
|
|
2437
2580
|
// It shows whether or not a meeting is next.
|
|
@@ -2764,7 +2907,7 @@
|
|
|
2764
2907
|
view: sxPref('view', 'sessions', ['sessions', 'usage', 'cost']),
|
|
2765
2908
|
recency: sxPref('recency', 'newest', ['newest', 'oldest', 'match']),
|
|
2766
2909
|
q: '', instant: [], remote: [], searching: false, searchError: '', ranFor: '', semantic: null, semanticReason: '', searchSeq: 0, searchTimer: null,
|
|
2767
|
-
hooks: null, hooksAt: 0,
|
|
2910
|
+
hooks: null, hooksAt: 0, hooksOffer: false, offerPutAway: false,
|
|
2768
2911
|
usage: null, usageAt: 0, usageLoading: false, usageError: '',
|
|
2769
2912
|
uPeriod: 'Last 30 days', uTool: 'All tools', uModel: 'All models',
|
|
2770
2913
|
// Usage on a wide screen (uWideModel): the custom range, the chart sentence, the chosen bar, the table's order.
|
|
@@ -2902,7 +3045,8 @@
|
|
|
2902
3045
|
var stats = view === 'sessions' ? sxStats(vis) : [];
|
|
2903
3046
|
var busy = view === 'sessions' ? SX.loading : SX.usageLoading;
|
|
2904
3047
|
hero.innerHTML = '<span class="cx-glyph"><svg class="icon"><use href="#i-sessions"/></svg></span>' +
|
|
2905
|
-
|
|
3048
|
+
// 7.0.33 A5: the heading names the view the top bar names (Usage, COS cost), not the place, so the page has one name.
|
|
3049
|
+
'<div class="cx-hero-text"><h1 class="cx-title">' + esc(SX_VIEWS[view] || 'Sessions') + '</h1><p class="cx-detail-line">' + esc(detail) + liveHeroNote(view) + '</p></div>' +
|
|
2906
3050
|
'<div class="cx-actions"><button type="button" class="cx-btn quiet" data-sx="refresh"' + (busy ? ' disabled' : '') + '><svg class="icon"><use href="#i-refresh"/></svg>Refresh</button></div>' +
|
|
2907
3051
|
(stats.length ? '<div class="cx-stats">' + stats.map(function (s) { return '<span class="cx-stat"><b>' + esc(typeof s[0] === 'number' ? s[0].toLocaleString() : s[0]) + '</b><span>' + s[1] + '</span></span>'; }).join('') + '</div>' : '');
|
|
2908
3052
|
// 7.0.23 step 2b: Updated and Opened are the sort menu now, and Pinned a group; no clock switches here.
|
|
@@ -3876,17 +4020,40 @@
|
|
|
3876
4020
|
if (Date.now() - SX.hooksAt < 60000) { paintHooks(); return; }
|
|
3877
4021
|
SX.hooksAt = Date.now();
|
|
3878
4022
|
api('/api/session-hooks/status').then(function (d) {
|
|
3879
|
-
SX.hooks = (d && d.sessionHooks && d.sessionHooks.state) || null;
|
|
3880
|
-
|
|
4023
|
+
SX.hooks = (d && d.sessionHooks && d.sessionHooks.state) || null;
|
|
4024
|
+
// 7.0.34 (X1): the 8 hour question wait is on by default and offered until this Mac reinstalls its hooks.
|
|
4025
|
+
SX.hooksOffer = !!(d && d.sessionHooks && d.sessionHooks.questionWait && d.sessionHooks.questionWait.offered === true);
|
|
4026
|
+
paintHooks();
|
|
4027
|
+
}, function (err) { SX.hooks = err && err.status === 404 ? 'route_absent' : null; SX.hooksOffer = false; paintHooks(); });
|
|
4028
|
+
}
|
|
4029
|
+
// 7.0.34 (X1): "Not now" puts the 8 hour offer away in THIS browser only. A store that cannot be read or written
|
|
4030
|
+
// just means the line shows again; a hooks-problem line is never hidden by it.
|
|
4031
|
+
var SX_OFFER_HIDE_KEY = 'cos.sx.questionWaitOfferHidden';
|
|
4032
|
+
function offerHidden() {
|
|
4033
|
+
try { return typeof localStorage === 'object' && !!localStorage && localStorage.getItem(SX_OFFER_HIDE_KEY) === '1'; } catch (_e) { return false; }
|
|
4034
|
+
}
|
|
4035
|
+
function hideOffer() {
|
|
4036
|
+
try { if (typeof localStorage === 'object' && localStorage) localStorage.setItem(SX_OFFER_HIDE_KEY, '1'); } catch (_e) { /* not kept: it shows next visit */ }
|
|
3881
4037
|
}
|
|
3882
4038
|
function paintHooks() {
|
|
3883
4039
|
var el = $('sessions-hooks'); if (!el) return;
|
|
3884
4040
|
var st = SX.hooks;
|
|
3885
|
-
if (['missing', 'drift', 'script_outdated', 'route_absent'].indexOf(st) < 0) {
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
4041
|
+
if (['missing', 'drift', 'script_outdated', 'route_absent'].indexOf(st) < 0) {
|
|
4042
|
+
// 7.0.34 (X1): read-only, no dot. A hooks problem above already says Reinstall hooks, so this shows only
|
|
4043
|
+
// when there is none (A8: each thing once). Installing hooks stays a Mac action.
|
|
4044
|
+
var offer = st === 'installed' && SX.hooksOffer && !SX.offerPutAway && !offerHidden();
|
|
4045
|
+
el.innerHTML = offer
|
|
4046
|
+
? '<div class="sx-hooks"><span>' + esc('Questions can now wait up to 8 hours.') + ' <span class="sx-hooks-where">' + esc('In COS Control on your Mac, choose Reinstall hooks.') + '</span> <button type="button" class="sx-link" data-sx-offer="hide">' + esc('Not now') + '</button></span></div>'
|
|
4047
|
+
: '';
|
|
4048
|
+
var notNow = offer && el.querySelector ? el.querySelector('[data-sx-offer="hide"]') : null;
|
|
4049
|
+
if (notNow) notNow.onclick = function () { SX.offerPutAway = true; hideOffer(); paintHooks(); };
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
// 7.0.33 A8: plain words, then what to do.
|
|
4053
|
+
var text = st === 'route_absent' ? 'Whether a session is working or waiting needs a newer COS on your Mac.'
|
|
4054
|
+
: st === 'missing' ? 'COS cannot yet tell when a Claude Code session is working or waiting on you.'
|
|
4055
|
+
: 'COS\u2019s link to Claude Code needs a refresh, so working and waiting may be out of date.';
|
|
4056
|
+
var where = st === 'route_absent' ? 'Update it from COS Control on your Mac.' : 'In COS Control on your Mac, choose ' + (st === 'missing' ? 'Install hooks' : 'Reinstall hooks') + '.';
|
|
3890
4057
|
el.innerHTML = '<div class="sx-hooks"><span class="sx-hooks-dot" aria-hidden="true"></span><span>' + esc(text) + ' <span class="sx-hooks-where">' + esc(where) + '</span></span></div>';
|
|
3891
4058
|
}
|
|
3892
4059
|
|
|
@@ -7635,13 +7802,18 @@
|
|
|
7635
7802
|
(recent.length ? '<h2 class="group-title">Recent sessions</h2><div class="list">' + recent.map(function (s) { return sessionRow(s); }).join('') + '</div>' : '');
|
|
7636
7803
|
return;
|
|
7637
7804
|
}
|
|
7638
|
-
|
|
7639
|
-
|
|
7805
|
+
// 7.0.33 A7 (Anand round 2, slide 20): typing "usage" put Go to Usage last, under four Ask rows. When the words name a
|
|
7806
|
+
// place (its whole name, or its first three letters or more, "go to" allowed in front), Go to comes first.
|
|
7807
|
+
var ql = q.toLowerCase().replace(/^go to\s+/, '');
|
|
7808
|
+
var jumps = JUMPS.filter(function (j) { return j[1].toLowerCase().indexOf(ql) === 0; });
|
|
7809
|
+
var placeFirst = jumps.length > 0 && (ql.length >= 3 || jumps.some(function (j) { return j[1].toLowerCase() === ql; }));
|
|
7810
|
+
var jumpRows = jumps.map(function (j) { return '<button type="button" class="nav-row" data-goto="' + j[0] + '"><svg class="icon"><use href="#i-' + j[0] + '"/></svg><span class="grow">Go to ' + esc(j[1]) + '</span><span class="chev"></span></button>'; }).join('');
|
|
7811
|
+
out.innerHTML = '<div class="list" style="margin-top:14px">' + (placeFirst ? jumpRows : '') +
|
|
7640
7812
|
'<button type="button" class="nav-row" data-action="ask-cos"><svg class="icon"><use href="#i-ask"/></svg><span class="grow">Ask COS<span class="note">' + esc(askRowNote(q)) + '</span></span></button>' +
|
|
7641
7813
|
'<button type="button" class="nav-row" data-action="ask-file"' + (ASK_FILE.busy ? ' disabled' : '') + '><svg class="icon"><use href="#i-work"/></svg><span class="grow">' + (ASK_FILE.busy ? 'Filing\u2026' : 'File to Work inbox') + fileNoteHtml(q) + '</span></button>' +
|
|
7642
7814
|
'<button type="button" class="nav-row" data-action="ask-meaning"><svg class="icon"><use href="#i-sessions"/></svg><span class="grow">Find a session about this<span class="note">Ranks every session by meaning</span></span></button>' +
|
|
7643
7815
|
'<button type="button" class="nav-row" data-action="ask-work"><svg class="icon"><use href="#i-work"/></svg><span class="grow">Find a task about this<span class="note">Searches Work by meaning</span></span></button>' +
|
|
7644
|
-
|
|
7816
|
+
(placeFirst ? '' : jumpRows) +
|
|
7645
7817
|
'</div><div id="ask-hits"></div><p class="status-line" id="ask-status"></p>';
|
|
7646
7818
|
paintAskRow();
|
|
7647
7819
|
clearTimeout(renderAsk.t);
|
|
@@ -7691,7 +7863,12 @@
|
|
|
7691
7863
|
return;
|
|
7692
7864
|
}
|
|
7693
7865
|
if (t.hasAttribute('data-open-ask')) return openAsk();
|
|
7694
|
-
if (t.hasAttribute('data-today'))
|
|
7866
|
+
if (t.hasAttribute('data-today')) {
|
|
7867
|
+
var dt = t.getAttribute('data-today');
|
|
7868
|
+
if (dt === 'pet-open') { if (t.getAttribute('aria-disabled') !== 'true') { petCardClose(); petTap($('today-pet').querySelector('.pet-btn')); } return; }
|
|
7869
|
+
return dt === 'pet' ? (petInBar() ? petCardToggle() : petTap(t)) : openTodayAction(t);
|
|
7870
|
+
}
|
|
7871
|
+
if (t.getAttribute('data-action') === 'pet-hide') return petHide();
|
|
7695
7872
|
if (t.hasAttribute('data-today-next')) return prepNext();
|
|
7696
7873
|
if (t.hasAttribute('data-next-prep')) return toggleNextPrep(t.getAttribute('data-next-prep'));
|
|
7697
7874
|
if (t.hasAttribute('data-next-prep-cancel')) return closeNextPrep(true);
|
|
@@ -7705,6 +7882,8 @@
|
|
|
7705
7882
|
}
|
|
7706
7883
|
if (t.hasAttribute('data-theme-choice')) { window.CosTheme.set(t.getAttribute('data-theme-choice')); paintAppearance(); return; }
|
|
7707
7884
|
if (t.hasAttribute('data-goto') || t.hasAttribute('data-pane')) {
|
|
7885
|
+
// 7.0.33 A11: a pick from the menu (the rail, the pane menu, Ask's Go to) starts a new trail (navStamp).
|
|
7886
|
+
if (t.closest('.rail, #section-menu, #ask-results')) NAV.fresh = Date.now();
|
|
7708
7887
|
if (inSheet) closeAsk(); closeMenu();
|
|
7709
7888
|
// "work/sort" is a pane and its arguments, not one name.
|
|
7710
7889
|
var dest = String(t.getAttribute('data-goto') || t.getAttribute('data-pane')).split('/');
|
|
@@ -7723,7 +7902,7 @@
|
|
|
7723
7902
|
if (action === 'drop-after') return dropHeld();
|
|
7724
7903
|
if (action === 'unqueue') return removeQueued(t.getAttribute('data-turn-key'));
|
|
7725
7904
|
if (action === 'revoke-all') {
|
|
7726
|
-
if (
|
|
7905
|
+
if (!window.COSWeb.armTap(t, t.querySelector('.grow').firstChild, 'Tap again to sign out every browser')) return;
|
|
7727
7906
|
api('/api/web-session/revoke-all', { method: 'POST', body: {} }).then(function () { showAuth('Every browser is signed out, this one too.'); }).catch(function (err) { setStatus($('settings-status'), err.message, true); });
|
|
7728
7907
|
return;
|
|
7729
7908
|
}
|
|
@@ -8090,6 +8269,9 @@
|
|
|
8090
8269
|
// once a detail has loaded; route() clears it on every navigation.
|
|
8091
8270
|
setCrumb: function (text) { crumbDetail = String(text || ''); navLabel(crumbDetail); paintTrail(); runHooks(routeHooks, parseRoute()); },
|
|
8092
8271
|
crumb: function () { return crumbDetail; },
|
|
8272
|
+
// 7.0.33 A11: the trail for the navigation bar from 600 px (shell.js paintNavbar): the same steps and names as the
|
|
8273
|
+
// phone's row, and this page's name.
|
|
8274
|
+
trailNow: function (r) { r = r || parseRoute(); var steps = r.args.length && !isTabRoute(r.args) ? navTrail() : null; return { steps: steps || [], here: steps || r.args.length ? trailName(location.hash, crumbDetail) : (crumbDetail || TITLES[r.pane] || 'Today') }; },
|
|
8093
8275
|
// Panes that read with their own fetch call this on a 401 so the sign-in screen shows.
|
|
8094
8276
|
showAuth: function (message) { showAuth(message); },
|
|
8095
8277
|
// Control's session state (the Sessions pane's sxState) and warm-up test, so the rail's
|
|
@@ -8124,12 +8306,29 @@
|
|
|
8124
8306
|
// Today as a place to act (panes/today-actions.js, Miles 11/06): what the Today cards show, a repaint after a
|
|
8125
8307
|
// decision, and the Sessions reader's own pieces, so the overlay sends and reads through the reader's code.
|
|
8126
8308
|
window.COSWeb.pane = function (name) { return PANES[name] || null; };
|
|
8309
|
+
window.COSWeb.plainMessage = plainMessage; window.COSWeb.notSetUp = notSetUp;
|
|
8127
8310
|
// Tap, then tap again (Today's Dismiss, Stop and Send anyway; a question card's Allow, Deny and Send): the second tap
|
|
8128
8311
|
// counts only once the first has been on screen ARM_CONFIRM_MS, so a double tap or a finger bounce arms and never
|
|
8129
8312
|
// confirms (W2-r2). The one rule both panes/today-actions.js and panes/questions.js read.
|
|
8130
8313
|
var ARM_CONFIRM_MS = 400;
|
|
8131
8314
|
window.COSWeb.armConfirmMs = ARM_CONFIRM_MS;
|
|
8132
8315
|
window.COSWeb.confirmReady = function (armedAt) { return typeof armedAt === 'number' && Date.now() - armedAt >= ARM_CONFIRM_MS; };
|
|
8316
|
+
// 7.0.33 A12: the same rule for a button armed in place (Sign out every browser, Settings' Release), which until now
|
|
8317
|
+
// stayed armed with no time limit, so a stray tap minutes later confirmed it. armTap(button, label, armedText) returns
|
|
8318
|
+
// true on a confirming tap; otherwise it arms (label says armedText) for ARM_EXPIRE_MS and puts the words back after.
|
|
8319
|
+
var ARM_EXPIRE_MS = 5000;
|
|
8320
|
+
window.COSWeb.armExpireMs = ARM_EXPIRE_MS;
|
|
8321
|
+
window.COSWeb.armTap = function (el, label, armedText) {
|
|
8322
|
+
var now = Date.now(), at = Number(el.getAttribute('data-armed-at')) || 0;
|
|
8323
|
+
if (el.getAttribute('data-armed') === '1' && now - at < ARM_EXPIRE_MS) return now - at >= ARM_CONFIRM_MS;
|
|
8324
|
+
if (el.getAttribute('data-armed') !== '1') el.setAttribute('data-idle-text', label.textContent);
|
|
8325
|
+
el.setAttribute('data-armed', '1'); el.setAttribute('data-armed-at', String(now)); label.textContent = armedText;
|
|
8326
|
+
setTimeout(function () {
|
|
8327
|
+
if (el.getAttribute('data-armed-at') !== String(now)) return;
|
|
8328
|
+
el.removeAttribute('data-armed'); el.removeAttribute('data-armed-at'); label.textContent = el.getAttribute('data-idle-text') || label.textContent;
|
|
8329
|
+
}, ARM_EXPIRE_MS);
|
|
8330
|
+
return false;
|
|
8331
|
+
};
|
|
8133
8332
|
window.COSWeb.today = {
|
|
8134
8333
|
data: function () {
|
|
8135
8334
|
return { needs: TODAY.needs, working: TODAY.working, open: TODAY.open, welcome: TODAY.welcome, offline: TODAY.offline, at: TODAY.at,
|
|
@@ -104,7 +104,9 @@
|
|
|
104
104
|
.nav-btn:disabled { opacity: 0.4; }
|
|
105
105
|
.crumbs { display: flex; align-items: center; min-width: 0; list-style: none; margin: 0 0 0 6px; padding: 0; font: 400 12.5px/1.2 var(--body); color: var(--sub); }
|
|
106
106
|
.crumb { white-space: nowrap; }
|
|
107
|
-
.
|
|
107
|
+
/* 7.0.33 A11: the phone trail's look: '/' between steps, a step you can go back to in the accent colour. */
|
|
108
|
+
.crumb + .crumb::before { content: '/'; content: '/' / ''; margin: 0 6px; color: var(--tertiary); }
|
|
109
|
+
.crumb a { color: var(--accent); }
|
|
108
110
|
.crumb.root { font-weight: 600; color: var(--ink); }
|
|
109
111
|
.crumb.current { font-weight: 600; color: var(--ink); }
|
|
110
112
|
.crumb.dim { color: var(--sub); }
|
|
@@ -246,6 +248,8 @@
|
|
|
246
248
|
.cx-said-bubble { max-width: min(80%, 560px); background: color-mix(in srgb, var(--gold) 22%, var(--panel)); border: 1px solid var(--said-edge);
|
|
247
249
|
border-radius: 18px 18px 6px 18px; padding: 10px 14px; line-height: 1.45; }
|
|
248
250
|
.cx-said-who { font-size: 12px; color: var(--sub); margin: 4px 4px 0; }
|
|
251
|
+
/* 7.0.33 A2: COS's answer time, under its text on the left, as "You" sits under the question on the right. */
|
|
252
|
+
.cx-said-when { display: block; margin: 6px 0 0; }
|
|
249
253
|
.cx-said.cos { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 10px; align-items: start; margin: 0 0 14px; }
|
|
250
254
|
.cx-said-text { min-width: 0; }
|
|
251
255
|
.cx-card-body { font-size: 15px; line-height: 1.5; color: var(--ink); overflow-wrap: anywhere; }
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
<form id="auth-form">
|
|
116
116
|
<div id="auth-code-block">
|
|
117
117
|
<label for="auth-code">Sign-in code</label>
|
|
118
|
-
<input id="auth-code" class="field code-field" type="text" inputmode="text" autocomplete="one-time-code" autocapitalize="characters" spellcheck="false" maxlength="
|
|
118
|
+
<input id="auth-code" class="field code-field" type="text" inputmode="text" autocomplete="one-time-code" autocapitalize="characters" spellcheck="false" maxlength="17" placeholder="K7P3-QX9M">
|
|
119
119
|
<p class="auth-foot" style="margin-top:8px">Scan the QR with your phone's camera. A browser that is already signed in shows one under Settings, Use COS on another phone or browser. COS Control on your Mac shows one under Open COS on your phone. Or type the code shown with the QR.</p>
|
|
120
120
|
<p class="auth-foot">The glasses pairing code does not work here.</p>
|
|
121
121
|
</div>
|
|
@@ -155,12 +155,12 @@
|
|
|
155
155
|
|
|
156
156
|
<div class="main-col">
|
|
157
157
|
<!-- Control's navigation bar (ActivityWindow navigationBar): shown off Today, from 600px. -->
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
<!-- 7.0.33 A10 and A11 (Anand round 2, slides 5 and 28): Back and the way you came, as on the phone. Home and
|
|
159
|
+
Settings left this bar: the rail has Today and Settings. -->
|
|
160
|
+
<nav class="navbar hidden" id="navbar" aria-label="How you got here">
|
|
160
161
|
<button type="button" class="nav-btn" id="nav-back" title="Go back one step"><svg class="icon"><use href="#i-back"/></svg><span>Back</span></button>
|
|
161
162
|
<ol class="crumbs" id="crumbs"></ol>
|
|
162
163
|
<span class="grow"></span>
|
|
163
|
-
<button type="button" class="nav-btn" id="nav-settings" aria-label="Open Activity settings"><svg class="icon"><use href="#i-settings"/></svg><span>Settings</span></button>
|
|
164
164
|
</nav>
|
|
165
165
|
<header class="topbar" id="topbar">
|
|
166
166
|
<div class="topbar-left">
|
|
@@ -245,11 +245,13 @@
|
|
|
245
245
|
<!-- SESSIONS -->
|
|
246
246
|
<section id="pane-sessions" class="pane wide hidden" data-title="Sessions">
|
|
247
247
|
<div class="cx sx" data-tint="sessions">
|
|
248
|
+
<!-- 7.0.33 A5 (Anand round 2, slide 17): the view switch comes first, so Sessions, Usage and COS cost sit in one place
|
|
249
|
+
on all three, right under the top bar; Usage's and COS cost's header follows it. -->
|
|
250
|
+
<div class="cx-controls sx-views" id="sessions-views"></div>
|
|
248
251
|
<header class="cx-hero sx-hero" id="sessions-hero" data-tint="sessions">
|
|
249
252
|
<span class="cx-glyph"><svg class="icon"><use href="#i-sessions"/></svg></span>
|
|
250
253
|
<div class="cx-hero-text"><h1 class="cx-title">Sessions</h1><p class="cx-detail-line">Claude · Codex · Cursor · reading the last 7 days…</p></div>
|
|
251
254
|
</header>
|
|
252
|
-
<div class="cx-controls sx-views" id="sessions-views"></div>
|
|
253
255
|
<div class="split sx-split" id="sessions-split">
|
|
254
256
|
<div class="split-list" id="sessions-list-col">
|
|
255
257
|
<!-- 7.0.23 step 2b: the list's own head (DESIGN_PLAN v2 "The list"): the Sessions menu, Search, one quiet line and the sort menu. -->
|