create-pmos 0.178.0 → 0.180.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 (59) hide show
  1. package/package.json +1 -1
  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/c3-report-setup-test.js +190 -0
  7. package/workspace/c3-reports-test.js +30 -97
  8. package/workspace/guide/console-calendar.md +17 -2
  9. package/workspace/guide/document-sheet.md +14 -0
  10. package/workspace/guide/index.md +3 -2
  11. package/workspace/guide/reports-dashboards.md +17 -28
  12. package/workspace/guide/reports-page.md +10 -5
  13. package/workspace/guide/reports-set-up.md +56 -0
  14. package/workspace/guide/reports.md +13 -1
  15. package/workspace/guide/settings.md +11 -2
  16. package/workspace/lib/doc-sync.js +28 -6
  17. package/workspace/lib/docs-cli.js +7 -11
  18. package/workspace/lib/four-panel.js +1 -1
  19. package/workspace/lib/headless.js +21 -2
  20. package/workspace/lib/notes-series.js +357 -0
  21. package/workspace/lib/notes.js +53 -4
  22. package/workspace/lib/report-checks.js +103 -0
  23. package/workspace/lib/report-cli.js +89 -4
  24. package/workspace/lib/report-export.js +347 -0
  25. package/workspace/lib/report-finder.js +156 -0
  26. package/workspace/lib/report-needs.js +1 -1
  27. package/workspace/lib/report-setups.js +743 -0
  28. package/workspace/lib/route-modules.js +1 -0
  29. package/workspace/lib/routes-c3-reports.js +35 -17
  30. package/workspace/lib/routes-doc-health.js +15 -1
  31. package/workspace/lib/routes-notes.js +27 -2
  32. package/workspace/lib/routes-report-setups.js +368 -0
  33. package/workspace/lib/schedule-logic.js +5 -2
  34. package/workspace/package-lock.json +2 -2
  35. package/workspace/package.json +1 -1
  36. package/workspace/public/pm/c3-demo-rep2.js +15 -0
  37. package/workspace/public/pm/c3-demo-rs.js +281 -0
  38. package/workspace/public/pm/c3-demo.js +1 -1
  39. package/workspace/public/pm/c3-doc-formats.js +152 -0
  40. package/workspace/public/pm/c3-doc-sheet.js +115 -25
  41. package/workspace/public/pm/c3-notes.js +299 -34
  42. package/workspace/public/pm/c3-quiet.js +2 -2
  43. package/workspace/public/pm/c3-report-print.css +20 -0
  44. package/workspace/public/pm/c3-report-setup.css +62 -0
  45. package/workspace/public/pm/c3-report-setup.js +260 -0
  46. package/workspace/public/pm/c3-report-store.js +208 -0
  47. package/workspace/public/pm/c3-reports.css +4 -0
  48. package/workspace/public/pm/c3-reports.js +47 -181
  49. package/workspace/public/pm/c3-settings.js +4 -3
  50. package/workspace/public/pm/c3-shell.js +15 -31
  51. package/workspace/public/pm/console3.css +13 -0
  52. package/workspace/run-tests.js +25 -1
  53. package/workspace/server.js +66 -5
  54. package/workspace/skills/doc-sync.skill +17 -8
  55. package/workspace/skills/report-find.skill +75 -0
  56. package/workspace/skills/report-setup.skill +67 -0
  57. package/workspace/test-files.json +12 -0
  58. package/workspace/test-lists/c3-notes-series.json +5 -0
  59. package/workspace/test-lists/report-workspace-ui.json +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.178.0",
3
+ "version": "0.180.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": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@066089d
1
+ kernel-source: pmos@5ed98ca
2
2
  assembled: 2026-10-13
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-10-13T01:59:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T02:13:11Z"}
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-10-13T01:59:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T02:13:11Z"}
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-10-13T01:59:58Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T02:13:11Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /* c3-report-setup-test.js — the report workspace on console3, UI slice (initiative report-workspace; mockup 41-reportsetup).
4
+ * DEMO mode: a real server and a real Chromium drive /c3.html?demo=1&tab=rep; c3-demo-rs.js answers the contract routes.
5
+ *
6
+ * tab Ask PMOS, Found in your work (3), Set up (3), Documents (6); the Reports tab badge counts the drafts waiting
7
+ * ask an ask runs, then answers from its own words (not a fixed report); Change revises it; Set up posts ONE setup with the ask and proposal
8
+ * found Set up posts the finding; Not a report hides the row, waits 5 seconds, and Undo posts nothing
9
+ * documents the filters; a draft opens in the document sheet with its Draft, Report and format chips, the format view and the Download items;
10
+ * an issued document is frozen: Edit says so and offers Start the next cycle
11
+ * copy Create the copy waits 5 seconds and Undo posts nothing; a PDF has no copy
12
+ * briefing the Report found card; Settings has the Report finder block and no schedules list in Sharing
13
+ * old tabs the shell event for ?tab=lib and ?tab=drafts lands on Charts and Reports
14
+ * phone at 400 wide none of it scrolls sideways
15
+ * Set RS_SHOTS=<dir> to save screenshots at 1440 and 400 wide. No script error may be raised. */
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { spawn } = require('child_process');
19
+ const { findBrowser } = require('./lib/find-browser');
20
+ const T = require('./test-run').configure({ name: 'c3-report-setup' });
21
+
22
+ const BASE = T.BASE;
23
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
24
+ const until = async (fn, ms = 10000, step = 100) => { const t0 = Date.now(); for (;;) { const v = await fn(); if (v) return v; if (Date.now() - t0 > ms) return v; await sleep(step); } };
25
+ const results = [];
26
+ const ok = (name, cond, extra = '') => { results.push({ name, cond: !!cond }); console.log(`${cond ? '✓' : '✗'} ${name}${!cond && extra !== '' ? ' — ' + String(extra).slice(0, 400) : ''}`); };
27
+ let srv = null; let browser = null;
28
+ const finish = async (code) => {
29
+ if (browser) await browser.close().catch(() => {});
30
+ if (srv) { try { srv.kill('SIGKILL'); } catch { /* gone */ } }
31
+ T.killTmux();
32
+ const bad = results.filter((x) => !x.cond).length;
33
+ console.log(`\n${results.length - bad}/${results.length} c3-report-setup assertions passed`);
34
+ process.exit(code !== undefined ? code : (bad ? 1 : 0));
35
+ };
36
+ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
37
+
38
+ const TAB = '[data-rs-tab]';
39
+ (async () => {
40
+ const exe = findBrowser();
41
+ if (!exe) { console.log('· no Chrome/Chromium on this machine, c3-report-setup suite skipped'); process.exit(0); }
42
+ let chromium;
43
+ try { ({ chromium } = require('playwright-core')); } catch { console.log('· playwright-core not installed, c3-report-setup suite skipped'); process.exit(0); }
44
+ srv = spawn(process.execPath, [path.join(__dirname, 'server.js')], { cwd: __dirname, stdio: 'ignore', env: T.env({ PMOS_WS_PORT: String(T.PORT) }) });
45
+ await T.waitForServer(BASE);
46
+ browser = await chromium.launch({ executablePath: exe });
47
+ require('./lib/c3-full-console').fullConsole(browser); // the desktop layout at phone width (c3-phone-companion)
48
+ const shots = process.env.RS_SHOTS || ''; if (shots) fs.mkdirSync(shots, { recursive: true });
49
+
50
+ const errors = [];
51
+ /* two reads of other initiatives (Horizon scan, Settings) have no demo answer: a 404 on them is not this suite's */
52
+ const watch = (page) => {
53
+ page.on('pageerror', (e) => errors.push('pageerror: ' + String((e && e.message) || e)));
54
+ page.on('response', (r) => { if (r.status() >= 400 && !/\/api\/(scan\/today|settings)\b/.test(r.url())) errors.push('http ' + r.status() + ' ' + r.url()); });
55
+ page.on('dialog', (d) => d.dismiss().catch(() => {}));
56
+ };
57
+ const ctx = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
58
+ const page = await ctx.newPage(); watch(page);
59
+ /* demo answers never reach the network, so the posts are counted by a wrapper over the page's fetch (installed after the demo's) */
60
+ const posts = []; await page.exposeFunction('__rsPost', (p, b) => { let j = null; try { j = JSON.parse(b || '{}'); } catch { j = null; } posts.push({ path: p, body: j }); });
61
+ const spy = (pg) => pg.evaluate(() => { if (window.__rsspy) return; window.__rsspy = 1; const f = window.fetch; window.fetch = (i, init) => { if (String((init && init.method) || 'GET').toUpperCase() === 'POST') window.__rsPost(new URL(typeof i === 'string' ? i : i.url, location.origin).pathname, String((init && init.body) || '{}')); return f(i, init); }; });
62
+ const clickToast = () => page.evaluate(() => { const b = document.querySelector('[data-c3-toast-action]'); if (b) b.click(); return !!b; });
63
+ const sent = (p) => posts.filter((x) => x.path === p);
64
+ const shot = async (pg, n) => { if (shots) await pg.screenshot({ path: path.join(shots, n + '.png'), fullPage: true }); };
65
+ const text = (sel) => page.textContent(sel).catch(() => '');
66
+ const goReports = async () => { await page.goto(BASE + '/c3.html?demo=1&tab=rep'); await page.waitForSelector('[data-rp3]'); await page.click('[data-rp3-tab="reports"]'); await page.waitForSelector('[data-rs-row]'); await spy(page); };
67
+
68
+ /* ── tab ── */
69
+ await goReports();
70
+ ok('the Reports tab holds Ask PMOS, Found in your work, Set up and Documents', !!(await page.$(TAB + ' [data-rs-askbox]')) && !!(await page.$('[data-rs-found]')) && !!(await page.$('[data-rs-setups]')) && !!(await page.$('[data-rs-docs]')));
71
+ ok('three findings (Likely, Likely, Not sure), three set-ups and six documents', (await page.$$('[data-rs-row]')).length === 3 && (await page.$$('[data-rs-reg]')).length === 3 && (await page.$$('[data-rs-doc]')).length === 6
72
+ && /Likely/.test(await text('[data-rs-row="rs1"]')) && /Not sure/.test(await text('[data-rs-row="rs3"]')));
73
+ ok('the Reports tab badge counts the drafts waiting (3)', (await text('[data-rp3-badge="reports"]')).trim() === '3');
74
+ ok('every set-up and document says nothing is sent, and a PDF has no live copy', /Nothing is sent/.test(await text(TAB)) && /PDF/.test(await text('[data-rs-reg="rg0"]')));
75
+ await shot(page, 'reports-1440');
76
+
77
+ /* ── ask: answered from its words ── */
78
+ await page.fill('[data-rs-ask-in]', 'Set up my quarterly pricing review for the board');
79
+ await page.click('[data-rs-act="ask"]');
80
+ ok('an ask runs: the card says PMOS is working and offers Cancel', !!(await until(() => page.$('[data-rs-card][data-rs-ask-state="running"] [data-rs-running]'))) && sent('/api/report-setups/ask').length === 1);
81
+ ok('the answer is a proposal made from the words, not a fixed report', !!(await until(() => page.$('[data-rs-card][data-rs-ask-state="ready"]'), 8000)) && /quarterly pricing review for the board/i.test(await text('[data-rs-card]')) && !/IDPF/.test(await text('[data-rs-card]')));
82
+ await page.click('[data-rs-card] [data-rs-act="change"]');
83
+ await page.fill('[data-rs-reply]', 'Draft it on Tuesday instead');
84
+ await page.click('[data-rs-card] [data-rs-act="send"]');
85
+ ok('Change posts the words and the card runs again, then reads Changed', !!(await until(() => page.$('[data-rs-card] [data-rs-changed]'), 8000)) && sent('/api/report-setups/change').length === 1 && sent('/api/report-setups/change')[0].body.text === 'Draft it on Tuesday instead');
86
+ ok('the revised proposal carries the new day', /Tue/.test(await until(async () => { const t = await text('[data-rs-card][data-rs-ask-state="ready"]'); return /Tue/.test(t) ? t : ''; }, 8000)));
87
+ await shot(page, 'rs-ask-1440');
88
+ await page.click('[data-rs-card] [data-rs-act="setup"]');
89
+ ok('Set up posts ONE setup with the ask and the proposal', !!(await until(() => sent('/api/report-setups/setup').length === 1)) && sent('/api/report-setups/setup')[0].body.ask && sent('/api/report-setups/setup')[0].body.proposal && /pricing/i.test(sent('/api/report-setups/setup')[0].body.proposal.name), JSON.stringify(sent('/api/report-setups/setup')).slice(0, 300));
90
+ ok('the set-up appears marked New with its first draft in Documents', !!(await until(async () => (await page.$$('[data-rs-reg]')).length === 4)) && /New/.test(await text('[data-rs-setups]')) && (await page.$$('[data-rs-doc]')).length === 7);
91
+ ok('nothing else was posted: no copy, no issue', sent('/api/docs/create-copy').length === 0 && sent('/api/report/issue').length === 0);
92
+
93
+ /* ── found: Set up, Not a report with Undo ── */
94
+ await page.click('[data-rs-row="rs2"] [data-rs-act="dismiss"]');
95
+ ok('Not a report hides the row at once and says PMOS will not suggest it again, with Undo', !(await page.$('[data-rs-row="rs2"]')) && /won.t suggest this again/.test(await text('[data-c3-toast]')) && !!(await page.$('[data-c3-toast-action]')));
96
+ await clickToast();
97
+ await sleep(5600);
98
+ ok('Undo brings the row back and no dismiss was ever posted', !!(await page.$('[data-rs-row="rs2"]')) && sent('/api/report-setups/dismiss').length === 0);
99
+ await page.click('[data-rs-row="rs3"] [data-rs-act="dismiss"]');
100
+ ok('no POST during the five seconds', sent('/api/report-setups/dismiss').length === 0);
101
+ ok('after five seconds the dismiss is posted once with the finding id', !!(await until(() => sent('/api/report-setups/dismiss').length === 1, 8000)) && sent('/api/report-setups/dismiss')[0].body.finding === 'rs3');
102
+ await page.click('[data-rs-row="rs1"] [data-rs-act="setup"]');
103
+ ok('Set up on a finding posts the finding and its proposal', !!(await until(() => sent('/api/report-setups/setup').length === 2)) && sent('/api/report-setups/setup')[1].body.finding === 'rs1' && sent('/api/report-setups/setup')[1].body.proposal.sections.length === 5);
104
+ ok('the finding leaves the list and its set-up arrives', !!(await until(async () => !(await page.$('[data-rs-row="rs1"]')) && !!(await page.$('[data-rs-reg="rs1"]')))));
105
+
106
+ /* ── documents ── */
107
+ await page.click('[data-rs-act="docf"][data-k="state"][data-v="issued"]');
108
+ ok('the Issued filter lists only issued documents', (await page.$$('[data-rs-doc] .tag.ok')).length === (await page.$$('[data-rs-doc]')).length && (await page.$$('[data-rs-doc]')).length >= 4);
109
+ await page.click('[data-rs-act="docf"][data-k="state"][data-v="all"]');
110
+ await page.click('[data-rs-doc$="rs1/2026-10-21.md"] [data-rs-act="opendoc"] >> nth=0');
111
+ ok('a draft opens in the document sheet with its format chip and an Issue button', !!(await until(() => page.$('[data-doc-format]'))) && !!(await page.$('[data-doc-issue]')) && /Google Slides/.test(await text('[data-doc-format]')));
112
+ ok('the Google Slides draft reads as slides, with the title card first', !!(await until(() => page.$('.rs-slides .rs-slide'))) && (await page.$$('.rs-slide')).length >= 6 && !!(await page.$('.rs-slide.title')));
113
+ ok('its copy chip says Google Slides: not created', /Google Slides: not created/.test(await text('[data-doc-copychip]')));
114
+ await shot(page, 'rs-draft-1440');
115
+ await page.click('[data-doc-more]');
116
+ const dl = await page.$$eval('[data-doc-download]', (a) => a.map((b) => b.getAttribute('data-doc-download')));
117
+ ok('the more menu offers the Slides downloads (PDF, pptx)', dl.includes('pdf') && dl.includes('pptx'), JSON.stringify(dl));
118
+ ok('Download posts nothing and presses nothing by itself', sent('/api/docs/create-copy').length === 0);
119
+ await page.click('[data-doc-more]');
120
+ /* Create the copy: five seconds and Undo */
121
+ await page.click('[data-doc-copychip]');
122
+ const cb = await until(() => page.$('[data-copy-create]'), 3000);
123
+ ok('the Copies panel offers Create the Google Slides copy for a copy that does not exist', !!cb && /Create the Google Slides copy/.test(await text('[data-copy-create]')));
124
+ if (cb) {
125
+ await cb.click();
126
+ ok('Create the copy posts nothing for five seconds', sent('/api/docs/create-copy').length === 0);
127
+ await until(() => clickToast(), 2000);
128
+ await sleep(5400);
129
+ ok('Undo means no copy was created', sent('/api/docs/create-copy').length === 0);
130
+ } else ok('Create the copy waits and Undo posts nothing', false, 'no create button found');
131
+ await page.keyboard.press('Escape');
132
+
133
+ /* issued, frozen */
134
+ await page.waitForSelector('[data-rs-doc]');
135
+ await page.click('[data-rs-doc$="rs1/2026-10-07.md"] [data-rs-act="opendoc"] >> nth=0');
136
+ ok('an issued document shows Issued and no Issue button', !!(await until(() => page.$('[data-doc-format]'))) && !(await page.$('[data-doc-issue]')));
137
+ await page.click('[data-dmode="edit"]');
138
+ ok('Edit on an issued document says it is frozen and offers Start the next cycle', !!(await until(() => page.$('[data-doc-next]'))) && /Issued reports are frozen/.test(await text('body')));
139
+ await shot(page, 'rs-doc-issued-1440');
140
+ await page.keyboard.press('Escape');
141
+
142
+ /* Issue: the PM's act on the existing route, with the cycle and the sha it read */
143
+ await page.waitForSelector('[data-rs-doc]');
144
+ await page.click('[data-rs-doc$="rs1/2026-10-21.md"] [data-rs-act="opendoc"] >> nth=0');
145
+ await until(() => page.$('[data-doc-issue]'));
146
+ await page.click('[data-doc-issue]');
147
+ const iss = await until(() => sent('/api/report/issue')[0]);
148
+ ok('Issue posts the existing report issue with the cadence, the slot and a sha', !!iss && iss.body.cadence === 'rs1' && iss.body.slot === '2026-10-21' && /^[0-9a-f]{40}$/.test(iss.body.sha || ''), JSON.stringify(iss && iss.body));
149
+ ok('after Issue the document is frozen: no Issue button', !!(await until(async () => !(await page.$('[data-doc-issue]')))));
150
+ await page.keyboard.press('Escape');
151
+
152
+
153
+ /* ── briefing and settings ── */
154
+ await page.goto(BASE + '/c3.html?demo=1');
155
+ ok('the Briefing has a Report found card with Set up, See what PMOS found and Not a report', !!(await until(() => page.$('[data-rs-card] [data-rs-act="b-setup"]'), 12000)) && !!(await page.$('[data-rs-act="b-see"]')) && !!(await page.$('[data-rs-act="b-no"]')) && /Nothing is sent/.test(await text('[data-rs-card]')));
156
+ await page.goto(BASE + '/c3.html?demo=1&tab=settings&section=connectors');
157
+ ok('Settings, Connectors has the Report finder: Weekly or Off, and the sources', !!(await until(() => page.$('[data-rs-finder]'), 12000)) && !!(await spy(page).then(() => 1)) && /Weekly/.test(await text('[data-rs-finder]')) && /Calendar/.test(await text('[data-rs-finder]')));
158
+ await page.click('[data-rs-finder] button:has-text("Off") >> nth=0');
159
+ ok('Off posts the finder cadence', !!(await until(() => sent('/api/report-setups/finder').length >= 1)) && sent('/api/report-setups/finder')[0].body.cadence === 'off');
160
+ await page.goto(BASE + '/c3.html?demo=1&tab=settings&section=sharing');
161
+ await page.waitForSelector('[data-c3set-sched-link]');
162
+ ok('Sharing links to Reports, Set up and no longer lists schedules', /Set up/.test(await text('[data-c3set-sched-link]')) && !/Scheduled shares/.test(await text('body')));
163
+
164
+ /* ── old tab names ── */
165
+ await page.goto(BASE + '/c3.html?demo=1&tab=rep'); await page.waitForSelector('[data-rp3]');
166
+ await page.evaluate(() => window.dispatchEvent(new CustomEvent('pmos:go-tab', { detail: { tab: 'rep', view: 'lib' } })));
167
+ ok('view=lib lands on Charts', !!(await until(async () => await page.getAttribute('[data-rp3-tab="charts"]', 'aria-selected') === 'true')));
168
+ await page.evaluate(() => window.dispatchEvent(new CustomEvent('pmos:go-tab', { detail: { tab: 'rep', view: 'drafts' } })));
169
+ ok('view=drafts lands on Reports', !!(await until(async () => await page.getAttribute('[data-rp3-tab="reports"]', 'aria-selected') === 'true')));
170
+
171
+ /* ── phone ── */
172
+ const ph = await browser.newContext({ viewport: { width: 400, height: 860 }, hasTouch: true, isMobile: true });
173
+ const pp = await ph.newPage(); watch(pp);
174
+ const noScroll = () => pp.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1);
175
+ await pp.goto(BASE + '/c3.html?demo=1&tab=rep'); await pp.waitForSelector('[data-rp3]'); await pp.click('[data-rp3-tab="reports"]'); await pp.waitForSelector('[data-rs-row]');
176
+ ok('400px: the Reports tab has no horizontal scroll', await noScroll());
177
+ await shot(pp, 'reports-400');
178
+ await pp.click('[data-rs-row="rs1"] [data-rs-act="open"], [data-rs-row="rs1"] .tt');
179
+ ok('400px: a proposal sheet has no horizontal scroll', !!(await until(() => pp.$('[data-rs-sheet]'))) && await noScroll());
180
+ await pp.keyboard.press('Escape');
181
+ await pp.click('[data-rs-doc] [data-rs-act="opendoc"] >> nth=0');
182
+ await until(() => pp.$('[data-doc-format]'));
183
+ ok('400px: the document sheet has no horizontal scroll', await noScroll());
184
+ await shot(pp, 'rs-draft-400');
185
+ await ph.close();
186
+
187
+ await sleep(300);
188
+ ok('no script error and no failed request', errors.length === 0, errors.slice(0, 4).join(' | '));
189
+ return finish();
190
+ })();
@@ -7,18 +7,10 @@
7
7
  * file in public/) that installs demo mode and mounts mountReports with the same ctx the shell passes ({ ws, pmGet, drafts }, drafts
8
8
  * doing what repTab does). The suite prints which one it used.
9
9
  *
10
- * tabs Dashboards, Library and Drafts; Dashboards first
11
- * scopes Portfolio draws its seven default reports from the Calendar's builders; Board and Initiative show a chooser, and a
12
- * report shows only the rows of the chosen board or initiative; a scope's add list holds only reports that fit it
13
- * cache the data route is read once per scope (switching back reads nothing)
14
- * edit up and down, Remove with Undo, Add from the report library, Reset to default; each change POSTs { key, reports }
15
- * and a failed POST is toasted while the page keeps the change
16
- * reload a stored layout is there after a reload
17
- * share the frame (Headline, Sections, Needs your call, Sources) for Leadership and for Team; Save as draft POSTs ONE draft;
18
- * the schedule: three options, the next draft, the warning that nothing is sent; Save schedule POSTs { key, plan, audience }
19
- * and the head and Drafts > Scheduled show it; a draft says whether a schedule or you made it
10
+ * tabs Dashboards, Reports and Charts (the old Library and Drafts); Dashboards first; a rule line under the tabs
11
+ * send the dashboard head has "Send this as a report..." and no Share button or schedule line; the sheet posts from-dashboard
20
12
  * library one row per report with data-home, never on the dashboard; a widget carries data-ref-row and opens its case in place
21
- * drafts the Drafts tab shows the report families
13
+ * reports the Reports tab mounts Ask PMOS, Found in your work, Set up and Documents (the full flow is c3-report-setup-test.js)
22
14
  * phone 400px: one column, no horizontal page scroll
23
15
  * errors no script error and no console error
24
16
  */
@@ -55,30 +47,17 @@ const HARNESS_HTML = `<!doctype html><html lang="en" data-pm-page-theme="light">
55
47
  <script type="module">
56
48
  const demo = await import('/pm/c3-demo.js');
57
49
  demo.installDemo(); if (window.__c3demoReady) await window.__c3demoReady;
58
- const { el } = await import('/pm/dom.js');
59
50
  const { ensureC3Css, c3Phone } = await import('/pm/pm-work-view.js');
60
51
  const { pmGet } = await import('/pm/http.js');
61
52
  const { mountReports } = await import('/pm/c3-reports.js');
62
- const { mountReportFamilies } = await import('/pm/report-families-view.js');
63
- const { mountOutbox } = await import('/pm/outbox-panel.js');
64
53
  ensureC3Css(); c3Phone(document.getElementById('c3-root'));
65
54
  const ws = demo.DEMO_WS;
66
- const drafts = (pane) => {
67
- const fam = el('div', { 'data-c3pg': 'families-host' }); const out = el('div', { class: 'brief', 'data-c3pg': 'outbox-host' });
68
- pane.append(fam, out);
69
- pmGet('/api/report-families/facts?ws=' + encodeURIComponent(ws)).then((j) => {
70
- mountReportFamilies(fam, { reportFacts: j && j.ok ? j.facts : {}, plan: null, portfolio: null });
71
- mountOutbox(out, {});
72
- const sync = () => { out.hidden = !fam.querySelector('[data-family="communications"][aria-pressed="true"]'); };
73
- sync(); new MutationObserver(sync).observe(fam, { subtree: true, attributes: true, attributeFilter: ['aria-pressed'] });
74
- });
75
- };
76
- mountReports(document.getElementById('host'), { ws, pmGet, drafts });
55
+ mountReports(document.getElementById('host'), { ws, pmGet });
77
56
  document.getElementById('c3-root').setAttribute('data-c3-demo', '1');
78
57
  </script></body></html>`;
79
58
 
80
59
  const D = '[data-rp3-pane="dash"]';
81
- const ACT = '[data-rp3]'; // Share, Edit dashboard and Reset sit on the tab row at desktop width (c3-quiet-pages), inside the pane on a phone
60
+ const ACT = '[data-rp3]'; // Send, Edit dashboard and Reset sit on the tab row at desktop width (c3-quiet-pages), inside the pane on a phone
82
61
  const order = (page) => page.$$eval(D + ' [data-rp3-w]', (a) => a.map((n) => n.getAttribute('data-rp3-w')));
83
62
  const DEF_P = ['north', 'outcomes', 'signals', 'delivery', 'bets', 'synergy', 'maturity'];
84
63
 
@@ -137,14 +116,15 @@ const DEF_P = ['north', 'outcomes', 'signals', 'delivery', 'bets', 'synergy', 'm
137
116
 
138
117
  /* ── tabs ── */
139
118
  const tabs = await page.$$eval('[data-rp3] [role="tab"]', (a) => a.map((b) => [b.getAttribute('data-rp3-tab'), b.textContent.trim(), b.getAttribute('aria-selected')]));
140
- ok('three tabs: Dashboards, Library, Drafts, with Dashboards selected', tabs.length === 3 && tabs[0][0] === 'dash' && /^Dashboards/.test(tabs[0][1]) && tabs[0][2] === 'true' && /^Library/.test(tabs[1][1]) && /^Drafts/.test(tabs[2][1]), JSON.stringify(tabs));
119
+ ok('three tabs: Dashboards, Reports, Charts, with Dashboards selected', tabs.length === 3 && tabs[0][0] === 'dash' && /^Dashboards/.test(tabs[0][1]) && tabs[0][2] === 'true' && /^Reports/.test(tabs[1][1]) && /^Charts/.test(tabs[2][1]), JSON.stringify(tabs));
120
+ ok('a rule line under the tabs says how Reports is organised', /HOW REPORTS IS ORGANISED/.test(await page.textContent('[data-rp3-rule]')) && /Live/.test(await page.textContent('[data-rp3-rule]')) && /Building blocks/.test(await page.textContent('[data-rp3-rule]')));
141
121
 
142
122
  ok('the tab row is the mockup\'s calbar: tabs on the left, the scope pills on the same row (no intro paragraph)', await until(() => page.evaluate(() => {
143
123
  const bar = document.querySelector('[data-rp3] > .calbar'); if (!bar || !bar.querySelector("[data-rp3-scope]")) return false;
144
124
  const t = bar.querySelector('.rp3-tabs').getBoundingClientRect(); const s = bar.querySelector('[data-rp3-scope]').getBoundingClientRect();
145
125
  return Math.abs(t.top - s.top) < 40 && !document.querySelector('.rp3-intro') && bar.querySelectorAll('.pill').length === 3;
146
126
  })));
147
- ok('the Library tab carries its count as a badge', /7/.test(await page.textContent('[data-rp3-tab="lib"] .bdg')));
127
+ ok('the Charts tab carries its count as a badge', /7/.test(await page.textContent('[data-rp3-tab="charts"] .bdg')));
148
128
  if (mode === 'shell') ok('the headline deck sits above the tabs (shell)', !!(await until(() => page.$('[data-rp3-deck] .deck > *'))) && await page.evaluate(() => document.querySelector('[data-rp3-deck]').getBoundingClientRect().top < document.querySelector('[data-rp3] > .calbar').getBoundingClientRect().top));
149
129
 
150
130
  /* ── portfolio ── */
@@ -248,85 +228,38 @@ const DEF_P = ['north', 'outcomes', 'signals', 'delivery', 'bets', 'synergy', 'm
248
228
  await page.click('[data-rp3-scope="portfolio"]'); await loaded(page, 7);
249
229
  ok('the portfolio keeps its own layout when a board changes', JSON.stringify(await order(page)) === JSON.stringify(DEF_P));
250
230
 
251
- /* ── share ── */
252
- ok('the head says the portfolio is scheduled, draft only (the demo seeds a monthly schedule)', /Scheduled: Monthly, 1st at 08:00, draft only/.test(await page.textContent(D + ' [data-rp3-schedline]')));
231
+ /* ── send this as a report ── */
232
+ ok('the head has Send this as a report and no Share button and no schedule line', !!(await page.$(ACT + ' [data-rp3-send]')) && !(await page.$(ACT + ' [data-rp3-share]')) && !(await page.$(ACT + ' [data-rp3-schedline]')) && /Send this as a report/.test(await page.textContent(ACT + ' [data-rp3-send]')));
253
233
  await spy(page);
254
- await page.click(ACT + ' [data-rp3-share]');
255
- const sheet = await until(() => page.$('[data-rp3-sheet] [data-rp3-frame="Headline"]'));
256
- ok('Share opens a sheet with the report frame', !!sheet);
257
- const frames = await page.$$eval('[data-rp3-sheet] [data-rp3-frame] h2', (a) => a.map((h) => h.textContent.replace(/\d+$/, '')));
258
- ok('the frame has Headline, Sections, Needs your call, Sources', JSON.stringify(frames) === JSON.stringify(['Headline', 'Sections', 'Needs your call', 'Sources']), JSON.stringify(frames));
259
- ok('Leadership: one sentence per report, no numbers', await page.$eval('[data-rp3-sheet] [data-rp3-aud]', (n) => n.getAttribute('data-rp3-aud')) === 'Leadership'
260
- && (await page.$$('[data-rp3-sheet] [data-rp3-frame="Sections"] ul.chgl > li')).length === 7 && !(await page.$('[data-rp3-sheet] [data-rp3-nums]')));
261
- const need = await page.textContent('[data-rp3-sheet] [data-rp3-frame="Needs your call"]');
262
- ok('Needs your call lists what the reports flag', /Conversion is off course/.test(need) && /Checkout v2 is delayed/.test(need), need.slice(0, 200));
263
- await page.click('[data-rp3-sheet] [data-rp3-audbtn="Team"]');
264
- ok('Team adds the numbers behind each sentence', !!(await until(async () => (await page.$$('[data-rp3-sheet] [data-rp3-nums]')).length >= 5)));
265
- ok('the Team numbers come from the records (Conversion now 3.1%)', /Conversion: now 3\.1%, should be 3\.75%, target 4%/.test(await page.textContent('[data-rp3-sheet] [data-rp3-nums="outcomes"]')));
266
- const sched = await page.$$eval('[data-rp3-sheet] [data-rp3-sched] button', (a) => a.map((b) => [b.disabled, b.getAttribute('aria-pressed'), b.textContent.trim()]));
267
- ok('the schedule control has its three options, enabled, the saved one (Monthly) pressed', sched.length === 3 && sched.every((x) => !x[0]) && sched[2][1] === 'true' && sched[2][2] === 'Monthly, 1st at 08:00', JSON.stringify(sched));
268
- ok('the next draft is the 1st at 08:00', /^Next draft: \w{3} 1 \w{3}, 08:00$/.test((await page.textContent('[data-rp3-sheet] [data-rp3-next]')).trim()));
269
- ok('the warning says a schedule makes a ready draft and nothing is sent to the team', /A scheduled send makes a ready draft and notifies you\. Nothing is sent to the team until you connect a channel\./.test(await page.textContent('[data-rp3-sheet] #rp3-sched-why')));
270
- ok('Save schedule is a primary button', !!(await page.$('[data-rp3-sheet] [data-rp3-sched-save]:not([disabled])')));
271
- await page.click('[data-rp3-sheet] [data-rp3-draft]');
272
- ok('Save as draft says one draft is in the outbox and nothing was sent', !!(await until(async () => /one draft in the outbox\. Nothing was sent/.test(await page.textContent('[data-rp3-sheet] [data-rp3-share-msg]')))));
273
- ok('Save as draft is off once the draft is saved', await page.$eval('[data-rp3-sheet] [data-rp3-draft]', (b) => b.disabled));
274
- P = await posts(page, '/api/outbox/draft');
275
- const B = P[0] && P[0].body;
276
- ok('ONE draft POST, to a known channel, for the chosen audience, its claim lines labelled', P.length === 1 && B.channel === 'customer-note' && B.recipient === 'Team' && /^Portfolio for Team/.test(B.body)
277
- && B.body.split('\n').filter((l) => /^- /.test(l)).every((l) => /\[reported\]$/.test(l)), JSON.stringify(B).slice(0, 300));
278
- ok('nothing else was POSTed from the sheet (no send, no schedule)', (await page.evaluate(() => window.__rp3spy.posts.length)) === 1);
279
- await page.click('[data-rp3-sheet] [data-sched="weekly"]');
280
- ok('choosing Weekly shows a Monday as the next draft', !!(await until(async () => /^Next draft: Mon \d+ \w{3}, 08:00$/.test(((await page.textContent('[data-rp3-sheet] [data-rp3-next]').catch(() => '')) || '').trim()))));
281
- ok('choosing is not saving: nothing is POSTed yet', (await page.evaluate(() => window.__rp3spy.posts.length)) === 1);
282
- await page.click('[data-rp3-sheet] [data-rp3-sched-save]');
283
- ok('Save schedule says you get a draft and a notification', !!(await until(async () => /Scheduled: Weekly, Mondays 08:00\. You get a draft and a notification\./.test(await page.textContent('[data-rp3-sheet] [data-rp3-share-msg]')))));
284
- P = await posts(page, '/api/report-dashboards/schedule');
285
- ok('Save schedule POSTs ONE schedule: the key, the plan and the sheet\'s audience', P.length === 1 && P[0].body.key === 'portfolio' && P[0].body.plan === 'weekly' && P[0].body.audience === 'Team', JSON.stringify(P));
286
- ok('the head now says Weekly, draft only', !!(await until(async () => /Scheduled: Weekly, Mondays 08:00, draft only/.test(await page.textContent(D + ' [data-rp3-schedline]')))));
287
- await page.keyboard.press('Escape');
288
- ok('Escape closes the sheet', !!(await until(async () => !(await page.$('[data-rp3-sheet]')))));
234
+ await page.click(ACT + ' [data-rp3-send]');
235
+ ok('the send sheet shows the proposed report with Output, Schedule and Audience, and says nothing is sent', !!(await until(async () => { const t = await page.textContent('[data-rs-send-sheet]').catch(() => ''); return /Proposed report/.test(t) && /Output/.test(t) && /Schedule/.test(t) && /Audience/.test(t) && /Nothing is sent/.test(t); }))
236
+ && !(await page.$('[data-rp3-sched]')));
237
+ await page.click('[data-rs-act="send-go"]');
238
+ ok('Set up posts ONE from-dashboard with the dashboard key, a name, an output, a schedule and an audience', !!(await until(async () => (await posts(page, '/api/report-setups/from-dashboard')).length === 1)) && (await (async () => { const b = (await posts(page, '/api/report-setups/from-dashboard'))[0].body; return b.key === 'portfolio' && b.name && b.output && b.schedule && b.schedule.cadence && b.audience; })()));
239
+ ok('after Set up the Reports tab is open', !!(await until(async () => await page.getAttribute('[data-rp3-tab="reports"]', 'aria-selected') === 'true')));
240
+ await page.click('[data-rp3-tab="dash"]');
289
241
 
290
242
  /* ── library and one home ── */
291
243
  ok('the dashboard has no data-home row; every widget carries data-ref-row', !(await page.$(D + ' [data-home]')) && (await page.$$(D + ' section.rp3-w[data-ref-row]')).length === 7);
292
244
  await page.click(D + ' [data-rp3-about="outcomes"]');
293
245
  ok('a widget opens its case in place (question, source, where used)', !!(await until(async () => { const x = await page.$(D + ' [data-rp3-w="outcomes"] .xp.open'); return x && /Used on: Portfolio/.test(await x.textContent()); })));
294
246
  await page.click(D + ' [data-rp3-golib="outcomes"]');
295
- ok('Open in the library goes to the Library tab', !!(await until(async () => await page.getAttribute('[data-rp3-tab="lib"]', 'aria-selected') === 'true')));
296
- const L = '[data-rp3-pane="lib"]';
247
+ ok('Open in Charts goes to the Charts tab', !!(await until(async () => await page.getAttribute('[data-rp3-tab="charts"]', 'aria-selected') === 'true')));
248
+ const L = '[data-rp3-pane="charts"]';
297
249
  const libRows = await page.$$eval(L + ' [data-home]', (a) => a.map((r) => r.getAttribute('data-home')));
298
250
  ok('the library has one row per report, each its home', libRows.length === 7 && new Set(libRows).size === 7, JSON.stringify(libRows));
299
251
  ok('library rows are not references', !(await page.$(L + ' [data-ref-row]')));
300
252
  const lrow = await page.textContent(L + ' [data-home="maturity"]');
301
- ok('a library row shows the question, the source and where it is used (short: On: Portfolio)', /Where does each pillar stand/.test(lrow) && /Pillar maturity records/.test(lrow) && /On: Portfolio/.test(lrow), lrow.slice(0, 200));
253
+ ok('a library row shows the question, the source and where it is used (Dashboards: Portfolio, Reports: none)', /Where does each pillar stand/.test(lrow) && /Pillar maturity records/.test(lrow) && /Dashboards: Portfolio/.test(lrow) && /Reports: /.test(lrow), lrow.slice(0, 200));
302
254
  ok('the row opened from the widget is open, with its card', !!(await until(() => page.$(L + ' [data-home="outcomes"][aria-expanded="true"] [data-cs="outcomes"]'))));
303
255
  await page.click(L + ' [data-home="bets"]');
304
256
  ok('a library row opens the report\'s card in place', !!(await until(() => page.$(L + ' [data-home="bets"] .xp.open [data-cs="bets"]'))));
305
- ok('a board layout shows in where it is used', /On: .*Board/.test(await page.textContent(L + ' [data-home="synergy"]')));
257
+ ok('a board layout shows in where it is used', /Dashboards: .*Board/.test(await page.textContent(L + ' [data-home="synergy"]')));
306
258
 
307
- /* ── drafts ── */
308
- const DR = '[data-rp3-pane="drafts"]';
309
- ok('the Drafts tab carries a count once drafts are read (the seeded two plus the one just made)', !!(await until(async () => (await page.textContent('[data-rp3-draftcount]').catch(() => '')) === '3')));
310
- await page.click('[data-rp3-tab="drafts"]');
311
- ok('the Drafts tab says nothing here is sent, and that a schedule only makes a draft', !!(await until(async () => /Nothing here is sent\. No sender channel is connected yet, so a schedule only makes a ready draft and notifies you\./.test(await page.textContent(DR + ' .fold.w')))));
312
- ok('the dashboard drafts are listed, with Preview and Discard, newest first', !!(await until(async () => (await page.$$(DR + ' [data-draft-row]')).length === 3)) && (await page.textContent(DR + ' [data-draft-row]')).includes('Portfolio') && (await page.$$(DR + ' [data-draft="preview"]')).length === 3);
313
- const dtext = await page.textContent(DR + ' [data-draft-row]');
314
- ok('a draft row shows its audience and that it was not sent', /For Team/.test(dtext) && /Not sent/.test(dtext), dtext);
315
- const sch = await page.textContent(DR + ' [data-rp3-scheduled]');
316
- ok('Scheduled lists the portfolio schedule, its audience and its next draft', /Portfolio/.test(sch) && /Weekly, Mondays 08:00/.test(sch) && /For Team/.test(sch) && /Next draft Mon \d+ \w{3}, 08:00/.test(sch), sch);
317
- ok('the drafts say who made them: a schedule or you', (await page.$$(DR + ' [data-rp3-madeby="schedule"]')).length === 1 && (await page.$$(DR + ' [data-rp3-madeby="you"]')).length === 2
318
- && /Made by schedule/.test(await page.textContent(DR + ' [data-draft-row="out-demo-rep3-s1"]')));
319
- await page.click(DR + ' [data-draft="preview"]');
320
- ok('Preview opens the draft text in a sheet', !!(await until(async () => /Headline:/.test((await page.textContent('[data-rp3-draft-text]').catch(() => '')) || ''))));
321
- await page.keyboard.press('Escape');
322
- await spy(page);
323
- await page.click(DR + ' [data-draft="discard"]');
324
- ok('Discard takes the draft off the list and POSTs the discard', !!(await until(async () => (await page.$$(DR + ' [data-draft-row]')).length === 2)) && (await posts(page, '/api/outbox/discard')).length === 1);
325
- await page.click('[data-c3-toast-action]');
326
- ok('Undo brings the draft back', !!(await until(async () => (await page.$$(DR + ' [data-draft-row]')).length === 3)));
327
- ok('Drafts carries what the Briefing used to: Handled, What I could stop, Data you can lean on (the demo has them)', !!(await until(() => page.$(DR + ' [data-rp3-more] [data-c3pg="handled"]'))) && !!(await page.$(DR + ' [data-rp3-more] [data-c3pg="trust"]')));
328
- ok('Reports to send holds the report families', !!(await until(() => page.$(DR + ' .card.sect [data-report-families] [data-family]'), 15000)) && /Reports to send/.test(await page.textContent(DR + ' [data-rp3-more] ~ .card.sect > .sh2')));
329
- ok('the dashboards are hidden while Drafts is open', await page.$eval(D, (n) => n.hidden));
259
+ /* ── reports ── */
260
+ await page.click('[data-rp3-tab="reports"]');
261
+ ok('the Reports tab mounts Ask PMOS, Found in your work, Set up and Documents; the old Drafts content is gone', !!(await until(() => page.$('[data-rs-tab] [data-rs-askbox]'))) && !!(await until(() => page.$('[data-rs-found]'))) && !!(await page.$('[data-rs-setups]')) && !!(await page.$('[data-rs-docs]')) && !(await page.$('[data-rp3-draftlist]')) && !(await page.$('[data-report-families]')));
262
+ ok('the dashboards are hidden while Reports is open', await page.$eval(D, (n) => n.hidden));
330
263
  await page.click('[data-rp3-tab="dash"]');
331
264
 
332
265
  /* ── phone ── */
@@ -341,12 +274,12 @@ const DEF_P = ['north', 'outcomes', 'signals', 'delivery', 'bets', 'synergy', 'm
341
274
  await pp.click(ACT + ' [data-rp3-edit]');
342
275
  ok('400px: Initiative scope in edit mode has no horizontal scroll', await noScroll(pp));
343
276
  await pp.click(ACT + ' [data-rp3-edit]');
344
- await pp.click(ACT + ' [data-rp3-share]'); await until(() => pp.$('[data-rp3-sheet] [data-rp3-frame]'));
345
- ok('400px: the share sheet has no horizontal scroll', await noScroll(pp));
277
+ await pp.click(ACT + ' [data-rp3-send]'); await until(() => pp.$('[data-rs-send-sheet]'));
278
+ ok('400px: the send sheet has no horizontal scroll', await noScroll(pp));
346
279
  await pp.keyboard.press('Escape');
347
- await pp.click('[data-rp3-tab="lib"]'); await pp.click('[data-rp3-pane="lib"] [data-home="delivery"]');
348
- await until(() => pp.$('[data-rp3-pane="lib"] [data-home="delivery"] .xp.open [data-cs="delivery"]'));
349
- ok('400px: the library with an open row has no horizontal scroll', await noScroll(pp));
280
+ await pp.click('[data-rp3-tab="charts"]'); await pp.click('[data-rp3-pane="charts"] [data-home="delivery"]');
281
+ await until(() => pp.$('[data-rp3-pane="charts"] [data-home="delivery"] .xp.open [data-cs="delivery"]'));
282
+ ok('400px: Charts with an open row has no horizontal scroll', await noScroll(pp));
350
283
  await ph.close();
351
284
 
352
285
  await sleep(300); // a late error from a closing view would land here
@@ -2,7 +2,7 @@
2
2
  type: Guide
3
3
  title: "Guide: the Calendar tab of the console page"
4
4
  description: "The Calendar tab of the console page (/): Calendar and Notes, the five zooms, the highlights, the layer chips, the markers, the context of a meeting, free time, the tidy-up suggestions and the empty states."
5
- timestamp: 2026-10-12
5
+ timestamp: 2026-10-13
6
6
  ---
7
7
 
8
8
  # Guide: the Calendar tab of the console page
@@ -13,7 +13,22 @@ The Calendar tab of the console page (`/`) shows time. It shows what happens and
13
13
 
14
14
  The switch at the start of the bar has two parts. **Calendar** shows the grid. **Notes** shows your notes.
15
15
 
16
- In **Notes**, the list is at the left, under the heading **Notes**. The notes are in groups by what they are about: **Portfolio** first, then one group for each board, in the order of the portfolio. **Not linked** is the last group. A note linked to an initiative from an earlier version, with no board, is in **Initiatives**. In each group, the newest meeting is first. A note with no meeting is placed by when its recording started, or else by when it was made. The number shows how many notes the list shows. Type in **Search notes** to keep only the notes whose title, board, initiative or summary has the words. A row shows when the note was made and its length. In a board group, a row shows its initiative, or **board** when the note is about the whole board. A row also shows **recording** or **paused** while the note is held, **no meeting** when the note has no meeting, and a warning mark when a statement differs from a file.
16
+ In **Notes**, the list is at the left, under the heading **Notes**. The notes are in groups by what they are about: **Portfolio** first, then one group for each board, in the order of the portfolio. **Not linked** is the last group. A note linked to an initiative from an earlier version, with no board, is in **Initiatives**. In each group, the newest meeting is first. A note with no meeting is placed by when its recording started, or else by when it was made. The number shows how many notes the list shows. Type in **Search notes** to keep only the notes that have every word. The search reads the title, the board, the initiative, the summary, the decisions, the next steps and what was said. A private note is found by its title only. A row shows when the note was made and its length. In a board group, a row shows its initiative, or **board** when the note is about the whole board. A row also shows **recording** or **paused** while the note is held, **no meeting** when the note has no meeting, and a warning mark when a statement differs from a file.
17
+
18
+ Notes of one regular meeting are a **series**: the same calendar series, or the same title. A series is one row. The row shows how many meetings it has, and how many next steps are still open. Press the arrow on the row to show its last three meetings. Press the row to open the series at the right. Notes older than 30 days fold into **Older than 30 days** at the end of each group. Press it to show them. A recorder left on after the meeting shows its length up to the last line heard, not up to when it stopped.
19
+
20
+ The series page shows what carries from one meeting to the next:
21
+
22
+ - **Next one** shows the next meeting of the series and what to bring up: the next steps still open after several meetings, the questions asked in several meetings, and your own lines. Type in **Add something to bring up** and press `Enter` to add a line. Press the cross to remove it.
23
+ - **Still open** shows each next step not yet done, its owner, the number of meetings it was raised in, and when it was last heard. Press **Done** when it is done. It stops carrying over. **Open again** puts it back.
24
+ - **Decided so far** shows each decision of the series, newest first, with why.
25
+ - **Keeps coming up** shows the topics heard in several meetings.
26
+ - **Still unanswered** shows the open questions. Press **Answered** when a question has its answer.
27
+ - **Every meeting** shows each note of the series with its gist. Press one to open it.
28
+
29
+ A private note adds its title and date to the series, and nothing else.
30
+
31
+ A note in a series shows the series and its place, for example `IDPF - DSU · 21 of 21`. Press it to open the series. The arrows open the meeting before and the meeting after. **After** shows **Since the last one** first: each next step of the meeting before, as **Done**, **Raised again** or **Not mentioned**, then the new next steps. Press **Not part of this series** to take a note out of its series. Press **Add to a series** to put it in one.
17
32
 
18
33
  Press a row to open the note at the right. The note has three stages. Press **Before**, **During** or **After** to change the stage.
19
34
 
@@ -71,3 +71,17 @@ PMOS never overwrites the document from a copy.
71
71
  Health shows the checks of the document and how many pass.
72
72
 
73
73
  Relations shows a graph of the documents around this one: the documents it uses on the left, its initiative and goals on top, and the documents that use it and its copies on the right. A dashed orange line shows a document that is out of step. Select a document to open it. The lists under the graph name the same documents. From Initiatives › Documents, **Open the full graph in Docs** shows the whole graph.
74
+
75
+ ## Report documents
76
+
77
+ A report that you set up opens in the same sheet. Each cycle of a report is one document. The sheet adds these parts for a report.
78
+
79
+ - **The head.** A chip says **Draft** or **Issued**. A second chip names the report. A third chip names the format: Google Slides, Google Doc, Google Sheets, Confluence or PDF.
80
+ - **Read.** The text is drawn in the format of the report. A Slides report shows slides. A Sheets report shows grids. A Google Doc or a Confluence report shows a page. A PDF report shows A4 pages. The words come from the markdown file. Edit the file, or ask for a change, and the format changes with it.
81
+ - **Issue.** Press **Issue** on a draft when you have sent it. PMOS sends nothing. Issue records that you did.
82
+ - **Download.** Open the menu with the three dots. A Slides report offers PDF and PowerPoint. A Sheets report offers Excel and CSV. A Google Doc report offers PDF and Word. A Confluence report offers PDF and Word. A PDF report offers PDF. For PDF, the sheet opens the print dialog. Choose "Save as PDF" in it.
83
+ - **Copies.** A report has one live copy, in the tool of its format. A PDF has none. Before the copy exists, the panel says "Not created" and offers **Create the Google Slides copy** (or the name of the tool of the report). PMOS waits five seconds before it starts, and **Undo** stops it. When PMOS is ahead of the copy, the button is **Update the copy**. PMOS writes to Google or to Confluence only when you press one of these buttons.
84
+
85
+ ## Issued reports are frozen
86
+
87
+ An issued report cannot be edited. **Edit** says so, and offers **Start the next cycle**. The next cycle is a new draft. If the next draft already exists, **Start the next cycle** opens it. If it does not exist, PMOS drafts it on the day of the schedule.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: Guide
3
3
  title: "Guide: start here"
4
- timestamp: 2026-10-06
4
+ timestamp: 2026-10-12
5
5
  ---
6
6
 
7
7
  # Guide: start here
@@ -71,7 +71,8 @@ On a desktop the workspace is one frame. [Layout](layout.md) explains each part.
71
71
  - [Board](board.md): one Kanban for the items of every initiative, its filters, Release, and `pmos board`.
72
72
  - [Integrated work](integrated-work.md): how `pmos verify-work` turns a merged commit into Done, and what "Done · your call · not merged" means.
73
73
  - [Report slides](report-slides.md): how the Reports page shows a report as the PM-OS biweekly slides, Present and Print / PDF, and how your notes become the words on the slides.
74
- - [Reports: dashboards of reports](reports-dashboards.md): the console Reports page, its dashboards at portfolio, board and initiative scope, how to edit and share one as a draft, and the Drafts tab.
74
+ - [Reports: dashboards of reports](reports-dashboards.md): the console Reports page, its dashboards at portfolio, board and initiative scope, how to edit one, how to send one as a report, and the Charts tab.
75
+ - [Set up a report by asking, and the report finder](reports-set-up.md): how to ask PMOS to draft a report you send, how the finder finds reports you already send, and how a cycle becomes a document.
75
76
  - [The document sheet](document-sheet.md): read a document as rich text, edit it, accept the agent's proposals section by section, read the edit log, and clear a stale document.
76
77
  - [Ask the agent from the page](ask-the-agent.md): the five presses that start an agent from the console, what the agent may do, and what stays yours.
77
78
  - [Sessions in console3](console3-sessions.md): the Sessions panel, how to open the terminal of a session on the same page, and how to end a session.