create-pmos 0.179.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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-report-setup-test.js +190 -0
- package/workspace/c3-reports-test.js +30 -97
- package/workspace/guide/document-sheet.md +14 -0
- package/workspace/guide/index.md +3 -2
- package/workspace/guide/reports-dashboards.md +17 -28
- package/workspace/guide/reports-page.md +10 -5
- package/workspace/guide/reports-set-up.md +56 -0
- package/workspace/guide/reports.md +13 -1
- package/workspace/guide/settings.md +11 -2
- package/workspace/lib/doc-sync.js +28 -6
- package/workspace/lib/docs-cli.js +7 -11
- package/workspace/lib/four-panel.js +1 -1
- package/workspace/lib/headless.js +21 -2
- package/workspace/lib/report-checks.js +103 -0
- package/workspace/lib/report-cli.js +89 -4
- package/workspace/lib/report-export.js +347 -0
- package/workspace/lib/report-finder.js +156 -0
- package/workspace/lib/report-needs.js +1 -1
- package/workspace/lib/report-setups.js +743 -0
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-c3-reports.js +35 -17
- package/workspace/lib/routes-doc-health.js +15 -1
- package/workspace/lib/routes-report-setups.js +368 -0
- package/workspace/lib/schedule-logic.js +5 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-demo-rep2.js +15 -0
- package/workspace/public/pm/c3-demo-rs.js +281 -0
- package/workspace/public/pm/c3-demo.js +1 -1
- package/workspace/public/pm/c3-doc-formats.js +152 -0
- package/workspace/public/pm/c3-doc-sheet.js +115 -25
- package/workspace/public/pm/c3-quiet.js +2 -2
- package/workspace/public/pm/c3-report-print.css +20 -0
- package/workspace/public/pm/c3-report-setup.css +62 -0
- package/workspace/public/pm/c3-report-setup.js +260 -0
- package/workspace/public/pm/c3-report-store.js +208 -0
- package/workspace/public/pm/c3-reports.css +4 -0
- package/workspace/public/pm/c3-reports.js +47 -181
- package/workspace/public/pm/c3-settings.js +4 -3
- package/workspace/public/pm/c3-shell.js +15 -31
- package/workspace/server.js +66 -5
- package/workspace/skills/doc-sync.skill +17 -8
- package/workspace/skills/report-find.skill +75 -0
- package/workspace/skills/report-setup.skill +67 -0
- package/workspace/test-files.json +10 -0
- 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.
|
|
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": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -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-13T02:
|
|
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-13T02:
|
|
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-13T02:
|
|
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§ion=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§ion=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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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]'; //
|
|
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,
|
|
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
|
|
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
|
-
/* ──
|
|
252
|
-
ok('the head
|
|
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-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
ok('the
|
|
259
|
-
ok('
|
|
260
|
-
|
|
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
|
|
296
|
-
const L = '[data-rp3-pane="
|
|
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 (
|
|
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', /
|
|
257
|
+
ok('a board layout shows in where it is used', /Dashboards: .*Board/.test(await page.textContent(L + ' [data-home="synergy"]')));
|
|
306
258
|
|
|
307
|
-
/* ──
|
|
308
|
-
|
|
309
|
-
ok('the
|
|
310
|
-
await page
|
|
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-
|
|
345
|
-
ok('400px: the
|
|
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="
|
|
348
|
-
await until(() => pp.$('[data-rp3-pane="
|
|
349
|
-
ok('400px:
|
|
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
|
|
@@ -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.
|
package/workspace/guide/index.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Guide: start here"
|
|
4
|
-
timestamp: 2026-10-
|
|
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
|
|
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.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Reports: dashboards of reports"
|
|
4
|
-
description: "How the console Reports page shows dashboards at portfolio, board and initiative scope, how to edit one, how to
|
|
5
|
-
timestamp: 2026-10-
|
|
4
|
+
description: "How the console Reports page shows dashboards at portfolio, board and initiative scope, how to edit one, how to send one as a report, and what the Charts tab holds."
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Reports: dashboards of reports
|
|
@@ -11,9 +11,13 @@ A report answers one question. A dashboard is a set of reports for one scope. Op
|
|
|
11
11
|
|
|
12
12
|
## The three tabs
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **
|
|
14
|
+
Each tab has one job. A line under the tabs repeats it: **Live** goes to Dashboards, **Sent** goes to Reports, **Building blocks** goes to Charts.
|
|
15
|
+
|
|
16
|
+
- **Dashboards.** The charts of one scope, drawn from the repository's records. A dashboard is live: it changes when your work changes. The tab shows how many dashboards there are.
|
|
17
|
+
- **Reports.** The documents you send, with their set-ups. See [Set up a report by asking](reports-set-up.md). The tab shows how many drafts wait for you.
|
|
18
|
+
- **Charts.** Every chart the workspace can draw, with the question it answers, the dashboards that use it and the reports that use it. The tab shows how many charts there are.
|
|
19
|
+
|
|
20
|
+
The old tab names still work. A link to Library opens Charts. A link to Drafts opens Reports.
|
|
17
21
|
|
|
18
22
|
The headline deck of the Reports page is above the tabs. It shows only report signals: claims that block sending, ticket keys in a leadership report, a digest that is due, and people who hold an old date. It never repeats the Briefing's cards. When there is no report signal, the deck says that nothing needs you. The scope buttons are on the same row as the tabs.
|
|
19
23
|
|
|
@@ -50,32 +54,17 @@ The Calendar page no longer carries these. They moved here.
|
|
|
50
54
|
|
|
51
55
|
The workspace keeps your layout for each scope in its runtime state. It does not write to the repository. **Reset to default** returns a scope to its first layout.
|
|
52
56
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
Press **Share**. A sheet shows the dashboard as a report: a headline, one section per report, what needs your call, and the sources. Choose **Leadership** for one sentence per report. Choose **Team** to add the numbers behind each sentence.
|
|
56
|
-
|
|
57
|
-
**Make a draft now** puts one draft in the outbox. Nothing is sent. With no channel connected, the outbox says so.
|
|
58
|
-
|
|
59
|
-
## Put a dashboard on a schedule
|
|
60
|
-
|
|
61
|
-
A schedule makes a ready draft of a dashboard and tells you on the bell. Nothing is sent. No sender channel is connected yet, so you send the draft yourself.
|
|
62
|
-
|
|
63
|
-
1. Press **Share**.
|
|
64
|
-
2. Choose the audience: **Leadership** or **Team**.
|
|
65
|
-
3. Choose **Weekly, Mondays 08:00** or **Monthly, 1st at 08:00**. The sheet shows the next draft.
|
|
66
|
-
4. Press **Save schedule**.
|
|
57
|
+
## Send a dashboard as a report
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
A dashboard is live. A report is a document that you send on a day. To send a dashboard as a report, press **Send this as a report…** in the head of the dashboard.
|
|
69
60
|
|
|
70
|
-
|
|
61
|
+
1. A sheet shows the proposed report: its name, what it is built from, the output, the schedule and the audience.
|
|
62
|
+
2. Change the name. Choose the output: **PDF** or **Google Slides**. Choose the schedule: **Weekly**, **Every 2 weeks** or **Monthly**. Choose the audience: **Leadership** or **Team**.
|
|
63
|
+
3. Press **Set up**.
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
PMOS shows the first draft date. Each cycle is a snapshot document of the dashboard as it was on that day. The document is in Reports, under Documents, and the set-up is under Set up. The dashboard stays live.
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
PMOS drafts only. It sends nothing. It writes to Google only when you press **Create the copy** in the document.
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
The Share sheet and the schedule control of a dashboard are gone. Each schedule is now the schedule of a report. See and change it in Reports, under Set up.
|
|
77
70
|
|
|
78
|
-
- **Drafts.** One row for each dashboard draft. A row says **Made by schedule** or **Made by you**. Press **Preview** to read the text. Press **Discard** to remove it from the list. **Undo** in the message brings it back. The log keeps the line.
|
|
79
|
-
- **Scheduled.** One row for each schedule: the dashboard, how often, the audience and the next draft.
|
|
80
|
-
- **Handled for you**, **What I could stop** and **Data you can lean on.** The Briefing used to show these. They come from the same briefing data. A card with no data does not show.
|
|
81
|
-
- **Reports to send.** The four report families, the claims check and the outbox. They work as before.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Reports: draft, read and issue"
|
|
4
4
|
description: "How to draft a report, add your notes, issue it, and print or copy it. How the report schedules and the Today cards for reports work."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Reports: draft, read and issue
|
|
@@ -15,11 +15,16 @@ The workspace keeps each report in a file of the repository, for example `planni
|
|
|
15
15
|
|
|
16
16
|
Open the Reports tab with the shortcut `g r`, from the command palette, or from the Today card of a report. Reports shows the repository that is open.
|
|
17
17
|
|
|
18
|
-
The page has three
|
|
18
|
+
The Reports page has three tabs: Dashboards, Reports and Charts. See [Reports: dashboards of reports](reports-dashboards.md) and [Set up a report by asking](reports-set-up.md).
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
20
|
+
The Reports tab is for the documents that you issue. It has four parts:
|
|
21
|
+
|
|
22
|
+
- **Ask PMOS.** Describe a report. PMOS drafts it.
|
|
23
|
+
- **Found in your work.** The reports PMOS found that you already issue.
|
|
24
|
+
- **Set up.** One row for each report with a schedule.
|
|
25
|
+
- **Documents.** One row for each cycle of each report. A draft or an issued document opens in the [document sheet](document-sheet.md).
|
|
26
|
+
|
|
27
|
+
The parts that this guide describes below, such as Draft now, Edit notes and the report schedules, are the controls of the cadence report. The Reports tab does not show them. The `pmos report` commands in "In a terminal" do the same acts.
|
|
23
28
|
|
|
24
29
|
## Draft a report
|
|
25
30
|
|