create-pmos 0.134.0 → 0.136.0
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/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/bin/notes-replay.js +235 -0
- package/workspace/c3-briefing-test.js +130 -0
- package/workspace/c3-foundation-test.js +159 -0
- package/workspace/c3-ib-items-test.js +105 -0
- package/workspace/c3-ib-more-test.js +120 -0
- package/workspace/c3-ib-triage-backlog-test.js +119 -0
- package/workspace/c3-ini-harness.js +66 -0
- package/workspace/c3-initiatives-test.js +140 -0
- package/workspace/c3-one-home-test.js +14 -1
- package/workspace/c3-reports-test.js +57 -18
- package/workspace/c3-settings-test.js +139 -0
- package/workspace/c3-work-test.js +42 -10
- package/workspace/fixtures/one-home-baseline.json +1 -9
- package/workspace/guide/briefing-cards.md +15 -3
- package/workspace/guide/briefing.md +12 -8
- package/workspace/guide/calendar-popup.md +2 -2
- package/workspace/guide/console-calendar.md +39 -10
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/initiative-boards.md +65 -0
- package/workspace/guide/jira.md +3 -1
- package/workspace/guide/layout.md +2 -0
- package/workspace/guide/my-work.md +5 -3
- package/workspace/guide/notes.md +16 -1
- package/workspace/guide/reports-dashboards.md +29 -5
- package/workspace/guide/settings.md +68 -0
- package/workspace/guide/welcome.md +10 -10
- package/workspace/lib/calendar-cli.js +3 -2
- package/workspace/lib/calendar-layers.js +21 -10
- package/workspace/lib/calendar-tidy.js +230 -0
- package/workspace/lib/engine/boards.js +542 -0
- package/workspace/lib/engine/breakdown-ops.js +180 -0
- package/workspace/lib/engine/calendar-import.js +84 -0
- package/workspace/lib/engine/calendar-model.js +185 -0
- package/workspace/lib/engine/card-state.js +92 -0
- package/workspace/lib/engine/derived/derive.js +92 -0
- package/workspace/lib/engine/derived/plan.js +3 -0
- package/workspace/lib/engine/derived/trust.js +3 -0
- package/workspace/lib/engine/doc-health.js +142 -0
- package/workspace/lib/engine/doc-sheet.js +75 -0
- package/workspace/lib/engine/intake.js +78 -0
- package/workspace/lib/engine/outbox.js +86 -0
- package/workspace/lib/engine/plan-forecast.js +342 -0
- package/workspace/lib/engine/promises.js +29 -0
- package/workspace/lib/engine/records/index.js +114 -0
- package/workspace/lib/engine/releases.js +96 -0
- package/workspace/lib/engine/replies.js +99 -0
- package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
- package/workspace/lib/engine/report-families.js +175 -0
- package/workspace/lib/engine/roadmap-ops.js +150 -0
- package/workspace/lib/engine/sessions-list.js +27 -0
- package/workspace/lib/engine/signal-rules.js +95 -0
- package/workspace/lib/engine/signals/cards.js +57 -0
- package/workspace/lib/engine/signals/detectors.js +370 -0
- package/workspace/lib/engine/signals/facts-cli.js +8 -0
- package/workspace/lib/engine/signals/facts.js +197 -0
- package/workspace/lib/engine/signals/index.js +33 -0
- package/workspace/lib/engine/signals/kinds.js +110 -0
- package/workspace/lib/engine/signals/layout-paths.js +44 -0
- package/workspace/lib/engine/signals/options.js +24 -0
- package/workspace/lib/engine/signals/score.js +72 -0
- package/workspace/lib/engine/signals/select.js +47 -0
- package/workspace/lib/engine/step-planner.js +65 -0
- package/workspace/lib/engine/trust/claim-rules.json +93 -0
- package/workspace/lib/engine/trust/import-calendar.js +21 -0
- package/workspace/lib/engine/trust/import-guard.js +36 -0
- package/workspace/lib/engine/trust/ledger.js +53 -0
- package/workspace/lib/engine/trust/outbox.js +45 -0
- package/workspace/lib/engine/trust/plan.js +5 -0
- package/workspace/lib/engine/trust/trust.js +21 -0
- package/workspace/lib/engine/truth-table.js +57 -0
- package/workspace/lib/engine/workspace-lib.js +24 -0
- package/workspace/lib/factory-join.js +1 -1
- package/workspace/lib/facts-async.js +4 -4
- package/workspace/lib/headless.js +95 -0
- package/workspace/lib/import-overlay.js +2 -1
- package/workspace/lib/jira-status.js +30 -3
- package/workspace/lib/notes-capture.js +9 -1
- package/workspace/lib/notes-final.js +38 -13
- package/workspace/lib/notes-replay.js +125 -0
- package/workspace/lib/notes-screen.js +180 -0
- package/workspace/lib/notes-whisper.js +10 -3
- package/workspace/lib/outbox-store.js +10 -6
- package/workspace/lib/pmos-verbs.js +3 -1
- package/workspace/lib/proactive-cli.js +7 -0
- package/workspace/lib/proactive.js +15 -2
- package/workspace/lib/records-dir.js +29 -0
- package/workspace/lib/repo-script.js +36 -0
- package/workspace/lib/report-dashboards.js +5 -2
- package/workspace/lib/report-frame.js +122 -0
- package/workspace/lib/report-schedules.js +113 -0
- package/workspace/lib/route-modules.js +9 -2
- package/workspace/lib/routes-boards.js +734 -0
- package/workspace/lib/routes-brief-page.js +13 -4
- package/workspace/lib/routes-briefing.js +22 -11
- package/workspace/lib/routes-c3-init.js +9 -8
- package/workspace/lib/routes-c3-life.js +2 -1
- package/workspace/lib/routes-c3-reports.js +99 -15
- package/workspace/lib/routes-c3-strategy.js +7 -5
- package/workspace/lib/routes-c3-timeline.js +3 -2
- package/workspace/lib/routes-calendar-import.js +3 -2
- package/workspace/lib/routes-calendar-views.js +9 -4
- package/workspace/lib/routes-doc-health.js +2 -1
- package/workspace/lib/routes-doc-sheet.js +2 -1
- package/workspace/lib/routes-forecast.js +2 -1
- package/workspace/lib/routes-hooks.js +3 -2
- package/workspace/lib/routes-initiatives.js +2 -1
- package/workspace/lib/routes-intake.js +2 -1
- package/workspace/lib/routes-notes.js +55 -9
- package/workspace/lib/routes-outbox.js +23 -3
- package/workspace/lib/routes-pm-items.js +19 -6
- package/workspace/lib/routes-records.js +10 -7
- package/workspace/lib/routes-releases.js +2 -1
- package/workspace/lib/routes-report-facts.js +4 -3
- package/workspace/lib/routes-report-families.js +2 -1
- package/workspace/lib/routes-roadmap-ops.js +17 -14
- package/workspace/lib/routes-runs.js +6 -5
- package/workspace/lib/routes-settings.js +51 -0
- package/workspace/lib/routes-signals.js +3 -2
- package/workspace/lib/routes-stage-tools.js +220 -0
- package/workspace/lib/run-answers.js +2 -1
- package/workspace/lib/settings-store.js +226 -0
- package/workspace/lib/stage-tools.js +208 -0
- package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
- package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
- package/workspace/lib/work-evidence.js +73 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/briefing-deck.js +63 -17
- package/workspace/public/pm/briefing-model.js +36 -7
- package/workspace/public/pm/c3-ambient.js +100 -0
- package/workspace/public/pm/c3-brief-needs.js +26 -6
- package/workspace/public/pm/c3-brief-pulse.js +12 -4
- package/workspace/public/pm/c3-brief-today.js +56 -21
- package/workspace/public/pm/c3-brief.js +2 -1
- package/workspace/public/pm/c3-calendar.js +161 -42
- package/workspace/public/pm/c3-calpop.js +5 -5
- package/workspace/public/pm/c3-demo-boards.js +649 -0
- package/workspace/public/pm/c3-demo-brief-page.js +1 -1
- package/workspace/public/pm/c3-demo-cal4.js +3 -2
- package/workspace/public/pm/c3-demo-cal5.js +52 -0
- package/workspace/public/pm/c3-demo-data.js +17 -27
- package/workspace/public/pm/c3-demo-rep3.js +46 -5
- package/workspace/public/pm/c3-demo.js +2 -1
- package/workspace/public/pm/c3-dock.js +1 -1
- package/workspace/public/pm/c3-ib-docs.js +80 -0
- package/workspace/public/pm/c3-ib-initiatives.js +134 -0
- package/workspace/public/pm/c3-ib-items.js +201 -0
- package/workspace/public/pm/c3-ib-releases.js +51 -0
- package/workspace/public/pm/c3-ib-sprints.js +55 -0
- package/workspace/public/pm/c3-ib-triage.js +178 -0
- package/workspace/public/pm/c3-ii-tabs.js +240 -0
- package/workspace/public/pm/c3-ini-records.js +197 -0
- package/workspace/public/pm/c3-ini-timeline.js +278 -0
- package/workspace/public/pm/c3-ini-ui.js +229 -0
- package/workspace/public/pm/c3-ini-work.js +139 -0
- package/workspace/public/pm/c3-initiatives.css +218 -0
- package/workspace/public/pm/c3-initiatives.js +741 -0
- package/workspace/public/pm/c3-plan.js +2 -2
- package/workspace/public/pm/c3-reports.css +47 -47
- package/workspace/public/pm/c3-reports.js +223 -185
- package/workspace/public/pm/c3-settings.css +74 -0
- package/workspace/public/pm/c3-settings.js +703 -0
- package/workspace/public/pm/c3-shell.js +102 -36
- package/workspace/public/pm/c3-terminal-icons.js +2 -2
- package/workspace/public/pm/c3-terminal.js +2 -2
- package/workspace/public/pm/calendar-views.js +1 -1
- package/workspace/public/pm/components.css +319 -213
- package/workspace/public/pm/console3.css +99 -23
- package/workspace/public/pm/dest-notes.js +26 -4
- package/workspace/public/pm/notes-screen-capture.js +165 -0
- package/workspace/public/pm/notes-screen-view.js +98 -0
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/pm-work-view.js +233 -146
- package/workspace/public/pm/shell-bell.js +3 -2
- package/workspace/public/pm/shell-recording.js +29 -8
- package/workspace/public/pm/tokens.css +2 -2
- package/workspace/public/pm/view-notes.js +46 -9
- package/workspace/public/pm/welcome.js +197 -89
- package/workspace/public/welcome.html +6 -4
- package/workspace/server.js +105 -6
- package/workspace/skills/day-check.skill +1 -1
- package/workspace/skills/jira-sync.skill +6 -4
- package/workspace/skills/morning-brief.skill +1 -1
- package/workspace/test-files.json +12 -3
- package/workspace/test-lists/c3-ib-items.json +9 -0
- package/workspace/test-lists/c3-ib-more.json +9 -0
- package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
- package/workspace/test-lists/c3-initiative-boards.json +6 -0
- package/workspace/test-lists/calendar-tidy-up.json +6 -0
- package/workspace/test-lists/console3-briefing.json +9 -0
- package/workspace/test-lists/console3-foundation.json +9 -0
- package/workspace/test-lists/console3-reports.json +3 -3
- package/workspace/test-lists/meeting-notes.json +10 -4
- package/workspace/test-lists/workspace-headless-jobs.json +5 -0
- package/workspace/test-lists/workspace-package-script-closure.json +6 -0
- package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
- package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
- package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
- package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
- package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
- package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
- package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
- package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
- package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
- package/workspace/lib/vendor/workspace/lib/state-root.js +0 -587
|
@@ -66,7 +66,8 @@ let unreadSeen = 0; // how many the person has already looked
|
|
|
66
66
|
let lastCount = 0;
|
|
67
67
|
let bellSheet = null;
|
|
68
68
|
let liveTimer = null; let liveDone = false;
|
|
69
|
-
|
|
69
|
+
/* b.notices: what a dashboard schedule made (routes-briefing.js); each goes to Reports > Drafts. */
|
|
70
|
+
const notifList = (b) => [...arrived, ...notifsOf(b), ...(b && Array.isArray(b.notices) ? b.notices : [])];
|
|
70
71
|
|
|
71
72
|
export function openBellList() {
|
|
72
73
|
if (bellSheet && bellSheet.root.isConnected) { bellSheet.close(); return bellSheet; }
|
|
@@ -128,7 +129,7 @@ export function mountBell(host, opts) {
|
|
|
128
129
|
};
|
|
129
130
|
rec.off = onBriefing((b) => {
|
|
130
131
|
rec.b = b;
|
|
131
|
-
const n = bellCount(b) + arrived.length;
|
|
132
|
+
const n = bellCount(b) + arrived.length + (b && Array.isArray(b.notices) ? b.notices.length : 0);
|
|
132
133
|
const bump = n > rec.count; rec.count = n; lastCount = n;
|
|
133
134
|
rec.sync();
|
|
134
135
|
if (bump) { badge.classList.remove('bump'); void badge.offsetWidth; badge.classList.add('bump'); }
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* (Stop asks first and offers "Pause instead"). All of it goes through pm/note-dock.js, which owns the real recorder, so the audio capture of
|
|
9
9
|
* pm/view-notes.js runs here without the main app's Notes place on screen. The chip opens the note in the main app, because console3 has no
|
|
10
10
|
* Notes place. While one meeting records, a red dot sits on the Calendar tab (`[data-c3-tab="cal"]`) and on a Notes tab if a frame draws one.
|
|
11
|
-
* On /c3.html?demo=1 the control drives a SIMULATED recorder (see below): it opens no microphone and saves nothing
|
|
12
|
-
* the simulation runs. Lifted: console3.html `.recg` and `recCtl` (lines 916 to 924, 4385 to 4400). The clock is the recorder's own start time.
|
|
11
|
+
* On /c3.html?demo=1 the control drives a SIMULATED recorder (see below): it opens no microphone and saves nothing; it shows the idle Record button and
|
|
12
|
+
* arrow, and the live group while the simulation runs. Lifted: console3.html `.recg` and `recCtl` (lines 916 to 924, 4385 to 4400). The clock is the recorder's own start time.
|
|
13
13
|
*/
|
|
14
14
|
import { el } from './dom.js';
|
|
15
15
|
import { button } from './primitives.js';
|
|
@@ -50,6 +50,25 @@ export const simDock = {
|
|
|
50
50
|
};
|
|
51
51
|
const live = new WeakMap();
|
|
52
52
|
|
|
53
|
+
/* Demo mode, idle: the mockup's Record button and the arrow beside it (the same two buttons the real control draws). Record starts the simulated recorder with an
|
|
54
|
+
* untitled note; the arrow lists the same choice and today's meetings that have not started. Nothing is opened or saved. */
|
|
55
|
+
function simIdle(d) {
|
|
56
|
+
const glyph = (dd) => { const sv = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); sv.setAttribute('class', 'ic sm'); sv.setAttribute('viewBox', '0 0 24 24'); sv.setAttribute('aria-hidden', 'true'); const pth = document.createElementNS('http://www.w3.org/2000/svg', 'path'); pth.setAttribute('d', dd); sv.appendChild(pth); return sv; };
|
|
57
|
+
const start = (title) => { if (d === simDock) simStart(title); };
|
|
58
|
+
const mic = button({ variant: 'ghost', label: [c3ic('mic', 'sm'), el('span', { class: 'c3rec__lbl' }, 'Record')], onClick: () => start('Untitled note'),
|
|
59
|
+
attrs: { title: 'Record an untitled note', 'aria-label': 'Record an untitled note', 'data-fr-rec-new': '' } });
|
|
60
|
+
const arrow = button({ variant: 'ghost', label: glyph('M6 9l6 6 6-6'), attrs: { title: 'Record for an upcoming meeting', 'aria-label': 'Record for an upcoming meeting', 'aria-haspopup': 'menu', 'data-fr-rec-upcoming': '' },
|
|
61
|
+
onClick: async (e) => {
|
|
62
|
+
const anchor = e.currentTarget;
|
|
63
|
+
const { menu } = await import('./primitives.js');
|
|
64
|
+
let evs = [];
|
|
65
|
+
try { const links = await import('./notes-links.js'); evs = links.upcomingWithoutNote(await links.upcomingToday()); } catch { evs = []; }
|
|
66
|
+
menu({ anchor, ariaLabel: 'Record a new meeting', items: [{ label: 'Untitled note', icon: 'plus', detail: 'not linked to a meeting', attrs: { 'data-fr-rec-untitled': '' }, onSelect: () => start('Untitled note') }, { sep: true },
|
|
67
|
+
...(evs.length ? evs.slice(0, 4).map((x) => ({ label: x.title, hint: x.time || '', attrs: { 'data-fr-rec-event': x.id || '' }, onSelect: () => start(x.title) })) : [{ label: 'No more meetings today', attrs: { 'data-fr-rec-none': '' }, onSelect: () => {} }])] });
|
|
68
|
+
} });
|
|
69
|
+
return [mic, arrow];
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
export function mountRecording(host, opts) {
|
|
54
73
|
if (!host) return;
|
|
55
74
|
disposeRecording(host);
|
|
@@ -58,8 +77,9 @@ export function mountRecording(host, opts) {
|
|
|
58
77
|
const d = (opts && opts.dock) || simDock;
|
|
59
78
|
const nowMs = () => (d.now ? d.now() : Date.now());
|
|
60
79
|
const grp = el('span', { class: 'recg', role: 'group', 'aria-label': 'Recording', hidden: true, 'data-shell': 'recording' });
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
const wrap = el('span', { class: 'c3rec', 'data-fr': 'record' }, grp); // the same wrapper the real control has, so the idle group takes its look
|
|
81
|
+
host.insertBefore(wrap, host.firstChild);
|
|
82
|
+
const rec = { grp: wrap, key: '', clock: null, tabs: [] };
|
|
63
83
|
const tabDot = (on) => {
|
|
64
84
|
for (const t of document.querySelectorAll(TAB)) {
|
|
65
85
|
let dot = t.querySelector('.shell-recdot');
|
|
@@ -78,15 +98,16 @@ export function mountRecording(host, opts) {
|
|
|
78
98
|
const key = [v.mode, st.id, v.title, v.dot].join(':');
|
|
79
99
|
if (key !== rec.key) {
|
|
80
100
|
rec.key = key;
|
|
81
|
-
grp.hidden =
|
|
101
|
+
grp.hidden = false; // the mockup shows the idle Record control too
|
|
82
102
|
grp.classList.toggle('paused', v.mode === 'paused');
|
|
83
|
-
grp.
|
|
84
|
-
|
|
103
|
+
grp.classList.toggle('idle', !v.shown);
|
|
104
|
+
grp.setAttribute('aria-label', v.shown ? (v.mode === 'recording' ? 'Recording ' : 'Paused: ') + v.title : 'New recording');
|
|
105
|
+
if (!v.shown) { rec.clock = null; grp.replaceChildren(...simIdle(d)); }
|
|
85
106
|
else {
|
|
86
107
|
rec.clock = el('span', { class: 'recclk', 'data-shell-rec-clock': '' });
|
|
87
108
|
const open = el('button', { type: 'button', title: (v.mode === 'recording' ? 'Recording: ' : 'Paused: ') + v.title + '. Open the live note', 'aria-label': 'Open the note: ' + v.title, 'data-shell-rec-open': '', onclick: () => d.openDock(st.id) },
|
|
88
109
|
el('span', { class: 'recdot' + (v.dot === 'static' ? ' static' : ''), 'data-dot': v.dot }), rec.clock, el('span', { class: 'rt' }, v.title),
|
|
89
|
-
|
|
110
|
+
el('span', { class: 'c3rec__eng', 'data-fr-rec-engine': '', title: 'The live transcript: engine and language (Notes settings). Demo value.' }, 'EN forced \u00b7 whisper'));
|
|
90
111
|
const kids = [open];
|
|
91
112
|
if (v.canMark) kids.push(btn('Mark this moment', 'highlighter', () => d.highlightNow(), 'data-shell-rec-mark'));
|
|
92
113
|
if (v.canPause) kids.push(btn('Pause', 'pause', () => d.pauseNow(), 'data-shell-rec-pause'));
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
--brand-mark:oklch(0.623 0.214 259.815);
|
|
64
64
|
--brand:var(--foreground);--brand-wash:color-mix(in oklab, var(--foreground) 8%, transparent);
|
|
65
65
|
--success:oklch(0.76 0.17 152);--warning:oklch(0.82 0.15 82);--info:oklch(0.79 0.11 195);
|
|
66
|
-
--purple:oklch(0.72 0.15 300);--destructive:oklch(0.704 0.191 22.216);
|
|
66
|
+
--purple:oklch(0.72 0.15 300);--focus-window:oklch(0.72 0.15 300);--destructive:oklch(0.704 0.191 22.216);
|
|
67
67
|
--term-bg:oklch(0.148 0.004 228.8);
|
|
68
68
|
--mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
69
69
|
--sans:"Space Grotesk",ui-sans-serif,system-ui,sans-serif;
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
--brand-mark:oklch(0.488 0.243 264.376);
|
|
92
92
|
--brand:var(--foreground);--brand-wash:color-mix(in oklab, var(--foreground) 8%, transparent);
|
|
93
93
|
--success:oklch(0.46 0.13 152);--warning:oklch(0.48 0.11 72);--info:oklch(0.47 0.09 195);
|
|
94
|
-
--purple:oklch(0.5 0.2 300);--destructive:oklch(0.5 0.21 27.325);
|
|
94
|
+
--purple:oklch(0.5 0.2 300);--focus-window:oklch(0.5 0.2 300);--destructive:oklch(0.5 0.21 27.325);
|
|
95
95
|
--term-bg:oklch(0.148 0.004 228.8);
|
|
96
96
|
}
|
|
97
97
|
@keyframes pmblink{0%,49%{opacity:1}50%,100%{opacity:0}}
|
|
@@ -47,6 +47,8 @@ import { mdRender } from './markdown.js';
|
|
|
47
47
|
import { icon } from './icons.js';
|
|
48
48
|
import { button, iconButton, field, row, sheet, empty, menu, toast, ensureComponentsCss } from './primitives.js';
|
|
49
49
|
import { toInt16, makeResampler, openCapture } from './audio-capture.js';
|
|
50
|
+
import { startScreen } from './notes-screen-capture.js';
|
|
51
|
+
import { screenStrip } from './notes-screen-view.js';
|
|
50
52
|
import { agendaHeard, peopleOf as contextPeople } from './notes-context.js';
|
|
51
53
|
|
|
52
54
|
const SAVE_MS = 700; // a pause this long in typing saves the note
|
|
@@ -72,6 +74,8 @@ let banner = null; // { tone, text }
|
|
|
72
74
|
let speech = null; // the browser recogniser: { mode: 'dictate'|'record', id, state, rec, interim, lang, msg, last }
|
|
73
75
|
let audio = null; // local Whisper capture: { id, state, sources, ctx, streams, started, clock, msg }
|
|
74
76
|
let engine = null; // { ws, whisper, jev, at }
|
|
77
|
+
/** Capture the shared screen (notes-screen-capture): on unless the PM switched it off on this machine. */
|
|
78
|
+
const screenOn = () => !(engine && engine.whisper && engine.whisper.screen === false);
|
|
75
79
|
let showList = false;
|
|
76
80
|
let inlineDraw = false; // the live block is drawing: the fields it shows are left in place (renderLive)
|
|
77
81
|
/** A node this view keeps across redraws (an editor field, the player). The pop-up patches itself (dom.js patchChildren), so
|
|
@@ -257,6 +261,9 @@ async function loadNote(ctx, id) {
|
|
|
257
261
|
if (editor && editor.id === id && dirty) { r.note.title = editor.title.value; r.note.body = editor.body.value; }
|
|
258
262
|
r.note.__audio = r.audio || null;
|
|
259
263
|
r.note.__recording = r.recording || null;
|
|
264
|
+
r.note.__shots = r.screen ? r.screen.frames || [] : [];
|
|
265
|
+
// a new line that points at the screen, or a decision or next step, keeps the picture on screen when it was said
|
|
266
|
+
if (audio && audio.id === id && audio.screen) audio.screen.hear(r.note.transcript || []);
|
|
260
267
|
r.note.bullets_state = r.bullets_state || null;
|
|
261
268
|
cache.set(id, r.note);
|
|
262
269
|
if (!had || sig(had) !== sig(r.note)) ctx.setState({});
|
|
@@ -365,7 +372,7 @@ async function setPrivate(ctx, n, priv) {
|
|
|
365
372
|
// a private press drops what is still buffered: nothing more is sent, transcribed or summarized
|
|
366
373
|
if (priv) await stopAll(ctx, { discard: true });
|
|
367
374
|
const r = await pmPost('/api/notes/private', { id: n.id, private: priv });
|
|
368
|
-
if (r && r.ok) { n.private = r.private; if (priv) { n.__recording = null; dropPlayer(); } touchRow(n); banner = priv ? { tone: 'ok', text: 'Marked private. Listening stopped, its audio was deleted, and nothing from this note goes to Jev or to any agent.' } : null; }
|
|
375
|
+
if (r && r.ok) { n.private = r.private; if (priv) { n.__recording = null; n.__shots = []; dropPlayer(); } touchRow(n); banner = priv ? { tone: 'ok', text: 'Marked private. Listening stopped, its audio was deleted, and nothing from this note goes to Jev or to any agent.' } : null; }
|
|
369
376
|
else banner = { tone: 'bad', text: (r && r.error) || 'not changed' };
|
|
370
377
|
ctx.setState({});
|
|
371
378
|
}
|
|
@@ -595,6 +602,14 @@ function paintNow() {
|
|
|
595
602
|
const k = noteLineAt(editor && editor.id === n.id ? editor.body.value : n.body, n.line_times, ms);
|
|
596
603
|
if (k >= 0) { const nl = document.querySelector('[data-nt-noteline="' + k + '"]'); if (nl) nl.classList.add('nt-now'); }
|
|
597
604
|
}
|
|
605
|
+
async function deleteShots(ctx, n) {
|
|
606
|
+
const sure = await askConfirm({ title: 'Delete this note\'s screenshots?', body: 'The screenshots of "' + (n.title || 'Untitled note') + '" are deleted from this machine. The notes, the transcript and the audio stay. This cannot be undone.', confirmLabel: 'Delete screenshots', danger: true });
|
|
607
|
+
if (!sure) return;
|
|
608
|
+
const r = await pmPost('/api/notes/screen-delete', { id: n.id });
|
|
609
|
+
if (r && r.ok) { n.__shots = []; banner = { tone: 'ok', text: 'The screenshots were deleted. The notes, the transcript and the audio are kept.' }; }
|
|
610
|
+
else banner = { tone: 'bad', text: (r && r.error) || 'the screenshots were not deleted' };
|
|
611
|
+
ctx.setState({});
|
|
612
|
+
}
|
|
598
613
|
async function deleteAudio(ctx, n) {
|
|
599
614
|
const sure = await askConfirm({ title: 'Delete this note\'s audio?', body: 'The recording of "' + (n.title || 'Untitled note') + '" is deleted from this machine. The notes and the transcript stay. This cannot be undone.', confirmLabel: 'Delete audio', danger: true });
|
|
600
615
|
if (!sure) return;
|
|
@@ -621,16 +636,18 @@ async function cleanNow(ctx, n) {
|
|
|
621
636
|
}
|
|
622
637
|
|
|
623
638
|
/* ── listening with local Whisper ─────────────────────────────────────────────────────────────── */
|
|
624
|
-
async function openSource(kind) {
|
|
639
|
+
async function openSource(kind, { screen = false } = {}) {
|
|
625
640
|
const md = navigator.mediaDevices;
|
|
626
641
|
if (kind === 'mic') return md.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 } });
|
|
627
642
|
// The meeting is often in Zoom or another app, not in this tab: offer the whole screen first (its system audio carries every app),
|
|
628
643
|
// keep every window and tab selectable, and leave PMOS's own tab out, because it has no sound. Chrome ignores a hint it does not know.
|
|
629
644
|
const s = await md.getDisplayMedia({ video: { displaySurface: 'monitor' }, audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false }, systemAudio: 'include', monitorTypeSurfaces: 'include', surfaceSwitching: 'include', selfBrowserSurface: 'exclude' });
|
|
630
645
|
const tracks = s.getAudioTracks();
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
646
|
+
// the picture of the same share is kept for screenshots when Capture the shared screen is on (notes-screen-capture); else only the sound is used
|
|
647
|
+
const video = screen ? s.getVideoTracks()[0] || null : null;
|
|
648
|
+
for (const t of s.getVideoTracks()) if (t !== video) t.stop();
|
|
649
|
+
if (!tracks.length) { if (video) video.stop(); throw Object.assign(new Error('The shared screen, window or tab has no audio. Share it again, and tick "Also share system audio" (entire screen) or "Share tab audio" (a tab).'), { noAudio: true }); }
|
|
650
|
+
return Object.assign(new MediaStream(tracks), { screenTrack: video });
|
|
634
651
|
}
|
|
635
652
|
/** The meeting source reaches PMOS as exact silence while the microphone is heard: the other people are not being recorded. Most often the
|
|
636
653
|
* sharing window had the wrong tab (PMOS's own tab has no sound). Say so once, and take the message back if sound arrives. */
|
|
@@ -651,14 +668,14 @@ async function startAudio(ctx, n, want) {
|
|
|
651
668
|
const opened = [];
|
|
652
669
|
// the meeting first, straight from the press: the browser lets a page share a screen only right after a click
|
|
653
670
|
for (const kind of want.slice().sort((x, y) => (x === 'meeting' ? -1 : y === 'meeting' ? 1 : 0))) {
|
|
654
|
-
try { opened.push([kind, await openSource(kind)]); }
|
|
671
|
+
try { opened.push([kind, await openSource(kind, { screen: kind === 'meeting' && screenOn() })]); }
|
|
655
672
|
catch (e) { a.msg = (a.msg ? a.msg + ' ' : '') + (kind === 'mic' ? 'The microphone was not opened' : 'The meeting audio was not opened') + ': ' + ((e && e.noAudio) ? e.message : ((e && e.message) || 'permission was refused') + '.'); }
|
|
656
673
|
}
|
|
657
674
|
await stopAll(ctx); await flushSave(ctx);
|
|
658
675
|
audio = a; if (banner && banner.audioMsg) banner = null; ctx.setState({});
|
|
659
676
|
if (!opened.length) { audio = null; banner = { tone: 'bad', text: a.msg || 'Nothing to listen to.' }; ctx.setState({}); return; }
|
|
660
677
|
const r = await pmPost('/api/notes/record', wsOf(ctx) ? { id: n.id, ws: wsOf(ctx) } : { id: n.id });
|
|
661
|
-
if (!r || !r.ok) { for (const [, s] of opened) for (const t of s.getTracks()) t.stop(); audio = null; banner = { tone: 'bad', text: (r && r.error) || 'listening did not start' }; ctx.setState({}); return; }
|
|
678
|
+
if (!r || !r.ok) { for (const [, s] of opened) { for (const t of s.getTracks()) t.stop(); if (s.screenTrack) s.screenTrack.stop(); } audio = null; banner = { tone: 'bad', text: (r && r.error) || 'listening did not start' }; ctx.setState({}); return; }
|
|
662
679
|
n.recorded_from = r.recorded_from;
|
|
663
680
|
// the clock counts the meeting time, as the line times do: Listen again carries on from where it stopped
|
|
664
681
|
a.started = Date.parse(r.clock_from || r.recorded_from) || a.started;
|
|
@@ -687,6 +704,14 @@ async function startAudio(ctx, n, want) {
|
|
|
687
704
|
});
|
|
688
705
|
for (const t of stream.getTracks()) t.addEventListener('ended', () => { if (audio !== a) return; a.ended++; if (a.ended >= a.streams.length) stopAudio(ctx); });
|
|
689
706
|
}
|
|
707
|
+
// screenshots of the shared screen, on the meeting clock, while this capture lasts (notes-screen-capture)
|
|
708
|
+
const shared = opened.find(([kind, s]) => kind === 'meeting' && s.screenTrack);
|
|
709
|
+
if (shared && a.cap) {
|
|
710
|
+
const track = shared[1].screenTrack;
|
|
711
|
+
a.screen = startScreen({ track, id: a.id, cap: a.cap, clock: () => Date.now() - a.started, post: (b) => pmPost('/api/notes/screen', b) });
|
|
712
|
+
// the PM stops sharing the picture only: the sound goes on, and the shots stop
|
|
713
|
+
track.addEventListener('ended', () => { if (a.screen) a.screen.stop(); });
|
|
714
|
+
} else if (shared) shared[1].screenTrack.stop();
|
|
690
715
|
a.state = 'on';
|
|
691
716
|
a.clock = setInterval(() => { tickClocks(a.started); checkSilence(ctx, a); }, 1000);
|
|
692
717
|
ctx.setState({});
|
|
@@ -727,6 +752,7 @@ async function stopAudio(ctx, { discard = false, resumable = true } = {}) {
|
|
|
727
752
|
audio = null;
|
|
728
753
|
if (banner && banner.silent) banner = null;
|
|
729
754
|
clearInterval(a.clock);
|
|
755
|
+
if (a.screen) a.screen.stop();
|
|
730
756
|
for (const s of a.streams) for (const t of s.getTracks()) { try { t.stop(); } catch { /* stopped */ } }
|
|
731
757
|
try { if (a.ctx) await a.ctx.close(); } catch { /* closed */ }
|
|
732
758
|
if (discard) { ctx.setState({}); return; }
|
|
@@ -855,6 +881,7 @@ export function highlightNow(ctx, id) {
|
|
|
855
881
|
if (at === null) return false;
|
|
856
882
|
if (highlight && highlight.state === 'listening') { hlOpen = true; ctx.setState({}); return true; }
|
|
857
883
|
highlight = { id: n.id, title: n.title || '', at, from: Math.max(0, at - HL_BEFORE_MS), state: 'listening', pressed: Date.now(), result: null, error: null };
|
|
884
|
+
if (audio && audio.id === n.id && audio.screen) audio.screen.cue(at, 'highlight'); // the picture on screen when the PM pressed it
|
|
858
885
|
hlOpen = true;
|
|
859
886
|
clearInterval(highlightTimer);
|
|
860
887
|
highlightTimer = setInterval(() => hlTick(ctx), 500);
|
|
@@ -1008,7 +1035,7 @@ async function chooseSources(ctx, n) {
|
|
|
1008
1035
|
}
|
|
1009
1036
|
async function setSwitch(ctx, patch) {
|
|
1010
1037
|
const ws = wsOf(ctx);
|
|
1011
|
-
if (!ws && patch.language === undefined && patch.model === undefined && patch.keep_audio === undefined && patch.clean_audio === undefined) return;
|
|
1038
|
+
if (!ws && patch.language === undefined && patch.model === undefined && patch.keep_audio === undefined && patch.clean_audio === undefined && patch.screen === undefined) return;
|
|
1012
1039
|
const r = await pmPost('/api/notes/switch', ws ? { ws, ...patch } : patch);
|
|
1013
1040
|
if (r && r.ok) engine = { ws, whisper: r.whisper, jev: r.jev, at: Date.now() };
|
|
1014
1041
|
ctx.setState({});
|
|
@@ -1048,7 +1075,13 @@ function openSettings(ctx, n) {
|
|
|
1048
1075
|
w && w.keep_audio !== false ? el('p', { class: 'nt-hint', 'data-nt-clean-hint': w.ffmpeg ? 'ready' : 'missing' }, w.ffmpeg
|
|
1049
1076
|
? (w.clean_audio === true ? 'After Stop, the replay gets a cleaned copy: less rumble and hiss, and an even loudness. The transcript is not changed.'
|
|
1050
1077
|
: 'Off. When on, the replay gets a cleaned copy after Stop: less rumble and hiss, and an even loudness.')
|
|
1051
|
-
: 'This needs ffmpeg, which was not found on this machine. Install it with brew install ffmpeg, then open Settings again.') : null
|
|
1078
|
+
: 'This needs ffmpeg, which was not found on this machine. Install it with brew install ffmpeg, then open Settings again.') : null,
|
|
1079
|
+
w ? field({ label: 'Capture the shared screen', kind: 'check', value: w.screen !== false,
|
|
1080
|
+
onCommit: (v) => setSwitch(ctx, { screen: v }).then(() => { h.close(); openSettings(ctx, n); }), attrs: { 'data-nt-screen-switch': '' } }) : null,
|
|
1081
|
+
w ? el('p', { class: 'nt-hint', 'data-nt-screen-hint': w.ocr ? 'ocr' : 'no-ocr' }, w.screen !== false
|
|
1082
|
+
? 'When you share the meeting\'s screen, a screenshot is kept each time the picture changes, on this machine, beside the transcript. Nothing is sent anywhere. '
|
|
1083
|
+
+ (w.ocr ? 'The text on each screenshot is read on this machine with tesseract.' : 'To also read the text on each screenshot, install tesseract (brew install tesseract).')
|
|
1084
|
+
: 'Off. Only the sound of the shared screen is used.') : null));
|
|
1052
1085
|
const fp = w && w.final;
|
|
1053
1086
|
if (fp) {
|
|
1054
1087
|
const have = [fp.transcriber === 'qwen' ? 'Qwen3-ASR' : fp.transcriber === 'parakeet' ? 'Parakeet' : fp.transcriber === 'whisper' ? 'Whisper with beam search' : null,
|
|
@@ -1081,6 +1114,7 @@ function openSettings(ctx, n) {
|
|
|
1081
1114
|
button({ label: busy === 'agent' ? 'Starting …' : 'Summary by agent', disabled: n.private || busy === 'agent', onClick: () => { h.close(); summarizeAgent(ctx, n); }, attrs: { 'data-nt-summarize-agent': '' } }),
|
|
1082
1115
|
n.__recording || (n.final_state && n.final_state.state === 'failed') ? button({ label: 'Transcribe again', disabled: n.private || finalPending(n), onClick: () => { h.close(); runFinal(ctx, n); }, attrs: { 'data-nt-final-again': '' } }) : null,
|
|
1083
1116
|
n.__recording && w && w.clean_audio === true && w.ffmpeg && !n.__recording.cleaned ? button({ label: 'Clean up this note\'s sound', disabled: n.private || listening(n), onClick: () => { h.close(); cleanNow(ctx, n); }, attrs: { 'data-nt-clean-now': '' } }) : null,
|
|
1117
|
+
(n.__shots || []).length ? button({ label: 'Delete this note\'s screenshots', variant: 'ghost', onClick: () => { h.close(); deleteShots(ctx, n); }, attrs: { 'data-nt-delete-shots': '' } }) : null,
|
|
1084
1118
|
n.__recording ? button({ label: 'Delete this note\'s audio', variant: 'ghost', onClick: () => { h.close(); deleteAudio(ctx, n); }, attrs: { 'data-nt-delete-audio': '' } }) : null,
|
|
1085
1119
|
button({ label: 'Delete', variant: 'ghost', onClick: () => { h.close(); deleteNote(ctx, n); }, attrs: { 'data-nt-delete': '' } }))));
|
|
1086
1120
|
return parts;
|
|
@@ -1597,6 +1631,7 @@ function noteColumn(ctx, n, ed) {
|
|
|
1597
1631
|
const bs = n.bullets_state;
|
|
1598
1632
|
const parts = [];
|
|
1599
1633
|
if (n.__audio && n.__audio.error && live) parts.push(el('p', { class: 'nt-hint nt-hint--bad', 'data-nt-audio-error': '' }, n.__audio.error));
|
|
1634
|
+
if (live && audio && audio.id === n.id && audio.screen) parts.push(el('p', { class: 'nt-hint', 'data-nt-screen-live': '' }, 'The shared screen is captured each time it changes, on this machine.'));
|
|
1600
1635
|
if (!live && n.final_notes) {
|
|
1601
1636
|
// the notes written after Stop replace the live draft (Run 9)
|
|
1602
1637
|
parts.push(...finalNotesSections(ctx, n), myNotes(ctx, n, ed), wisprSection(ctx, n));
|
|
@@ -1703,6 +1738,8 @@ export function renderBoard(inner, ctx) {
|
|
|
1703
1738
|
const pl = !listening(n) ? playerFor(ctx, n) : null;
|
|
1704
1739
|
if (pl) wrap.appendChild(el('div', { class: 'nt-replay', 'data-nt-part': 'replay' }, el('span', { class: 'nt-sec__h' }, 'Replay'), pl.el,
|
|
1705
1740
|
el('span', { class: 'nt-hint' }, 'Kept on this machine. Press a time in the transcript to play from there.')));
|
|
1741
|
+
// the screenshots of the shared screen, on the replay's clock (notes-screen-capture)
|
|
1742
|
+
if (!listening(n) && !n.private && (n.__shots || []).length) wrap.appendChild(screenStrip({ id: n.id, frames: n.__shots, seek: pl ? (ms) => seek(ctx, n, ms) : null, replay: pl ? pl.el : null }));
|
|
1706
1743
|
wrap.appendChild(el('div', { class: 'nt-body2' }, noteColumn(ctx, n, ed), transcriptColumn(ctx, n)));
|
|
1707
1744
|
if (pl) queueMicrotask(paintNow);
|
|
1708
1745
|
if (((n.agent && n.agent.started && !n.agent.markdown) || finalPending(n)) && !agentTimer) pollAgent(ctx, n.id);
|