create-pmos 0.52.0 → 0.53.1
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/mods/pmos-cockpit/README.md +13 -0
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
- package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/plan-items-stage.js +271 -0
- package/payload/scripts/pmos-log.sh +18 -7
- package/payload/scripts/workflow-state.js +28 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/README.md +2 -0
- package/workspace/REMOTE.md +24 -63
- package/workspace/bin/pmos.js +19 -3
- package/workspace/cut-stub.js +40 -0
- package/workspace/first-paint-audit.js +145 -0
- package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
- package/workspace/fixtures/initiatives-api/list.json +176 -0
- package/workspace/fixtures/initiatives-api/post-403.json +5 -0
- package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
- package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
- package/workspace/fixtures/initiatives-api/sheet.json +112 -0
- package/workspace/fixtures/report-api/draft.json +177 -0
- package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
- package/workspace/fixtures/today-api/provider-rows.json +204 -0
- package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
- package/workspace/guide/board.md +81 -0
- package/workspace/guide/breakdown.md +154 -0
- package/workspace/guide/command-palette.md +75 -0
- package/workspace/guide/index.md +28 -17
- package/workspace/guide/initiatives-roadmap.md +130 -0
- package/workspace/guide/initiatives-screen.md +89 -0
- package/workspace/guide/initiatives-timeline.md +57 -0
- package/workspace/guide/integrated-work.md +94 -0
- package/workspace/guide/knowledge-graph.md +3 -68
- package/workspace/guide/launcher.md +51 -0
- package/workspace/guide/layout.md +102 -32
- package/workspace/guide/live-notes.md +3 -3
- package/workspace/guide/pm-only-acts.md +89 -0
- package/workspace/guide/pm-only-lines.md +61 -0
- package/workspace/guide/proactive-engine.md +21 -5
- package/workspace/guide/reports-page.md +80 -0
- package/workspace/guide/reports.md +73 -0
- package/workspace/guide/timeline.md +70 -0
- package/workspace/guide/today.md +85 -0
- package/workspace/guide/validate-with-typesafe.md +1 -1
- package/workspace/lib/actor.js +91 -0
- package/workspace/lib/board-cache.js +32 -2
- package/workspace/lib/board-cli.js +108 -0
- package/workspace/lib/board-fold.js +232 -0
- package/workspace/lib/breakdown-export.js +75 -0
- package/workspace/lib/brief.js +20 -2
- package/workspace/lib/capture.js +98 -0
- package/workspace/lib/engine/plan-items-stage.js +271 -0
- package/workspace/lib/engine/workflow-state.js +28 -2
- package/workspace/lib/factory-join.js +158 -0
- package/workspace/lib/goals.js +165 -0
- package/workspace/lib/grant-mint.js +113 -0
- package/workspace/lib/health-rules-factory.js +92 -0
- package/workspace/lib/health-rules-plan.js +164 -0
- package/workspace/lib/health.js +79 -0
- package/workspace/lib/initiative-cli.js +222 -0
- package/workspace/lib/initiative-display.js +183 -0
- package/workspace/lib/initiative-needs.js +127 -0
- package/workspace/lib/initiative-rows.js +210 -0
- package/workspace/lib/initiative-writer.js +141 -0
- package/workspace/lib/issues.js +206 -0
- package/workspace/lib/item-cli.js +195 -0
- package/workspace/lib/item-spans.js +137 -0
- package/workspace/lib/launcher-cli.js +207 -0
- package/workspace/lib/measure-cli.js +100 -0
- package/workspace/lib/measurements.js +189 -0
- package/workspace/lib/mobile-nav.js +57 -53
- package/workspace/lib/needs-providers.js +120 -0
- package/workspace/lib/placements.js +440 -0
- package/workspace/lib/plan-items.js +922 -0
- package/workspace/lib/plan-ledgers.js +85 -0
- package/workspace/lib/plan-rules.js +72 -0
- package/workspace/lib/plan-tree.js +380 -0
- package/workspace/lib/pm-guard.js +174 -0
- package/workspace/lib/pm-lines-needs.js +331 -0
- package/workspace/lib/pmos-verbs.js +69 -0
- package/workspace/lib/proactive-cli.js +58 -2
- package/workspace/lib/proactive.js +9 -6
- package/workspace/lib/qr.js +293 -0
- package/workspace/lib/remote-access.js +93 -1
- package/workspace/lib/report-cli.js +152 -0
- package/workspace/lib/report-draft.js +578 -0
- package/workspace/lib/report-needs.js +213 -0
- package/workspace/lib/route-modules.js +128 -0
- package/workspace/lib/routes-board.js +91 -0
- package/workspace/lib/routes-capture.js +52 -0
- package/workspace/lib/routes-initiatives.js +371 -0
- package/workspace/lib/routes-reports.js +163 -0
- package/workspace/lib/routes-timeline.js +197 -0
- package/workspace/lib/routes-today.js +32 -0
- package/workspace/lib/today-cli.js +99 -0
- package/workspace/lib/today.js +333 -0
- package/workspace/lib/triage.js +163 -0
- package/workspace/lib/verify-work-cli.js +81 -0
- package/workspace/lib/work-check-needs.js +99 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +4 -5
- package/workspace/public/icons/apple-touch-icon.png +0 -0
- package/workspace/public/icons/icon-192.png +0 -0
- package/workspace/public/icons/icon-512.png +0 -0
- package/workspace/public/index.html +40 -19
- package/workspace/public/pm/app.js +330 -30
- package/workspace/public/pm/bd-board.js +113 -0
- package/workspace/public/pm/bd-drag.js +59 -0
- package/workspace/public/pm/bd-model.js +152 -0
- package/workspace/public/pm/bd-timeline.js +233 -0
- package/workspace/public/pm/bd-tree.js +244 -0
- package/workspace/public/pm/board-copy.js +57 -0
- package/workspace/public/pm/breakdown-api.js +58 -0
- package/workspace/public/pm/breakdown-copy.js +138 -0
- package/workspace/public/pm/components.css +3135 -0
- package/workspace/public/pm/cycle-review.js +125 -0
- package/workspace/public/pm/data.js +40 -187
- package/workspace/public/pm/dest-board.js +438 -0
- package/workspace/public/pm/dest-initiatives.js +134 -0
- package/workspace/public/pm/dest-reports.js +270 -0
- package/workspace/public/pm/dest-settings.js +217 -0
- package/workspace/public/pm/dest-today.js +182 -0
- package/workspace/public/pm/dialogs.js +88 -0
- package/workspace/public/pm/display-word.js +56 -0
- package/workspace/public/pm/dock.js +175 -0
- package/workspace/public/pm/fixture.js +8 -227
- package/workspace/public/pm/frame-copy.js +99 -0
- package/workspace/public/pm/handoff-box.js +82 -0
- package/workspace/public/pm/http.js +88 -0
- package/workspace/public/pm/icons.js +53 -0
- package/workspace/public/pm/initiative-list.js +218 -0
- package/workspace/public/pm/initiative-sheet.js +261 -0
- package/workspace/public/pm/initiatives-api.js +59 -0
- package/workspace/public/pm/initiatives-copy.js +215 -0
- package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
- package/workspace/public/pm/initiatives-timeline.js +410 -0
- package/workspace/public/pm/initiatives-ui.js +123 -0
- package/workspace/public/pm/item-sheet.js +251 -0
- package/workspace/public/pm/keep-card.js +204 -0
- package/workspace/public/pm/lifecycle.js +7 -1
- package/workspace/public/pm/mobile.css +49 -20
- package/workspace/public/pm/mobile.js +157 -29
- package/workspace/public/pm/modes-live.js +7 -883
- package/workspace/public/pm/modes.js +32 -1024
- package/workspace/public/pm/new-initiative-sheet.js +74 -0
- package/workspace/public/pm/next-step-ui.js +3 -2
- package/workspace/public/pm/palette-copy.js +51 -0
- package/workspace/public/pm/palette.js +312 -0
- package/workspace/public/pm/panel-breakdown.js +702 -0
- package/workspace/public/pm/panel-cockpit.js +63 -295
- package/workspace/public/pm/panel-detail.js +25 -172
- package/workspace/public/pm/panel-initiatives.js +1 -19
- package/workspace/public/pm/panel-repos.js +31 -81
- package/workspace/public/pm/panel-tabs.js +200 -111
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/portfolio-report.js +61 -0
- package/workspace/public/pm/primitives.js +656 -0
- package/workspace/public/pm/print.css +33 -0
- package/workspace/public/pm/provider-cards.js +136 -0
- package/workspace/public/pm/registry.js +194 -0
- package/workspace/public/pm/release-card.js +110 -0
- package/workspace/public/pm/report-view.js +309 -0
- package/workspace/public/pm/reports-copy.js +114 -0
- package/workspace/public/pm/roadmap.js +271 -0
- package/workspace/public/pm/sig-watch.js +41 -0
- package/workspace/public/pm/state.js +8 -4
- package/workspace/public/pm/status-banner.js +13 -5
- package/workspace/public/pm/tab-doc.js +2 -4
- package/workspace/public/pm/tab-term.js +12 -2
- package/workspace/public/pm/tab-web.js +3 -2
- package/workspace/public/pm/timeline-copy.js +48 -0
- package/workspace/public/pm/today-cards.js +244 -0
- package/workspace/public/pm/today-copy.js +126 -0
- package/workspace/public/pm/today-goals.js +172 -0
- package/workspace/public/pm/tokens.css +16 -18
- package/workspace/public/pm/topbar.js +102 -0
- package/workspace/public/pm/triage-card.js +60 -0
- package/workspace/public/pm/undo-hold.js +103 -0
- package/workspace/public/pm/view-addrepo.js +2 -1
- package/workspace/public/pm/view-livenotes.js +3 -1
- package/workspace/public/pm/view-today.js +7 -162
- package/workspace/public/vendor/Sortable.min.js +2 -0
- package/workspace/public/vendor/lucide-sprite.js +79 -0
- package/workspace/replaced-tests.json +2877 -0
- package/workspace/run-tests.js +56 -5
- package/workspace/server.js +446 -40
- package/workspace/sheet-answer.js +61 -0
- package/workspace/skills/measure.skill +41 -0
- package/workspace/skills/morning-brief.skill +15 -9
- package/workspace/skills/report-biweekly.skill +23 -25
- package/workspace/skills/tracker-refresh.skill +50 -0
- package/workspace/test-files.json +21 -3
- package/workspace/test-lists/base.json +6 -0
- package/workspace/test-lists/run2-a.json +6 -0
- package/workspace/test-lists/run2-b.json +13 -0
- package/workspace/test-lists/run2-c.json +19 -0
- package/workspace/test-lists/run2-d.json +22 -0
- package/workspace/test-lists/run3-0.json +5 -0
- package/workspace/test-lists/run3-a.json +5 -0
- package/workspace/test-lists/run3-b.json +6 -0
- package/workspace/test-lists/run3-c.json +15 -0
- package/workspace/test-lists/run3-d.json +6 -0
- package/workspace/test-lists/run4-a.json +8 -0
- package/workspace/test-lists/run4-b.json +6 -0
- package/workspace/test-lists/run4-c.json +13 -0
- package/workspace/test-lists/run4-d.json +9 -0
- package/workspace/test-lists/run5-a.json +6 -0
- package/workspace/test-lists/run5-b.json +6 -0
- package/workspace/test-lists/run5-c.json +15 -0
- package/workspace/test-lists/workspace-launcher.json +5 -0
- package/workspace/test-lists/workspace-phone-app.json +5 -0
- package/workspace/test-run.js +27 -1
- package/workspace/third-party/README.md +44 -0
- package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
- package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
- package/workspace/design-parity.js +0 -673
- package/workspace/public/pm/tab-task.js +0 -86
- package/workspace/public/pm/view-guide.js +0 -141
- package/workspace/public/pm/view-knowledge.js +0 -225
- package/workspace/public/pm/view-memory.js +0 -122
- package/workspace/public/pm/view-portfolio.js +0 -153
- package/workspace/public/pm/view-reports.js +0 -190
- package/workspace/public/pm/view-scheduled.js +0 -161
- package/workspace/public/pm/view-skills.js +0 -216
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* PMOS workspace — the suites whose subject R29 cut (pm-os-absorb Run 3, slice 3D).
|
|
3
|
+
*
|
|
4
|
+
* The All repos, Knowledge, Memory, Skills and Scheduled views, and the Reports tab, are gone from the shell. The suites that
|
|
5
|
+
* drew them in a browser (pm-r29-portfolio, -knowledge, -memory, -skills-scheduled, -reports-addrepo) have nothing to draw.
|
|
6
|
+
* Each keeps its file and its name, and asserts what is left of its subject: the view is not in the table, its module is not
|
|
7
|
+
* on disk, and the thing that replaced it is still there. Every removed assertion is in workspace/replaced-tests.json
|
|
8
|
+
* (C03 to C11). No browser and no server: these are reads of the sources, so they run in milliseconds.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const PM = path.join(__dirname, 'public', 'pm');
|
|
14
|
+
const read = (f) => { try { return fs.readFileSync(path.join(PM, f), 'utf8'); } catch { return null; } };
|
|
15
|
+
const gone = (f) => !fs.existsSync(path.join(PM, f));
|
|
16
|
+
/** The keys of `export const VIEWS = { … }` in panel-cockpit.js, in order. */
|
|
17
|
+
const viewKeys = () => {
|
|
18
|
+
const m = /export const VIEWS = \{([\s\S]*?)\n\};/.exec(read('panel-cockpit.js') || '');
|
|
19
|
+
return m ? [...m[1].matchAll(/^ {2}(\w+):/gm)].map((x) => x[1]) : [];
|
|
20
|
+
};
|
|
21
|
+
/** The keys of `BOARD_MODULES = { … }` in panel-cockpit.js. */
|
|
22
|
+
const boardKeys = () => {
|
|
23
|
+
const m = /BOARD_MODULES = \{([\s\S]*?)\};/.exec(read('panel-cockpit.js') || '');
|
|
24
|
+
return m ? [...m[1].matchAll(/(\w+):\s*'\.\/view-/g)].map((x) => x[1]) : [];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function suite(label, cases) {
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const [name, fn] of cases) {
|
|
30
|
+
let cond = false; let extra = '';
|
|
31
|
+
try { const r = fn(); cond = r === true || (r && r.cond === true); extra = r && r.extra ? r.extra : ''; } catch (e) { extra = String((e && e.message) || e); }
|
|
32
|
+
results.push(cond);
|
|
33
|
+
console.log(`${cond ? '✓' : '✗'} ${name}${extra ? ' — ' + extra : ''}`);
|
|
34
|
+
}
|
|
35
|
+
const bad = results.filter((x) => !x).length;
|
|
36
|
+
console.log(`\n${results.length - bad}/${results.length} ${label} assertions passed`);
|
|
37
|
+
process.exit(bad ? 1 : 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { suite, read, gone, viewKeys, boardKeys, PM };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* first-paint-audit.js — the first-paint instrument, shared by every first-paint suite (pm-os-absorb seams).
|
|
3
|
+
*
|
|
4
|
+
* Not a suite: first-paint-count.test.js (the Breakdown, Run 1) and each destination's first-paint suite (Initiatives
|
|
5
|
+
* 2D, Today 3B, Board 4D, Reports 5C) require it, so every surface is counted with ONE instrument: shoot.js's audit(),
|
|
6
|
+
* the one the PM's numbers came from. A "control" is a visible button, link, input, select, textarea, [role=button] or
|
|
7
|
+
* [draggable=true] inside the viewport on first paint.
|
|
8
|
+
*
|
|
9
|
+
* EXPECT PRD Q5's budgets at 1280×800, counted outside the dock and the terminal
|
|
10
|
+
* audit(phone) shoot.js's audit, verbatim; runs in the page (page.evaluate(audit, false))
|
|
11
|
+
* countIn([scope, exclude]) the same selector and visibility test, inside `scope` and outside `exclude`; in the page
|
|
12
|
+
* stableCount([scope, exclude, minus]) countIn once the screen is still (two animation frames apart); in the page,
|
|
13
|
+
* after installCounter(page)
|
|
14
|
+
* installCounter(page) the init script stableCount needs (window.__countIn); call it before page.goto
|
|
15
|
+
*
|
|
16
|
+
* Do not tune the selector or the visibility test for one surface: change them here, for all, or not at all.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const EXPECT = Object.freeze({ today: 17, initiatives: 24, board: 33, reports: 14 });
|
|
20
|
+
|
|
21
|
+
/* ── shoot.js audit(), copied verbatim (scratchpad/specimens2/shoot.js, 2026-10-05) ─────────────────────────────── */
|
|
22
|
+
// runs in the page
|
|
23
|
+
function audit(phone) {
|
|
24
|
+
const SEL = 'button, a[href], a[role], input:not([type="hidden"]), select, textarea, [role="button"], [draggable="true"]';
|
|
25
|
+
const vw = window.innerWidth;
|
|
26
|
+
const vh = window.innerHeight;
|
|
27
|
+
const visible = (el) => {
|
|
28
|
+
if (el.closest("dialog:not([open])")) return false;
|
|
29
|
+
const pop = el.closest("[popover]");
|
|
30
|
+
if (pop && !pop.matches(":popover-open")) return false;
|
|
31
|
+
const r = el.getBoundingClientRect();
|
|
32
|
+
if (r.width < 1 || r.height < 1) return false;
|
|
33
|
+
for (let n = el; n && n.nodeType === 1; n = n.parentElement) {
|
|
34
|
+
const cs = getComputedStyle(n);
|
|
35
|
+
if (cs.display === "none" || cs.visibility === "hidden" || Number(cs.opacity) === 0) return false;
|
|
36
|
+
}
|
|
37
|
+
return r.bottom > 0 && r.right > 0 && r.top < vh && r.left < vw;
|
|
38
|
+
};
|
|
39
|
+
const describe = (el) => {
|
|
40
|
+
const t = (el.getAttribute("aria-label") || el.textContent || el.value || "").trim().replace(/\s+/g, " ");
|
|
41
|
+
const r = el.getBoundingClientRect();
|
|
42
|
+
return `${el.tagName.toLowerCase()}${el.className && typeof el.className === "string" ? "." + el.className.trim().split(/\s+/).slice(0, 2).join(".") : ""} "${t.slice(0, 28)}" ${Math.round(r.width)}x${Math.round(r.height)}`;
|
|
43
|
+
};
|
|
44
|
+
const all = [...document.querySelectorAll(SEL)].filter(visible);
|
|
45
|
+
const notBar = all.filter((el) => !el.closest(".specbar"));
|
|
46
|
+
const small = phone
|
|
47
|
+
? notBar.filter((el) => {
|
|
48
|
+
const r = el.getBoundingClientRect();
|
|
49
|
+
// inline text links inside prose are exempt from the 44px rule
|
|
50
|
+
if (el.tagName === "A" && !el.className && getComputedStyle(el).display === "inline") return false;
|
|
51
|
+
return r.width < 43.5 || r.height < 43.5;
|
|
52
|
+
})
|
|
53
|
+
: [];
|
|
54
|
+
const sw = document.documentElement.scrollWidth;
|
|
55
|
+
const offenders = [];
|
|
56
|
+
if (sw > vw) {
|
|
57
|
+
for (const el of document.querySelectorAll("body *")) {
|
|
58
|
+
const r = el.getBoundingClientRect();
|
|
59
|
+
if (r.right > vw + 0.5 && r.width > 0) {
|
|
60
|
+
let clipped = false;
|
|
61
|
+
for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
|
|
62
|
+
const o = getComputedStyle(p).overflowX;
|
|
63
|
+
if (o === "auto" || o === "scroll" || o === "hidden" || o === "clip") {
|
|
64
|
+
clipped = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!clipped) offenders.push({ el: describe(el), right: Math.round(r.right) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
offenders.sort((a, b) => b.right - a.right);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
title: document.title,
|
|
75
|
+
controlsInViewport: all.length,
|
|
76
|
+
controlsExcludingSpecbar: notBar.length,
|
|
77
|
+
smallTargets: small.length,
|
|
78
|
+
smallTargetExamples: small.slice(0, 8).map(describe),
|
|
79
|
+
scrollWidth: sw,
|
|
80
|
+
innerWidth: vw,
|
|
81
|
+
horizontalOverflow: sw > vw,
|
|
82
|
+
overflowOffenders: offenders.slice(0, 6),
|
|
83
|
+
pageHeight: document.documentElement.scrollHeight
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The same selector and the same visibility test as audit(), counted inside `scope` and outside every `exclude`.
|
|
89
|
+
* Runs in the page. Returns { n, names }.
|
|
90
|
+
*/
|
|
91
|
+
function countIn([scope, exclude]) {
|
|
92
|
+
const SEL = 'button, a[href], a[role], input:not([type="hidden"]), select, textarea, [role="button"], [draggable="true"]';
|
|
93
|
+
const vw = window.innerWidth;
|
|
94
|
+
const vh = window.innerHeight;
|
|
95
|
+
const visible = (el) => {
|
|
96
|
+
if (el.closest("dialog:not([open])")) return false;
|
|
97
|
+
const pop = el.closest("[popover]");
|
|
98
|
+
if (pop && !pop.matches(":popover-open")) return false;
|
|
99
|
+
const r = el.getBoundingClientRect();
|
|
100
|
+
if (r.width < 1 || r.height < 1) return false;
|
|
101
|
+
for (let n = el; n && n.nodeType === 1; n = n.parentElement) {
|
|
102
|
+
const cs = getComputedStyle(n);
|
|
103
|
+
if (cs.display === "none" || cs.visibility === "hidden" || Number(cs.opacity) === 0) return false;
|
|
104
|
+
}
|
|
105
|
+
return r.bottom > 0 && r.right > 0 && r.top < vh && r.left < vw;
|
|
106
|
+
};
|
|
107
|
+
const roots = scope ? [...document.querySelectorAll(scope)] : [document];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
for (const root of roots) for (const el of root.querySelectorAll(SEL)) {
|
|
110
|
+
if (seen.has(el) || !visible(el)) continue;
|
|
111
|
+
if (exclude && el.closest(exclude)) continue;
|
|
112
|
+
seen.add(el);
|
|
113
|
+
}
|
|
114
|
+
const names = [...seen].map((el) => `${el.tagName.toLowerCase()} "${(el.getAttribute('aria-label') || el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 24)}"`);
|
|
115
|
+
return { n: seen.size, names };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* countIn, but only once the screen is still: the count of `scope` minus `exclude` must be the same two animation
|
|
120
|
+
* frames apart (up to 60 tries). With `minus`, the controls inside `minus` are counted out in the SAME task, so the two
|
|
121
|
+
* numbers of a difference come from one paint. Runs in the page; needs window.__countIn (an init script).
|
|
122
|
+
*/
|
|
123
|
+
async function stableCount([scope, exclude, minus]) {
|
|
124
|
+
const frame = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
125
|
+
const both = () => {
|
|
126
|
+
const a = window.__countIn([scope, exclude]);
|
|
127
|
+
const b = minus ? window.__countIn([scope, exclude ? exclude + ', ' + minus : minus]) : null;
|
|
128
|
+
return { n: a.n, names: a.names, without: b ? b.n : null };
|
|
129
|
+
};
|
|
130
|
+
let prev = both();
|
|
131
|
+
for (let k = 0; k < 60; k++) {
|
|
132
|
+
await frame();
|
|
133
|
+
const cur = both();
|
|
134
|
+
if (cur.n === prev.n && cur.without === prev.without) return { ...cur, still: true, tries: k + 1 };
|
|
135
|
+
prev = cur;
|
|
136
|
+
}
|
|
137
|
+
return { ...prev, still: false, tries: 60 };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Add the init script that stableCount calls. Call it once per page, before the first goto. */
|
|
141
|
+
function installCounter(page) {
|
|
142
|
+
return page.addInitScript({ content: 'window.__countIn = ' + countIn.toString() + ';' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { EXPECT, audit, countIn, stableCount, installCounter };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ok": true,
|
|
3
|
+
"ws": "w-north",
|
|
4
|
+
"view": "list",
|
|
5
|
+
"sig": {
|
|
6
|
+
"placements": "3f786850e387550fdab836ed7e6dc881de23001b",
|
|
7
|
+
"items": "89e6c98d92887913cadf06b2adb97f26cde4849b",
|
|
8
|
+
"measurements": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
9
|
+
"records": "2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69"
|
|
10
|
+
},
|
|
11
|
+
"now": "2026-10-05T12:00:00.000Z",
|
|
12
|
+
"rules": {
|
|
13
|
+
"stall_days": 5,
|
|
14
|
+
"dormant_days": 45,
|
|
15
|
+
"report_cycle_days": 14,
|
|
16
|
+
"now_cap": null,
|
|
17
|
+
"via_required_from": null
|
|
18
|
+
},
|
|
19
|
+
"groups": [
|
|
20
|
+
{
|
|
21
|
+
"objective": {
|
|
22
|
+
"id": "O2",
|
|
23
|
+
"title": "Partners self-serve",
|
|
24
|
+
"period": "Q4 2026"
|
|
25
|
+
},
|
|
26
|
+
"anchors": [
|
|
27
|
+
{
|
|
28
|
+
"anchor": "KR 2.1",
|
|
29
|
+
"kind": "kr",
|
|
30
|
+
"title": "Partner sign-in without a ticket",
|
|
31
|
+
"measurement": {
|
|
32
|
+
"value": 41,
|
|
33
|
+
"target": 55,
|
|
34
|
+
"unit": "%",
|
|
35
|
+
"as_of": "2026-10-02",
|
|
36
|
+
"source": "planning/okrs/fy26-q4.md@1a2b3c4"
|
|
37
|
+
},
|
|
38
|
+
"rows": [
|
|
39
|
+
{
|
|
40
|
+
"id": "partner-sso",
|
|
41
|
+
"title": "Partner SSO",
|
|
42
|
+
"lane": "feature",
|
|
43
|
+
"anchor": "KR 2.1",
|
|
44
|
+
"anchor_kind": "kr",
|
|
45
|
+
"horizon": "now",
|
|
46
|
+
"rank": 0,
|
|
47
|
+
"confirmed": true,
|
|
48
|
+
"dormant": false,
|
|
49
|
+
"check_by": "2026-10-30",
|
|
50
|
+
"stage": {
|
|
51
|
+
"id": "build",
|
|
52
|
+
"label": "Build"
|
|
53
|
+
},
|
|
54
|
+
"display": {
|
|
55
|
+
"word": "Build",
|
|
56
|
+
"stage": "build",
|
|
57
|
+
"waitingOn": "pm",
|
|
58
|
+
"act": "approval",
|
|
59
|
+
"idleDays": 1,
|
|
60
|
+
"line2": "Waiting on you: approval"
|
|
61
|
+
},
|
|
62
|
+
"health": {
|
|
63
|
+
"level": "on",
|
|
64
|
+
"reasons": []
|
|
65
|
+
},
|
|
66
|
+
"progress": {
|
|
67
|
+
"done": 7,
|
|
68
|
+
"total": 12,
|
|
69
|
+
"label": "7 of 12"
|
|
70
|
+
},
|
|
71
|
+
"next_due": "2026-11-14",
|
|
72
|
+
"last_activity": "2026-10-04T09:00:00Z"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"objective": null,
|
|
80
|
+
"anchors": [
|
|
81
|
+
{
|
|
82
|
+
"anchor": "health:tech",
|
|
83
|
+
"kind": "health",
|
|
84
|
+
"title": "Tech health",
|
|
85
|
+
"measurement": null,
|
|
86
|
+
"rows": [
|
|
87
|
+
{
|
|
88
|
+
"id": "audit-log",
|
|
89
|
+
"title": "Audit log export",
|
|
90
|
+
"lane": "chore",
|
|
91
|
+
"anchor": "health:tech",
|
|
92
|
+
"anchor_kind": "health",
|
|
93
|
+
"horizon": "now",
|
|
94
|
+
"rank": 1,
|
|
95
|
+
"confirmed": false,
|
|
96
|
+
"dormant": false,
|
|
97
|
+
"check_by": "2026-10-01",
|
|
98
|
+
"stage": {
|
|
99
|
+
"id": "evaluate",
|
|
100
|
+
"label": "Evaluate"
|
|
101
|
+
},
|
|
102
|
+
"display": {
|
|
103
|
+
"word": "Evaluate",
|
|
104
|
+
"stage": "evaluate",
|
|
105
|
+
"waitingOn": "agent",
|
|
106
|
+
"act": "rubric",
|
|
107
|
+
"idleDays": 1,
|
|
108
|
+
"line2": "Waiting on agent: rubric"
|
|
109
|
+
},
|
|
110
|
+
"health": {
|
|
111
|
+
"level": "at-risk",
|
|
112
|
+
"reasons": [
|
|
113
|
+
{
|
|
114
|
+
"level": "at-risk",
|
|
115
|
+
"code": "check-by-passed",
|
|
116
|
+
"text": "check-by 1 Oct passed",
|
|
117
|
+
"source": "planning/runs/placements.jsonl"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
"progress": {
|
|
122
|
+
"done": 3,
|
|
123
|
+
"total": 5,
|
|
124
|
+
"label": "3 of 5"
|
|
125
|
+
},
|
|
126
|
+
"next_due": "2026-10-20",
|
|
127
|
+
"last_activity": "2026-09-29T15:00:00Z"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "old-import",
|
|
131
|
+
"title": "Legacy importer",
|
|
132
|
+
"lane": "chore",
|
|
133
|
+
"anchor": "health:tech",
|
|
134
|
+
"anchor_kind": "health",
|
|
135
|
+
"horizon": "later",
|
|
136
|
+
"rank": 0,
|
|
137
|
+
"confirmed": true,
|
|
138
|
+
"dormant": true,
|
|
139
|
+
"check_by": null,
|
|
140
|
+
"stage": {
|
|
141
|
+
"id": "intake",
|
|
142
|
+
"label": "Intake"
|
|
143
|
+
},
|
|
144
|
+
"display": {
|
|
145
|
+
"word": "Intake",
|
|
146
|
+
"stage": "intake",
|
|
147
|
+
"waitingOn": null,
|
|
148
|
+
"act": null,
|
|
149
|
+
"idleDays": 1,
|
|
150
|
+
"line2": ""
|
|
151
|
+
},
|
|
152
|
+
"health": {
|
|
153
|
+
"level": "on",
|
|
154
|
+
"reasons": []
|
|
155
|
+
},
|
|
156
|
+
"progress": {
|
|
157
|
+
"done": 0,
|
|
158
|
+
"total": 0,
|
|
159
|
+
"label": "no breakdown"
|
|
160
|
+
},
|
|
161
|
+
"next_due": null,
|
|
162
|
+
"last_activity": "2026-07-01T10:00:00Z"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"flags": [
|
|
170
|
+
{
|
|
171
|
+
"kind": "unreadable",
|
|
172
|
+
"init": null,
|
|
173
|
+
"why": "line 9: not JSON"
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ok": true,
|
|
3
|
+
"written": 1,
|
|
4
|
+
"sig": {
|
|
5
|
+
"placements": "3f786850e387550fdab836ed7e6dc881de23001b",
|
|
6
|
+
"items": "89e6c98d92887913cadf06b2adb97f26cde4849b",
|
|
7
|
+
"measurements": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
8
|
+
"records": "2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ok": true,
|
|
3
|
+
"ws": "w-north",
|
|
4
|
+
"view": "roadmap",
|
|
5
|
+
"sig": {
|
|
6
|
+
"placements": "3f786850e387550fdab836ed7e6dc881de23001b",
|
|
7
|
+
"items": "89e6c98d92887913cadf06b2adb97f26cde4849b",
|
|
8
|
+
"measurements": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
9
|
+
"records": "2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69"
|
|
10
|
+
},
|
|
11
|
+
"now": "2026-10-05T12:00:00.000Z",
|
|
12
|
+
"rules": {
|
|
13
|
+
"stall_days": 5,
|
|
14
|
+
"dormant_days": 45,
|
|
15
|
+
"report_cycle_days": 14,
|
|
16
|
+
"now_cap": null,
|
|
17
|
+
"via_required_from": null
|
|
18
|
+
},
|
|
19
|
+
"header": {
|
|
20
|
+
"now": {
|
|
21
|
+
"count": 2,
|
|
22
|
+
"kr": 1,
|
|
23
|
+
"health": 1,
|
|
24
|
+
"cap": null
|
|
25
|
+
},
|
|
26
|
+
"next": {
|
|
27
|
+
"count": 1
|
|
28
|
+
},
|
|
29
|
+
"later": {
|
|
30
|
+
"count": 0,
|
|
31
|
+
"dormant": 1
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"horizons": {
|
|
35
|
+
"now": [
|
|
36
|
+
{
|
|
37
|
+
"id": "partner-sso",
|
|
38
|
+
"title": "Partner SSO",
|
|
39
|
+
"lane": "feature",
|
|
40
|
+
"anchor": "KR 2.1",
|
|
41
|
+
"anchor_kind": "kr",
|
|
42
|
+
"horizon": "now",
|
|
43
|
+
"rank": 0,
|
|
44
|
+
"confirmed": true,
|
|
45
|
+
"dormant": false,
|
|
46
|
+
"check_by": "2026-10-30",
|
|
47
|
+
"stage": {
|
|
48
|
+
"id": "build",
|
|
49
|
+
"label": "Build"
|
|
50
|
+
},
|
|
51
|
+
"display": {
|
|
52
|
+
"word": "Build",
|
|
53
|
+
"stage": "build",
|
|
54
|
+
"waitingOn": "pm",
|
|
55
|
+
"act": "approval",
|
|
56
|
+
"idleDays": 1,
|
|
57
|
+
"line2": "Waiting on you: approval"
|
|
58
|
+
},
|
|
59
|
+
"health": {
|
|
60
|
+
"level": "on",
|
|
61
|
+
"reasons": []
|
|
62
|
+
},
|
|
63
|
+
"progress": {
|
|
64
|
+
"done": 7,
|
|
65
|
+
"total": 12,
|
|
66
|
+
"label": "7 of 12"
|
|
67
|
+
},
|
|
68
|
+
"next_due": "2026-11-14",
|
|
69
|
+
"last_activity": "2026-10-04T09:00:00Z"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "audit-log",
|
|
73
|
+
"title": "Audit log export",
|
|
74
|
+
"lane": "chore",
|
|
75
|
+
"anchor": "health:tech",
|
|
76
|
+
"anchor_kind": "health",
|
|
77
|
+
"horizon": "now",
|
|
78
|
+
"rank": 1,
|
|
79
|
+
"confirmed": false,
|
|
80
|
+
"dormant": false,
|
|
81
|
+
"check_by": "2026-10-01",
|
|
82
|
+
"stage": {
|
|
83
|
+
"id": "evaluate",
|
|
84
|
+
"label": "Evaluate"
|
|
85
|
+
},
|
|
86
|
+
"display": {
|
|
87
|
+
"word": "Evaluate",
|
|
88
|
+
"stage": "evaluate",
|
|
89
|
+
"waitingOn": "agent",
|
|
90
|
+
"act": "rubric",
|
|
91
|
+
"idleDays": 1,
|
|
92
|
+
"line2": "Waiting on agent: rubric"
|
|
93
|
+
},
|
|
94
|
+
"health": {
|
|
95
|
+
"level": "at-risk",
|
|
96
|
+
"reasons": [
|
|
97
|
+
{
|
|
98
|
+
"level": "at-risk",
|
|
99
|
+
"code": "check-by-passed",
|
|
100
|
+
"text": "check-by 1 Oct passed",
|
|
101
|
+
"source": "planning/runs/placements.jsonl"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"progress": {
|
|
106
|
+
"done": 3,
|
|
107
|
+
"total": 5,
|
|
108
|
+
"label": "3 of 5"
|
|
109
|
+
},
|
|
110
|
+
"next_due": "2026-10-20",
|
|
111
|
+
"last_activity": "2026-09-29T15:00:00Z"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"next": [
|
|
115
|
+
{
|
|
116
|
+
"id": "billing-v2",
|
|
117
|
+
"title": "Billing v2",
|
|
118
|
+
"lane": "feature",
|
|
119
|
+
"anchor": "KR 1.3",
|
|
120
|
+
"anchor_kind": "kr",
|
|
121
|
+
"horizon": "next",
|
|
122
|
+
"rank": 0,
|
|
123
|
+
"confirmed": true,
|
|
124
|
+
"dormant": false,
|
|
125
|
+
"check_by": null,
|
|
126
|
+
"stage": {
|
|
127
|
+
"id": "spec",
|
|
128
|
+
"label": "Spec"
|
|
129
|
+
},
|
|
130
|
+
"display": {
|
|
131
|
+
"word": "Spec",
|
|
132
|
+
"stage": "spec",
|
|
133
|
+
"waitingOn": null,
|
|
134
|
+
"act": null,
|
|
135
|
+
"idleDays": 1,
|
|
136
|
+
"line2": ""
|
|
137
|
+
},
|
|
138
|
+
"health": {
|
|
139
|
+
"level": "on",
|
|
140
|
+
"reasons": []
|
|
141
|
+
},
|
|
142
|
+
"progress": {
|
|
143
|
+
"done": 0,
|
|
144
|
+
"total": 0,
|
|
145
|
+
"label": "no breakdown"
|
|
146
|
+
},
|
|
147
|
+
"next_due": null,
|
|
148
|
+
"last_activity": "2026-09-20T10:00:00Z"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"later": []
|
|
152
|
+
},
|
|
153
|
+
"dormant": [
|
|
154
|
+
{
|
|
155
|
+
"id": "old-import",
|
|
156
|
+
"title": "Legacy importer",
|
|
157
|
+
"lane": "chore",
|
|
158
|
+
"anchor": "health:tech",
|
|
159
|
+
"anchor_kind": "health",
|
|
160
|
+
"horizon": "later",
|
|
161
|
+
"rank": 0,
|
|
162
|
+
"confirmed": true,
|
|
163
|
+
"dormant": true,
|
|
164
|
+
"check_by": null,
|
|
165
|
+
"stage": {
|
|
166
|
+
"id": "intake",
|
|
167
|
+
"label": "Intake"
|
|
168
|
+
},
|
|
169
|
+
"display": {
|
|
170
|
+
"word": "Intake",
|
|
171
|
+
"stage": "intake",
|
|
172
|
+
"waitingOn": null,
|
|
173
|
+
"act": null,
|
|
174
|
+
"idleDays": 1,
|
|
175
|
+
"line2": ""
|
|
176
|
+
},
|
|
177
|
+
"health": {
|
|
178
|
+
"level": "on",
|
|
179
|
+
"reasons": []
|
|
180
|
+
},
|
|
181
|
+
"progress": {
|
|
182
|
+
"done": 0,
|
|
183
|
+
"total": 0,
|
|
184
|
+
"label": "no breakdown"
|
|
185
|
+
},
|
|
186
|
+
"next_due": null,
|
|
187
|
+
"last_activity": "2026-07-01T10:00:00Z"
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"proposals": [
|
|
191
|
+
{
|
|
192
|
+
"eid": "p0q1r2s3t4u5",
|
|
193
|
+
"initiative": "billing-v2",
|
|
194
|
+
"title": "Billing v2",
|
|
195
|
+
"horizon": "now",
|
|
196
|
+
"after": "audit-log",
|
|
197
|
+
"by": "agent:sess-7",
|
|
198
|
+
"at": "2026-10-04T11:00:00Z"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"flags": [
|
|
202
|
+
{
|
|
203
|
+
"kind": "unreadable",
|
|
204
|
+
"init": null,
|
|
205
|
+
"why": "line 9: not JSON"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|