create-pmos 0.527.0 → 0.529.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.
Files changed (58) hide show
  1. package/package.json +57 -29
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/adler-moments-test.js +4 -3
  7. package/workspace/briefing-premium-calendar-3-test.js +11 -9
  8. package/workspace/briefing-premium-calendar-test.js +1 -1
  9. package/workspace/changelog.json +56 -0
  10. package/workspace/ev-everywhere-test.js +2 -6
  11. package/workspace/fixtures/premium-viewer-fixtures.js +4 -2
  12. package/workspace/guide/home.md +7 -6
  13. package/workspace/guide/idle-lab.md +1 -1
  14. package/workspace/guide/notes.md +19 -7
  15. package/workspace/guide/reports-set-up.md +4 -1
  16. package/workspace/lib/bg-queue.js +10 -3
  17. package/workspace/lib/meeting-replay.js +11 -2
  18. package/workspace/lib/meeting-suggest.js +105 -0
  19. package/workspace/lib/meeting-terms.js +57 -17
  20. package/workspace/lib/okr-goals.js +23 -3
  21. package/workspace/lib/report-chat.js +3 -3
  22. package/workspace/lib/routes-lab.js +44 -2
  23. package/workspace/lib/routes-meeting-points.js +27 -3
  24. package/workspace/lib/routes-morning-goals.js +5 -1
  25. package/workspace/lib/routes-report-flow.js +35 -5
  26. package/workspace/news-viewers-test.js +2 -1
  27. package/workspace/package-lock.json +2 -2
  28. package/workspace/package.json +1 -1
  29. package/workspace/premium-vw-meet-test.js +17 -11
  30. package/workspace/product-lane-test.js +1 -1
  31. package/workspace/program-charter-test.js +2 -2
  32. package/workspace/public/pm/bd-ini-panel.js +1 -1
  33. package/workspace/public/pm/bd-span.js +10 -7
  34. package/workspace/public/pm/cozy-board.css +1 -0
  35. package/workspace/public/pm/cozy-lab-ins.js +13 -4
  36. package/workspace/public/pm/cozy-search.js +10 -17
  37. package/workspace/public/pm/cozy-settings.js +8 -2
  38. package/workspace/public/pm/cozy-today.css +30 -14
  39. package/workspace/public/pm/cozy-vw-mtg.css +122 -24
  40. package/workspace/public/pm/good-morning.js +30 -17
  41. package/workspace/public/pm/home-app.js +11 -1
  42. package/workspace/public/pm/home-data.js +4 -1
  43. package/workspace/public/pm/home-day.js +12 -5
  44. package/workspace/public/pm/premium-board.js +7 -5
  45. package/workspace/public/pm/premium-initiative-head.js +9 -1
  46. package/workspace/public/pm/premium-insights.js +5 -3
  47. package/workspace/public/pm/premium-meeting.js +2 -1
  48. package/workspace/public/pm/premium-mtg-replay.js +149 -73
  49. package/workspace/public/pm/premium-report-read.js +11 -4
  50. package/workspace/public/pm/premium-report-tab.js +33 -4
  51. package/workspace/public/pm/premium-vw-mtg.js +176 -53
  52. package/workspace/public/pm/program-model.js +2 -1
  53. package/workspace/public/pm/report-chat.js +1 -0
  54. package/workspace/public/pm/report-flow.js +7 -3
  55. package/workspace/public/premium-board.html +4 -4
  56. package/workspace/server.js +1 -1
  57. package/workspace/task-milestones-test.js +2 -2
  58. package/workspace/timeline-holds-test.js +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.527.0",
3
+ "version": "0.529.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -32,6 +32,62 @@
32
32
  "audit"
33
33
  ],
34
34
  "pmosChanges": [
35
+ {
36
+ "v": "0.529.0",
37
+ "at": "2026-11-04T13:14:44.790Z",
38
+ "f": 4,
39
+ "prs": [
40
+ {
41
+ "n": 1243,
42
+ "t": "A look change from a report's chat becomes its next look, kept with ✓ and redrawn into the open draft; the drafting session's answer shows",
43
+ "s": "A look change from a report's chat becomes its next look, kept with ✓ and redrawn into the open draft; the drafting session's answer shows",
44
+ "k": "fix"
45
+ },
46
+ {
47
+ "n": 1245,
48
+ "t": "The Lab's background record says in plain words what Claude did, what came out and when",
49
+ "s": "The Lab's background record says in plain words what Claude did, what came out and when",
50
+ "k": "fix"
51
+ },
52
+ {
53
+ "n": 1244,
54
+ "t": "Meeting player, Jev crop display and word fix A",
55
+ "s": "Meeting player, Jev crop display and word fix A",
56
+ "k": "new"
57
+ }
58
+ ]
59
+ },
60
+ {
61
+ "v": "0.528.0",
62
+ "at": "2026-11-04T12:00:44.809Z",
63
+ "f": 4,
64
+ "prs": [
65
+ {
66
+ "n": 1229,
67
+ "t": "Dates carry their year, and plain words across the workspace (workspace grill W4)",
68
+ "s": "Dates carry their year, and plain words across the workspace (workspace grill W4)",
69
+ "k": "fix"
70
+ },
71
+ {
72
+ "n": 1241,
73
+ "t": "Good morning on live data — a stale source said once, a quiet quarter, goal tags, say-it-once folds",
74
+ "s": "Good morning on live data — a stale source said once, a quiet quarter, goal tags, say-it-once folds",
75
+ "k": "fix"
76
+ },
77
+ {
78
+ "n": 1239,
79
+ "t": "The Lab's background record leaves out a chat's own runs",
80
+ "s": "The Lab's background record leaves out a chat's own runs",
81
+ "k": "fix"
82
+ },
83
+ {
84
+ "n": 1242,
85
+ "t": "Report reader no longer flickers while a draft is written",
86
+ "s": "Report reader no longer flickers while a draft is written",
87
+ "k": "fix"
88
+ }
89
+ ]
90
+ },
35
91
  {
36
92
  "v": "0.527.0",
37
93
  "at": "2026-11-04T11:15:45.443Z",
@@ -385,34 +441,6 @@
385
441
  "k": "new"
386
442
  }
387
443
  ]
388
- },
389
- {
390
- "v": "0.509.0",
391
- "at": "2026-11-03T14:23:32.696Z",
392
- "f": 4,
393
- "prs": [
394
- {
395
- "n": 1198,
396
- "t": "No initiative without an expected outcome",
397
- "s": "No initiative without an expected outcome",
398
- "k": "new"
399
- }
400
- ]
401
- },
402
- {
403
- "v": "0.508.0",
404
- "at": "2026-11-03T12:39:33.903Z",
405
- "f": 4,
406
- "prs": [
407
- {
408
- "n": 1196,
409
- "t": "Decision memory keeps the guard's audit in the per-user folder",
410
- "s": "Decision memory keeps the guard's audit in the per-user folder",
411
- "k": "fix",
412
- "h": "Decision checks stay private",
413
- "d": "The record of what the decision guard checked went to a shared place. Now it stays in your own folder."
414
- }
415
- ]
416
444
  }
417
445
  ]
418
446
  }
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@e3e4aeb7
1
+ kernel-source: pmos@96caad55
2
2
  assembled: 2026-11-04
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T13:14:41Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T13:14:41Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T13:14:41Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -4,7 +4,7 @@
4
4
  * The real Home from the real server, at 390 px with touch; the board and briefing are the fixtures, and GET /api/sources is stubbed with
5
5
  * Jira failed. POST /api/sources/refresh is recorded.
6
6
  * E1 the ears peek over the menu button while a call is the PM's, and the button's name says it; the menu open, no ears
7
- * F1 Your day opens on the rain cloud with Adler honest, "Jira isn't answering.", and no second face
7
+ * F1 Your day (Good morning) says "Jira isn't answering." in one quiet line with its read-again icon; no rain cloud
8
8
  * F2 its icon reads Jira again (POST /api/sources/refresh with source jira)
9
9
  * P1 a pull from the top of Your day lets Adler peek up, says it reads again, and reads Home again
10
10
  * S1 Settings > Updates: a face on the updates card; General: one face only
@@ -90,8 +90,9 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
90
90
 
91
91
  /* F1-F2 */
92
92
  await page.click('#hmList [data-hm-day]');
93
- const f1 = await until(() => page.evaluate(() => { const b = document.querySelector('#czViewer .yd-open'); if (!b || !b.querySelector('.k-rain')) return null; return { line: b.querySelector('.yd-ht b').textContent, faces: b.querySelectorAll('svg.ad-svg').length, sync: !!b.querySelector('[data-yd="sync"][aria-label="Read Jira again"]') }; }), 15000);
94
- ok('F1 Your day opens on the rain cloud, Adler honest, "Jira isn\'t answering.", and no second face', f1 && f1.line === 'Jira isn’t answering.' && f1.faces === 0 && f1.sync, JSON.stringify(f1));
93
+ // Good morning (batch 27 item 10, frames G1-G3): a failed source is one quiet line under the lead, never the lead and never the rain cloud
94
+ const f1 = await until(() => page.evaluate(() => { const b = document.querySelector('#czViewer .gm-lead .yd-open'); const q = b && b.querySelector('.gm-src'); if (!q) return null; return { quiet: q.querySelector('span').textContent, line: b.querySelector('.yd-ht b').textContent, rain: !!b.querySelector('.k-rain, .yd-scene'), sync: !!q.querySelector('[data-yd="sync"][aria-label="Read Jira again"]') }; }), 15000);
95
+ ok('F1 Your day says "Jira isn\'t answering." in one quiet line with its read-again icon; no rain cloud, and the lead is the day\'s own line', f1 && /^Jira isn’t answering\./.test(f1.quiet) && !/answering/.test(f1.line) && !f1.rain && f1.sync, JSON.stringify(f1));
95
96
  await shot(page, 'sync-fail');
96
97
  await page.click('#czViewer [data-yd="sync"]');
97
98
  const f2 = await until(() => posts.find((p) => p.path === '/api/sources/refresh'));
@@ -145,21 +145,23 @@ const json = (r, b, status = 200) => r.fulfill({ status, contentType: 'applicati
145
145
  const r = C.page;
146
146
  // batch 25 item 11 (MvC): no tabs; the kept points sit left, the timeline is docked, the lines are the Transcript part of the one strip
147
147
  const unfold = async (id) => { if (await r.evaluate((k) => document.querySelector(`#czViewer .vzr-fold [data-a="strip"][data-k="${k}"]`).getAttribute('aria-expanded') !== 'true', id)) await tap(r, `.vzr-fold [data-a="strip"][data-k="${id}"]`); };
148
- ok('R7 a kept recording has the replay card under the summary', await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vzp-card .vzp-pl') && !document.querySelector('#czViewer [role="tablist"]')), 15000));
149
- const playing = () => r.evaluate(() => /^Pause/.test((document.querySelector('#czViewer .vzp-dt [data-a="play"]') || { getAttribute: () => '' }).getAttribute('aria-label') || ''));
150
- ok('R7 Replay shows the player, the strip and the kept points (MtReplay)', await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vzp-dt .vzp-strip') && document.querySelectorAll('#czViewer .vzp-kr').length > 0)));
151
- await tap(r, '.vzp-dt [data-a="play"]');
148
+ ok('R7 a kept recording has the replay card under the summary', await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vzp-card .vzr-np') && !document.querySelector('#czViewer [role="tablist"]')), 15000));
149
+ const playing = () => r.evaluate(() => /^Pause/.test((document.querySelector('#czViewer .vzr-np:not(.vzr-ph1) [data-a="play"]') || { getAttribute: () => '' }).getAttribute('aria-label') || ''));
150
+ ok('R7 Replay shows the player, the strip and the kept points (MtReplay)', await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vzr-np:not(.vzr-ph1) .vzp-strip') && document.querySelectorAll('#czViewer .vzp-kr').length > 0)));
151
+ await tap(r, '.vzr-np:not(.vzr-ph1) [data-a="play"]');
152
152
  ok('R7 Play plays', await until(playing));
153
- const pos = () => r.evaluate(() => (document.querySelector('#czViewer .vzp-dt [data-clk="pos"]') || {}).textContent || '');
153
+ const pos = () => r.evaluate(() => (document.querySelector('#czViewer .vzr-np:not(.vzr-ph1) [data-clk="pos"]') || {}).textContent || '');
154
154
  const p0 = await pos();
155
155
  ok('R7 the playhead keeps time on its own clock', await until(async () => (await pos()) !== p0 && /^00:0[1-9]$/.test(await pos()), 5000), await pos());
156
- await tap(r, '.vzp-dt [data-a="play"]');
156
+ await tap(r, '.vzr-np:not(.vzr-ph1) [data-a="play"]');
157
157
  const p1 = await pos();
158
158
  await r.waitForTimeout(600); // longer than the playhead's 250 ms step: a paused playhead does not move
159
159
  ok('R7 Pause stops the playhead', (await pos()) === p1 && !(await playing()), p1 + ' / ' + await pos());
160
- await r.$eval('#czViewer .vzp-dt .vzp-th[data-a="seekto"]', (b) => b.click());
161
- ok('R7 a capture plays from its time, with the topic of that time', await until(async () => /^03:1\d$/.test(await pos()) && await r.evaluate(() => /Viewer beside the chat/.test((document.querySelector('#czViewer .vzp-top[aria-current="true"]') || {}).textContent || ''))), await pos());
162
- await tap(r, '.vzp-dt [data-a="play"]');
160
+ await tap(r, '.vzr-np:not(.vzr-ph1) [data-a="caps"]'); // batch 27 item 12 (MpD4): the captures row is hidden until its icon is pressed
161
+ await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vzr-np:not(.vzr-ph1) .vzp-th[data-a="seekto"]')));
162
+ await r.$eval('#czViewer .vzr-np:not(.vzr-ph1) .vzp-th[data-a="seekto"]', (b) => b.click());
163
+ ok('R7 a capture plays from its time, with the chapter of that time named above the bar (MpD1)', await until(async () => /^03:1\d$/.test(await pos()) && await r.evaluate(() => /Viewer beside the chat/.test((document.querySelector('#czViewer .vzr-np:not(.vzr-ph1) .vzr-nt b') || {}).textContent || ''))), await pos());
164
+ await tap(r, '.vzr-np:not(.vzr-ph1) [data-a="play"]');
163
165
  /* R12 every line: the Transcript fold seeks the replay */
164
166
  await unfold('tr');
165
167
  ok('R12 the Transcript lists every line, each with its time to play from', await until(async () => /Saved views move in their own work-item\./.test(await text(r)) && !!(await r.$('#czViewer [data-a="trseek"][data-t="760000"]'))), (await text(r)).slice(0, 300));
@@ -102,7 +102,7 @@ const json = (r, b, status = 200) => r.fulfill({ status, contentType: 'applicati
102
102
 
103
103
  /* C8 a kept recording offers Replay */
104
104
  const R = await open('/premium-viewer.html?open=meeting&note=n1');
105
- ok('C8 a kept recording offers Replay, and the card reads its replay', await until(() => R.page.evaluate(() => !!document.querySelector('#czViewer .vzp-card .vzp-pl')), 15000) && R.S.gets.some((g) => /meeting-replay\?note=n1/.test(g)));
105
+ ok('C8 a kept recording offers Replay, and the card reads its replay', await until(() => R.page.evaluate(() => !!document.querySelector('#czViewer .vzp-card .vzr-np')), 15000) && R.S.gets.some((g) => /meeting-replay\?note=n1/.test(g)));
106
106
  await R.ctx.close();
107
107
 
108
108
  /* C3 honest states */
@@ -1,4 +1,60 @@
1
1
  [
2
+ {
3
+ "v": "0.529.0",
4
+ "at": "2026-11-04T13:14:44.790Z",
5
+ "f": 4,
6
+ "prs": [
7
+ {
8
+ "n": 1243,
9
+ "t": "A look change from a report's chat becomes its next look, kept with ✓ and redrawn into the open draft; the drafting session's answer shows",
10
+ "s": "A look change from a report's chat becomes its next look, kept with ✓ and redrawn into the open draft; the drafting session's answer shows",
11
+ "k": "fix"
12
+ },
13
+ {
14
+ "n": 1245,
15
+ "t": "The Lab's background record says in plain words what Claude did, what came out and when",
16
+ "s": "The Lab's background record says in plain words what Claude did, what came out and when",
17
+ "k": "fix"
18
+ },
19
+ {
20
+ "n": 1244,
21
+ "t": "Meeting player, Jev crop display and word fix A",
22
+ "s": "Meeting player, Jev crop display and word fix A",
23
+ "k": "new"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "v": "0.528.0",
29
+ "at": "2026-11-04T12:00:44.809Z",
30
+ "f": 4,
31
+ "prs": [
32
+ {
33
+ "n": 1229,
34
+ "t": "Dates carry their year, and plain words across the workspace (workspace grill W4)",
35
+ "s": "Dates carry their year, and plain words across the workspace (workspace grill W4)",
36
+ "k": "fix"
37
+ },
38
+ {
39
+ "n": 1241,
40
+ "t": "Good morning on live data — a stale source said once, a quiet quarter, goal tags, say-it-once folds",
41
+ "s": "Good morning on live data — a stale source said once, a quiet quarter, goal tags, say-it-once folds",
42
+ "k": "fix"
43
+ },
44
+ {
45
+ "n": 1239,
46
+ "t": "The Lab's background record leaves out a chat's own runs",
47
+ "s": "The Lab's background record leaves out a chat's own runs",
48
+ "k": "fix"
49
+ },
50
+ {
51
+ "n": 1242,
52
+ "t": "Report reader no longer flickers while a draft is written",
53
+ "s": "Report reader no longer flickers while a draft is written",
54
+ "k": "fix"
55
+ }
56
+ ]
57
+ },
2
58
  {
3
59
  "v": "0.527.0",
4
60
  "at": "2026-11-04T11:15:45.443Z",
@@ -101,12 +101,8 @@ const put = (root, rel, text) => { fs.mkdirSync(path.dirname(path.join(root, rel
101
101
  const sl = await until(async () => { const r = await pg.evaluate(() => [...document.querySelectorAll('#evRes .ev-mi[data-ev-i]')].map((b) => b.textContent)); return r.length && !r.some((t) => /Usage billing PRD/.test(t)) ? r : null; });
102
102
  ok('S1 the Slides chip keeps only the deck', !!sl && sl.some((t) => /Billing launch deck/.test(t)), JSON.stringify(sl));
103
103
  await pg.click('#evChips [data-ev-kind="all"]');
104
- await pg.click('[data-ev-wt]');
105
- const wts = await until(() => pg.evaluate(() => [...document.querySelectorAll('.ev-wtm label')].map((l) => l.textContent)));
106
- ok('S2 the worktree chip lists main and the billing-spec worktree', !!wts && wts.some((t) => /^main/.test(t)) && wts.some((t) => /billing-spec/.test(t)), JSON.stringify(wts));
107
- await pg.click('.ev-wtm input[data-ev-wt-id]');
108
- ok('S2 leaving the worktree out drops its draft', !!(await until(() => pg.evaluate(() => { const r = [...document.querySelectorAll('#evRes .ev-mi')].map((b) => b.textContent); return r.some((t) => /Usage billing PRD/.test(t)) && !r.some((t) => /Billing spec draft/.test(t)); }))));
109
- await pg.click('.ev-wtm input[data-ev-wt-id]'); // back in, so the next run of this suite starts the same
104
+ // workspace grill W4: no worktree chip; Search reads every worktree and the footer says so in the PM's words
105
+ ok('S2 no worktree chip, and the footer says "Searches your work"', await pg.evaluate(() => !document.querySelector('[data-ev-wt]') && /Searches your work/.test((document.querySelector('.ev-sp-ft') || {}).textContent || '')));
110
106
  await until(() => pg.evaluate(() => [...document.querySelectorAll('#evRes .ev-mi')].some((b) => /Billing spec draft/.test(b.textContent))));
111
107
  await pg.click('#evRes .ev-mi:has-text("Billing spec draft")');
112
108
  ok('S3 a draft opens in the Viewer, read from its worktree', !!(await until(() => pg.evaluate(() => { const v = document.querySelector('#czViewer:not([hidden])'); return v && /Usage meters per seat/.test(v.textContent); }))), await pg.evaluate(() => (document.querySelector('#czViewer') || {}).textContent || ''));
@@ -84,8 +84,10 @@ function install(page, o = {}) {
84
84
  page.route('**/api/notes/series?*', (r) => json(r, { ok: true, series: { key: 'viewer-sync', meetings: [{ id: 'n0', at: '2026-10-15T10:00:00Z', decided: 1, steps: 3 }, { id: 'n1', at: W.NOTE.meeting.start, decided: 2, steps: 3 }] } })),
85
85
  page.route('**/api/notes/search*', (r) => json(r, { ok: true, ids: ['n4'], hits: [{ id: 'n4', snip: '…which teams use saved views for teams every week…', open: true }] })),
86
86
  page.route('**/api/meeting-points/*', (r) => { // a correction and its Undo (the answers lib/meeting-learn.js gives)
87
- const b = post(r); if (/forget$/.test(new URL(r.request().url()).pathname)) { S.terms = (S.terms || []).filter((x) => x.id !== b.lesson); return json(r, { ok: true }); }
88
- if (/term$/.test(new URL(r.request().url()).pathname)) { const t = { id: 't0123456789', note: b.note, heard: b.heard, means: b.means }; S.terms = (S.terms || []).concat([t]); return json(r, { ok: true, term: t, terms: S.terms }); }
87
+ if (/suggest$/.test(new URL(r.request().url()).pathname)) return json(r, { ok: true, suggest: S.suggest || null }); // batch 27 item 12: the ✦ spelling (none unless a test sets one)
88
+ const b = post(r); if (/keep$/.test(new URL(r.request().url()).pathname)) { S.terms = (S.terms || []).map((x) => (x.id === b.lesson ? { ...x, keep: b.keep ? 'yes' : 'no' } : x)); return json(r, { ok: true, term: S.terms.find((x) => x.id === b.lesson), terms: S.terms }); }
89
+ if (/forget$/.test(new URL(r.request().url()).pathname)) { S.terms = (S.terms || []).filter((x) => x.id !== b.lesson); return json(r, { ok: true }); }
90
+ if (/term$/.test(new URL(r.request().url()).pathname)) { const t = { id: 't0123456789', note: b.note, heard: b.heard, means: b.means, keep: 'ask' }; S.terms = (S.terms || []).concat([t]); return json(r, { ok: true, term: t, terms: S.terms }); }
89
91
  const phrase = require('../lib/meeting-learn.js').phraseOf(b.orig && b.orig.text, b.text);
90
92
  S.edits = { ...(S.edits || {}), [b.id]: { kind: b.kind, text: b.text, orig: b.orig } };
91
93
  return json(r, { ok: true, edits: S.edits, lesson: b.learn === false ? null : { id: 'l0123456789', phrase, kind: b.kind, meaning: b.text, orig: b.orig }, check: [] });
@@ -17,17 +17,17 @@ Home, the Board, Insights and the Viewer use one typeface, Plus Jakarta Sans, wi
17
17
  - Press **Good morning** to open **Your day** in the Viewer. This is the same on a computer and on a phone. To open the calendar, press the calendar icon in Your day's header. A program's milestone changes that wait on you are the first line of the program's card.
18
18
  - A blue dot after **Good morning** means that something is new for you. It is under **Since yesterday** in Your day.
19
19
  - Your day is the **Good morning** page. It shows the date and greeting, then the quarter, then two columns. On a computer it opens wide.
20
- - **The quarter.** One bar with all goals of the products Home reads: reached, on track, still reading, at risk and will miss, each with its count. A black tick marks your line, for example "line 18". One sentence says how many goals are on track or better, and one line says why. **By product** opens Intelligence. With no goals, there is no bar. With no line, there is no tick, and Claude can propose a line with ✦: press ✓ to keep it, or ✕ to not set one this quarter. A product that could not be read is named and is not counted.
21
- - **The left column.** Claude's lead, with one line of what Claude did for you. **Your call**: the one decision that needs you, and what Yes does when that is known. **Your move**: the top 3 items of the ranked list, each with the goal it moves and its due day (orange only when late). Then one line: **N more in Your move**, **N drafted for you** and **N can wait**. Press one to open its fold under the page. **Since yesterday**: up to 3 new items, then **New for you · N** to see all of them. ✓✓ marks all of them seen.
20
+ - **The quarter.** One bar with all goals of the products Home reads: reached, on track, still reading, at risk and will miss, each with its count. A black tick marks your line, for example "line 18". One sentence says how many goals are on track or better, and one line says why. When no goal has a reading yet, the sentence says that instead, for example "3 goals, none read yet." **By product** opens Intelligence. With no goals, there is no bar. With no line, there is no tick, and Claude can propose a line with ✦: press ✓ to keep it, or ✕ to not set one this quarter. A product that could not be read is named and is not counted.
21
+ - **The left column.** Claude's lead, with one line of what Claude did for you. When a source such as Jira cannot be read, one quiet line under the lead says so and what it shows instead, with an icon to read it again. The source never takes the lead's place. **Your call**: the one decision that needs you, and what Yes does when that is known. **Your move**: the top 3 items of the ranked list, each with the goal it moves and its due day (orange only when late). An item shows its goal when its initiative is anchored to a key result that a goal reads. Then one line: **N more in Your move**, **N drafted for you** and **N can wait**. Press one to open its fold under the page. A fold shows only after you press its line, so it is not said twice. **Since yesterday**: up to 3 new items, then **New for you · N** to see all of them. ✓✓ marks all of them seen.
22
22
  - **The right column.** Today's agenda, not to scale: a line at the time now, each meeting with its prep note, ✦ picks in free time, and "3 h free" for a long free stretch. The meeting where your call is decided has an orange edge. Under it, **Notes since yesterday**: meeting notes with their main lines, and recordings with no initiative. When Claude finds the initiative a recording belongs to, press the link icon to link it.
23
23
  - **In the hour before a meeting**, the meeting's prep leads: what to agree, the points, and the record icon. The quarter shows only when it moved since the morning. Past meetings fade, with their notes under them.
24
24
  - **On a phone or in a narrow Viewer**, the page is one column: the quarter, the lead, your call, Your move, the agenda, then Since yesterday.
25
- - Under the page, the folds stay: the rest of **Your move**, **Drafted for you**, **Can wait**, **Now** (on a phone) and **This week** (the Weekly review).
25
+ - Under the page are **Now** (on a phone) and **This week** (the Weekly review). The rest of **Your move**, **Drafted for you** and **Can wait** open there from their line.
26
26
  - **Drafted for you** lists what Claude drafted today and the weekly update. Press a row to open it: a finished draft opens what Claude wrote, and the weekly update opens the update.
27
27
  - Your day does not show the meeting on now or the next meeting. Home's meeting line shows them. On a phone, the **Now** card does not show while a meeting is in progress.
28
28
  - When a conversation in Mentions waits on your answer, Your day names the oldest one under its first line: who asked, where, how many more wait, and what they asked. Press its icon to open that conversation in Mentions.
29
29
  - **Since yesterday** shows only when something is new: tasks given to you, and work that finished while you were away. It shows the 3 newest items, then **New for you · N**. Press an item to open it in the Viewer. Press **New for you · N** to see all of them. Press ✓✓ in its header to mark all of them seen. An item leaves when you open it or mark it seen. Mentions are not here: **Mentions** shows each one once.
30
- - After 17:00, Your day is the end of the day. The quarter says how it moved since the morning. **Today made possible** lists what moved today, and **Still yours** lists what is left. The right column shows tomorrow's meetings.
30
+ - After 17:00, Your day is the end of the day. The quarter shows only when it moved since the morning, and then says how. **Today made possible** lists what moved today. Decisions caught in today's notes count only after you confirm them; until then one line says how many wait, and it opens your notes. **Your call** stays while it is open. **Still yours** lists what is left. The right column shows tomorrow's meetings.
31
31
  - A **program** comes first, above the products. Its header shows the program icon, its name, the next milestone (◆ and the date) and how many of its initiatives are ready for that milestone. Inside it, the program's own tasks come first, then its initiatives under a small heading for each product. Each initiative shows once. Press the program's name to open the program in the Viewer. The chat then talks about the program. Press the chevron to close or open the group.
32
32
  - The **product picker** is the chip on the top line. It shows the tiles of the ticked products, overlapped, with **+N** after 3, or **All** when every product is ticked. It shows even with one product. A git worktree of a repository is not a product: it does not appear in the list. Its repository stands for it. Hold the pointer on it to read the names. Press it to open the list: **All products**, then each product with its tile, its name and one line (for example "3 initiatives · 1 of 2 goals"), then **Add a product**. Press a product to tick or untick it; the list stays open, and the last ticked product stays ticked. The pick applies when the list closes (Escape, a press outside, or the chip again). On a phone the list is a sheet at the bottom. The pick is one saved setting (`ws.briefing.include`) that Home, the Board and Insights share, so a product that you untick on one page goes from all three. With **All** ticked, a product that you add later is ticked too. The **Product** row in Settings opens the same list.
33
33
  - A program with no open initiative yet still shows, with "No initiatives yet" or "No open initiatives". A program shows only when it holds work of a ticked product. Home reads again when you come back to its tab, so a program made on the Board shows at once.
@@ -162,8 +162,9 @@ Some features are still not designed. Home keeps each one usable as a plain card
162
162
  ## Search, Guide and Settings
163
163
 
164
164
  - **Guide** (the **?** icon in the menu) opens this guide in the Viewer.
165
- - **Search** (Ctrl K or ⌘K, or `/`) finds initiatives, documents, sessions and files in the repository and its worktrees.
166
- - **Settings** holds the workspace settings. **General** holds the theme (Light, Dark or Auto). **Connectors** shows when PMOS last read each source. A source that is old or failed has an amber dot and ↻ to read it again. ↻ in the Connectors header reads every source again. While a source is old, an amber box at the top of Settings names it, with ↻.
165
+ - **Search** (Ctrl K or ⌘K, or `/`) finds initiatives, documents, sessions and files in your work. It always reads the repository and all its worktrees, so it has no worktree picker.
166
+ - **Dates** show the year only when the year is not this year, for example "Jun 23 ’27". New for you shows a due date as "due 25 Nov".
167
+ - **Settings** holds the workspace settings. **General** holds the theme (Light, Dark or Auto) and **Product**, which names the product the page reads. **Connectors** shows when PMOS last read each source. A source that is old or failed has an amber dot and ↻ to read it again. ↻ in the Connectors header reads every source again. While a source is old, an amber box at the top of Settings names it, with ↻.
167
168
 
168
169
  ## The other pages
169
170
 
@@ -39,7 +39,7 @@ The Lab shows only once there is something to show. Top to bottom:
39
39
 
40
40
  - One ✦ line for the week: the idle time Claude used, how many results you kept, and how many findings fit no initiative.
41
41
  - The findings that fit no initiative, each with ✓ and ✕.
42
- - **What Claude did in the background**: every background job this week, not only the idle lab. A result that waits on an initiative is a link there. It is not a second copy.
42
+ - **What Claude did in the background**: every background job this week, not only the idle lab. Each row says in plain words what Claude did, why it ran and what came out, and when. For example: "Checked Inline login for eShop for risks · monthly check · 3 findings waiting · Sat 00:32", or "Redrew TSC Monthly Product Update in its saved look · 19:37". **See what it found** shows only when a result waits on the initiative. It opens the initiative, where the result is. It is not a second copy.
43
43
  - **You kept**: how many prototypes and scans you kept.
44
44
  - **Next time you step away**: what Claude plans to do next. Claude picks these by itself. To skip one, say so in chat.
45
45
 
@@ -2,7 +2,7 @@
2
2
  type: Guide
3
3
  title: "Guide: notes"
4
4
  description: "How to take notes in a meeting: one note top to bottom, typed notes as timed lines, key points written each minute and labelled by Jev, delete a note; final notes written after Stop from the whole recording, with speakers and a quote for each item, decisions with their why and who, next steps with an open question, a discussion paragraph, and Wispr Flow's notes merged in and fetched by hand when the fetch fails; listen to your microphone and the meeting on this machine, see decisions, actions and questions caught as they are said, check them against the repository for conflicts, capture highlights during the meeting, create tasks from next steps, correct and tag lines, see what changed in your notes, replay the meeting, see screenshots of the shared screen beside the transcript, link a note to its meeting and its initiative, and get a summary."
5
- timestamp: 2026-11-03
5
+ timestamp: 2026-11-04
6
6
  ---
7
7
 
8
8
  # Guide: notes
@@ -245,19 +245,31 @@ A meeting with a recording opens wide, in two columns. Press **Back to the side*
245
245
 
246
246
  - **Left:** the summary (two lines; press **Read it all** for the rest), then the kept points. Each point is one line. The icon shows its kind: a green dot is a decision, a box is a next step, a question mark is an open question. The header counts each kind. A next step said within two minutes after a decision, in the same topic, shows under that decision, with **Make a task**.
247
247
  - **Confirm:** press **✓** on a row to confirm it (press it again to undo). **Confirm N** in the header confirms every point that is left.
248
- - **Right:** press a row to see its evidence: the screen at that moment, the quote, and the screen captures near it. Press **▶** to play from the point. The pen opens the edit card. **Not right** moves the point to **Left out**.
248
+ - **Right:** one player. The screen capture at the playing time is at the top. Under it is the name of the chapter that plays, with **chapter N of M**. The bar under the name shows each chapter as one part, with a gap between parts, and an icon for each kept point in its chapter. Move the pointer over the bar to see the capture and the chapter at that place. Press the bar to play from there.
249
+ - **Controls:** back 15 seconds, play or pause, and forward 15 seconds, with **‹** and **›** for the previous and next chapter. The speed (1×, 1.5×, 2×) is at the left. At the right, the captures icon shows or hides the row of screen captures, and the chapters icon opens the list of chapters. Press a chapter in the list to play from its start. Escape closes the list.
250
+ - **Caption:** under the controls, the line said at the playing time shows larger, with the speaker. The line before it and the line after it show lighter.
251
+ - **A kept point:** press a row to play from that point. The selected row shows the pen (it opens the edit card) and **Not right** (it moves the point to **Left out**).
249
252
  - **Under the points:** one row of parts. Press one to open it: **Left out**, **Next steps**, **Since the last one**, **Facts mentioned**, **Names and terms**, **Prep and material** and **Transcript**.
250
- - **Bottom:** the timeline stays in view, with the full name of each chapter and a dot for each kept point.
251
253
 
252
- The captures row shows every kept screen capture. It does not yet show only the shared screen: that needs Jev for Images, which PMOS does not have yet.
254
+ The captures row is hidden until you press the captures icon. It shows the five captures near the playing time, and **+N more** shows the rest.
255
+
256
+ When Jev is on for the repository (Settings › **Label lines with Jev**) and Jev gave a box for a capture, the player and the captures row show only the slide or document in that box. The meeting window and the faces are cut off, and a capture that shows only faces is not in the captures row. The icon at the top right of the picture shows the whole frame. When Jev is off, or gave no box, you see the whole frame. Note: PMOS does not yet send the captures to Jev, so for now every capture shows the whole frame.
253
257
 
254
258
  ### Fix a word that was heard wrong
255
259
 
256
- In the summary, a quote, **Left out** or the transcript, select the word (double-click it, or drag over up to four words). The word becomes a field. Type the right spelling and press **✓** or Enter. The word shows fixed in every place this meeting shows it, and the message says how many places. **Undo** puts it back. **Names and terms** lists the people of the invite and the words you fixed.
260
+ Select a word or a few words anywhere in the meeting's notes: a kept point, the summary, **Next steps**, **Facts mentioned**, **Left out**, the caption or the transcript. You can also double-click one word. A small box opens under the words:
261
+
262
+ - **Heard as “…”** says what was heard. The field holds the fix. Type the right spelling and press **✓** or Enter. Escape closes the box and changes nothing.
263
+ - **✦ Claude suggests …** shows only when a real match was found: a fix you made before, a name in the meeting's invite (its title, agenda, material or people), or a word that the workspace's repository writes. The line names where the spelling came from. Press a spelling to put it in the field. When nothing matches, no suggestion shows.
264
+ - **N places in this meeting** counts where the words show, by kind: kept points, next steps, facts, **Left out** and transcript lines.
265
+
266
+ On a phone, the box opens at the bottom of the screen.
267
+
268
+ After the fix, the words show fixed in every place this meeting shows them, and the message says **Fixed in N places**, with **Undo**. The fix shows in **Names and terms**: the word, what was heard, the places, and an Undo icon.
257
269
 
258
- Later notes spell the word your way: the fix goes to your agent with the notes it writes. In other meetings, the same heard words are underlined. Press one to fix it there too, with your fix already typed.
270
+ **Names and terms** also asks **Remember “…” for your next meetings?** Press **✓** to remember it: later notes spell the word your way (the fix goes to your agent with the notes it writes), and in other meetings the same heard words are underlined, with your fix ready. Press **✕** to keep the fix in this meeting only. Until you press **✓**, the fix is for this meeting only. A fix made before this change stays remembered.
259
271
 
260
- The note's own files do not change. The fix shows where PMOS draws the note.
272
+ The note's own files do not change. The fix shows where PMOS draws the note. A one-time hint under the kept points says how to select a word. It goes away after your first fix.
261
273
 
262
274
  ### Fix or delete a key point
263
275
 
@@ -58,7 +58,10 @@ Each cycle is a document: Draft or Issued, with its output, its copy state and w
58
58
  - PMOS writes to Google or to Confluence only when you press **Create the copy** or **Update the copy**.
59
59
  - A download happens only when you press a download item.
60
60
  - You issue a report. An issued report is frozen.
61
- - When a report has an open draft, keep talking in the chat. Each change you ask for, such as a date, a wording or a look, goes to that draft by itself. You do not press anything. The draft updates beside the chat, and Claude says in one line what it passed. An issued report does not change. Change its template for the next cycle.
61
+ - While a draft is written, the report waits until the file is still, then shows it once. The slides do not flicker, and the scroll position stays.
62
+ - When a report has an open draft, keep talking in the chat. Each change to its words, such as a date, a status or a wording, goes to that draft by itself. You do not press anything. The draft updates beside the chat, and Claude says in one line what it passed. An issued report does not change. Change its template for the next cycle.
63
+ - The report's session writes one line when it is done with your change: what it changed, or why it did not. The line shows in the report beside the chat. The report says "drafting it" only while a draft is written.
64
+ - A change to the look, such as the colours, the tables or the Gantt, or a deck or screenshot to follow, is not a change to the words. Attach the file in the chat if there is one. Claude starts the next look of the report from your words and the file. It shows in the report's template with ✓ and ✕. Press ✓ to keep it: the open draft is drawn again in the new look, and its numbers stay the same. Nothing is issued.
62
65
 
63
66
  ## Send a dashboard as a report
64
67
 
@@ -39,7 +39,7 @@
39
39
  * THE RECORD, <stateDir>/bg-queue.jsonl:
40
40
  * turn { at, kind: 'turn', job, step, go, why } a job's turn changed (it was held and now goes, or the reason changed). A steady
41
41
  * minute writes nothing.
42
- * done { at, kind: 'done', job, title, ws, ini?, session, meta? } a background session ended (ended(), from the server's one
42
+ * done { at, kind: 'done', job, title, ws, ini?, session, meta?, ref? } a background session ended (ended(), from the server's one
43
43
  * session-end hook). A session the PM started, or a chat, is not written.
44
44
  * status() is the last plan, for the Lab ("what runs next"). record({ ws, since }) is the done lines, newest first, for the Lab's
45
45
  * "What Claude did in the background" (lib/routes-lab.js): [{ job, title, meta?, at, ini?, board? }].
@@ -138,9 +138,12 @@ const ORIGIN_JOB = Object.freeze({ drive: 'driver', grill: 'grill', propose: 'pr
138
138
  impact: 'impact', coredoc: 'coredocs', 'core-docs': 'coredocs', 'scan-web': 'scan', 'scan-repo': 'scan', 'scan-suggest': 'scan',
139
139
  'scan-research-web': 'scan', 'scan-research-repo': 'scan', 'report-find': 'finder', 'ms-review': 'milestones', 'next-step': 'prepare',
140
140
  'idle-lab-proto': 'idle-lab', 'idle-lab-scan': 'idle-lab' });
141
+ // Runs a chat makes for the PM while they use it, not background work: briefing-ask is the read-only run behind a chat (its suggested next
142
+ // prompt, a quick ask). Wawan, 2026-11-04: the Lab's "What Claude did in the background" listed "chat: next prompt" fourteen times.
143
+ const NOT_BACKGROUND = new Set(['briefing-ask']);
141
144
  function jobOfOrigin(origin) {
142
145
  const o = String(origin || '');
143
- if (!o || /^chat:/.test(o)) return null;
146
+ if (!o || /^chat:/.test(o) || NOT_BACKGROUND.has(o.split(':')[0])) return null;
144
147
  const head = o.split(':')[0];
145
148
  return ORIGIN_JOB[head] || head || null;
146
149
  }
@@ -179,6 +182,8 @@ function create(host = {}) {
179
182
  const job = jobOfOrigin(s.origin);
180
183
  if (!job) return;
181
184
  const line = { at: new Date(now).toISOString(), kind: 'done', job, title: String(s.name || job).slice(0, 160), ws: s.ws || null, session: s.id || null };
185
+ const ref = String(s.origin).split(':').slice(1).join(':').slice(0, 120); // what the job was for (a report's id for report-design), for the Lab's words
186
+ if (ref) line.ref = ref;
182
187
  const ini = s.initiative || initiativeOfOrigin(s.origin);
183
188
  if (ini) line.ini = String(ini);
184
189
  if (s.outcome && typeof s.outcome === 'string') line.meta = s.outcome.slice(0, 160);
@@ -192,13 +197,15 @@ function create(host = {}) {
192
197
  for (const raw of text.split('\n')) {
193
198
  if (!raw.trim()) continue;
194
199
  let l; try { l = JSON.parse(raw); } catch { continue; }
195
- if (!l || l.kind !== 'done') continue;
200
+ if (!l || l.kind !== 'done' || NOT_BACKGROUND.has(l.job)) continue; // a line written before the chat's own runs were left out
196
201
  if (ws && l.ws && l.ws !== ws) continue;
197
202
  if (Date.parse(l.at) < from) continue;
198
203
  const row = { job: l.job, title: l.title, at: l.at };
199
204
  if (l.meta) row.meta = l.meta;
200
205
  if (l.ini) row.ini = l.ini;
201
206
  if (l.board) row.board = l.board;
207
+ if (l.ref) row.ref = l.ref;
208
+ if (l.session) row.session = l.session;
202
209
  out.push(row);
203
210
  }
204
211
  return out.reverse();
@@ -23,7 +23,7 @@
23
23
  * topics: [{ id, text, src, state, minutes, n, first_t, stretches, parked, by }] visited ones first, in the order they came up
24
24
  * runs: [{ topic, n, from_ms, to_ms }]
25
25
  * notes: [{ id, t, cat, body, heard, why, src }] PMOS's notes with the time they were made
26
- * shots: [{ t, w, h, why?, text? }] kept shots only
26
+ * shots: [{ t, w, h, why?, text?, jev? }] kept shots only; jev = { kind, box? } when Jev for images read the shot
27
27
  * recordings: how many times the recording was started (1 for most notes)
28
28
  * gaps: [{ from_ms, to_ms }] where the recording was off between two starts (empty when there was one)
29
29
  * aside_count }
@@ -105,13 +105,22 @@ function timeOfKey(item, lines) {
105
105
  return null;
106
106
  }
107
107
 
108
+ /** What Jev for images said of a shot (batch 27 item 12, MpJ), when a shot's index row holds it: the kind ('content', a slide or a
109
+ * document; 'faces', only people) and the box around the useful area, as fractions of the frame [x, y, w, h]. Anything else is dropped. */
110
+ function jevOf(j) {
111
+ if (!j || typeof j !== 'object' || !['content', 'faces'].includes(j.kind)) return null;
112
+ const b = Array.isArray(j.box) && j.box.length === 4 && j.box.every((x) => Number.isFinite(x) && x >= 0 && x <= 1) ? j.box.map((x) => Math.round(x * 1000) / 1000) : null;
113
+ const box = b && b[2] >= 0.05 && b[3] >= 0.05 && b[0] + b[2] <= 1.001 && b[1] + b[3] <= 1.001 ? b : null;
114
+ return { kind: j.kind, ...(box && j.kind === 'content' ? { box } : {}) };
115
+ }
116
+
108
117
  /** Join the three records. */
109
118
  function build({ note, event = null, stateLines = [], frames = [], audio = null, topics = [], now = Date.now() } = {}) {
110
119
  const n = note || {};
111
120
  const lines = linesOf(n);
112
121
  const start = startOf(n, event);
113
122
  const shots = (frames || []).filter((f) => f && finite(f.t) && !f.aside).sort((a, b) => a.t - b.t)
114
- .map((f) => ({ t: f.t, w: f.w || 0, h: f.h || 0, ...(f.why ? { why: f.why } : {}), ...(f.text ? { text: str(f.text, SHOT_TEXT_MAX) } : {}) }));
123
+ .map((f) => ({ t: f.t, w: f.w || 0, h: f.h || 0, ...(f.why ? { why: f.why } : {}), ...(f.text ? { text: str(f.text, SHOT_TEXT_MAX) } : {}), ...(jevOf(f.jev) ? { jev: jevOf(f.jev) } : {}) }));
115
124
  const asideCount = (frames || []).filter((f) => f && f.aside).length;
116
125
 
117
126
  // Focus's state: topics, stretches, PMOS's notes. Stretches are measured from meeting time 0.