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.
Files changed (229) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/mods/pmos-cockpit/README.md +13 -0
  4. package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
  5. package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
  6. package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
  7. package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
  8. package/payload/planning/evals/eval-calibration.md +1 -1
  9. package/payload/planning/okrs/health-budgets.md +1 -1
  10. package/payload/scripts/plan-items-stage.js +271 -0
  11. package/payload/scripts/pmos-log.sh +18 -7
  12. package/payload/scripts/workflow-state.js +28 -2
  13. package/payload/state/discoveries.md +1 -1
  14. package/payload/state/log.md +1 -1
  15. package/workspace/README.md +2 -0
  16. package/workspace/REMOTE.md +24 -63
  17. package/workspace/bin/pmos.js +19 -3
  18. package/workspace/cut-stub.js +40 -0
  19. package/workspace/first-paint-audit.js +145 -0
  20. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  21. package/workspace/fixtures/initiatives-api/list.json +176 -0
  22. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  23. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  24. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  25. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  26. package/workspace/fixtures/report-api/draft.json +177 -0
  27. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  28. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  29. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  30. package/workspace/guide/board.md +81 -0
  31. package/workspace/guide/breakdown.md +154 -0
  32. package/workspace/guide/command-palette.md +75 -0
  33. package/workspace/guide/index.md +28 -17
  34. package/workspace/guide/initiatives-roadmap.md +130 -0
  35. package/workspace/guide/initiatives-screen.md +89 -0
  36. package/workspace/guide/initiatives-timeline.md +57 -0
  37. package/workspace/guide/integrated-work.md +94 -0
  38. package/workspace/guide/knowledge-graph.md +3 -68
  39. package/workspace/guide/launcher.md +51 -0
  40. package/workspace/guide/layout.md +102 -32
  41. package/workspace/guide/live-notes.md +3 -3
  42. package/workspace/guide/pm-only-acts.md +89 -0
  43. package/workspace/guide/pm-only-lines.md +61 -0
  44. package/workspace/guide/proactive-engine.md +21 -5
  45. package/workspace/guide/reports-page.md +80 -0
  46. package/workspace/guide/reports.md +73 -0
  47. package/workspace/guide/timeline.md +70 -0
  48. package/workspace/guide/today.md +85 -0
  49. package/workspace/guide/validate-with-typesafe.md +1 -1
  50. package/workspace/lib/actor.js +91 -0
  51. package/workspace/lib/board-cache.js +32 -2
  52. package/workspace/lib/board-cli.js +108 -0
  53. package/workspace/lib/board-fold.js +232 -0
  54. package/workspace/lib/breakdown-export.js +75 -0
  55. package/workspace/lib/brief.js +20 -2
  56. package/workspace/lib/capture.js +98 -0
  57. package/workspace/lib/engine/plan-items-stage.js +271 -0
  58. package/workspace/lib/engine/workflow-state.js +28 -2
  59. package/workspace/lib/factory-join.js +158 -0
  60. package/workspace/lib/goals.js +165 -0
  61. package/workspace/lib/grant-mint.js +113 -0
  62. package/workspace/lib/health-rules-factory.js +92 -0
  63. package/workspace/lib/health-rules-plan.js +164 -0
  64. package/workspace/lib/health.js +79 -0
  65. package/workspace/lib/initiative-cli.js +222 -0
  66. package/workspace/lib/initiative-display.js +183 -0
  67. package/workspace/lib/initiative-needs.js +127 -0
  68. package/workspace/lib/initiative-rows.js +210 -0
  69. package/workspace/lib/initiative-writer.js +141 -0
  70. package/workspace/lib/issues.js +206 -0
  71. package/workspace/lib/item-cli.js +195 -0
  72. package/workspace/lib/item-spans.js +137 -0
  73. package/workspace/lib/launcher-cli.js +207 -0
  74. package/workspace/lib/measure-cli.js +100 -0
  75. package/workspace/lib/measurements.js +189 -0
  76. package/workspace/lib/mobile-nav.js +57 -53
  77. package/workspace/lib/needs-providers.js +120 -0
  78. package/workspace/lib/placements.js +440 -0
  79. package/workspace/lib/plan-items.js +922 -0
  80. package/workspace/lib/plan-ledgers.js +85 -0
  81. package/workspace/lib/plan-rules.js +72 -0
  82. package/workspace/lib/plan-tree.js +380 -0
  83. package/workspace/lib/pm-guard.js +174 -0
  84. package/workspace/lib/pm-lines-needs.js +331 -0
  85. package/workspace/lib/pmos-verbs.js +69 -0
  86. package/workspace/lib/proactive-cli.js +58 -2
  87. package/workspace/lib/proactive.js +9 -6
  88. package/workspace/lib/qr.js +293 -0
  89. package/workspace/lib/remote-access.js +93 -1
  90. package/workspace/lib/report-cli.js +152 -0
  91. package/workspace/lib/report-draft.js +578 -0
  92. package/workspace/lib/report-needs.js +213 -0
  93. package/workspace/lib/route-modules.js +128 -0
  94. package/workspace/lib/routes-board.js +91 -0
  95. package/workspace/lib/routes-capture.js +52 -0
  96. package/workspace/lib/routes-initiatives.js +371 -0
  97. package/workspace/lib/routes-reports.js +163 -0
  98. package/workspace/lib/routes-timeline.js +197 -0
  99. package/workspace/lib/routes-today.js +32 -0
  100. package/workspace/lib/today-cli.js +99 -0
  101. package/workspace/lib/today.js +333 -0
  102. package/workspace/lib/triage.js +163 -0
  103. package/workspace/lib/verify-work-cli.js +81 -0
  104. package/workspace/lib/work-check-needs.js +99 -0
  105. package/workspace/package-lock.json +1 -1
  106. package/workspace/package.json +4 -5
  107. package/workspace/public/icons/apple-touch-icon.png +0 -0
  108. package/workspace/public/icons/icon-192.png +0 -0
  109. package/workspace/public/icons/icon-512.png +0 -0
  110. package/workspace/public/index.html +40 -19
  111. package/workspace/public/pm/app.js +330 -30
  112. package/workspace/public/pm/bd-board.js +113 -0
  113. package/workspace/public/pm/bd-drag.js +59 -0
  114. package/workspace/public/pm/bd-model.js +152 -0
  115. package/workspace/public/pm/bd-timeline.js +233 -0
  116. package/workspace/public/pm/bd-tree.js +244 -0
  117. package/workspace/public/pm/board-copy.js +57 -0
  118. package/workspace/public/pm/breakdown-api.js +58 -0
  119. package/workspace/public/pm/breakdown-copy.js +138 -0
  120. package/workspace/public/pm/components.css +3135 -0
  121. package/workspace/public/pm/cycle-review.js +125 -0
  122. package/workspace/public/pm/data.js +40 -187
  123. package/workspace/public/pm/dest-board.js +438 -0
  124. package/workspace/public/pm/dest-initiatives.js +134 -0
  125. package/workspace/public/pm/dest-reports.js +270 -0
  126. package/workspace/public/pm/dest-settings.js +217 -0
  127. package/workspace/public/pm/dest-today.js +182 -0
  128. package/workspace/public/pm/dialogs.js +88 -0
  129. package/workspace/public/pm/display-word.js +56 -0
  130. package/workspace/public/pm/dock.js +175 -0
  131. package/workspace/public/pm/fixture.js +8 -227
  132. package/workspace/public/pm/frame-copy.js +99 -0
  133. package/workspace/public/pm/handoff-box.js +82 -0
  134. package/workspace/public/pm/http.js +88 -0
  135. package/workspace/public/pm/icons.js +53 -0
  136. package/workspace/public/pm/initiative-list.js +218 -0
  137. package/workspace/public/pm/initiative-sheet.js +261 -0
  138. package/workspace/public/pm/initiatives-api.js +59 -0
  139. package/workspace/public/pm/initiatives-copy.js +215 -0
  140. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  141. package/workspace/public/pm/initiatives-timeline.js +410 -0
  142. package/workspace/public/pm/initiatives-ui.js +123 -0
  143. package/workspace/public/pm/item-sheet.js +251 -0
  144. package/workspace/public/pm/keep-card.js +204 -0
  145. package/workspace/public/pm/lifecycle.js +7 -1
  146. package/workspace/public/pm/mobile.css +49 -20
  147. package/workspace/public/pm/mobile.js +157 -29
  148. package/workspace/public/pm/modes-live.js +7 -883
  149. package/workspace/public/pm/modes.js +32 -1024
  150. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  151. package/workspace/public/pm/next-step-ui.js +3 -2
  152. package/workspace/public/pm/palette-copy.js +51 -0
  153. package/workspace/public/pm/palette.js +312 -0
  154. package/workspace/public/pm/panel-breakdown.js +702 -0
  155. package/workspace/public/pm/panel-cockpit.js +63 -295
  156. package/workspace/public/pm/panel-detail.js +25 -172
  157. package/workspace/public/pm/panel-initiatives.js +1 -19
  158. package/workspace/public/pm/panel-repos.js +31 -81
  159. package/workspace/public/pm/panel-tabs.js +200 -111
  160. package/workspace/public/pm/panel-trees.js +5 -2
  161. package/workspace/public/pm/portfolio-report.js +61 -0
  162. package/workspace/public/pm/primitives.js +656 -0
  163. package/workspace/public/pm/print.css +33 -0
  164. package/workspace/public/pm/provider-cards.js +136 -0
  165. package/workspace/public/pm/registry.js +194 -0
  166. package/workspace/public/pm/release-card.js +110 -0
  167. package/workspace/public/pm/report-view.js +309 -0
  168. package/workspace/public/pm/reports-copy.js +114 -0
  169. package/workspace/public/pm/roadmap.js +271 -0
  170. package/workspace/public/pm/sig-watch.js +41 -0
  171. package/workspace/public/pm/state.js +8 -4
  172. package/workspace/public/pm/status-banner.js +13 -5
  173. package/workspace/public/pm/tab-doc.js +2 -4
  174. package/workspace/public/pm/tab-term.js +12 -2
  175. package/workspace/public/pm/tab-web.js +3 -2
  176. package/workspace/public/pm/timeline-copy.js +48 -0
  177. package/workspace/public/pm/today-cards.js +244 -0
  178. package/workspace/public/pm/today-copy.js +126 -0
  179. package/workspace/public/pm/today-goals.js +172 -0
  180. package/workspace/public/pm/tokens.css +16 -18
  181. package/workspace/public/pm/topbar.js +102 -0
  182. package/workspace/public/pm/triage-card.js +60 -0
  183. package/workspace/public/pm/undo-hold.js +103 -0
  184. package/workspace/public/pm/view-addrepo.js +2 -1
  185. package/workspace/public/pm/view-livenotes.js +3 -1
  186. package/workspace/public/pm/view-today.js +7 -162
  187. package/workspace/public/vendor/Sortable.min.js +2 -0
  188. package/workspace/public/vendor/lucide-sprite.js +79 -0
  189. package/workspace/replaced-tests.json +2877 -0
  190. package/workspace/run-tests.js +56 -5
  191. package/workspace/server.js +446 -40
  192. package/workspace/sheet-answer.js +61 -0
  193. package/workspace/skills/measure.skill +41 -0
  194. package/workspace/skills/morning-brief.skill +15 -9
  195. package/workspace/skills/report-biweekly.skill +23 -25
  196. package/workspace/skills/tracker-refresh.skill +50 -0
  197. package/workspace/test-files.json +21 -3
  198. package/workspace/test-lists/base.json +6 -0
  199. package/workspace/test-lists/run2-a.json +6 -0
  200. package/workspace/test-lists/run2-b.json +13 -0
  201. package/workspace/test-lists/run2-c.json +19 -0
  202. package/workspace/test-lists/run2-d.json +22 -0
  203. package/workspace/test-lists/run3-0.json +5 -0
  204. package/workspace/test-lists/run3-a.json +5 -0
  205. package/workspace/test-lists/run3-b.json +6 -0
  206. package/workspace/test-lists/run3-c.json +15 -0
  207. package/workspace/test-lists/run3-d.json +6 -0
  208. package/workspace/test-lists/run4-a.json +8 -0
  209. package/workspace/test-lists/run4-b.json +6 -0
  210. package/workspace/test-lists/run4-c.json +13 -0
  211. package/workspace/test-lists/run4-d.json +9 -0
  212. package/workspace/test-lists/run5-a.json +6 -0
  213. package/workspace/test-lists/run5-b.json +6 -0
  214. package/workspace/test-lists/run5-c.json +15 -0
  215. package/workspace/test-lists/workspace-launcher.json +5 -0
  216. package/workspace/test-lists/workspace-phone-app.json +5 -0
  217. package/workspace/test-run.js +27 -1
  218. package/workspace/third-party/README.md +44 -0
  219. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  220. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  221. package/workspace/design-parity.js +0 -673
  222. package/workspace/public/pm/tab-task.js +0 -86
  223. package/workspace/public/pm/view-guide.js +0 -141
  224. package/workspace/public/pm/view-knowledge.js +0 -225
  225. package/workspace/public/pm/view-memory.js +0 -122
  226. package/workspace/public/pm/view-portfolio.js +0 -153
  227. package/workspace/public/pm/view-reports.js +0 -190
  228. package/workspace/public/pm/view-scheduled.js +0 -161
  229. 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,10 @@
1
+ ---
2
+ type: Reference
3
+ title: "Capture 2026-10-04 1"
4
+ timestamp: 2026-10-04T08:12:00Z
5
+ source: capture
6
+ by: pm:wawan
7
+ synthetic: true
8
+ ---
9
+
10
+ Partner admins want to export the audit log as CSV before the quarterly review.
@@ -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,5 @@
1
+ {
2
+ "ok": false,
3
+ "status": 403,
4
+ "error": "only the PM can move an item to Now, from the page; this request has no Origin header"
5
+ }
@@ -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
+ }