create-pmos 0.493.0 → 0.495.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/package.json +99 -27
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/briefing-premium-adddoc-test.js +2 -1
  7. package/workspace/briefing-premium-board-test.js +5 -4
  8. package/workspace/briefing-premium-initiative-test.js +4 -2
  9. package/workspace/changelog.json +98 -0
  10. package/workspace/guide/briefing-premium.md +4 -1
  11. package/workspace/guide/claude-proposes.md +2 -2
  12. package/workspace/guide/data-freshness.md +3 -1
  13. package/workspace/guide/initiatives-screen.md +3 -1
  14. package/workspace/guide/report-slides.md +14 -1
  15. package/workspace/guide/reports.md +2 -0
  16. package/workspace/guide/workflows.md +15 -3
  17. package/workspace/guide/your-impact.md +6 -7
  18. package/workspace/ini-workflow-test.js +2 -1
  19. package/workspace/lib/board-trail.js +30 -3
  20. package/workspace/lib/engine/workflows.js +19 -6
  21. package/workspace/lib/git-shape.js +21 -0
  22. package/workspace/lib/grill-runs.js +21 -6
  23. package/workspace/lib/grill.js +1 -1
  24. package/workspace/lib/impact.js +29 -16
  25. package/workspace/lib/initiative-cli.js +15 -1
  26. package/workspace/lib/open-mission.js +14 -0
  27. package/workspace/lib/page-warm.js +68 -0
  28. package/workspace/lib/report-design.js +6 -1
  29. package/workspace/lib/report-export.js +150 -9
  30. package/workspace/lib/report-pyramid.js +535 -0
  31. package/workspace/lib/report-structure.js +5 -1
  32. package/workspace/lib/report-templates.js +111 -20
  33. package/workspace/lib/routes-boards.js +36 -3
  34. package/workspace/lib/routes-brief-page.js +21 -3
  35. package/workspace/lib/routes-grill.js +10 -4
  36. package/workspace/lib/routes-impact.js +12 -4
  37. package/workspace/lib/routes-initiatives.js +1 -0
  38. package/workspace/lib/routes-programs.js +6 -4
  39. package/workspace/lib/routes-report-design.js +15 -0
  40. package/workspace/lib/routes-report-flow.js +115 -19
  41. package/workspace/lib/routes-report-setups.js +4 -2
  42. package/workspace/lib/routes-report-structure.js +3 -0
  43. package/workspace/lib/routes-workflows.js +6 -1
  44. package/workspace/lib/wf-bridge.js +5 -3
  45. package/workspace/lib/workflow-cli.js +2 -2
  46. package/workspace/lib/workflows-store.js +15 -1
  47. package/workspace/metric-drafts-ui-test.js +2 -1
  48. package/workspace/package-lock.json +2 -2
  49. package/workspace/package.json +1 -1
  50. package/workspace/premium-board-actions-test.js +2 -1
  51. package/workspace/premium-chat-report-tab-test.js +5 -1
  52. package/workspace/premium-vw-backlog-test.js +3 -2
  53. package/workspace/premium-vw-item-test.js +3 -2
  54. package/workspace/premium-vw-jira-test.js +3 -2
  55. package/workspace/program-panel-test.js +2 -1
  56. package/workspace/public/pm/cozy-home.css +1 -0
  57. package/workspace/public/pm/cozy-vw-rp.css +12 -0
  58. package/workspace/public/pm/fetch-share.js +38 -0
  59. package/workspace/public/pm/home-app.js +24 -4
  60. package/workspace/public/pm/home-chat.js +53 -11
  61. package/workspace/public/pm/home-impact.css +23 -13
  62. package/workspace/public/pm/home-impact.js +49 -32
  63. package/workspace/public/pm/home-ini.css +2 -0
  64. package/workspace/public/pm/ini-view.css +55 -0
  65. package/workspace/public/pm/ini-view.js +7 -4
  66. package/workspace/public/pm/premium-board.js +1 -0
  67. package/workspace/public/pm/premium-insights.js +1 -0
  68. package/workspace/public/pm/premium-report-read.js +25 -6
  69. package/workspace/public/pm/premium-report-tab.js +69 -14
  70. package/workspace/public/pm/premium-shell.js +57 -6
  71. package/workspace/public/pm/premium.css +1 -0
  72. package/workspace/public/pm/report-flow.js +93 -10
  73. package/workspace/public/pm/wf-page.css +8 -0
  74. package/workspace/public/pm/wf-page.js +33 -5
  75. package/workspace/server.js +24 -3
  76. package/workspace/skills/report-template.skill +10 -0
  77. package/workspace/split-merge-ui-test.js +3 -2
  78. package/workspace/stage-card-test.js +2 -1
  79. package/workspace/task-press-test.js +2 -1
  80. package/workspace/test-lists/page-open-speed.json +5 -0
  81. package/workspace/test-lists/report-pyramid.json +9 -0
  82. package/workspace/tk-tasks-test.js +2 -1
@@ -1,6 +1,7 @@
1
1
  /* pm/wf-page.js — Settings › Workflows (initiative workflows per initiative, D99; canvas WfAModel, WfAStage, WfACond, WfARemove, WfANew,
2
2
  * WfADraft). One page, reached from Settings:
3
- * the list every workflow with its count of initiatives and +; Product feature first, archived ones last and quiet
3
+ * the list every workflow with its count of initiatives and +; Product feature first, archived ones last and quiet. The star on a row
4
+ * (batch 24 item 3, canvas DfA) makes that workflow the default a new initiative starts on (POST /api/workflows/default)
4
5
  * the picked workflow one numbered list across the To do, In progress and Done columns; drag a stage to reorder it or to move it to
5
6
  * another column; Add a stage per column; the dashed Won't do · Archived column (in every workflow); ⋯ = Copy, Archive.
6
7
  * The name is the rename press (RnA*: tap it, one field with a check).
@@ -35,6 +36,8 @@ const DRAG = 'M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01', PLUS = '
35
36
  const S = { list: null, sel: null, draft: null, edit: null, err: '', msg: '', renaming: false, renErr: '', busy: false, pop: null, chat: null, poll: 0, phoneOpen: false };
36
37
  const wfOf = (id) => (S.edit && id === S.edit.id ? S.edit : (S.list || []).find((w) => w.id === id) || null);
37
38
  const cur = () => (S.sel && S.sel !== 'new' ? wfOf(S.sel) : null);
39
+ /** A New workflow chat is still going (its key is kept until Create), or PMOS has drafted one. */
40
+ const draftLive = () => !!(store.get(CHAT_KEY) || S.draft);
38
41
  const readOnly = (wf) => !!wf && (wf.builtin || wf.id === PRODUCT_FEATURE_ID);
39
42
 
40
43
  function toast(text, undo) {
@@ -62,19 +65,30 @@ async function readDraft() {
62
65
  const d = r && r.ok && r.draft ? r.draft.draft || null : null; // { draft, note, at } | null
63
66
  const was = JSON.stringify(S.draft), now = JSON.stringify(d);
64
67
  S.draft = d;
68
+ if (was !== now && S.sel !== 'new') drawList(); // the draft row's name follows the draft
65
69
  if (was !== now && S.sel === 'new') { drawList(); if (d) showDraft(); else if (VW.current() && VW.current().id === 'wf-draft') VW.close(); }
66
70
  }
67
71
 
68
72
  /* ── the list (WfAModel's left column) ── */
73
+ // the star (batch 24 item 3, canvas DfA): gold and filled on the default, an outline on hover or focus elsewhere; archived rows have none
74
+ const STAR = 'M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9 6.8 19.6l1-5.8-4.3-4.1 5.9-.9z';
75
+ function starHtml(w) {
76
+ if (w.archived) return '';
77
+ const label = w.default ? 'Default for new initiatives' : 'Make ' + w.name + ' the default for new initiatives';
78
+ return `<button type="button" class="wfs${w.default ? ' on' : ''}" data-wf-star="${esc(w.id)}" aria-pressed="${w.default ? 'true' : 'false'}" aria-label="${esc(label)}" title="${w.default ? 'Default for new initiatives' : 'Make default'}">`
79
+ + `<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="${w.default ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="${STAR}"/></svg></button>`;
80
+ }
69
81
  function rowHtml(w) {
70
82
  const n = w.count > 0 ? `<span class="n">${w.count}</span>` : ''; // zero is silence
71
- return `<button type="button" class="wfl${S.sel === w.id ? ' sel' : ''}${w.archived ? ' arch' : ''}" data-wf="${esc(w.id)}"${S.sel === w.id ? ' aria-current="true"' : ''}>${wfIcon(w, 22)}<span class="t">${esc(w.name)}</span>${n}</button>`;
83
+ return `<div class="wfl${S.sel === w.id ? ' sel' : ''}${w.archived ? ' arch' : ''}"><button type="button" class="wfb" data-wf="${esc(w.id)}"${S.sel === w.id ? ' aria-current="true"' : ''}>${wfIcon(w, 22)}<span class="t">${esc(w.name)}</span></button>${starHtml(w)}${n}</div>`;
72
84
  }
73
85
  function drawList() {
74
86
  const el = $('#wfList'); if (!el) return;
75
87
  const L = S.list;
76
88
  const on = (L || []).filter((w) => !w.archived), off = (L || []).filter((w) => w.archived);
77
- const newRow = S.sel === 'new' ? `<button type="button" class="wfl sel" data-wf="new" aria-current="true"><span class="wfi new" aria-hidden="true">${svg(PLUS, 14)}</span><span class="t">${esc((S.draft && S.draft.name) || 'New workflow')}</span><span class="n">draft</span></button>` : '';
89
+ // a live draft keeps its row while another workflow is open, so its chat and draft are one press away (batch 24 item 4)
90
+ const onNew = S.sel === 'new';
91
+ const newRow = onNew || draftLive() ? `<button type="button" class="wfl${onNew ? ' sel' : ''}" data-wf="new"${onNew ? ' aria-current="true"' : ''}><span class="wfi new" aria-hidden="true">${svg(PLUS, 14)}</span><span class="t">${esc((S.draft && S.draft.name) || 'New workflow')}</span><span class="n">draft</span></button>` : '';
78
92
  el.innerHTML = `<div class="lhead"><h2>Workflows</h2><button type="button" class="ib sm" data-wf-new aria-label="New workflow: describe it and PMOS drafts it" title="New workflow: describe it and PMOS drafts it">${svg(PLUS, 16)}</button></div>`
79
93
  + (L === null ? '<p class="wf-mut wf-small wf-pad">Reading the workflows.</p>' : on.map(rowHtml).join('') + newRow
80
94
  + (off.length ? `<p class="wf-sub">Archived</p>${off.map(rowHtml).join('')}` : ''));
@@ -234,6 +248,17 @@ async function archive(wf, on) {
234
248
  await load();
235
249
  toast(on ? 'Archived ' + wf.name + '.' : 'Restored ' + wf.name + '.', () => archive(wf, !on));
236
250
  }
251
+ async function makeDefault(id, undoTo) {
252
+ const w = (S.list || []).find((x) => x.id === id);
253
+ if (!w || (w.default && !undoTo)) return;
254
+ const was = (S.list || []).find((x) => x.default);
255
+ const r = await post('/api/workflows/default', { id });
256
+ if (!(r && r.ok)) { S.msg = (r && r.error) || 'Not changed.'; drawMain(); return; }
257
+ for (const x of S.list) { if (x.id === id) x.default = true; else delete x.default; }
258
+ drawList();
259
+ const b = document.querySelector(`#wfList [data-wf-star="${CSS.escape(id)}"]`); if (b) b.focus();
260
+ if (!undoTo) toast('New initiatives start on ' + w.name + '.', was ? () => makeDefault(was.id, true) : null);
261
+ }
237
262
  async function rename(wf, name) {
238
263
  const v = String(name || '').trim();
239
264
  if (!v || v === wf.name) { S.renaming = false; S.renErr = ''; drawMain(); return; }
@@ -389,8 +414,10 @@ function bindDrag(main) {
389
414
  function bind() {
390
415
  $('#wfList').addEventListener('click', (e) => {
391
416
  if (e.target.closest('[data-wf-new]')) { startNew(); return; }
392
- const b = e.target.closest('[data-wf]'); if (!b) return;
393
- if (b.dataset.wf === 'new') return;
417
+ const star = e.target.closest('[data-wf-star]'); if (star) { makeDefault(star.dataset.wfStar); return; }
418
+ const row = e.target.closest('.wfl');
419
+ const b = e.target.closest('[data-wf]') || (row && row.querySelector('[data-wf]')); if (!b) return;
420
+ if (b.dataset.wf === 'new') { if (S.sel !== 'new') startNew(); return; } // back to the live draft: its chat and draft viewer
394
421
  select(b.dataset.wf);
395
422
  });
396
423
  const main = $('#wfMain');
@@ -431,5 +458,6 @@ export function boot() {
431
458
  draw();
432
459
  load();
433
460
  if (new URLSearchParams(location.search).get('new') === '1') startNew();
461
+ else if (store.get(CHAT_KEY)) readDraft(); // a draft from before shows as its row, by name
434
462
  }
435
463
  if (typeof document !== 'undefined' && document.getElementById('wfMain')) boot();
@@ -135,6 +135,7 @@ const memoryLib = require('./lib/memory'); // curated repository mem
135
135
  const audit = require('./lib/audit'); // the workflow audit — the sweep the agents' CLI runs, read-only here (D77)
136
136
  const syncLib = require('./lib/sync'); // press-only fetch / integrate / commit records / push (D70)
137
137
  const kitUpdate = require('./lib/kit-update.js'); // workspace-kit-update: press-only check and update of the kit and the app
138
+ const gitShape = require('./lib/git-shape.js');
138
139
  const gitInfo = require('./lib/git-info'); // read-mostly branch awareness + select/create at session start
139
140
  const startPolicy = require('./lib/start-policy'); // ONE stated rule for where a session starts (repo-first R1/R3)
140
141
  const treeShare = require('./lib/tree-share'); // who shares a working tree, and who is DECLARED to be driving it
@@ -347,6 +348,9 @@ const layoutModel = require('./lib/layout'); // the arrangement model
347
348
  // REFUSED (BIND.refused) so the server exits — there is no code path to a public interface.
348
349
  const BIND = remoteAccess.resolveBinds({ env: process.env });
349
350
  const TOKEN = BIND.token || null; // non-loopback peers must present it; never logged (D08)
351
+ // page-open-speed: a few seconds after a real start, fill the read caches Home and the Board use, so the first page opens like a later one
352
+ const pageWarm = require('./lib/page-warm.js').create({
353
+ binds: BIND.binds, port: () => PORT, stateDir: STATE, start: !REQUIRED_AS_LIBRARY });
350
354
  // workspace-online-mode (D89): the Online switch. Its record lives in the per-user state folder; while it is on, the
351
355
  // workspace runs the tunnel itself and admits the public hostname, and a request to that hostname passes on a verified
352
356
  // Cloudflare Access assertion for the PM's email instead of the token. An environment-set PMOS_PUBLIC_HOST wins and keeps
@@ -923,7 +927,19 @@ function isLinkedRow(w) {
923
927
  }
924
928
  // The rows a PM picks as products (the switcher, Home, the Briefing, the Board's every-product list): every registered row
925
929
  // but a linked worktree. Settings Remove forgets the repository and its worktree rows together (forgetRepository).
926
- function productWorkspaces() { return [...workspaces.values()].filter((w) => !isLinkedRow(w)); }
930
+ function productWorkspaces() {
931
+ // Which tree is the main one changes only when the folder's .git does (gitShape.shapeStamp), so each answer is kept until that stamp
932
+ // moves, not for repoIdentity's 15 s clock (page-open-speed: Home ran git ~20 times for this list after a break).
933
+ const memo = productWorkspaces.memo || (productWorkspaces.memo = new Map()); // root -> { stamp, linked }
934
+ return [...workspaces.values()].filter((w) => {
935
+ const stamp = gitShape.shapeStamp(w.root);
936
+ const hit = memo.get(w.root);
937
+ if (hit && hit.stamp === stamp) return !hit.linked;
938
+ const linked = isLinkedRow(w);
939
+ memo.set(w.root, { stamp, linked });
940
+ return !linked;
941
+ });
942
+ }
927
943
  function wsList() {
928
944
  return [...workspaces.values()].map((w) => {
929
945
  const rid = repoIdentity(w.root);
@@ -2946,13 +2962,13 @@ const proposalRuns = require('./lib/proposal-runs.js').create({
2946
2962
  });
2947
2963
  const proposalTick = () => { try { proposalRuns.tick({}); } catch (e) { warnWriteFail('proposal tick', e); } };
2948
2964
  // Grill and Forecast, part G-A (lib/grill.js, lib/grill-runs.js): Claude grills an initiative by itself before it leaves a stage, when its
2949
- // forecast turns at risk or will miss (lib/forecast.js), when its scope or dates move a lot, and monthly when running work has late tasks.
2965
+ // forecast turns at risk or will miss (lib/forecast.js), when its scope or dates move a lot, and monthly on every running initiative with open work.
2950
2966
  // Each finding is a proposal under its ✦; only the PM's ✓ writes. Settings grill.mode and grill.cap (a monthly credit cap). PMOS_GRILL=off.
2951
2967
  const grillShape = (w) => {
2952
2968
  const RB = require('./lib/routes-boards.js'), TLR = require('./lib/routes-task-links.js'), PR = require('./lib/proposals.js'), ctx = proposalCtx(), out = new Map();
2953
2969
  for (const b of RB.frameFor(ctx, w).boards) {
2954
2970
  const f = TLR.frame(ctx, w, b); if (!f) continue;
2955
- for (const r of f.rows || []) if (r && r.id && !out.has(r.id)) out.set(r.id, { ini: r.id, board: b.id, stage: r.stage || '', stages: (r.wfStages || []).map((s) => ({ key: s.key, name: s.name })), tasks: [] });
2971
+ for (const r of f.rows || []) if (r && r.id && !out.has(r.id)) out.set(r.id, { ini: r.id, board: b.id, resting: !!(r.stopped || r.archived), stage: r.stage || '', stages: (r.wfStages || []).map((s) => ({ key: s.key, name: s.name })), tasks: [] });
2956
2972
  for (const x of f.items) { if (!x || !x.uid || x.kind === 'Epic') continue; const t = PR.taskOf(x); if (t.ini && out.has(t.ini)) out.get(t.ini).tasks.push(t); }
2957
2973
  }
2958
2974
  return [...out.values()];
@@ -2974,6 +2990,11 @@ const grillRuns = require('./lib/grill-runs.js').create({
2974
2990
  return { ini: x.ini, board: x.board, n: x.tasks.filter((t) => !t.closed).length, end: ends.length ? PR.isoN(Math.max(...ends)) : '' };
2975
2991
  }); },
2976
2992
  checks: (w, day) => lateChecks(w, day),
2993
+ // the monthly look covers every running initiative with open work, not only late work (2026-11-02); stopped and archived ones rest
2994
+ looks: (w) => { const PR = require('./lib/proposals.js'); return grillShape(w).filter((x) => !x.resting).map((x) => {
2995
+ const ends = x.tasks.filter((t) => !t.closed).map((t) => t.e).filter((n) => n != null);
2996
+ return { ini: x.ini, board: x.board, n: x.tasks.filter((t) => !t.closed).length, end: ends.length ? PR.isoN(Math.max(...ends)) : '' };
2997
+ }); },
2977
2998
  // the initiatives that move a goal, from the last Intelligence model (lib/insights-premium.js goalsOf: a goal's moves are the initiatives
2978
2999
  // anchored to it); only those of this workspace
2979
3000
  goalInis: (w, id) => { const m = require('./lib/insights-premium.js').peekModel(); const g = m && (m.goals || []).find((x) => x.id === id); if (!g) return [];
@@ -42,3 +42,13 @@ When the lines after this procedure say "This report has a saved structure" (a s
42
42
  - Put the report's words in the note `report.headline`, still between its markers and still one note per marker. Write one short paragraph for each section of the outline that is not the headline, needs or sources section. Start each paragraph with the section's title in bold. Quote the values from the data file with their comparison and say what the flags mean. Start the paragraph with "Warning:" when a chart is flagged. The headline itself comes first: two sentences.
43
43
  - Under the last paragraph, write one paragraph that starts with "Sources:". List the sources reached, with the time you read each, and the sources not reached, each with its reason. The script's own "Sources reached and not reached" section stays as the script wrote it.
44
44
  - The PM changes the structure in the studio. A change applies from the next cycle. Never edit a file under `structure/`.
45
+
46
+ When the lines after this procedure name a draft and a change ("The draft: <path> (slot <slot>)" and "The change the PM asked for"), this run is a change, not a new cycle. Steps 4 to 6 change as follows, and every rule stays:
47
+ - Work on THAT file only. Do not draft a new slot. Never run `pmos report draft <id>` without the slot: it drafts today's slot, which is a second file.
48
+ - If the named file does not exist, stop and say so in one line. Do not write it yourself.
49
+ - A change to the words: edit only the note or notes that the change is about, in the file, between their markers, even when they already have text. Keep every other note and every line outside the notes as it is. Rules 3, 6 and 7 still apply.
50
+ - Do NOT redraft for a change to the words. A redraft reads today's data and rewrites every block, so the numbers, the block titles and the place of the notes can change. Run `pmos report draft <id> <slot> --redraft`, with the slot from the lines, only when the PM asks for fresh numbers (for example "refresh", "update the numbers", "use today's data"). Then read the file once more.
51
+ - KEEP EVERY CAVEAT. A sentence that says something is provisional, not decided, unverified, at risk, not reached or a warning stays, word for word, also when the change asks for shorter text. Make the other sentences shorter. If the change cannot be made without removing a caveat, keep the caveat and say so.
52
+ - A change to a number, a date, a section, a slide or the order: you cannot make it, because the script writes every number and the template sets the sections (rules 6 and 7). Change nothing. Say in one line which setting it needs: a structured report changes in the studio (it applies from the next cycle); other reports change in the template.
53
+ - When you finish, say in one line which notes you changed, and quote each sentence you removed.
54
+ - A message that starts with `[PMOS report change]` is the same: it names the draft, its slot and the change.
@@ -14,6 +14,7 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
  const { spawn } = require('child_process');
16
16
  const { findBrowser } = require('./lib/find-browser');
17
+ const { openMission } = require('./lib/open-mission');
17
18
  const T = require('./test-run').configure({ name: 'split-merge-ui' });
18
19
  const { makeRepos, codeRepo, write, git } = require('./plan-test-repos');
19
20
 
@@ -68,7 +69,7 @@ const shot = (p, name) => (SHOTS ? p.screenshot({ path: path.join(SHOTS, name) }
68
69
 
69
70
  browser = await chromium.launch({ executablePath: exe });
70
71
  const open = async (o = {}) => {
71
- const ctx = await browser.newContext({ viewport: { width: o.w || 1440, height: o.h || 900 } });
72
+ const ctx = await openMission(await browser.newContext({ viewport: { width: o.w || 1440, height: o.h || 900 } }));
72
73
  const page = await ctx.newPage();
73
74
  const errors = [];
74
75
  page.on('pageerror', (e) => errors.push(String((e && e.stack) || e)));
@@ -133,7 +134,7 @@ const shot = (p, name) => (SHOTS ? p.screenshot({ path: path.join(SHOTS, name) }
133
134
 
134
135
  /* H — the merged one on Home */
135
136
  {
136
- const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
137
+ const ctx = await openMission(await browser.newContext({ viewport: { width: 1440, height: 900 } }));
137
138
  const p = await ctx.newPage();
138
139
  const errors = [];
139
140
  p.on('pageerror', (e) => errors.push(String((e && e.message) || e)));
@@ -13,6 +13,7 @@ const fs = require('fs');
13
13
  const { spawn } = require('child_process');
14
14
  const { pathToFileURL } = require('url');
15
15
  const { findBrowser } = require('./lib/find-browser');
16
+ const { openMission } = require('./lib/open-mission');
16
17
  const T = require('./test-run').configure({ name: 'stage-card' });
17
18
  const IP = require('./lib/initiative-premium.js');
18
19
  const F = require('./fixtures/initiative-premium-facts.js');
@@ -100,7 +101,7 @@ async function pure() {
100
101
  // the board the page reads (the one view draws the board's row): the scenario's stage, so the page and the board agree as the records do
101
102
  const boardAt = (stage) => { const f = BF.facts(); f.rows = f.rows.map((r) => (r.id === 'self-serve-v2' ? { ...r, stage } : r)); return BP.build(f); };
102
103
  const open = async ({ w = 1280, h = 900, theme = 'light', model = SCEN.build(), board = BOARD, sessions = null, trail = null } = {}) => {
103
- const ctx = await browser.newContext({ viewport: { width: w, height: h } });
104
+ const ctx = await openMission(await browser.newContext({ viewport: { width: w, height: h } }));
104
105
  const page = await ctx.newPage();
105
106
  const errors = [], posts = [];
106
107
  page.on('pageerror', (e) => errors.push(String(e)));
@@ -10,6 +10,7 @@
10
10
  const path = require('path');
11
11
  const { spawn } = require('child_process');
12
12
  const { findBrowser } = require('./lib/find-browser');
13
+ const { openMission } = require('./lib/open-mission');
13
14
  const T = require('./test-run').configure({ name: 'task-press' });
14
15
  const IP = require('./lib/initiative-premium.js');
15
16
  const F = require('./fixtures/initiative-premium-facts.js');
@@ -47,7 +48,7 @@ const json = (r, b, status = 200) => r.fulfill({ status, contentType: 'applicati
47
48
  const shot = async (p, name) => { if (SHOTS) await p.screenshot({ path: path.join(SHOTS, name) }).catch(() => {}); };
48
49
 
49
50
  async function page(url) {
50
- const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
51
+ const ctx = await openMission(await browser.newContext({ viewport: { width: 1440, height: 900 } }));
51
52
  const p = await ctx.newPage();
52
53
  const errors = [], posts = [];
53
54
  p.on('pageerror', (e) => errors.push(String((e && e.message) || e)));
@@ -0,0 +1,5 @@
1
+ {
2
+ "_comment": "Page open speed (2026-11-02): the file-stamp caches that replace git runs on a page open stay as fresh as a git run; the start-up warm-up can be turned off and asks one path at a time.",
3
+ "modules": ["page-open-speed.test.js"],
4
+ "seconds": { "page-open-speed.test.js": 2 }
5
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_comment": "The finished report slides, the Pyramid deck (canvas RsA): lib/report-pyramid.js read from a rendered cadence draft. Pure; no server.",
3
+ "modules": [
4
+ "report-pyramid.test.js"
5
+ ],
6
+ "seconds": {
7
+ "report-pyramid.test.js": 1
8
+ }
9
+ }
@@ -12,6 +12,7 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { spawn } = require('child_process');
14
14
  const { findBrowser } = require('./lib/find-browser');
15
+ const { openMission } = require('./lib/open-mission');
15
16
  const T = require('./test-run').configure({ name: 'tk-tasks' });
16
17
  const IP = require('./lib/initiative-premium.js');
17
18
  const F = require('./fixtures/initiative-premium-facts.js');
@@ -79,7 +80,7 @@ const LAYOUT = { ok: true, auto: true, held: [], by: 'stage', names: [], of: {},
79
80
 
80
81
  // the layout answers like lib/initiative-layout.js folds it: a press changes it, and the next read sees the change
81
82
  const open = async ({ w = 1440, h = 900, theme = 'light', view = VIEW, layout = {}, page: pg = 'home', touch = false } = {}) => {
82
- const ctx = await browser.newContext({ viewport: { width: w, height: h }, hasTouch: touch, isMobile: touch });
83
+ const ctx = await openMission(await browser.newContext({ viewport: { width: w, height: h }, hasTouch: touch, isMobile: touch }));
83
84
  const page = await ctx.newPage();
84
85
  const errors = [], posts = [];
85
86
  let cur = view, L = { ...LAYOUT, ...layout };