create-pmos 0.124.0 → 0.126.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/nine-stages.js +81 -0
- package/payload/scripts/workflow-state.js +2 -0
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/c3-one-home-test.js +131 -0
- package/workspace/fixtures/initiatives-api/list.json +171 -3
- package/workspace/fixtures/initiatives-api/roadmap.json +228 -4
- package/workspace/fixtures/initiatives-api/sheet.json +57 -1
- package/workspace/fixtures/one-home-baseline.json +83 -0
- package/workspace/guide/briefing-cards.md +40 -0
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/layout.md +2 -0
- package/workspace/lib/engine/nine-stages.js +81 -0
- package/workspace/lib/engine/workflow-state.js +2 -0
- package/workspace/lib/facts-async.js +51 -0
- package/workspace/lib/item-steps.js +52 -0
- package/workspace/lib/jira-status.js +8 -3
- package/workspace/lib/jobs-to-be-done.js +52 -0
- package/workspace/lib/needs-overdue.js +41 -0
- package/workspace/lib/outbox-store.js +87 -0
- package/workspace/lib/pm-items.js +115 -0
- package/workspace/lib/route-modules.js +21 -0
- package/workspace/lib/routes-briefing.js +109 -0
- package/workspace/lib/routes-c3-init.js +53 -0
- package/workspace/lib/routes-c3-life.js +119 -0
- package/workspace/lib/routes-c3-strategy.js +169 -0
- package/workspace/lib/routes-c3-timeline.js +191 -0
- package/workspace/lib/routes-calendar-import.js +60 -0
- package/workspace/lib/routes-calendar-views.js +61 -0
- package/workspace/lib/routes-doc-health.js +79 -0
- package/workspace/lib/routes-doc-sheet.js +169 -0
- package/workspace/lib/routes-forecast.js +85 -0
- package/workspace/lib/routes-hooks.js +121 -0
- package/workspace/lib/routes-initiatives.js +3 -0
- package/workspace/lib/routes-intake.js +24 -0
- package/workspace/lib/routes-outbox.js +97 -0
- package/workspace/lib/routes-pm-items.js +216 -0
- package/workspace/lib/routes-records.js +61 -0
- package/workspace/lib/routes-releases.js +129 -0
- package/workspace/lib/routes-report-facts.js +84 -0
- package/workspace/lib/routes-report-families.js +32 -0
- package/workspace/lib/routes-roadmap-ops.js +141 -0
- package/workspace/lib/routes-runs.js +119 -0
- package/workspace/lib/routes-signals.js +50 -0
- package/workspace/lib/run-answers.js +69 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/c3.html +26 -0
- package/workspace/public/pm/briefing-deck.js +413 -0
- package/workspace/public/pm/briefing-model.js +211 -0
- package/workspace/public/pm/c3-breakdown.js +240 -0
- package/workspace/public/pm/c3-brief.js +278 -0
- package/workspace/public/pm/c3-cal-strategy.js +528 -0
- package/workspace/public/pm/c3-case.js +185 -0
- package/workspace/public/pm/c3-daypart.js +176 -0
- package/workspace/public/pm/c3-demo-brief-ix.js +350 -0
- package/workspace/public/pm/c3-demo-cal-ix.js +74 -0
- package/workspace/public/pm/c3-demo-cal2.js +66 -0
- package/workspace/public/pm/c3-demo-cal3.js +173 -0
- package/workspace/public/pm/c3-demo-data.js +560 -0
- package/workspace/public/pm/c3-demo-init.js +373 -0
- package/workspace/public/pm/c3-demo-life.js +77 -0
- package/workspace/public/pm/c3-demo-misc2.js +46 -0
- package/workspace/public/pm/c3-demo-plan2.js +30 -0
- package/workspace/public/pm/c3-demo-rep-ix.js +126 -0
- package/workspace/public/pm/c3-demo-rep2.js +92 -0
- package/workspace/public/pm/c3-demo-term.js +153 -0
- package/workspace/public/pm/c3-demo-time.js +49 -0
- package/workspace/public/pm/c3-demo-work-ix.js +80 -0
- package/workspace/public/pm/c3-demo-work2.js +13 -0
- package/workspace/public/pm/c3-demo.js +113 -0
- package/workspace/public/pm/c3-doc-sheet.js +314 -0
- package/workspace/public/pm/c3-dock.js +180 -0
- package/workspace/public/pm/c3-docs.js +290 -0
- package/workspace/public/pm/c3-expand.js +96 -0
- package/workspace/public/pm/c3-init-extras.js +170 -0
- package/workspace/public/pm/c3-lifecycle-panels.js +476 -0
- package/workspace/public/pm/c3-misc-panels.js +89 -0
- package/workspace/public/pm/c3-plan-panels.js +163 -0
- package/workspace/public/pm/c3-plan.js +192 -0
- package/workspace/public/pm/c3-roadmap.js +249 -0
- package/workspace/public/pm/c3-sheet.js +107 -0
- package/workspace/public/pm/c3-shell.js +674 -0
- package/workspace/public/pm/c3-signals.js +196 -0
- package/workspace/public/pm/c3-strategy-panels.js +45 -0
- package/workspace/public/pm/c3-swipe.js +230 -0
- package/workspace/public/pm/c3-terminal-data.js +26 -0
- package/workspace/public/pm/c3-terminal-edge.js +91 -0
- package/workspace/public/pm/c3-terminal-icons.js +41 -0
- package/workspace/public/pm/c3-terminal-results.js +141 -0
- package/workspace/public/pm/c3-terminal.js +52 -0
- package/workspace/public/pm/c3-tickets.js +163 -0
- package/workspace/public/pm/c3-timeline.js +201 -0
- package/workspace/public/pm/c3-toast.js +16 -0
- package/workspace/public/pm/calendar-views.js +1014 -0
- package/workspace/public/pm/console3-lift.md +220 -0
- package/workspace/public/pm/console3.css +1547 -0
- package/workspace/public/pm/dest-initiatives.js +16 -3
- package/workspace/public/pm/dest-mywork.js +8 -1
- package/workspace/public/pm/dest-reports.js +47 -1
- package/workspace/public/pm/dest-today.js +55 -1
- package/workspace/public/pm/doc-health-panel.js +110 -0
- package/workspace/public/pm/doc-rail.js +9 -3
- package/workspace/public/pm/lifecycle-lens-model.js +228 -0
- package/workspace/public/pm/lifecycle-lens.js +261 -0
- package/workspace/public/pm/new-initiative-sheet.js +116 -21
- package/workspace/public/pm/outbox-panel.js +125 -0
- package/workspace/public/pm/pm-work-view.js +895 -0
- package/workspace/public/pm/releases-view.js +248 -0
- package/workspace/public/pm/report-families-view.js +503 -0
- package/workspace/public/pm/run-card.js +244 -0
- package/workspace/public/pm/sessions-panel.js +149 -0
- package/workspace/public/pm/shell-bell.js +162 -0
- package/workspace/public/pm/shell-model.js +83 -0
- package/workspace/public/pm/shell-phone.js +124 -0
- package/workspace/public/pm/shell-readaloud.js +53 -0
- package/workspace/public/pm/shell-recording.js +110 -0
- package/workspace/public/pm/shell-ticker.js +90 -0
- package/workspace/public/pm/slide-confirm.js +163 -0
- package/workspace/public/pm/timeline-forecast-view.js +164 -0
- package/workspace/public/pm/today-day.js +8 -2
- package/workspace/public/pm/tokens.css +1 -0
- package/workspace/server.js +5 -2
- package/workspace/test-files.json +21 -3
- package/workspace/test-lists/browser-suites-green.json +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.126.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
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
kernel-source: pmos@
|
|
2
|
-
assembled: 2026-10-
|
|
1
|
+
kernel-source: pmos@ef9256d
|
|
2
|
+
assembled: 2026-10-11
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T02:44:29Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T02:44:29Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* The nine stage names as a label layer over the workflow engine's derived stage (run 3).
|
|
4
|
+
*
|
|
5
|
+
* The engine derives one of six rungs (intake, spec, contract, build, eval, accepted) from the steps
|
|
6
|
+
* it found. This file names the nine stages the PM sees and says which of them each lane uses. It
|
|
7
|
+
* never decides a stage itself: a stage is `done` only when a derived signal shows it, and a stage
|
|
8
|
+
* no record can yet show is `no-signal`, not guessed.
|
|
9
|
+
*
|
|
10
|
+
* The lane sets below are a proposal for the PM to correct (see the run 3 contract in the PRD).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const STAGES = ['discover', 'ideate', 'strategize', 'prioritize', 'plan', 'build', 'deliver', 'monitor', 'evaluate'];
|
|
14
|
+
|
|
15
|
+
const LABELS = {
|
|
16
|
+
discover: 'Discover', ideate: 'Ideate', strategize: 'Strategize', prioritize: 'Prioritize', plan: 'Plan',
|
|
17
|
+
build: 'Build', deliver: 'Deliver', monitor: 'Monitor', evaluate: 'Evaluate',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Which stages each product-code lane uses. Stages not listed read `skipped`.
|
|
21
|
+
const LANE_STAGES = {
|
|
22
|
+
feature: STAGES,
|
|
23
|
+
discovery: ['discover', 'ideate', 'strategize', 'prioritize', 'evaluate'],
|
|
24
|
+
chore: ['plan', 'build', 'evaluate'],
|
|
25
|
+
incident: ['build', 'deliver', 'monitor', 'evaluate'],
|
|
26
|
+
content: ['ideate', 'plan', 'build', 'deliver'],
|
|
27
|
+
prototype: ['discover', 'ideate', 'evaluate'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Stages that have a derived signal today. Every other stage reads `no-signal` until its record exists.
|
|
31
|
+
const SIGNALLED = new Set(['discover', 'plan', 'build', 'evaluate']);
|
|
32
|
+
|
|
33
|
+
const RANK = { intake: 0, spec: 1, contract: 2, build: 3, eval: 4, accepted: 5 };
|
|
34
|
+
|
|
35
|
+
// Stages that gain a signal only when the caller passes the records (scripts/records readAll plus optional `releases`).
|
|
36
|
+
// With no records the output is exactly the older output, so the lifecycle-lens mirror stays equal.
|
|
37
|
+
const RECORD_SIGNALLED = new Set(['ideate', 'strategize', 'prioritize', 'monitor', 'deliver']);
|
|
38
|
+
|
|
39
|
+
// records: { initiative?, anchor?, ideas, hypotheses, decisions, bets, 'kr-metrics', releases } (every list optional).
|
|
40
|
+
// The initiative id and anchor come from `derived` (id, anchor) or from records.initiative / records.anchor.
|
|
41
|
+
function recordSignals(derived, records, buildDone) {
|
|
42
|
+
const id = (derived && derived.id) || records.initiative || null;
|
|
43
|
+
const anchor = (derived && derived.anchor) || records.anchor || null;
|
|
44
|
+
const mine = (list) => (list || []).filter((r) => id && r.initiative === id);
|
|
45
|
+
const kr = (records['kr-metrics'] || []).filter((m) => anchor && (m.rid === anchor || m.parent_kr === anchor || 'kr:' + m.rid === anchor));
|
|
46
|
+
return {
|
|
47
|
+
ideate: mine(records.ideas).length + mine(records.hypotheses).length > 0,
|
|
48
|
+
strategize: mine(records.decisions).length + mine(records.bets).length > 0,
|
|
49
|
+
prioritize: mine(records.bets).some((b) => ['now', 'next', 'later'].includes(b.horizon) && Number.isInteger(b.rank)),
|
|
50
|
+
monitor: !!buildDone && kr.some((m) => (m.readings || []).length > 0),
|
|
51
|
+
deliver: mine(records.releases).length > 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function nineStages(lane, derived, records) {
|
|
56
|
+
const laneSet = new Set(LANE_STAGES[lane] || STAGES);
|
|
57
|
+
const rank = RANK[derived && derived.stage] ?? 0;
|
|
58
|
+
const step = (key) => (derived && derived.steps || []).find((s) => s.key === key);
|
|
59
|
+
const done = {
|
|
60
|
+
discover: rank >= 1,
|
|
61
|
+
plan: rank >= 2,
|
|
62
|
+
build: rank >= 3 || (step('build') && step('build').status === 'done'),
|
|
63
|
+
evaluate: rank >= 4,
|
|
64
|
+
};
|
|
65
|
+
const accepted = rank >= 5;
|
|
66
|
+
const fromRecords = records && typeof records === 'object' ? recordSignals(derived, records, done.build) : null;
|
|
67
|
+
const out = [];
|
|
68
|
+
let currentSet = false;
|
|
69
|
+
for (const key of STAGES) {
|
|
70
|
+
let status;
|
|
71
|
+
if (!laneSet.has(key)) status = 'skipped';
|
|
72
|
+
else if (!SIGNALLED.has(key) && !(fromRecords && RECORD_SIGNALLED.has(key) && (key !== 'deliver' || fromRecords.deliver))) status = 'no-signal';
|
|
73
|
+
else if (done[key] || (fromRecords && fromRecords[key])) status = 'done';
|
|
74
|
+
else status = 'pending';
|
|
75
|
+
if (status === 'pending' && !currentSet) { status = 'current'; currentSet = true; }
|
|
76
|
+
out.push({ key, label: LABELS[key], status, accepted: key === 'evaluate' ? accepted : undefined });
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { STAGES, LABELS, LANE_STAGES, nineStages };
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* Zero dependencies, stock Node. It runs in CI, in a terminal, and inside the workspace server.
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
+
const { nineStages } = require('./nine-stages');
|
|
39
40
|
const fs = require('fs');
|
|
40
41
|
const path = require('path');
|
|
41
42
|
const { execFileSync } = require('child_process');
|
|
@@ -1178,6 +1179,7 @@ function computeWorkflowDerived(id, opts) {
|
|
|
1178
1179
|
ok: true, id, lane, laneKnown: knownLane, legacy, in_change: inChange,
|
|
1179
1180
|
last_activity: lastActivity, accepted: accepted.length > 0, stage, record: recRel,
|
|
1180
1181
|
title: fm.title || id, anchor: fm.parent_kr || null, profile: 'product-code',
|
|
1182
|
+
nine_stages: nineStages(effLane, { stage, steps }),
|
|
1181
1183
|
...facts,
|
|
1182
1184
|
steps, blockers: blockers.map((s) => s.key), gate_failures: gateFailures.map((s) => s.rule || s.key),
|
|
1183
1185
|
next: next ? { step: next.key, why: next.evidence, action: next.next || next.evidence } : null,
|
|
@@ -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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T02:44:29Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
package/payload/state/log.md
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/* c3-one-home-test.js — the one-home rule as a check (console3 feedback plan, section 2; initiative browser-suites-green).
|
|
4
|
+
*
|
|
5
|
+
* Rule: an item is actionable in exactly one place, its home. Anywhere else it may appear only as a count, a date marker or
|
|
6
|
+
* a reference that opens the home. This suite loads the console3 page with the demo fixture (/c3.html?demo=1), opens every
|
|
7
|
+
* tab, and collects each item that carries an action control (a button, or an element with data-a) inside that tab's pane.
|
|
8
|
+
* An item is named by its title text, so the same item drawn by two different renderers still matches. It fails when one
|
|
9
|
+
* item has an action control on more than one tab.
|
|
10
|
+
*
|
|
11
|
+
* The page breaks the rule today, and the restructure that fixes it is Phase 2 of the plan. Until then the duplicates that
|
|
12
|
+
* exist now are listed in fixtures/one-home-baseline.json: they are printed, not failed. A duplicate that is NOT in the
|
|
13
|
+
* baseline fails the suite, so the count can only go down. When a duplicate goes away, remove it from the baseline (the
|
|
14
|
+
* suite says which ones are stale). Run with --write-baseline to rewrite the file from the current page.
|
|
15
|
+
*/
|
|
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: 'one-home' });
|
|
21
|
+
|
|
22
|
+
const BASE = T.BASE;
|
|
23
|
+
const BASELINE = path.join(__dirname, 'fixtures', 'one-home-baseline.json');
|
|
24
|
+
const WRITE = process.argv.includes('--write-baseline');
|
|
25
|
+
const TABS = ['brief', 'cal', 'work', 'init', 'rep'];
|
|
26
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
27
|
+
const results = [];
|
|
28
|
+
const ok = (name, cond, extra = '') => { results.push({ name, cond: !!cond }); console.log(`${cond ? '✓' : '✗'} ${name}${!cond && extra !== '' ? ' — ' + extra : ''}`); };
|
|
29
|
+
let srv = null; let browser = null;
|
|
30
|
+
const finish = async (code) => {
|
|
31
|
+
if (browser) await browser.close().catch(() => {});
|
|
32
|
+
if (srv) { try { srv.kill('SIGKILL'); } catch { /* gone */ } }
|
|
33
|
+
T.killTmux();
|
|
34
|
+
const bad = results.filter((x) => !x.cond).length;
|
|
35
|
+
console.log(`\n${results.length - bad}/${results.length} one-home assertions passed`);
|
|
36
|
+
process.exit(code !== undefined ? code : (bad ? 1 : 0));
|
|
37
|
+
};
|
|
38
|
+
process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
39
|
+
|
|
40
|
+
/* Runs in the page: every item in a pane that carries an action control, named by its title. */
|
|
41
|
+
function collect(tab) {
|
|
42
|
+
const pane = document.querySelector('[data-c3-pane="' + tab + '"]');
|
|
43
|
+
if (!pane) return null;
|
|
44
|
+
const norm = (s) => String(s || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
45
|
+
const visible = (e) => !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length) && !e.closest('[hidden]');
|
|
46
|
+
const ITEM = '[data-row], [data-id], [data-due-row], [data-needs-row], [data-draft-row], li, article';
|
|
47
|
+
const out = new Map();
|
|
48
|
+
// A control is a button, an element with a press action (data-a), or a row that acts by itself: a swipe row or a focusable
|
|
49
|
+
// row (Work's rows have no buttons; a swipe or an arrow key is the action).
|
|
50
|
+
for (const ctl of pane.querySelectorAll('button, [data-a], [role="button"], [data-swipe], [data-row][tabindex]')) {
|
|
51
|
+
if (!visible(ctl) || ctl.disabled) continue;
|
|
52
|
+
const item = ctl.closest(ITEM);
|
|
53
|
+
if (!item || !pane.contains(item)) continue;
|
|
54
|
+
const t = item.querySelector('[data-pm-title], .tt, h2, b, strong, .t, [data-title], h3, h4');
|
|
55
|
+
const title = norm(t ? t.textContent : (item.textContent || '').split('\n')[0]).slice(0, 80);
|
|
56
|
+
if (title.length < 4) continue;
|
|
57
|
+
const id = item.getAttribute('data-id') || item.getAttribute('data-needs-row') || item.getAttribute('data-due-row') || item.getAttribute('data-draft-row') || '';
|
|
58
|
+
const label = ctl === item ? 'row' : norm(ctl.getAttribute('aria-label') || ctl.textContent).slice(0, 40);
|
|
59
|
+
if (!out.has(title)) out.set(title, { title, id, controls: [] });
|
|
60
|
+
if (!out.get(title).controls.includes(label)) out.get(title).controls.push(label);
|
|
61
|
+
}
|
|
62
|
+
return [...out.values()];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
(async () => {
|
|
66
|
+
const exe = findBrowser();
|
|
67
|
+
if (!exe) { console.log('✗ no Chrome/Chromium on this machine — the one-home suite cannot run'); return finish(1); }
|
|
68
|
+
let chromium;
|
|
69
|
+
try { ({ chromium } = require('playwright-core')); } catch { console.log('✗ playwright-core is not installed — the one-home suite cannot run'); return finish(1); }
|
|
70
|
+
|
|
71
|
+
srv = spawn(process.execPath, [path.join(__dirname, 'server.js')], { cwd: __dirname, stdio: 'ignore', env: T.env({ PMOS_WS_PORT: String(T.PORT) }) });
|
|
72
|
+
await T.waitForServer(BASE);
|
|
73
|
+
browser = await chromium.launch({ executablePath: exe });
|
|
74
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
|
|
75
|
+
const page = await ctx.newPage();
|
|
76
|
+
const errors = [];
|
|
77
|
+
page.on('pageerror', (e) => errors.push(String(e && e.message || e)));
|
|
78
|
+
page.on('dialog', (d) => d.dismiss().catch(() => {}));
|
|
79
|
+
|
|
80
|
+
await page.goto(BASE + '/c3.html?demo=1');
|
|
81
|
+
const up = await page.waitForSelector('[data-c3-demo="1"] [data-c3-tab="brief"]', { timeout: 30000 }).catch(() => null);
|
|
82
|
+
ok('the console3 page comes up in demo mode', !!up);
|
|
83
|
+
if (!up) return finish(1);
|
|
84
|
+
|
|
85
|
+
const byTab = {};
|
|
86
|
+
for (const tab of TABS) {
|
|
87
|
+
await page.click('[data-c3-tab="' + tab + '"]');
|
|
88
|
+
// A pane builds on first show and its views read the demo's /api answers; wait until its item count settles.
|
|
89
|
+
let last = -1; let items = null;
|
|
90
|
+
for (let i = 0; i < 20; i++) {
|
|
91
|
+
await sleep(250);
|
|
92
|
+
items = await page.evaluate(collect, tab);
|
|
93
|
+
const n = items ? items.length : -1;
|
|
94
|
+
if (n === last && n > 0) break;
|
|
95
|
+
last = n;
|
|
96
|
+
}
|
|
97
|
+
byTab[tab] = items || [];
|
|
98
|
+
ok(`the ${tab} tab shows items with action controls`, byTab[tab].length > 0, JSON.stringify(items));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Every title that has an action control on two or more tabs.
|
|
102
|
+
const where = new Map();
|
|
103
|
+
for (const tab of TABS) for (const it of byTab[tab]) {
|
|
104
|
+
if (!where.has(it.title)) where.set(it.title, []);
|
|
105
|
+
where.get(it.title).push({ tab, controls: it.controls });
|
|
106
|
+
}
|
|
107
|
+
const dupes = [...where.entries()].filter(([, w]) => w.length > 1).map(([title, w]) => ({ title, tabs: w.map((x) => x.tab) }))
|
|
108
|
+
.sort((a, b) => a.title.localeCompare(b.title));
|
|
109
|
+
|
|
110
|
+
if (process.env.ONE_HOME_DUMP) for (const tab of TABS) console.log(tab, JSON.stringify(byTab[tab].map((i) => [i.title, i.id, i.controls.join(",")])));
|
|
111
|
+
console.log(`\none-home: ${where.size} actionable items over ${TABS.length} tabs; ${dupes.length} actionable on more than one tab`);
|
|
112
|
+
for (const d of dupes) console.log(` · ${d.tabs.join(' + ')} ${d.title}`);
|
|
113
|
+
|
|
114
|
+
if (WRITE) {
|
|
115
|
+
fs.writeFileSync(BASELINE, JSON.stringify({
|
|
116
|
+
_comment: 'Items the console3 page makes actionable on more than one tab today (c3-one-home-test.js). Known-failing until the Phase 2 restructure of the console3 feedback plan; the suite fails only on a duplicate that is not listed here. Remove an entry when its duplicate is gone.',
|
|
117
|
+
duplicates: dupes,
|
|
118
|
+
}, null, 2) + '\n');
|
|
119
|
+
console.log(`wrote ${path.relative(__dirname, BASELINE)} with ${dupes.length} entries`);
|
|
120
|
+
}
|
|
121
|
+
const base = fs.existsSync(BASELINE) ? JSON.parse(fs.readFileSync(BASELINE, 'utf8')).duplicates || [] : [];
|
|
122
|
+
const known = new Set(base.map((d) => d.title));
|
|
123
|
+
const fresh = dupes.filter((d) => !known.has(d.title));
|
|
124
|
+
const stale = base.filter((d) => !where.has(d.title) || where.get(d.title).length < 2);
|
|
125
|
+
ok('no item is actionable on more than one tab, other than the known duplicates in the baseline', fresh.length === 0,
|
|
126
|
+
fresh.map((d) => `${d.tabs.join('+')}: ${d.title}`).join(' | '));
|
|
127
|
+
if (stale.length) console.log(`· ${stale.length} baseline entr${stale.length === 1 ? 'y is' : 'ies are'} no longer duplicated; remove ${stale.length === 1 ? 'it' : 'them'} from the baseline: ${stale.map((d) => d.title).join(' | ')}`);
|
|
128
|
+
console.log(`· known-failing: ${dupes.length - fresh.length} duplicate(s) wait for the Phase 2 restructure`);
|
|
129
|
+
ok('the page raised no script error', errors.length === 0, errors.slice(0, 3).join(' | '));
|
|
130
|
+
return finish();
|
|
131
|
+
})();
|
|
@@ -69,7 +69,63 @@
|
|
|
69
69
|
"label": "7 of 12"
|
|
70
70
|
},
|
|
71
71
|
"next_due": "2026-11-14",
|
|
72
|
-
"last_activity": "2026-10-04T09:00:00Z"
|
|
72
|
+
"last_activity": "2026-10-04T09:00:00Z",
|
|
73
|
+
"nine_stages": [
|
|
74
|
+
{
|
|
75
|
+
"key": "discover",
|
|
76
|
+
"label": "Discover",
|
|
77
|
+
"status": "pending",
|
|
78
|
+
"accepted": false
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"key": "ideate",
|
|
82
|
+
"label": "Ideate",
|
|
83
|
+
"status": "pending",
|
|
84
|
+
"accepted": false
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"key": "strategize",
|
|
88
|
+
"label": "Strategize",
|
|
89
|
+
"status": "pending",
|
|
90
|
+
"accepted": false
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"key": "prioritize",
|
|
94
|
+
"label": "Prioritize",
|
|
95
|
+
"status": "pending",
|
|
96
|
+
"accepted": false
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"key": "plan",
|
|
100
|
+
"label": "Plan",
|
|
101
|
+
"status": "pending",
|
|
102
|
+
"accepted": false
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"key": "build",
|
|
106
|
+
"label": "Build",
|
|
107
|
+
"status": "pending",
|
|
108
|
+
"accepted": false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"key": "deliver",
|
|
112
|
+
"label": "Deliver",
|
|
113
|
+
"status": "pending",
|
|
114
|
+
"accepted": false
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"key": "monitor",
|
|
118
|
+
"label": "Monitor",
|
|
119
|
+
"status": "pending",
|
|
120
|
+
"accepted": false
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"key": "evaluate",
|
|
124
|
+
"label": "Evaluate",
|
|
125
|
+
"status": "pending",
|
|
126
|
+
"accepted": false
|
|
127
|
+
}
|
|
128
|
+
]
|
|
73
129
|
}
|
|
74
130
|
]
|
|
75
131
|
}
|
|
@@ -124,7 +180,63 @@
|
|
|
124
180
|
"label": "3 of 5"
|
|
125
181
|
},
|
|
126
182
|
"next_due": "2026-10-20",
|
|
127
|
-
"last_activity": "2026-09-29T15:00:00Z"
|
|
183
|
+
"last_activity": "2026-09-29T15:00:00Z",
|
|
184
|
+
"nine_stages": [
|
|
185
|
+
{
|
|
186
|
+
"key": "discover",
|
|
187
|
+
"label": "Discover",
|
|
188
|
+
"status": "pending",
|
|
189
|
+
"accepted": false
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"key": "ideate",
|
|
193
|
+
"label": "Ideate",
|
|
194
|
+
"status": "pending",
|
|
195
|
+
"accepted": false
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"key": "strategize",
|
|
199
|
+
"label": "Strategize",
|
|
200
|
+
"status": "pending",
|
|
201
|
+
"accepted": false
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"key": "prioritize",
|
|
205
|
+
"label": "Prioritize",
|
|
206
|
+
"status": "pending",
|
|
207
|
+
"accepted": false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"key": "plan",
|
|
211
|
+
"label": "Plan",
|
|
212
|
+
"status": "pending",
|
|
213
|
+
"accepted": false
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"key": "build",
|
|
217
|
+
"label": "Build",
|
|
218
|
+
"status": "pending",
|
|
219
|
+
"accepted": false
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"key": "deliver",
|
|
223
|
+
"label": "Deliver",
|
|
224
|
+
"status": "pending",
|
|
225
|
+
"accepted": false
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"key": "monitor",
|
|
229
|
+
"label": "Monitor",
|
|
230
|
+
"status": "pending",
|
|
231
|
+
"accepted": false
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"key": "evaluate",
|
|
235
|
+
"label": "Evaluate",
|
|
236
|
+
"status": "pending",
|
|
237
|
+
"accepted": false
|
|
238
|
+
}
|
|
239
|
+
]
|
|
128
240
|
},
|
|
129
241
|
{
|
|
130
242
|
"id": "old-import",
|
|
@@ -159,7 +271,63 @@
|
|
|
159
271
|
"label": "no breakdown"
|
|
160
272
|
},
|
|
161
273
|
"next_due": null,
|
|
162
|
-
"last_activity": "2026-07-01T10:00:00Z"
|
|
274
|
+
"last_activity": "2026-07-01T10:00:00Z",
|
|
275
|
+
"nine_stages": [
|
|
276
|
+
{
|
|
277
|
+
"key": "discover",
|
|
278
|
+
"label": "Discover",
|
|
279
|
+
"status": "pending",
|
|
280
|
+
"accepted": false
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"key": "ideate",
|
|
284
|
+
"label": "Ideate",
|
|
285
|
+
"status": "pending",
|
|
286
|
+
"accepted": false
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"key": "strategize",
|
|
290
|
+
"label": "Strategize",
|
|
291
|
+
"status": "pending",
|
|
292
|
+
"accepted": false
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"key": "prioritize",
|
|
296
|
+
"label": "Prioritize",
|
|
297
|
+
"status": "pending",
|
|
298
|
+
"accepted": false
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"key": "plan",
|
|
302
|
+
"label": "Plan",
|
|
303
|
+
"status": "pending",
|
|
304
|
+
"accepted": false
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"key": "build",
|
|
308
|
+
"label": "Build",
|
|
309
|
+
"status": "pending",
|
|
310
|
+
"accepted": false
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"key": "deliver",
|
|
314
|
+
"label": "Deliver",
|
|
315
|
+
"status": "pending",
|
|
316
|
+
"accepted": false
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"key": "monitor",
|
|
320
|
+
"label": "Monitor",
|
|
321
|
+
"status": "pending",
|
|
322
|
+
"accepted": false
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"key": "evaluate",
|
|
326
|
+
"label": "Evaluate",
|
|
327
|
+
"status": "pending",
|
|
328
|
+
"accepted": false
|
|
329
|
+
}
|
|
330
|
+
]
|
|
163
331
|
}
|
|
164
332
|
]
|
|
165
333
|
}
|