create-pmos 0.124.0 → 0.126.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/scripts/nine-stages.js +81 -0
  6. package/payload/scripts/workflow-state.js +2 -0
  7. package/payload/state/discoveries.md +1 -1
  8. package/payload/state/log.md +1 -1
  9. package/workspace/c3-one-home-test.js +131 -0
  10. package/workspace/fixtures/initiatives-api/list.json +171 -3
  11. package/workspace/fixtures/initiatives-api/roadmap.json +228 -4
  12. package/workspace/fixtures/initiatives-api/sheet.json +57 -1
  13. package/workspace/fixtures/one-home-baseline.json +83 -0
  14. package/workspace/guide/briefing-cards.md +40 -0
  15. package/workspace/guide/index.md +1 -0
  16. package/workspace/guide/layout.md +2 -0
  17. package/workspace/lib/engine/nine-stages.js +81 -0
  18. package/workspace/lib/engine/workflow-state.js +2 -0
  19. package/workspace/lib/facts-async.js +51 -0
  20. package/workspace/lib/item-steps.js +52 -0
  21. package/workspace/lib/jira-status.js +8 -3
  22. package/workspace/lib/jobs-to-be-done.js +52 -0
  23. package/workspace/lib/needs-overdue.js +41 -0
  24. package/workspace/lib/outbox-store.js +87 -0
  25. package/workspace/lib/pm-items.js +115 -0
  26. package/workspace/lib/route-modules.js +21 -0
  27. package/workspace/lib/routes-briefing.js +109 -0
  28. package/workspace/lib/routes-c3-init.js +53 -0
  29. package/workspace/lib/routes-c3-life.js +119 -0
  30. package/workspace/lib/routes-c3-strategy.js +169 -0
  31. package/workspace/lib/routes-c3-timeline.js +191 -0
  32. package/workspace/lib/routes-calendar-import.js +60 -0
  33. package/workspace/lib/routes-calendar-views.js +61 -0
  34. package/workspace/lib/routes-doc-health.js +79 -0
  35. package/workspace/lib/routes-doc-sheet.js +169 -0
  36. package/workspace/lib/routes-forecast.js +85 -0
  37. package/workspace/lib/routes-hooks.js +121 -0
  38. package/workspace/lib/routes-initiatives.js +3 -0
  39. package/workspace/lib/routes-intake.js +24 -0
  40. package/workspace/lib/routes-outbox.js +97 -0
  41. package/workspace/lib/routes-pm-items.js +216 -0
  42. package/workspace/lib/routes-records.js +61 -0
  43. package/workspace/lib/routes-releases.js +129 -0
  44. package/workspace/lib/routes-report-facts.js +84 -0
  45. package/workspace/lib/routes-report-families.js +32 -0
  46. package/workspace/lib/routes-roadmap-ops.js +141 -0
  47. package/workspace/lib/routes-runs.js +119 -0
  48. package/workspace/lib/routes-signals.js +50 -0
  49. package/workspace/lib/run-answers.js +69 -0
  50. package/workspace/package-lock.json +2 -2
  51. package/workspace/package.json +1 -1
  52. package/workspace/public/c3.html +26 -0
  53. package/workspace/public/pm/briefing-deck.js +413 -0
  54. package/workspace/public/pm/briefing-model.js +211 -0
  55. package/workspace/public/pm/c3-breakdown.js +240 -0
  56. package/workspace/public/pm/c3-brief.js +278 -0
  57. package/workspace/public/pm/c3-cal-strategy.js +528 -0
  58. package/workspace/public/pm/c3-case.js +185 -0
  59. package/workspace/public/pm/c3-daypart.js +176 -0
  60. package/workspace/public/pm/c3-demo-brief-ix.js +350 -0
  61. package/workspace/public/pm/c3-demo-cal-ix.js +74 -0
  62. package/workspace/public/pm/c3-demo-cal2.js +66 -0
  63. package/workspace/public/pm/c3-demo-cal3.js +173 -0
  64. package/workspace/public/pm/c3-demo-data.js +560 -0
  65. package/workspace/public/pm/c3-demo-init.js +373 -0
  66. package/workspace/public/pm/c3-demo-life.js +77 -0
  67. package/workspace/public/pm/c3-demo-misc2.js +46 -0
  68. package/workspace/public/pm/c3-demo-plan2.js +30 -0
  69. package/workspace/public/pm/c3-demo-rep-ix.js +126 -0
  70. package/workspace/public/pm/c3-demo-rep2.js +92 -0
  71. package/workspace/public/pm/c3-demo-term.js +153 -0
  72. package/workspace/public/pm/c3-demo-time.js +49 -0
  73. package/workspace/public/pm/c3-demo-work-ix.js +80 -0
  74. package/workspace/public/pm/c3-demo-work2.js +13 -0
  75. package/workspace/public/pm/c3-demo.js +113 -0
  76. package/workspace/public/pm/c3-doc-sheet.js +314 -0
  77. package/workspace/public/pm/c3-dock.js +180 -0
  78. package/workspace/public/pm/c3-docs.js +290 -0
  79. package/workspace/public/pm/c3-expand.js +96 -0
  80. package/workspace/public/pm/c3-init-extras.js +170 -0
  81. package/workspace/public/pm/c3-lifecycle-panels.js +476 -0
  82. package/workspace/public/pm/c3-misc-panels.js +89 -0
  83. package/workspace/public/pm/c3-plan-panels.js +163 -0
  84. package/workspace/public/pm/c3-plan.js +192 -0
  85. package/workspace/public/pm/c3-roadmap.js +249 -0
  86. package/workspace/public/pm/c3-sheet.js +107 -0
  87. package/workspace/public/pm/c3-shell.js +674 -0
  88. package/workspace/public/pm/c3-signals.js +196 -0
  89. package/workspace/public/pm/c3-strategy-panels.js +45 -0
  90. package/workspace/public/pm/c3-swipe.js +230 -0
  91. package/workspace/public/pm/c3-terminal-data.js +26 -0
  92. package/workspace/public/pm/c3-terminal-edge.js +91 -0
  93. package/workspace/public/pm/c3-terminal-icons.js +41 -0
  94. package/workspace/public/pm/c3-terminal-results.js +141 -0
  95. package/workspace/public/pm/c3-terminal.js +52 -0
  96. package/workspace/public/pm/c3-tickets.js +163 -0
  97. package/workspace/public/pm/c3-timeline.js +201 -0
  98. package/workspace/public/pm/c3-toast.js +16 -0
  99. package/workspace/public/pm/calendar-views.js +1014 -0
  100. package/workspace/public/pm/console3-lift.md +220 -0
  101. package/workspace/public/pm/console3.css +1547 -0
  102. package/workspace/public/pm/dest-initiatives.js +16 -3
  103. package/workspace/public/pm/dest-mywork.js +8 -1
  104. package/workspace/public/pm/dest-reports.js +47 -1
  105. package/workspace/public/pm/dest-today.js +55 -1
  106. package/workspace/public/pm/doc-health-panel.js +110 -0
  107. package/workspace/public/pm/doc-rail.js +9 -3
  108. package/workspace/public/pm/lifecycle-lens-model.js +228 -0
  109. package/workspace/public/pm/lifecycle-lens.js +261 -0
  110. package/workspace/public/pm/new-initiative-sheet.js +116 -21
  111. package/workspace/public/pm/outbox-panel.js +125 -0
  112. package/workspace/public/pm/pm-work-view.js +895 -0
  113. package/workspace/public/pm/releases-view.js +248 -0
  114. package/workspace/public/pm/report-families-view.js +503 -0
  115. package/workspace/public/pm/run-card.js +244 -0
  116. package/workspace/public/pm/sessions-panel.js +149 -0
  117. package/workspace/public/pm/shell-bell.js +162 -0
  118. package/workspace/public/pm/shell-model.js +83 -0
  119. package/workspace/public/pm/shell-phone.js +124 -0
  120. package/workspace/public/pm/shell-readaloud.js +53 -0
  121. package/workspace/public/pm/shell-recording.js +110 -0
  122. package/workspace/public/pm/shell-ticker.js +90 -0
  123. package/workspace/public/pm/slide-confirm.js +163 -0
  124. package/workspace/public/pm/timeline-forecast-view.js +164 -0
  125. package/workspace/public/pm/today-day.js +8 -2
  126. package/workspace/public/pm/tokens.css +1 -0
  127. package/workspace/server.js +5 -2
  128. package/workspace/test-files.json +21 -3
  129. package/workspace/test-lists/browser-suites-green.json +5 -0
@@ -0,0 +1,560 @@
1
+ /* pm/c3-demo-data.js — the fixtures of the /c3.html demo mode (?demo=1). Every value here is the console3 mockup's own sample content
2
+ * (prototype/pmos-console-handoff/mockups/console3.html: CARDS, TIX, KRS, SIGS, the S.items table and the meetings), shaped as the
3
+ * response of the real route it stands in for. Nothing here is real state; c3-demo.js serves it and shows the "Demo data" pill.
4
+ * The mockup's "now" is Thu 8 Oct 2026, 08:12 (c3-demo.js freezes the page clock there).
5
+ *
6
+ * Fields the real routes do not send and the views draw only when present (demo-only, additive): on a briefing card `sub`, `kpis`,
7
+ * `factsX`, `pov`, `hero.rich`, `move.suggested.conf`; on the briefing `needs`; on a work item `mock_row` (which switches on `kind`, `time`, `mins`, `src`, `init`,
8
+ * `agent_move`, `left`); on a calendar event `mock_routine`, `mock_hot`, `mock_decide_by`; on the forecast `mock_status_w`; on the calendar response `free_gaps`. */
9
+
10
+ export const NOW_ISO = '2026-10-08T08:12:00';
11
+ export const TODAY = '2026-10-08';
12
+ const DAY = 86400000;
13
+ const pad = (n) => String(n).padStart(2, '0');
14
+ /* the mockup counts days from 14 Sep: day 24 is Thu 8 Oct, day 40 is 24 Oct, day 47 is 31 Oct */
15
+ const dOf = (n) => new Date(Date.UTC(2026, 8, 14 + n)).toISOString().slice(0, 10);
16
+ const addDays = (iso, n) => new Date(Date.parse(iso + 'T00:00:00Z') + n * DAY).toISOString().slice(0, 10);
17
+ const ymd = (s) => String(s).slice(0, 10).split('-').map(Number);
18
+ const utcOf = (s) => { const [y, m, d] = ymd(s); return Date.UTC(y, m - 1, d); };
19
+
20
+ /* ── workspaces, the dock: acme/checkout and its branches and sessions ─────────────────────────────────────────────────────────── */
21
+
22
+ export const WS_ID = '881665c933';
23
+ export function workspaces() {
24
+ const w = (id, name, root, extra) => ({ id, name, label: null, initials: null, profile: 'pm-artifacts', root, home: false, shape: 'pmos', sessions: 0, repo_key: root, repo_name: name, ...(extra || {}) });
25
+ return { workspaces: [w(WS_ID, 'acme/checkout', '/demo/acme/checkout', { home: true, branch: 'main' }), w('demo00research', 'acme/research', '/demo/acme/research'),
26
+ w('demo000search', 'acme/search', '/demo/acme/search'), w('demo0000pmos', 'wawancsui/pmos', '/demo/wawancsui/pmos')] };
27
+ }
28
+ const T_MAIN = '/demo/acme/checkout'; const T_V2 = '/demo/acme/checkout-v2'; const T_SSO = '/demo/acme/checkout-sso'; const T_TRI = '/demo/acme/checkout-triage';
29
+ export function worktrees() {
30
+ const t = (dir, branch, main) => ({ dir, branch, main: !!main, missing: false, ws: WS_ID, sessions: 0, removable: false, reasons: [] });
31
+ return { ok: true, repoRoot: T_MAIN, worktrees: [t(T_MAIN, 'main', true), t(T_V2, 'initiative/checkout-v2'), t(T_SSO, 'initiative/sso'), t(T_TRI, 'automation/triage')] };
32
+ }
33
+ const NOWMS = Date.parse(NOW_ISO);
34
+ export function sessions() {
35
+ const s = (id, name, tree, shown, createdMin, lastMin, line, label, agent) => ({ id, ws: WS_ID, name, agent: agent || 'cloud', state: 'working', shown, label, created: new Date(NOWMS - createdMin * 60000).toISOString(), lastOutputAt: NOWMS - lastMin * 60000,
36
+ model: 'cloud', model_line: line, cwd: '.', tree, automatic: false, origin: null });
37
+ return { sessions: [
38
+ { ...s('s1', 'Prepare my day', '/demo/wawancsui/pmos', 'exited', 80, 73, '38k tokens · wrote the briefing only', 'ended', 'cloud'), ws: 'demo0000pmos' },
39
+ s('s2', 'PRD section 4 redraft', T_V2, 'working', 38, 2, '41k tokens · writes PRD draft only · cannot send', 'working', 'cloud'),
40
+ { ...s('s3', 'Interview guide', '/demo/acme/research', 'needs', 12, 9, '18k tokens · writes guide draft only', 'needs you', 'codex'), ws: 'demo00research' },
41
+ s('s4', 'Triage Jira tickets', T_TRI, 'quiet', 180, 180, '22k tokens · proposes only, applies nothing in Jira', 'idle 3 h', 'local'),
42
+ { ...s('s5', 'EN-418 index job', '/demo/acme/search', 'blocked', 120, 1000, '61k tokens · read only', 'blocked', 'cloud'), ws: 'demo000search' },
43
+ ] };
44
+ }
45
+ export const transcript = (id) => ({ ok: true, text: id === 's2' ? 'Reading the 3-flow scope\nRewriting section 4\nWaiting for the scope decision before saving' : 'Proposed closing 6 duplicates\nProposed owners for 3 tickets\nNothing applied in Jira' });
46
+
47
+ /* ── runs (the dock's "Runs and results"): one on a card, one started by me, one past ─────────────────────────────────────────── */
48
+
49
+ export function runs() {
50
+ const card = (state, title, extra) => ({ state, title, steps: [], ask: null, permission: null, attention: null, failure: null, recover: [], problems: [], unlabelled: 0, fallbackText: null, result: null, claims: [], blocked: null, nextMoves: [], fallback: null, asked: null, permissions: [], ...(extra || {}) });
51
+ const iso = (m) => new Date(NOWMS - m * 60000).toISOString();
52
+ return { ok: true,
53
+ groups: {
54
+ onCard: [{ id: 's2', plan_item: null, name: 'PRD section 4 redraft', origin: 'card', cardId: 'decision', startedAt: iso(38), endedAt: null }],
55
+ startedByMe: [{ id: 's4', plan_item: null, name: 'Triage Jira tickets', origin: 'manual', cardId: null, startedAt: iso(180), endedAt: null }],
56
+ past: [{ id: 's1', plan_item: null, name: 'Prepare my day', origin: 'automation', cardId: null, startedAt: iso(80), endedAt: iso(73) }],
57
+ automatic: [], automaticHidden: true,
58
+ },
59
+ cards: {
60
+ s2: card('working', 'Rewriting section 4 for the 3-flow scope'),
61
+ s4: card('working', 'Proposing owners for 3 tickets'),
62
+ s1: card('done', 'Built the briefing: 5 cards', { claims: [{ text: 'Read Jira, Confluence, Calendar, Slack', basis: 'fact', source: 'run log', ok: true }, { text: 'Wrote the briefing only', basis: 'fact', source: 'run log', ok: true }] }),
63
+ } };
64
+ }
65
+
66
+ /* ── the Briefing cards, per scope (the tab the deck sits on) ──────────────────────────────────────────────────────────────────── */
67
+
68
+ const C = { ok: 'var(--success)', warn: 'var(--warning)', bad: 'var(--destructive)', acc: 'var(--acc)', mfg: 'var(--muted-foreground)', opp: 'var(--purple)' };
69
+ const card = (key, tag, headline, sub, rich, o) => ({ key, tag, headline, sub, hero: { kind: 'rows', points: null, rich }, facts: [], ...(o || {}) });
70
+
71
+ const DECISION = card('decision', 'Critical', 'Cut Checkout v2 to 3 flows to hold 24 Oct?', 'Dara needs it before Sprint planning. Jira shows 3 blockers on the full scope.',
72
+ { kind: 'chain', ring: { frac: 0.36, lines: ['5h 48m', 'left'], tone: C.acc }, steps: [['Decide', 'by 14:00', C.acc], ['Sprint planning', 'blocked', C.bad], ['Ship 24 Oct', 'at risk', C.warn]] },
73
+ { kpis: [['5h 48m', 'left', C.acc], ['3', 'blockers', C.bad], ['82%', 'confidence', C.ok]], factsX: [['user', 'Dara is waiting'], ['alert', '3 blockers on full scope']],
74
+ move: { suggested: { id: 'cut', label: 'Cut to 3 flows', conf: 82 }, options: [{ id: 'cut', label: 'Cut to 3 flows', slipAfter: 0 }, { id: 'add', label: 'Cut and add one engineer', slipAfter: 0 }, { id: 'keep', label: 'Keep the full scope', slipAfter: 7 }], note: 'Dara\'s estimate for flow 4 is a range of 3 to 6 days.' },
75
+ deadline: { days: 0.24, source: 'calendar', text: 'Sprint planning at 14:00, 5h 48m left' }, waitsFor: 'Dara', why: { text: 'Sprint planning cannot start until scope is decided; the 24 Oct date depends on it.', impact: 0.9, urgency: 0.85, irreversible: 0.4, score: 0.82 },
76
+ pinned: false });
77
+ const METRIC = card('metric', 'Just in', 'Conversion is down 0.2 points', 'Landed at 07:30. The biggest loss is cart to address: 45% of carts stop.',
78
+ { kind: 'num', big: '3.1', unit: '%', delta: '-0.2 pts since last week', tone: C.bad, arr: [3.5, 3.4, 3.4, 3.3, 3.3, 3.2, 3.1], target: 4 },
79
+ { kpis: [['3.1%', 'now', C.bad], ['4.0%', 'target', C.mfg], ['45%', 'lost at address', C.warn]], factsX: [['bolt', 'Data landed 07:30'], ['alert', 'Mobile shows the same pattern']],
80
+ move: { suggested: { id: 'x', label: 'Draft an experiment for the address step', conf: 71 }, options: [{ id: 'x', label: 'Draft an experiment for the address step' }, { id: 'w', label: 'Watch 3 more days' }, { id: 't', label: 'Tell Priya in the 10:30 update' }], note: 'One week of data. A seasonal dip is not ruled out.' },
81
+ deadline: null, why: { text: 'Conversion is the earliest warning on the checkout bet.', impact: 0.8, urgency: 0.6, irreversible: 0.2, score: 0.71 } });
82
+ const DATE = card('date', 'Critical', 'Checkout v2 may slip 7 days', 'Forecast Oct 31 vs promised Oct 24 (+7 days). Scope is the cause, not capacity.',
83
+ { kind: 'rows', rows: [['target', 'Promised to leadership on 2 Oct', '', '24 Oct'], ['alert', 'Forecast on the full scope', 'bad', '31 Oct'], ['scale', 'Cause: scope, not capacity', 'warn', '']] },
84
+ { kpis: [['+7 d', 'vs promise', C.bad], ['24 Oct', 'promised', 'var(--foreground)'], ['74%', 'confidence', C.acc]], factsX: [['target', 'Promised to leadership on 2 Oct'], ['alert', 'Cause: scope, not capacity']],
85
+ move: { suggested: { id: 'warn', label: 'Warn Priya now, with the fix attached', conf: 74 }, options: [{ id: 'warn', label: 'Warn Priya now, with the fix attached' }, { id: 'wait', label: 'Wait for the 14:00 decision' }] }, deadline: null });
86
+ const INSIGHT = card('insight', 'Opportunity', 'Label printing is the main pain', '3 of 3 calls name it so far. Interview 3 is today at 13:00.',
87
+ { kind: 'bars', bars: [['Labels are the main pain', 1, C.ok, '3 of 3'], ['Customers miss the 30-day window', 0.67, C.warn, '2 of 3'], ['Support is a workaround', 0, 'var(--ring)', 'untested']] },
88
+ { kpis: [['3 of 3', 'calls agree', C.ok], ['2 of 5', 'interviews done', C.acc], ['76%', 'confidence', C.opp]], factsX: [['cal', 'Interview 3 at 13:00'], ['target', 'Stop rule after interview 5']],
89
+ move: { suggested: { id: 'a', label: 'Add a support-route question to interview 3', conf: 76 }, options: [{ id: 'a', label: 'Add a support-route question to interview 3' }, { id: 'k', label: 'Keep the guide as it is' }] } });
90
+ const SSO = card('sso', 'Big win', 'SSO is live with 3 customers', 'No incidents in 7 days. Release on 14 Oct is on track.',
91
+ { kind: 'ring', done: 5, total: 8, cap: '5 of 8 gates', sub: 'release checklist' },
92
+ { kpis: [['3', 'customers live', C.ok], ['0', 'incidents', C.ok], ['5 of 8', 'gates', C.acc]], factsX: [['user', 'Marta and Priya have not heard yet'], ['flag', 'First enterprise milestone']],
93
+ move: { suggested: { id: 's', label: 'Lead the weekly status with it', conf: 90 }, options: [{ id: 's', label: 'Lead the weekly status with it' }, { id: 'n', label: 'Send Marta and Priya a note now' }, { id: 'h', label: 'Hold until the release' }] } });
94
+ const SEARCH = card('search', 'Small win', 'Search got 28% faster', 'p95 latency fell from 420 to 300 ms after the 6 Oct merge.',
95
+ { kind: 'num', big: '300', unit: 'ms', delta: '-120 ms since the merge', tone: C.ok, arr: [420, 418, 410, 405, 300, 296, 300], target: 350 },
96
+ { kpis: [['-28%', 'p95 latency', C.ok], ['300 ms', 'now', 'var(--foreground)'], ['350 ms', 'target', C.mfg]], factsX: [['check', 'Merged 6 Oct, CI green'], ['report', 'Measured over 7 days']],
97
+ move: { suggested: { id: 'b', label: 'Bank it in Friday\'s report', conf: 93 }, options: [{ id: 'b', label: 'Bank it in Friday\'s report' }, { id: 's', label: 'Skip' }] } });
98
+ const BOARD = card('board', 'Opportunity', 'The board update is 70% ready', 'Three sections wait on Thursday\'s numbers. It sends on 20 Dec.',
99
+ { kind: 'ring', done: 7, total: 10, cap: '7 of 10 sections', sub: '3 wait on numbers' },
100
+ { kpis: [['70%', 'ready', C.acc], ['3', 'waiting', C.warn], ['20 Dec', 'sends', 'var(--foreground)']], factsX: [['clock', 'Sends 20 Dec'], ['spark', 'The agent is filling the rest']],
101
+ move: { suggested: { id: 'f', label: 'Let the agent fill from Thursday\'s numbers', conf: 79 }, options: [{ id: 'f', label: 'Let the agent fill from Thursday\'s numbers' }, { id: 'r', label: 'Review the sections yourself first' }] } });
102
+
103
+ /* Calendar tab (the mockup's calCards, day) */
104
+ const CAL_DM = card('dm', 'Critical', 'Sprint planning', '14:00 to 15:00 · Checkout v2. The sprint cannot start until scope is decided. The 24 Oct date depends on it.',
105
+ { kind: 'ring', done: 5, total: 6, size: 76, cap: 'Prep 5 of 6', lines: [['check', 'Agenda', true], ['check', 'What changed', true], ['check', 'Outcome', true], ['check', 'Reading', true], ['check', 'People', true], ['clock', 'Pre-read', false]] },
106
+ { kpis: [['5 of 6', 'prep ready', C.ok], ['3', 'attendees', C.acc], ['78%', 'confidence', C.bad]], factsX: [['user', 'Dara, Lee, Sam'], ['cal', '14:00 to 15:00']],
107
+ move: { suggested: { id: 'k', label: 'Keep 14:00 and decide before', conf: 78 }, options: [{ id: 'k', label: 'Keep 14:00 and decide before' }] }, deadline: { days: 0.24, source: 'calendar', text: '14:00' } });
108
+ const CAL_DD = card('dd', 'Critical', 'Two items are slipping', 'The sandbox keys are 2 days late and the scope decision is due at 14:00. Fix the order of work.',
109
+ { kind: 'rows', rows: [['alert', 'Payments sandbox keys', 'bad', 'overdue'], ['alert', 'Confirm the 1 Oct numbers before the 10:30 update', 'warn', 'at risk'], ['alert', 'Cut Checkout v2 to 3 core flows to hold 24 Oct?', 'warn', 'at risk']] },
110
+ { kpis: [['4', 'due today', C.acc], ['2 d', 'oldest late', C.bad], ['80%', 'confidence', C.ok]], factsX: [['clock', '4 due today'], ['alert', 'Both block 24 Oct']] });
111
+ const CAL_DF = card('df', 'Opportunity', '1.8 h of focus at 11:15', 'The longest free window today. Best used before the 14:00 decision.',
112
+ { kind: 'tiles', tiles: [['1.8 h', 'free', C.opp], ['11:15', 'starts', 'var(--foreground)'], ['74%', 'confidence', C.acc]] },
113
+ { kpis: [['1.8 h', 'free', C.opp], ['11:15', 'starts', 'var(--foreground)'], ['74%', 'confidence', C.acc]], factsX: [['clock', '11:15 to 13:00'], ['doc', 'Pre-read is half drafted']] });
114
+ const CAL_DL = card('dl', 'Small win', 'Get back 45 minutes', 'Skip Standup and trim one sync. 3.8 h of meetings today, 3.3 h tied to a promise.',
115
+ { kind: 'tiles', tiles: [['3.3 h', 'tied to a promise', C.acc], ['30 min', 'routine', C.mfg], ['45 min', 'you could win back', C.ok]] },
116
+ { kpis: [['45 min', 'to win back', C.ok], ['3.8 h', 'meetings', C.acc], ['71%', 'confidence', C.ok]], factsX: [['check', 'Standup has an async summary'], ['clock', 'Sync can end in 15 min']] });
117
+ const CAL_DN = card('dn', 'Just in', 'Marta confirmed 3 slots and Priya replied', 'Two quick items since you last looked. One unblocks the interview.',
118
+ { kind: 'rows', rows: [['mail', 'Marta: 3 interview slots confirmed', 'ok', '08:05'], ['user', 'Priya: replied on the numbers', 'ok', '08:10']] },
119
+ { kpis: [['2', 'new', C.acc], ['<2 min', 'each', C.ok], ['88%', 'confidence', C.ok]], factsX: [['bolt', '2 new'], ['clock', 'Both take under 2 minutes']] });
120
+
121
+ /* Work tab (the mockup's workCards, Triage) */
122
+ const WORK_KQ = card('kq', 'Small win', 'Do 3 quick ones in one go', '3 items take under 2 minutes. Anything outward stays a draft for you to send.',
123
+ { kind: 'segs', total: '4 min', segs: [['Sam: ship the logging change', 1, C.acc], ['15 Oct interview invite', 1, C.ok], ['interview slots with 3 customers', 2, C.opp]] },
124
+ { kpis: [['3', 'items', C.acc], ['4 min', 'total', C.ok], ['0', 'sent for you', C.mfg]], factsX: [['bolt', 'Under 2 minutes each'], ['shield', 'Nothing is sent for you']],
125
+ move: { suggested: { id: 'all', label: 'Do all 3 now', conf: 90 }, options: [{ id: 'all', label: 'Do all 3 now' }] } });
126
+ const WORK_KD = card('kd', 'Critical', '5 need a real decision', 'Put them in Today with time blocked, so they do not rot in the inbox.',
127
+ { kind: 'segs', total: '25 min', segs: [['Assign owners to 2 unowned workshop actions', 5, C.bad], ['Approve pricing-page copy before handoff', 5, C.warn], ['Review the 13:00 interview guide', 5, C.acc], ['Confirm the growth assumption in the Q4 forecast', 10, C.opp]] },
128
+ { kpis: [['5', 'decisions', C.bad], ['40 min', 'total', C.warn], ['78%', 'confidence', C.ok]], factsX: [['scale', 'Decide or review'], ['clock', 'About 40 min in total']],
129
+ move: { suggested: { id: 'td', label: 'Put them in Today and block time', conf: 78 }, options: [{ id: 'td', label: 'Put them in Today and block time' }] } });
130
+ const WORK_KA = card('ka', 'Opportunity', 'The agent drafted 3 replies', 'Review them as a set. Nothing leaves until you send it.',
131
+ { kind: 'rows', rows: [['reply', 'Confirm the 1 Oct numbers before th…', 'ok', '10:30'], ['reply', 'Say yes to Sam: ship the logging ch…', 'ok', 'today'], ['reply', 'Accept the 15 Oct interview invite', 'ok', 'today']] },
132
+ { kpis: [['84%', 'confidence', C.acc], ['2', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['spark', 'Drafted from your history'], ['out', 'Goes to Review first']],
133
+ move: { suggested: { id: 'ap', label: 'Approve all as drafts', conf: 84 }, options: [{ id: 'ap', label: 'Approve all as drafts' }, { id: 'ed', label: 'Open each first' }] } });
134
+ const WORK_KJ = card('kj', 'Just in', "Priya's request is the newest and has a clock", 'The 1 Oct numbers, due 10:30. Two sources disagree, so the case file has the answer.',
135
+ { kind: 'ring', done: 14, total: 152, size: 96, cap: '2h 18m left', sub: 'until the 10:30 update' },
136
+ { kpis: [['2h 18m', 'left', C.warn], ['2', 'sources disagree', C.bad], ['90%', 'confidence', C.ok]], factsX: [['user', 'Priya, 07:58'], ['sheet', 'Sheet says 3.1%']],
137
+ move: { suggested: { id: 'r', label: 'Reply 3.1% and cite the sheet', conf: 90 }, options: [{ id: 'r', label: 'Reply 3.1% and cite the sheet' }, { id: 't', label: 'Put it in Today' }] } });
138
+
139
+ /* Work tab, the other lenses (the mockup's workCards: Today, Week, Waiting, Later; Triage's fourth card too) */
140
+ const mv = (id, label, conf, more) => ({ suggested: { id, label, conf }, options: [{ id, label }, ...(more || [])] });
141
+ const WORK_KN = card('kn', 'Small win', 'Drop 3 noise items', 'Newsletter, digest and file updates. The agent has read them and found nothing for you.',
142
+ { kind: 'tiles', tiles: [['3', 'FYI items', C.mfg], ['9', 'real work', C.acc]] },
143
+ { kpis: [['94%', 'confidence', C.acc], ['2', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['filter', 'Kept out of your list']], move: mv('ar', 'Archive all 3', 94) });
144
+ const WK_KO = card('ko', 'Critical', 'Do these three first', 'Ranked by what each one unblocks and when it is due.',
145
+ { kind: 'axis', focus: [11.25, 13], now: 8.2, marks: [[10.5, '10:30', C.bad, 19], [13, '13:00', C.acc, 27], [14, '14:00', C.bad, 19]] },
146
+ { kpis: [['6', 'committed', C.acc], ['1.8 h', 'focus time', C.opp], ['78%', 'confidence', C.ok]], factsX: [['target', 'Hold the 24 Oct date'], ['clock', 'Best window 11:15']], move: mv('lk', 'Lock these and block 11:15 to 13:00', 78) });
147
+ const WK_KR = card('kr', 'Critical', 'Two items are at risk or overdue', 'The sandbox keys are 2 days late and the scope decision is due at 14:00.',
148
+ { kind: 'rows', rows: [['alert', 'Payments sandbox keys', 'bad', 'overdue'], ['alert', 'Confirm the 1 Oct numbers before …', 'warn', 'at risk'], ['alert', 'Cut Checkout v2 to 3 core flows t…', 'warn', 'at risk']] },
149
+ { kpis: [['80%', 'confidence', C.ok], ['2', 'options', C.opp], ['2', 'evidence', C.ok]], factsX: [['alert', 'Both block 24 Oct']], move: mv('es', 'Escalate the keys, then decide before 13:30', 80) });
150
+ const WK_KW = card('kw', 'Just in', '2 things wait on others', 'Dara is 2 days silent on the keys. A nudge is drafted for each.',
151
+ { kind: 'bars', bars: [['Dara: Payments sandbox keys', 1, C.bad, 'asked Tue'], ['Lee: Annual-plan label', 0.4, C.warn, 'asked Wed']] },
152
+ { kpis: [['2', 'waiting', C.warn], ['2 d', 'oldest', C.bad], ['82%', 'confidence', C.ok]], factsX: [['user', 'Dara, Lee'], ['out', 'Nudges go to Review']], move: mv('nu', 'Nudge both (drafted)', 82) });
153
+ const WK_KL = card('kl', 'Opportunity', 'Today is 1.5 h of work against 1.8 h of focus', 'Move the two lowest-priority items to tomorrow and the day fits.',
154
+ { kind: 'tiles', tiles: [['1.5 h', 'planned', C.acc], ['1.8 h', 'focus time', C.ok], ['0 h', 'over', C.bad]] },
155
+ { kpis: [['2', 'to move', C.warn], ['0', 'promises hurt', C.ok], ['72%', 'confidence', C.acc]], factsX: [['clock', 'Capacity is the constraint']], move: mv('mv', 'Move the two lowest to tomorrow', 72) });
156
+ const WK_KDN = card('kdn', 'Big win', '0 done so far today', 'Each one closed without a context switch.', { kind: 'ring', done: 0, total: 6, size: 96, cap: '0 closed', sub: '6 still open' },
157
+ { kpis: [['1', 'facts', C.acc]], factsX: [['check', 'Momentum is good']] });
158
+ const DAYS7 = (dots) => ['Thu 8', 'Fri 9', 'Mon 12', 'Tue 13', 'Wed 14', 'Thu 15', 'Fri 16'].map((d, i) => [d, dots.includes(i)]);
159
+ const WK_KWK = card('kwk', 'This week', '2 items land in the next 7 days', 'Nothing is overdue. The next one is a promise to a customer.', { kind: 'days', days: DAYS7([2, 3]) },
160
+ { eyebrow: { label: 'This week', icon: 'cal' }, kpis: [['2', 'facts', C.acc]], factsX: [['cal', 'Next: Mon 12 Oct']] });
161
+ const WK_KJB = card('kjb', 'Jobs', '4 jobs to be done, in progress', 'Work grouped by the outcome it serves, not by where it came from.',
162
+ { kind: 'bars', bars: [['Hold the 24 Oct date', 1 / 7, C.acc, '1 of 7'], ['Keep leadership informed', 0, C.acc, '0 of 1'], ['Learn why returns hurt', 0, C.acc, '0 of 4'], ['Keep customer promises', 0, C.acc, '0 of 1']] },
163
+ { eyebrow: { label: 'Jobs', icon: 'target' }, kpis: [['4', 'facts', C.acc]], factsX: [['target', 'JTBD']] });
164
+ const WK_KPR = card('kpr', 'Opportunity', 'A customer promise falls on Monday', 'Confirm the SSO date to Marta after the freeze. It is yours and dated.', { kind: 'days', days: DAYS7([2]) },
165
+ { kpis: [['86%', 'confidence', C.acc], ['2', 'options', C.opp], ['2', 'evidence', C.ok]], factsX: [['user', 'Marta'], ['flag', 'Keeps trust']], move: mv('td', 'Bring it into Today for Monday', 86) });
166
+ const WT_KAG = card('kag', 'Critical', 'Dara has been silent for 2 days', 'The sandbox keys block Checkout v2 and SSO billing. The nudge is drafted.',
167
+ { kind: 'bars', bars: [['Dara: Payments sandbox keys', 1, C.bad, 'asked Tue'], ['Lee: Annual-plan label', 0.4, C.warn, 'asked Wed']] },
168
+ { kpis: [['84%', 'confidence', C.acc], ['2', 'options', C.opp], ['2', 'evidence', C.ok]], factsX: [['user', 'Dara, Lee'], ['out', 'Nudges go to Review']], move: mv('nu', 'Nudge Dara now (drafted)', 84) });
169
+ const WT_KCH = card('kch', 'Opportunity', 'Chase two people in one step', 'Both nudges are drafted and polite. You send them.',
170
+ { kind: 'bars', bars: [['Dara: Payments sandbox keys', 1, C.bad, 'asked Tue'], ['Lee: Annual-plan label', 0.4, C.warn, 'asked Wed']] },
171
+ { kpis: [['82%', 'confidence', C.acc], ['2', 'options', C.opp], ['2', 'evidence', C.ok]], factsX: [['out', 'Goes to Review first']], move: mv('nu', 'Nudge both (drafted)', 82) });
172
+ const WT_KDP = card('kdp', 'Dependencies', '2 external dependencies', 'Two replies stand between you and the date.',
173
+ { kind: 'bars', bars: [['Payments sandbox keys', 1, C.bad, 'asked Tue'], ['Annual-plan label', 0.6, C.warn, 'asked Wed']] },
174
+ { eyebrow: { label: 'Dependencies', icon: 'wait' }, kpis: [['2', 'facts', C.acc]], factsX: [['init', 'Checkout v2']] });
175
+ const LT_KLP = card('klp', 'Opportunity', 'The Q1 discovery plan is ready to promote', 'Planning starts in November and the returns findings arrive this week.', { kind: 'days', days: DAYS7([2]) },
176
+ { kpis: [['68%', 'confidence', C.acc], ['2', 'options', C.opp], ['1', 'evidence', C.ok]], factsX: [['spark', 'Has a window now']], move: mv('pr', 'Promote to this week', 68) });
177
+ const LT_KLS = card('kls', 'Small win', 'Nothing is stale', 'No someday item is older than 30 days.', { kind: 'ring', done: 1, total: 1, size: 96, cap: 'All fresh', sub: 'reviewed monthly' },
178
+ { kpis: [['1', 'facts', C.acc]], factsX: [['check', 'Next review 1 Nov']] });
179
+ const WK_KEV = card('kev', 'Just in', '6 open at the end of the day', "Migrate, schedule or drop each one. Carry-overs lead tomorrow's briefing.",
180
+ { kind: 'rows', rows: [['moon', 'Cut Checkout v2 to 3 core flows to…', '', '2026-10-08'], ['moon', 'Reply to Marta: SSO 14 Oct, SCIM in…', '', '2026-10-08'], ['moon', 'Weekly status for leadership', '', '2026-10-08']] },
181
+ { eyebrow: { label: 'Evening review', icon: 'moon' }, kpis: [['6', 'open', C.acc]], factsX: [['moon', 'Evening review']], move: mv('mg', 'Migrate all to tomorrow', 80) });
182
+ /* the deck the Work tab asks for, by lens (scope "work:<lens>"); Close on Today adds the evening card first, as the mockup's S.tod === 'e' does */
183
+ const WORK_LENS = { triage: [WORK_KQ, WORK_KD, WORK_KA, WORK_KN, WORK_KJ], today: [WK_KO, WK_KR, WK_KW, WK_KL, WK_KDN], week: [WK_KWK, WK_KJB, WK_KPR], waiting: [WT_KAG, WT_KCH, WT_KDP], later: [LT_KLP, LT_KLS] };
184
+
185
+ /* Initiatives tab (the mockup's iniCards, Strategize) */
186
+ const INI_DIR = card('ydir', 'Year direction', 'North star is 4% behind and widening', 'It leaves tolerance in Dec \'26 and reaches the pivot line in Mar \'27.',
187
+ { kind: 'multi', lines: [{ pts: [10.4, 10.7, 10.9, 11.2, 11.6, 12.2, 12.6, 13.0, 13.4, 13.8, 14.2, 14.6, 15.0, 15.4, 15.8, 16.2, 16.6, 17.0], color: 'var(--ring)', width: 2, dash: '4 3' }, { pts: [10.3, 10.5, 10.6, 10.8, 10.9, 11.0, 11.1], color: 'var(--acc)', width: 3 },
188
+ { from: 6, pts: [11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9, 12.0, 12.1, 12.2], color: 'var(--warning)', width: 3, dash: '2 5' }], labels: [['now', 0.34, 'b'], ['plan', 1, 't'], ['at today\'s pace', 1, 'bw']] },
189
+ { eyebrow: { label: 'Year direction', icon: 'target' }, kpis: [['72%', 'confidence', C.acc], ['3', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['clock', '3 drifting'], ['eye', '4 on watch'], ['up', '1 upside']], pov: 'PRODUCT',
190
+ move: { suggested: { id: 'adj', label: 'Adjust: shift effort to Enterprise and Retain', conf: 72 }, options: [{ id: 'adj', label: 'Adjust: shift effort to Enterprise and Retain' }] } });
191
+ const INI_MAT = card('ymat', 'Maturity', 'Two pillars one stage short', 'Convert and Onboard decide whether the year lands.',
192
+ { kind: 'bars', bars: [['Convert', 0.6, C.warn, 'Prove -> Repeat'], ['Onboard', 0.4, C.warn, 'Discover -> Prove'], ['Enterprise', 1, C.ok, 'Scale'], ['Retain', 0.9, C.ok, 'Scale']] }, { factsX: [['flag', 'Target: September 2027']] });
193
+ const INI_SIG = card('ysig', 'Critical', 'Conversion is the earliest warning', 'Below 3.0% for 2 weeks is the pivot trigger.',
194
+ { kind: 'num', big: '2.9', unit: '%', delta: 'lands here at this slope', tone: C.bad, arr: [3.5, 3.4, 3.4, 3.3, 3.3, 3.2, 3.1], target: 4 }, { factsX: [['clock', 'About 2 weeks to the trigger'], ['alert', 'Cart to address loses 45%']] });
195
+
196
+ /* Reports tab (the mockup's repCards, Project / Leadership) */
197
+ const REP_LEAD = card('rp1', 'Critical', 'Friday\'s status says 31 Oct, you told them 24 Oct', 'The report leads with the answer and asks for one decision.',
198
+ { kind: 'rows', rows: [['alert', 'Forecast 31 Oct', 'bad', '+7 d'], ['scale', 'Ask: cut to 3 flows', 'warn', '14:00'], ['check', '0 of 6 sections approved', '', '']] },
199
+ { pov: 'LEADERSHIP', kpis: [['64%', 'confidence', C.acc], ['3', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['out', 'Send after the decision']],
200
+ move: { suggested: { id: 'r', label: 'Resolve the flagged sections first', conf: 64 }, options: [{ id: 'r', label: 'Resolve the flagged sections first' }] } });
201
+ const REP_NEXT = card('rp2', 'Opportunity', 'Leadership has not heard SSO is live', 'Lead the weekly status with it.',
202
+ { kind: 'rows', rows: [['flag', 'SSO beta live with 3 customers', 'ok', ''], ['out', 'Weekly status goes out Friday', '', '16:30']] }, { pov: 'LEADERSHIP', kpis: [['80%', 'confidence', C.ok]], factsX: [['flag', 'First enterprise milestone']] });
203
+
204
+ const unfilled = (c) => ({ ...c });
205
+ const PLAN_BRIEF = [DECISION, METRIC, DATE, INSIGHT, SSO].map(unfilled);
206
+ const BY_SCOPE = {
207
+ brief: PLAN_BRIEF,
208
+ cal: [CAL_DM, CAL_DD, CAL_DF, CAL_DL, CAL_DN],
209
+ work: [WORK_KQ, WORK_KD, WORK_KA, { ...METRIC, key: 'kj', tag: 'Just in', headline: 'Priya\'s request is the newest and has a clock', sub: 'The 1 Oct numbers, due 10:30. Two sources disagree, so the case file has the answer.' }, { ...SEARCH, key: 'kdn', tag: 'Big win', headline: '0 done so far today', sub: 'Each one closed without a context switch.' }],
210
+ init: [INI_DIR, INI_MAT, INI_SIG, { ...SSO, key: 'ysso' }, { ...BOARD, key: 'ybd' }],
211
+ rep: [REP_LEAD, REP_NEXT, { ...BOARD, key: 'rbd', pov: 'PRODUCT' }, { ...SEARCH, key: 'rsr' }, { ...SSO, key: 'rss' }],
212
+ };
213
+ const LENS_ORDER = {
214
+ Plan: ['decision', 'metric', 'date', 'insight', 'sso'],
215
+ Unblock: ['decision', 'insight', 'date', 'metric', 'board'],
216
+ Close: ['decision', 'date', 'sso', 'search', 'metric'],
217
+ Reflect: ['sso', 'search', 'board', 'metric', 'insight'],
218
+ };
219
+ const ALL = Object.fromEntries([DECISION, METRIC, DATE, INSIGHT, SSO, SEARCH, BOARD].map((c) => [c.key, c]));
220
+
221
+ /* the Needs-you list: the mockup's inbox rows (needs()), in its order */
222
+ const NEEDS = [
223
+ { key: 'i2', kind: 'reply', headline: 'Confirm the 1 Oct numbers before the 10:30 update', left: { text: '2h 18m left', pct: 0.3, tone: 'warn' }, p: 'P1', time: '10:30', who: 'P', src: 'Slack', icon: 'reply' },
224
+ { key: 'i6', kind: 'review', headline: 'Review the 13:00 interview guide', left: { text: '4h 48m left', pct: 0.55, tone: 'acc' }, p: 'P2', time: '13:00', branch: 'returns-guide', src: 'Agent', icon: 'eye' },
225
+ { key: 'i3', kind: 'decide', headline: 'Assign owners to 2 unowned workshop actions', p: 'P2', time: 'today', src: 'Image', icon: 'scale' },
226
+ { key: 'i4', kind: 'review', headline: 'Approve pricing-page copy before handoff', p: 'P2', time: 'Fri', who: 'L', old: true, src: 'Figma', icon: 'eye' },
227
+ { key: 'i5', kind: 'review', headline: 'Confirm the growth assumption in the Q4 forecast', p: 'P2', time: 'Fri', src: 'Sheets', icon: 'eye' },
228
+ { key: 'i7', quick: true, kind: 'reply', headline: 'Pick interview slots with 3 customers', p: 'P3', time: 'this week', who: 'M', src: 'Gmail', icon: 'reply' },
229
+ { key: 'i8', kind: 'decide', headline: 'Self-serve onboarding: continue or park?', p: 'P3', time: 'this week', src: 'Notes', icon: 'scale' },
230
+ { key: 'i15', quick: true, kind: 'reply', headline: 'Say yes to Sam: ship the logging change', p: 'P3', time: 'today', who: 'S', src: 'Slack', icon: 'reply' },
231
+ { key: 'i16', quick: true, kind: 'reply', headline: 'Accept the 15 Oct interview invite', p: 'P3', time: 'today', src: 'Gmail', icon: 'reply' },
232
+ ];
233
+
234
+ /* the mockup's Review drafts (S.drafts), "6 handled for you" (HX and the handled rows) and "Data you can lean on" (CF), as the Briefing draws them */
235
+ const REVIEW = { working: 1, skims: 6, rows: [
236
+ { id: 'd1', lvl: 0, rec: 'o', t: 'Reply to Marta: SSO 14 Oct, SCIM in Nov', out: true, to: 'M', toName: 'Marta · Customer success', n: 2 },
237
+ { id: 'd2', lvl: 0, rec: 'o', t: 'Weekly status for leadership', out: true, to: 'P', toName: 'Priya · VP Product', n: 4, old: 1, conflict: true, sess: true },
238
+ { id: 'd3', lvl: 2, rec: 'r', t: 'Prep note for 10:30 Checkout sync', n: 3 },
239
+ { id: 'd4', lvl: 2, rec: 'r', t: 'Close 6 duplicate tickets, assign 3 more', basis: 'Jira', sess: true },
240
+ { id: 'd5', lvl: 2, rec: 'r', t: "Summary of Tuesday's customer call", docs: ['doc'], basis: '1 call, a lead not a finding' },
241
+ { id: 'd6', lvl: 2, rec: 'r', t: 'Clean diagram of the whiteboard flow', docs: ['img'], basis: '1 image' },
242
+ { id: 'd7', lvl: 2, rec: 'r', t: 'Fix: set the PRD ship date to 24 Oct', docs: ['doc'], basis: 'Jira field wins' },
243
+ { id: 'd8', lvl: 2, rec: 'r', t: 'Fix: set the PRD baseline to 3.1%', docs: ['sheet'], basis: 'Analytics sheet wins' },
244
+ ] };
245
+ const HANDLED = { note: 'nothing needs a look', rows: [
246
+ { t: 'Prepared 5 meetings: agenda, context and pre-read drafts', d: ['Agenda, context and pre-read for 5 meetings. Nothing was sent.', 'Playbook: meeting prep · auto · 14 days, 0 reverts', '12 sources read, 2 older than 3 days and marked', 'Delete the drafts. No one saw them.'] },
247
+ { t: 'Archived 3 FYI items and attached context to 9 others', d: ['Archived 3 FYI items and linked context to 9 others.', 'Playbook: inbox triage · auto · 9 days, 1 priority note', 'Sender and subject matched your past archives', 'Restore from Archive, one tap each.'] },
248
+ { t: 'Marked the PRD draft done: PR merged 6 Oct', d: ['Marked the PRD task done after its PR merged.', 'Rule: a merged PR closes its task · auto', 'Merged 6 Oct, CI green', 'Reopen the task. The PR is untouched.'] },
249
+ { t: 'Checked 10 sources: 9 fresh, 1 old', d: ['Checked 10 sources: 9 fresh, 1 old.', 'Playbook: source check · auto · read-only', 'Figma was last read Tue 17:10', 'Nothing to undo. Nothing was changed.'] },
250
+ { t: "Re-ranked your list after Priya's 07:58 message", d: ['Re-ranked your list after a message from Priya.', 'Rule: a person with a deadline raises priority · auto', 'Priya, 07:58: needs numbers before 10:30', 'Ranking returns to the earlier order.'] },
251
+ { t: 'Held 3 interview slots that avoid your standing meetings', d: ['Held 3 interview slots that avoid your standing meetings.', 'Playbook: scheduling · approve mode · held, not accepted', 'No overlaps; 2 alternates kept', 'Release the holds. Marta is not told.'] },
252
+ ] };
253
+ const TRUST = { fresh: '9 of 10 sources fresh', stale: 'Figma was last read Tue 17:10. Its items are marked old.', items: [
254
+ { fact: 'Checkout v2 ship date', win: 'Jira 24 Oct', lose: 'PRD 31 Oct' }, { fact: 'Conversion baseline', win: 'Analytics sheet 3.1%', lose: 'PRD 3.4%' }] };
255
+ export function briefing(url) {
256
+ const q = new URL(url, location.origin).searchParams;
257
+ const lens = ['Plan', 'Unblock', 'Close', 'Reflect'].includes(q.get('lens')) ? q.get('lens') : 'Plan';
258
+ const scope = q.get('scope') || 'brief';
259
+ let cards = (BY_SCOPE[scope] || PLAN_BRIEF);
260
+ const wl = /^work:(\w+)$/.exec(scope || '');
261
+ if (wl && WORK_LENS[wl[1]]) { cards = WORK_LENS[wl[1]].slice(); if (wl[1] === 'today' && lens === 'Close') cards = [WK_KEV, ...cards].slice(0, 5); }
262
+ if (scope === 'brief') cards = LENS_ORDER[lens].map((k) => ALL[k]);
263
+ else if (lens !== 'Plan' && !wl) cards = cards.slice().reverse();
264
+ return { ok: true, lens, zoom: q.get('zoom') || 'week', cards: cards.map((c) => ({ ...c })), ticker: [], banner: null, unread: 2, kept_out: scope === 'brief' ? '3 FYI · raised 1 from a person with a deadline' : undefined, review: scope === 'brief' ? REVIEW : undefined, handled: scope === 'brief' ? HANDLED : undefined, trust: scope === 'brief' ? TRUST : undefined, foot: scope === 'brief' ? { left: 'used as is 68% · P1 missed 0', right: 'drag a row → done, ← later · tap to open · j k move' } : undefined, needs: scope === 'brief' ? NEEDS : undefined };
265
+ }
266
+
267
+ /* ── the calendar ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── */
268
+
269
+ const MEET = [
270
+ { id: 'm1', t: 'Standup', s: 9.5, e: 10, kind: 'ready', routine: true },
271
+ { id: 'm2', t: 'Checkout sync', s: 10.5, e: 11.25, kind: 'prep ready' },
272
+ { id: 'm3', t: 'Customer interview', s: 13, e: 14, kind: 'review prep' },
273
+ { id: 'm4', t: 'Sprint planning', s: 14, e: 15, kind: 'blocked on your decision', hot: true, decide_by: '14:00' },
274
+ { id: 'm5', t: 'Leadership update', s: 16.5, e: 17, kind: 'ready' },
275
+ ];
276
+ const hh = (x) => pad(Math.floor(x)) + ':' + pad(Math.round((x % 1) * 60));
277
+ const ev = (date, m) => ({ id: m.id + '@' + date, title: m.t, at: date + 'T' + hh(m.s) + ':00', end: date + 'T' + hh(m.e) + ':00', kind: m.kind, ...(m.routine ? { mock_routine: true } : {}), ...(m.hot ? { mock_hot: true, mock_decide_by: m.decide_by } : {}) });
278
+ function eventsOn(date) {
279
+ const dow = new Date(utcOf(date)).getUTCDay();
280
+ if (date === TODAY) return MEET.map((m) => ev(date, m));
281
+ if (dow === 0 || dow === 6) return [];
282
+ const base = [{ id: 'm1', t: 'Standup', s: 9.5, e: 10, kind: 'ready', routine: true }];
283
+ if (dow === 1 || dow === 3) base.push({ id: 'm2', t: 'Checkout sync', s: 10.5, e: 11.25, kind: 'ready' });
284
+ if (dow === 2) base.push({ id: 'm3', t: 'Customer interview', s: 13, e: 14, kind: 'ready' });
285
+ if (dow === 5) base.push({ id: 'm5', t: 'Weekly status to leadership', s: 16, e: 16.5, kind: 'ready' });
286
+ return base.map((m) => ev(date, m));
287
+ }
288
+ function rangeOf(view, date) {
289
+ const [y, m, d] = ymd(date);
290
+ const iso = (t) => t.toISOString().slice(0, 10);
291
+ if (view === 'day') return { start: date, end: date, unit: 'day' };
292
+ if (view === 'week') { const o = (new Date(Date.UTC(y, m - 1, d)).getUTCDay() + 6) % 7; const s = addDays(date, -o); return { start: s, end: addDays(s, 6), unit: 'day' }; }
293
+ if (view === 'month') return { start: iso(new Date(Date.UTC(y, m - 1, 1))), end: iso(new Date(Date.UTC(y, m, 0))), unit: 'day' };
294
+ if (view === 'quarter') { const q0 = Math.floor((m - 1) / 3) * 3; return { start: iso(new Date(Date.UTC(y, q0, 1))), end: iso(new Date(Date.UTC(y, q0 + 3, 0))), unit: 'week' }; }
295
+ return { start: y + '-01-01', end: y + '-12-31', unit: 'month' };
296
+ }
297
+ const ROWS = [
298
+ { id: 'checkout', pulse: { c: 'red', why: 'Forecast Oct 31 vs promised Oct 24 (+7 days)' }, title: 'Checkout v2', planned: { start: dOf(17), end: dOf(40) }, forecast: { start: dOf(17), end: dOf(47) }, promised: dOf(40), status: 'delayed', slip_days: 7, tolerance_days: 2 },
299
+ { id: 'sso', pulse: { c: 'amber', why: '3 of 8 release checks open, 6 days to go' }, title: 'SSO', planned: { start: dOf(1), end: dOf(30) }, forecast: { start: dOf(1), end: dOf(30) }, promised: dOf(30), status: 'on time', slip_days: 0, tolerance_days: 2 },
300
+ { id: 'search', pulse: { c: 'amber', why: 'EN-418 blocked on platform, no reply since Tue' }, title: 'Search relevance', planned: { start: dOf(6), end: dOf(46) }, forecast: { start: dOf(6), end: dOf(46) }, promised: dOf(46), status: 'at risk', slip_days: 0, tolerance_days: 2 },
301
+ { id: 'returns', pulse: { c: 'green', why: 'Learning on schedule. Stop rule has a review date.' }, title: 'Returns experience', planned: { start: dOf(10), end: dOf(31) }, forecast: { start: dOf(10), end: dOf(31) }, promised: dOf(31), status: 'on time', slip_days: 0, tolerance_days: 2 },
302
+ { id: 'onb', pulse: { c: 'park', why: 'No movement in 9 days. Stop rule found no signals. Continue or park?' }, title: 'Self-serve onboarding', planned: null, forecast: null, promised: null, status: 'no plan', slip_days: 0, tolerance_days: null },
303
+ ];
304
+ const KRS = [
305
+ { kr: 'Conversion', metric: 'Checkout conversion, %', expected: 3.75, actual: 3.1, shortfall: 0.17, early_warning: true },
306
+ { kr: 'Retention, day 30', metric: 'Day-30 retention, %', expected: 42.9, actual: 41.5, shortfall: 0.1, early_warning: true },
307
+ { kr: 'Enterprise design partners', metric: 'Partners live', expected: 3, actual: 3, shortfall: 0, early_warning: false },
308
+ { kr: 'Returns discovery decided', metric: 'Interviews done, of 5', expected: 2.5, actual: 2, shortfall: 0.08, early_warning: false },
309
+ ];
310
+ /* the mockup's dated events beyond today: promises, interviews, releases, reviews (its S.items, TIX and SIGS tables) */
311
+ const MILESTONES = [
312
+ ['2026-10-09', 'Copy freeze (Lee)'], ['2026-10-09', 'Weekly status to leadership, 15:00'], ['2026-10-12', 'SSO freeze'], ['2026-10-12', 'Confirm the SSO date to Marta'], ['2026-10-13', 'Interview 5: park or continue Returns'],
313
+ ['2026-10-14', 'SSO general release'], ['2026-10-15', 'Customer interview (accepted invite)'], ['2026-10-16', 'Next update to leadership'], ['2026-10-21', 'A11y audit slot, if scope is cut'], ['2026-10-24', 'Ship Checkout v2 (promised)'],
314
+ ['2026-10-28', 'A11y audit slot, full scope'], ['2026-10-31', 'Checkout v2 forecast on the full scope'], ['2026-11-06', 'Search relevance beta'], ['2026-11-16', 'Q1 discovery plan'], ['2026-12-14', 'Board update review'], ['2026-12-20', 'Quarterly review, board update sends'],
315
+ ];
316
+ const milestonesIn = (a, b) => MILESTONES.filter(([d]) => d >= a && d <= b).map(([d, t], i) => ({ id: 'ms-' + d + '-' + i, title: t, at: d + 'T10:00:00', end: d + 'T11:00:00', kind: 'milestone' }));
317
+ /* the mockup's STRAT (console3.html line 1677): the year pane (a row per pillar, a column per quarter) and the five-year pane (pillar by year) */
318
+ const MOCK_WEEK = [{ d: 'Thu 8', v: 3.8, lk: 3.3, mk: [] }, { d: 'Fri 9', v: 2.5, lk: 1, mk: [['copy', 'Copy freeze']] }, { d: 'Mon 12', v: 3, lk: 2, mk: [['promise', 'SSO date to Marta']] },
319
+ { d: 'Tue 13', v: 2.5, lk: 2, mk: [['learn', 'Interview 5: review the stop rule']] }, { d: 'Wed 14', v: 2.5, lk: 2, mk: [['ship', 'SSO release']] }, { d: 'Thu 15', v: 1.5, lk: 1, mk: [] }, { d: 'Fri 16', v: 2.5, lk: 1, mk: [['report', 'Leadership weekly']] }];
320
+ const MOCK_YEAR = { quarters: ["Q4 '26", "Q1 '27", "Q2 '27", "Q3 '27"], pillars: [
321
+ { n: 'Convert', icon: 'target', cells: [[['Checkout v2', 'bad'], ['Search relevance', 'warn']], [['Checkout v2.1: saved cards', null]], [['Wallet payments discovery', null]], [['Payment methods build', null]]] },
322
+ { n: 'Retain', icon: 'repo', cells: [[['Returns discovery', 'ok']], [['Returns label printing', null]], [['Returns build', null]], [['Exchanges discovery', null]]] },
323
+ { n: 'Onboard', icon: 'user', cells: [[['Continue or park?', 'park']], [['Onboarding discovery, if kept', null]], [['Guided setup build', null]], []] },
324
+ { n: 'Enterprise', icon: 'shield', cells: [[['SSO release, SCIM', 'warn']], [['Audit logs discovery', null]], [['Audit logs build', null]], [['Roles', null]]] },
325
+ ] };
326
+ const MOCK_FIVE = { years: ['Y1', 'Y2', 'Y3', 'Y4', 'Y5'], pillars: [
327
+ { n: 'Convert', icon: 'target', cols: [['2', 'Checkout v2, Search relevance'], ['', 'Payment methods and wallets'], ['', 'One-page checkout in every channel'], ['', 'Checkout as an embeddable platform'], ['', 'Checkout that adapts to each shopper']] },
328
+ { n: 'Retain', icon: 'repo', cols: [['1', 'Returns experience'], ['', 'Exchanges and store credit'], ['', 'A post-purchase hub'], ['', 'Returns prevention'], ['', 'Post-purchase that prevents returns']] },
329
+ { n: 'Onboard', icon: 'user', cols: [['1', 'Self-serve onboarding'], ['', 'Guided setup for new merchants'], ['', 'Templates by industry'], ['', 'Zero-touch setup'], ['', 'Setup finished before the first call']] },
330
+ { n: 'Enterprise', icon: 'shield', cols: [['1', 'SSO'], ['', 'Audit logs and roles'], ['', 'Multi-region'], ['', 'Compliance suite'], ['', 'An enterprise marketplace']] },
331
+ ] };
332
+ export function calendarViews(url) {
333
+ const q = new URL(url, location.origin).searchParams;
334
+ const view = ['day', 'week', 'month', 'quarter', 'year'].includes(q.get('view')) ? q.get('view') : 'week';
335
+ const date = /^\d{4}-\d{2}-\d{2}$/.test(q.get('date') || '') ? q.get('date') : TODAY;
336
+ const r = rangeOf(view, date);
337
+ const cells = [];
338
+ const dayEvents = (d) => [...eventsOn(d), ...milestonesIn(d, d)].filter((e, i, a) => a.findIndex((x) => x.title === e.title && x.at === e.at) === i);
339
+ if (r.unit === 'day') for (let d = r.start; d <= r.end; d = addDays(d, 1)) cells.push({ key: d, start: d, end: d, events: dayEvents(d) });
340
+ else if (r.unit === 'week') {
341
+ const o = (new Date(utcOf(r.start)).getUTCDay() + 6) % 7;
342
+ for (let s = addDays(r.start, -o); s <= r.end; s = addDays(s, 7)) cells.push({ key: s, start: s, end: addDays(s, 6), events: [...(s <= TODAY && TODAY <= addDays(s, 6) ? MEET.map((m) => ev(TODAY, m)) : []), ...milestonesIn(s, addDays(s, 6))] });
343
+ } else {
344
+ const y = ymd(date)[0];
345
+ for (let m = 1; m <= 12; m++) { const s = y + '-' + pad(m) + '-01'; const e = new Date(Date.UTC(y, m, 0)).toISOString().slice(0, 10); cells.push({ key: y + '-' + pad(m), start: s, end: e, events: milestonesIn(s, e) }); }
346
+ }
347
+ const days = Math.round((utcOf(r.end) - utcOf(r.start)) / DAY) + 1;
348
+ const strat = view === 'quarter' || view === 'year' ? {
349
+ north_star: { progress: 0.62, scored: 4, total: 4, initiatives: { total: 5, done: 1 } },
350
+ deviations: KRS.filter((k) => k.actual != null), unscored: [],
351
+ pivot: { signal: false, reason: 'A pivot needs 3 checks behind the line and not closing. Conversion has fallen six weeks in a row: one more check behind the line makes it a signal.' }, reason: null,
352
+ } : null;
353
+ const day = view === 'day' && date === TODAY;
354
+ const scope = q.get('scope');
355
+ return { ok: true, view, range: { view, date, start: r.start, end: r.end, days }, unit: r.unit, cells, gantt: ROWS.map((g) => ({ ...g })), strategy: strat, events_source: 'calendar-memory', today: TODAY,
356
+ mock_marks: view === 'quarter' ? { dates: ['2026-12-10', '2026-12-20'], label: 'board review and update' } : undefined,
357
+ mock_week: view === 'week' ? MOCK_WEEK : undefined,
358
+ ribbon_heads: { week: { i: 'cal', h: 'Thu is the heaviest day: 3.8 h of meetings', s: '12 of 18 h are tied to a promise or a decision. The rest is routine.', ring: [12, 18] },
359
+ quarter: { i: 'init', h: '1 red, 2 amber, 1 green. Checkout v2 swings the quarter', s: 'Promised dates against forecast. Tap a row to open the initiative.', ring: [1, 4] },
360
+ year: { i: 'target', h: 'Enterprise carries the most bets in Q1 and Q2', s: 'Q4: 2 of 5 delivered. Tap a dot to open the initiative.', ring: [2, 5] },
361
+ five: { i: 'flag', h: 'Direction, not a plan', s: '12.4k to 120k weekly accounts by 2031. Detail fades after year one on purpose.', ring: [1, 5] } },
362
+ mock_year: view === 'year' ? MOCK_YEAR : undefined, mock_five: view === 'year' ? MOCK_FIVE : undefined,
363
+ span: day ? [8, 17.5] : null,
364
+ free_gaps: day ? [[11.25, 13, '1.8 h free'], [15, 16.5, '1.5 h free']] : null,
365
+ lens_head: day ? (scope === 'cal' ? { h: 'Sprint planning at 14:00 matters most', s: '5 meetings, 3.8 h. Best focus window: 1.8 h from 11:15.', ring: [0, 5] }
366
+ : { h: 'Sprint planning at 14:00 matters most', s: 'It cannot start until you decide. 3 of 5 meetings are prepped.', ring: [3, 5] }) : null,
367
+ selected: day ? { id: 'm4', title: 'Sprint planning', time: '14:00 to 15:00', text: 'Checkout v2: the sprint cannot start until scope is decided. The 24 Oct date depends on it.', status: 'Blocked on your decision', tone: 'bad', who: ['D', 'L', 'S'] } : null };
368
+ }
369
+
370
+ /* ── work items (the mockup's S.items by place and bucket) and the four jobs ───────────────────────────────────────────────────── */
371
+
372
+ const JOBS = [
373
+ { id: 'j1', when: 'the scope is too big for the date', want: 'a decision fast and a clear plan', so: 'we ship on 24 Oct', mock_name: 'Hold the 24 Oct date', done: 1, total: 7 },
374
+ { id: 'j2', when: 'leadership asks for numbers', want: 'one answer that is true', so: 'their picture stays accurate', mock_name: 'Keep leadership informed', done: 0, total: 1 },
375
+ { id: 'j3', when: 'customers return items', want: 'to see where it hurts', so: 'we fix the right thing', mock_name: 'Learn why returns hurt', done: 0, total: 4 },
376
+ { id: 'j4', when: 'I promise a customer a date', want: 'to keep it or tell them early', so: 'trust holds', mock_name: 'Keep customer promises', done: 0, total: 1 },
377
+ ];
378
+ let seq = 0;
379
+ const it = (title, o) => ({ title, due: null, priority: null, waiting_on: null, job: null, id: 'item-demo' + String(++seq).padStart(3, '0'), source: 'local', created_at: '2026-10-08T05:00:00.000Z', steps: [], mock_row: true, ...(o || {}) });
380
+ const TRIAGE = [
381
+ it('Confirm the 1 Oct numbers before the 10:30 update', { kind: 'reply', priority: 'P1', time: '10:30', mins: 5, job: 'j2', src: 'Slack', init: 'Checkout v2', agent_move: true, left: { text: '2h 18m left', pct: 0.3, tone: 'warn' } }),
382
+ it('Assign owners to 2 unowned workshop actions', { kind: 'decide', priority: 'P2', time: 'today', mins: 5, job: 'j1', src: 'Image', init: 'Checkout v2', agent_move: true }),
383
+ it('Approve pricing-page copy before handoff', { kind: 'review', priority: 'P2', time: 'Fri', mins: 5, job: 'j1', src: 'Figma', init: 'Checkout v2', agent_move: true }),
384
+ it('Review the 13:00 interview guide', { kind: 'review', priority: 'P2', time: '13:00', mins: 5, job: 'j3', src: 'Agent', init: 'Returns experience', agent_move: true, left: { text: '4h 48m left', pct: 0.55, tone: 'acc' } }),
385
+ it('Confirm the growth assumption in the Q4 forecast', { kind: 'review', priority: 'P2', time: 'Fri', mins: 10, job: 'j1', src: 'Sheets', init: 'Checkout v2', agent_move: true }),
386
+ it('Say yes to Sam: ship the logging change', { kind: 'reply', priority: 'P3', time: 'today', mins: 1, src: 'Slack', init: 'Search relevance', agent_move: true }),
387
+ it('Accept the 15 Oct interview invite', { kind: 'reply', priority: 'P3', time: 'today', mins: 1, job: 'j3', src: 'Gmail', init: 'Returns experience' }),
388
+ it('Pick interview slots with 3 customers', { kind: 'reply', priority: 'P3', time: 'this week', mins: 2, job: 'j3', src: 'Gmail', init: 'Returns experience', agent_move: true }),
389
+ it('Self-serve onboarding: continue or park?', { kind: 'decide', priority: 'P3', time: 'this week', mins: 15, src: 'Notes', init: 'Self-serve onboarding', agent_move: true }),
390
+ ];
391
+ const TODAYL = [
392
+ it('Cut Checkout v2 to 3 core flows to hold 24 Oct?', { due: TODAY, priority: 'P1', kind: 'decide', time: '14:00', mins: 15, job: 'j1', src: 'Confluence', init: 'Checkout v2', left: { text: '5h 48m left', pct: 0.36, tone: 'acc' } }),
393
+ it('Reply to Marta: SSO 14 Oct, SCIM in Nov', { due: TODAY, priority: 'P2', kind: 'reply', mins: 5, job: 'j4', src: 'Gmail', init: 'SSO' }),
394
+ it('Weekly status for leadership', { due: TODAY, priority: 'P2', kind: 'review', mins: 10, job: 'j2', src: 'Slack', init: 'Checkout v2' }),
395
+ it('Prep note for 10:30 Checkout sync', { due: TODAY, kind: 'review', mins: 5, job: 'j1', src: 'Agent', init: 'Checkout v2' }),
396
+ it('Close 6 duplicate tickets, assign 3 more', { due: TODAY, kind: 'do', mins: 5, job: 'j3', src: 'Jira', init: 'Search relevance' }),
397
+ it('Clean diagram of the whiteboard flow', { due: TODAY, kind: 'review', mins: 5, job: 'j1', src: 'Image', init: 'Checkout v2' }),
398
+ ];
399
+ const WEEK = [
400
+ it('Confirm the SSO date to Marta', { due: '2026-10-12', priority: 'P2', job: 'j4', kind: 'reply', src: 'Jira', init: 'SSO' }),
401
+ it('Share returns insights with sales', { due: '2026-10-13', job: 'j3', kind: 'do', src: 'Notes', init: 'Returns experience' }),
402
+ ];
403
+ const WAITING = [
404
+ it('Payments sandbox keys', { waiting_on: 'Dara', due: '2026-10-06', job: 'j1', priority: 'P2', kind: 'do', src: 'Jira', init: 'Checkout v2' }),
405
+ it('Annual-plan label', { waiting_on: 'Lee', due: '2026-10-07', job: 'j1', priority: 'P3', kind: 'do', src: 'Figma', init: 'Checkout v2' }),
406
+ ];
407
+ const LATER = [it('Write the Q1 discovery plan', { due: '2026-11-16', kind: 'do', src: 'You' })];
408
+ export function pmItems() {
409
+ const lists = { triage: TRIAGE, today: TODAYL, week: WEEK, waiting: WAITING, later: LATER };
410
+ const due_today = [
411
+ { id: 'i9', title: 'Payments sandbox keys', sub: 'asked Tue · Checkout v2', right: '2 d late', tone: 'bad' },
412
+ { id: 'i2', title: 'Confirm the 1 Oct numbers before the 10:30 update', sub: '10:30 · Checkout v2', right: '2h 18m left', tone: 'warn', left: { text: '2h 18m left', pct: 0.3, tone: 'warn' } },
413
+ { id: 'i1', title: 'Cut Checkout v2 to 3 core flows to hold 24 Oct?', sub: '14:00 · Checkout v2', right: 'At risk', tone: 'warn', left: { text: '5h 48m left', pct: 0.36, tone: 'acc' } },
414
+ { id: 'i6', title: 'Review the 13:00 interview guide', sub: '13:00 · Returns experience', right: '4h 48m left', tone: 'warn', left: { text: '4h 48m left', pct: 0.55, tone: 'acc' } },
415
+ ];
416
+ return { ok: true, today: TODAY, lists, jobs: JOBS, items: Object.values(lists).flat(), due_today,
417
+ flow_note: 'The agent clarified every item before you saw it. Your part is the commit.',
418
+ noise: [{ title: 'Weekly product newsletter', src: 'Gmail' }, { title: 'Jira digest: 14 updates', src: 'Jira' }, { title: 'Figma: 3 files updated', src: 'Figma' }],
419
+ flow: [['Captured', 12, 'var(--muted-foreground)'], ['Clarified', 9, 'var(--acc)'], ['Committed', 1, 'var(--warning)'], ['Done', 0, 'var(--success)']],
420
+ lens_text: { triage: ['9 to triage, 3 take under 2 minutes', 'Nothing stays past 17:30. The agent has read all of it and drafted 3 replies.'] } };
421
+ }
422
+
423
+ /* ── initiatives (the roadmap rows the Initiatives tab reads), forecast, releases ──────────────────────────────────────────────── */
424
+
425
+ const NINE = ['discover', 'ideate', 'strategize', 'prioritize', 'plan', 'build', 'deliver', 'monitor', 'evaluate'];
426
+ const NL = { discover: 'Discover', ideate: 'Ideate', strategize: 'Strategize', prioritize: 'Prioritize', plan: 'Plan', build: 'Build', deliver: 'Deliver', monitor: 'Monitor', evaluate: 'Evaluate' };
427
+ function nine(cur, done, skip) { return NINE.map((k) => ({ key: k, label: NL[k], status: skip.includes(k) ? 'skipped' : k === cur ? 'current' : done.includes(k) ? 'done' : NINE.indexOf(k) > NINE.indexOf(cur) ? 'pending' : 'no-signal' })); }
428
+ const row = (id, title, lane, anchor, horizon, stageId, curStage, doneS, skip, health, pr) => ({ id, title, lane, anchor, anchor_kind: anchor.startsWith('health') ? 'health' : 'kr', horizon, rank: 0, confirmed: true, dormant: false, check_by: null,
429
+ stage: { id: stageId, label: stageId }, display: { word: curStage, stage: stageId, waitingOn: null, act: null, idleDays: 0, line2: '' }, health: { level: health, reasons: [] }, progress: { done: pr[0], total: pr[1], label: pr[0] + ' of ' + pr[1] },
430
+ next_due: null, last_activity: '2026-10-07T16:00:00.000Z', nine_stages: nine(curStage, doneS, skip) });
431
+ const ROADMAP = [
432
+ row('checkout', 'Checkout v2', 'feature', 'KR1', 'now', 'build', 'build', ['discover', 'ideate', 'strategize', 'prioritize', 'plan'], [], 'at-risk', [1, 7]),
433
+ row('sso', 'SSO', 'feature', 'KR3', 'now', 'handoff', 'deliver', ['discover', 'ideate', 'strategize', 'prioritize', 'plan', 'build'], [], 'on', [3, 8]),
434
+ row('search', 'Search relevance', 'feature', 'health:search', 'now', 'build', 'monitor', ['discover', 'ideate', 'strategize', 'prioritize', 'plan', 'build', 'deliver'], [], 'at-risk', [3, 8]),
435
+ row('returns', 'Returns experience', 'discovery', 'KR2', 'now', 'intake', 'discover', [], ['plan', 'build', 'deliver', 'monitor'], 'on', [2, 5]),
436
+ row('onb', 'Self-serve onboarding', 'discovery', 'KR2', 'later', 'intake', 'discover', [], ['plan', 'build', 'deliver', 'monitor'], 'at-risk', [0, 1]),
437
+ ];
438
+ /* the mockup's north star (NSA, nsPlan, nsProj), early signals (SIGS) and maturity table (MATP): the Strategize lens of Lifecycle */
439
+ const NSA = [9.4, 9.8, 10.2, 10.5, 10.9, 11.2, 11.5, 11.7, 11.9, 12.1, 12.15, 12.28, 12.4];
440
+ const nsPlan = (i) => (i <= 12 ? 9.4 + (12.9 - 9.4) * (i / 12) : 12.9 + (18 - 12.9) * ((i - 12) / 11));
441
+ const nsSlope = (NSA[12] - NSA[9]) / 3;
442
+ const nsProj = (i) => NSA[12] + nsSlope * (i - 12);
443
+ const nsCross = (f) => { for (let i = 13; i <= 23; i++) if (nsProj(i) < nsPlan(i) * f) return i; return -1; };
444
+ const MNS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
445
+ const mlab = (i) => MNS[(i + 9) % 12] + ' \'' + (25 + Math.floor((i + 9) / 12));
446
+ function strategy() {
447
+ const plan = []; const proj = []; for (let i = 0; i <= 23; i++) { plan[i] = nsPlan(i); proj[i] = nsProj(i); }
448
+ const months = []; for (let i = 6; i <= 23; i += 3) months.push([i, mlab(i)]);
449
+ return {
450
+ head: { h: 'North star 4% behind plan, gap widening', s: 'Leaves tolerance in ' + mlab(nsCross(0.92)) + '. Two of four pillars on course for September.', ring: [2, 4] },
451
+ north: { i0: 6, i1: 23, now: 12, min: 8, max: 19, ticks: [10, 14, 18], actual: NSA, plan, proj, leaves: nsCross(0.92), pivot: nsCross(0.85), months, sub: '4% against plan today' },
452
+ signals: [
453
+ { sev: 'drift', t: 'Conversion has fallen six weeks in a row', ev: '3.5% to 3.1%. The address step loses 45% of carts.', thr: 'Below 3.0% for 2 weeks: pivot the checkout bet', ttt: 'about 2 weeks', sp: [3.5, 3.4, 3.4, 3.3, 3.3, 3.2, 3.1] },
454
+ { sev: 'drift', t: 'Retention is flat while the top pain is returns', ev: 'Day 30 at 41.5% against 45%. 3 of 3 calls name label printing.', thr: 'No lift after interview 5: pull Returns forward', ttt: '5 days', sp: [41, 41.2, 41.4, 41.3, 41.5, 41.4, 41.5] },
455
+ { sev: 'drift', t: 'Checkout v2 critical path is 7 days late', ev: 'Sandbox keys and the a11y slot move the ship date from 24 to 31 Oct.', thr: 'More than 5 days late: warn leadership', ttt: 'tripped', sp: [0, 0, 2, 4, 7, 7] },
456
+ { sev: 'watch', t: 'Scope keeps changing on Checkout v2', ev: 'Scope changed 3 times in 4 weeks.', thr: 'A fourth change: re-baseline the plan', ttt: 'next change', sp: [1, 1, 2, 2, 3, 3, 3] },
457
+ { sev: 'up', t: 'Enterprise pull is stronger than planned', ev: '3 design partners live. 2 more asked for SCIM early.', thr: '5 partners by Dec: make Enterprise a Q1 bet', ttt: 'on course', sp: [0, 0, 1, 1, 2, 3, 3] },
458
+ ],
459
+ maturity: { stages: ['Discover', 'Prove', 'Repeat', 'Scale', 'Lead'], pillars: [
460
+ { n: 'Convert', now: 2, tgt: 4, proj: 3, ev: '3 of 7 criteria met', nx: 'Conversion at 3.8% or more for 4 weeks' }, { n: 'Retain', now: 2, tgt: 3, proj: 3, ev: '2 of 6 criteria met', nx: 'Returns contact rate at 0.5' },
461
+ { n: 'Onboard', now: 1, tgt: 2, proj: 1, ev: '0 of 5 criteria met', nx: 'a revived onboarding bet' }, { n: 'Enterprise', now: 2, tgt: 3, proj: 3, ev: '4 of 6 criteria met', nx: '5 design partners live' }] },
462
+ };
463
+ }
464
+ export function roadmap(ws) {
465
+ return { ok: true, ws, view: 'roadmap', sig: { placements: 'none', items: 'none', measurements: 'none', records: 'demo' }, now: NOW_ISO + 'Z', rules: { stall_days: 5, dormant_days: 45, report_cycle_days: 14, now_cap: null },
466
+ header: { now: { count: 4, kr: 4, health: 0, cap: null }, next: { count: 0 }, later: { count: 1, dormant: 0 } }, horizons: { now: ROADMAP.filter((r) => r.horizon === 'now'), next: [], later: ROADMAP.filter((r) => r.horizon === 'later') },
467
+ dormant: [], proposals: [], flags: [], scan: null, unrecorded: [], strategy: strategy() };
468
+ }
469
+
470
+ /* the mockup's TIX, by initiative: start and end are day numbers (14 Sep = 0). */
471
+ const TIX = [
472
+ ['CHK-139', 'Pricing page copy', 'checkout', 'done', 1, 7, []], ['CHK-141', 'Payments sandbox keys', 'checkout', 'doing', 3, 10, [], 2], ['CHK-142', 'Saved-card removal', 'checkout', 'todo', 11, 17, ['CHK-141']],
473
+ ['CHK-143', 'Address autocomplete', 'checkout', 'todo', 11, 20, ['CHK-141']], ['CHK-144', 'A11y audit slot', 'checkout', 'todo', 21, 24, ['CHK-142'], 0, 28], ['CHK-145', 'Ship Checkout v2', 'checkout', 'todo', 24, 24, ['CHK-144', 'CHK-143'], 0, 0, true],
474
+ ['EN-418', 'Index job fix', 'search', 'doing', 2, 12, [], 3], ['EN-421', 'Ranking weights v2', 'search', 'todo', 13, 24, ['EN-418']], ['EN-425', 'Relevance beta', 'search', 'todo', 25, 30, ['EN-421'], 0, 0, true],
475
+ ['RET-11', 'Interview 3', 'returns', 'doing', 8, 8, []], ['RET-12', 'Interview 4 slot', 'returns', 'todo', 9, 12, []], ['RET-13', 'Interview 5', 'returns', 'todo', 13, 13, ['RET-12']], ['RET-14', 'Synthesis and stop rule', 'returns', 'todo', 14, 15, ['RET-13'], 0, 0, true],
476
+ ['SSO-32', 'Beta feedback round', 'sso', 'done', 1, 6, []], ['SSO-31', 'SCIM docs', 'sso', 'doing', 3, 12, []], ['SSO-33', 'Confirm date to Marta', 'sso', 'todo', 12, 12, []], ['SSO-34', 'SSO general release', 'sso', 'todo', 14, 14, ['SSO-31', 'SSO-33'], 0, 0, true],
477
+ ['ONB-2', 'Continue or park decision', 'onb', 'todo', 8, 12, []],
478
+ ];
479
+ const TD = 24;
480
+ export function forecast(initiative) {
481
+ const rows = TIX.filter((t) => t[2] === initiative);
482
+ const P = {};
483
+ const items = rows.map(([k, title, , state, s, e, dep, slip, slot, ms]) => {
484
+ let fs = s; let fe = e; let dm = -1; let st;
485
+ if (state === 'done') { P[k] = { fe: e }; st = 'on time'; fs = s; fe = e; }
486
+ else {
487
+ dep.forEach((d) => { if (P[d]) dm = Math.max(dm, P[d].fe); });
488
+ const dur = e - s;
489
+ if (slot) fs = Math.max(fs, slot);
490
+ if (dm >= 0 && !ms) fs = Math.max(fs, dm + 1);
491
+ fe = ms ? Math.max(e, dm) : fs + dur + (slip || 0);
492
+ P[k] = { fe };
493
+ st = fe > e ? (e < TD ? 'overdue' : 'delayed') : 'on time';
494
+ }
495
+ return { id: k, title, state, start: dOf(s), due: dOf(e), after: dep, forecast: { start: dOf(fs), end: dOf(fe), status: st, slip_days: Math.max(0, fe - e) } };
496
+ });
497
+ const crit = initiative === 'checkout' ? ['CHK-141', 'CHK-142', 'CHK-144', 'CHK-145'] : [];
498
+ const cap = initiative === 'checkout' ? {
499
+ weeks: ['2026-10-05', '2026-10-12'],
500
+ people: [{ id: 'Dara', points_per_week: 8, weeks: [{ week: '2026-10-05', load: 7, over: false }, { week: '2026-10-12', load: 11, over: true }] },
501
+ { id: 'Lee', points_per_week: 10, weeks: [{ week: '2026-10-05', load: 4, over: false }, { week: '2026-10-12', load: 8, over: false }] },
502
+ { id: 'Wawan', points_per_week: 8, weeks: [{ week: '2026-10-05', load: 3, over: false }, { week: '2026-10-12', load: 5, over: false }] }], over: ['Dara'] } : { weeks: ['2026-10-05', '2026-10-12'], people: [], over: [] };
503
+ const prop = initiative === 'checkout' ? { proposal: true, applied: false, moves: [{ item: 'CHK-143', points: 3, from: 'Dara', to: 'Sam', week: '2026-10-12', reason: 'Dara is 3 points over capacity that week' }] } : { proposal: true, applied: false, moves: [] };
504
+ return { ok: true, mock_status_w: 124, initiative, today: TODAY, items, critical: crit, critical_days: crit.length ? 21 : 0, problems: [], capacity: cap, proposal: prop };
505
+ }
506
+ export function forecastAt(initiative, date) {
507
+ const f = forecast(initiative);
508
+ return { ok: true, initiative, date, items: f.items.filter((i) => i.start <= date || date >= addDays(TODAY, -14)).map((i) => ({ id: i.id, title: i.title, state: i.state, due: i.due })) };
509
+ }
510
+
511
+ export function releases() {
512
+ return { ok: true, board: { name: 'Checkout board' }, releases: [
513
+ { id: 'rel-sso-14oct', type: 'both', initiative_ids: ['sso'], deploy_at: '2026-10-14T09:00:00Z', release_at: '2026-10-14T12:00:00Z', accepted_at: null, tags: { detected: ['SSO-1.0'], reason: null }, gapFlags: [{ flag: 'initiative_ticket_without_tag', ticket: 'SSO-33' }],
514
+ summary: [{ text: 'SSO beta live with 3 customers, 0 incidents in 7 days', source: 'fact' }, { text: 'SCIM follows in November (EN-412)', source: 'EN-412' }] },
515
+ { id: 'rel-checkout-24oct', type: 'deploy', initiative_ids: ['checkout'], deploy_at: '2026-10-24T09:00:00Z', release_at: '2026-10-24T12:00:00Z', accepted_at: null, tags: { detected: [], reason: 'no Jira tag yet' }, gapFlags: [], summary: [] },
516
+ ] };
517
+ }
518
+
519
+ /* ── reports ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── */
520
+
521
+ export const reportFamilies = () => JSON.parse("{\"ok\":true,\"families\":[{\"id\":\"project\",\"name\":\"Project\",\"intent\":\"Decide and unblock\",\"purpose\":\"Will we hit the date, what blocks us, what do you need to decide\",\"cadence\":\"Weekly and on change\",\"variants\":[{\"id\":\"leadership\",\"name\":\"Leadership\",\"audience\":\"Leadership\",\"gate\":\"block\"},{\"id\":\"engineering\",\"name\":\"Engineering\",\"audience\":\"Engineering\",\"gate\":\"warn\"},{\"id\":\"customer-success\",\"name\":\"Customer success\",\"audience\":\"Customer success\",\"gate\":\"strict\",\"external\":true}],\"sections\":[\"headline\",\"progress\",\"risks\",\"decisions_needed\",\"blockers\",\"asks\"]},{\"id\":\"product\",\"name\":\"Product\",\"intent\":\"Align and invest\",\"purpose\":\"Align the audience on outcomes and where to invest.\",\"cadence\":\"per planning cycle\",\"variants\":[{\"id\":\"executives\",\"name\":\"Executives\",\"audience\":\"Executives\",\"gate\":\"block\"},{\"id\":\"product-council\",\"name\":\"Product council\",\"audience\":\"Product council\",\"gate\":\"block\"},{\"id\":\"team\",\"name\":\"Team\",\"audience\":\"Team\",\"gate\":\"warn\"}],\"sections\":[\"outcomes\",\"bets\",\"tradeoffs\",\"investment_asks\"]},{\"id\":\"communications\",\"name\":\"Communications\",\"intent\":\"Inform, enable, ask\",\"purpose\":\"Tell people what they need, give them what they need to act, and ask.\",\"cadence\":\"on demand\",\"variants\":[{\"id\":\"message-queue\",\"name\":\"Message queue\",\"audience\":\"Per message\",\"gate\":\"warn\"},{\"id\":\"who-holds-what\",\"name\":\"Who holds what\",\"audience\":\"PM\",\"gate\":\"warn\"},{\"id\":\"external\",\"name\":\"External message\",\"audience\":\"External\",\"gate\":\"strict\",\"external\":true,\"needs_approval\":true}],\"sections\":[\"message_queue\",\"who_holds_what\",\"asks\"],\"needs_approval_external\":true},{\"id\":\"periodic\",\"name\":\"Periodic\",\"intent\":\"Rhythm\",\"purpose\":\"Keep a steady rhythm; one template, with the change since last time.\",\"cadence\":\"weekly, monthly, quarterly, per retro\",\"variants\":[{\"id\":\"weekly-digest\",\"name\":\"Weekly digest\",\"audience\":\"Team\",\"gate\":\"warn\",\"cadence\":\"weekly\"},{\"id\":\"monthly-review\",\"name\":\"Monthly review\",\"audience\":\"Leadership\",\"gate\":\"block\",\"cadence\":\"monthly\"},{\"id\":\"quarterly-board-update\",\"name\":\"Quarterly board update\",\"audience\":\"Board\",\"gate\":\"block\",\"cadence\":\"quarterly\"},{\"id\":\"retro\",\"name\":\"Retro\",\"audience\":\"Team\",\"gate\":\"warn\",\"cadence\":\"per retro\"}],\"sections\":[\"since_last_time\",\"highlights\",\"metrics\",\"next\"],\"template\":\"same template; change since last time\"}],\"labels\":[\"fact\",\"cited\",\"reported\",\"estimate\",\"assumption\",\"judgement\"],\"required\":{\"fact\":[\"source\"],\"cited\":[\"source\"],\"reported\":[\"by\",\"at\"],\"estimate\":[\"method\",\"range\"],\"assumption\":[\"owner\",\"falsifier\",\"review_by\"],\"judgement\":[\"by\"]}}");
522
+ const claim = (text, basis, source, ok = true) => ({ text, basis, source, ok });
523
+ export function reportFacts() {
524
+ const sec = (name, title, claims) => ({ name, title, claims });
525
+ return { ok: true, facts: {
526
+ project: [sec('headline', 'Headline', [claim('Forecast 31 Oct against 24 Oct promised', 'fact', 'Jira field')]), sec('progress', 'Progress', [claim('SSO beta live with 3 customers', 'fact', 'Jira')]),
527
+ sec('risks', 'Risks', [claim('Payments sandbox keys are 2 days late', 'fact', 'Jira')]), sec('decisions_needed', 'Decisions needed', [claim('Cut to 3 flows to hold 24 Oct', 'estimate', 'Dara, 3 to 6 days', false)]),
528
+ sec('blockers', 'Blockers', [claim('Accessibility audit slot not before 28 Oct', 'fact', 'Calendar')]), sec('asks', 'Asks', [claim('Priya approves one engineer if scope is kept', 'assumption', 'owner Wawan', false)])],
529
+ product: [sec('outcomes', 'Outcomes', [claim('North star is 4% behind plan and the gap is widening', 'fact', 'analytics')]), sec('bets', 'Bets', [claim('6 bets across 4 pillars, 2 at risk', 'fact', 'Jira')]),
530
+ sec('tradeoffs', 'Trade-offs', [claim('Q4 demand is 54 person-weeks against 48 of capacity', 'estimate', 'plan, 8 to 12 weeks', false)]), sec('investment_asks', 'Investment asks', [claim('Shift 10 points of capacity from Convert to Onboard', 'assumption', 'owner Wawan', false)])],
531
+ communications: [sec('message_queue', 'Message queue', [claim('Reply to Marta: SSO 14 Oct, SCIM in Nov', 'fact', 'Jira field')]), sec('who_holds_what', 'Who holds what', [claim('Priya holds Ship 24 Oct, on track (now outdated)', 'fact', 'Slack, 2 Oct')]),
532
+ sec('asks', 'Asks', [claim('Dara: sandbox keys by Friday', 'fact', 'Jira')])],
533
+ periodic: [sec('since_last_time', 'Since last time', [claim('SSO beta live with 3 customers', 'fact', 'Jira')]), sec('highlights', 'Highlights', [claim('Search is 28% faster', 'fact', 'analytics')]), sec('metrics', 'Metrics', [claim('Conversion 3.1% against 4.0% target', 'fact', 'analytics')]), sec('next', 'Next', [claim('Interview 5 on 13 Oct decides park or continue', 'fact', 'Calendar')])],
534
+ truthTable: [{ person: 'Priya', claims: [{ id: 'tt1', text: 'Ship 24 Oct, on track', state: 'stale' }] }, { person: 'Marta', claims: [{ id: 'tt2', text: 'SSO 14 Oct', state: 'true' }] }, { person: 'Lee', claims: [{ id: 'tt3', text: 'Copy freeze 9 Oct', state: 'true' }] }, { person: 'Dara', claims: [{ id: 'tt4', text: 'Ship 24 Oct', state: 'contradicted' }] }],
535
+ heads: { project: { h: 'Checkout v2 status for Leadership: 0 of 6 sections approved', s: 'Will we hit the date, what blocks us, what do you need to decide.', ring: [0, 6] } },
536
+ audience: { project: 'Sponsors and the delivery team' },
537
+ reports: [{ id: 'pj1', title: 'Checkout v2 status', due: 'Fri 9 Oct, 15:00' }, { id: 'pj2', title: 'SSO beta status', due: 'Fri 9 Oct, 15:00' }, { id: 'pj3', title: 'Search relevance status', due: 'Fri 9 Oct, 15:00' }],
538
+ badges: { communications: 6 },
539
+ doc: { title: 'Checkout v2 status', aud: 'Leadership', send: '0 of 6 approved. Slide to send, or swipe sections first.', rows: [
540
+ { id: 'a', h: 'Bottom line', t: 'Checkout v2 will ship 31 Oct, 7 days after the promised 24 Oct, unless scope is cut to 3 flows today.', by: 'Agent', st: 'ok' },
541
+ { id: 'b', h: 'Status against the promise', t: 'Red. Promised 24 Oct, forecast 31 Oct. The cause is scope, not capacity: 33 points became 46 in three additions.', by: 'Agent', st: 'ok' },
542
+ { id: 'c', h: 'What changed since last report', t: 'The a11y audit slot moved from 23 to 28 Oct. Saved-card removal and the audit were added by the agent after review gates and accepted by you.', by: 'Agent', st: 'ok' },
543
+ { id: 'd', h: 'Top risks', t: 'Vendor sandbox keys are 2 days late with no reply. The audit vendor has no slot before 28 Oct.', by: 'Agent', st: 'ok' },
544
+ { id: 'e', h: 'Decision we need', t: 'Cut to 3 flows, which holds 24 Oct and is recommended, or accept 31 Oct. Needed by 14:00 today.', by: 'You', st: 'check' },
545
+ { id: 'f', h: 'Confidence', t: 'Medium, 78%. The vendor may deliver the keys sooner. If they do not reply by Friday the date moves again.', by: 'Agent', st: 'check' }] },
546
+ sent: [['Fri 2 Oct', 'Checkout v2 status', 'Leadership', 'opened by 5 of 6', 'Said: ship 24 Oct, on track'], ['Fri 2 Oct', 'SSO beta status', 'Customer success', 'opened by 3 of 3', '']],
547
+ forecast_history: { title: 'Checkout v2', promised: 24, points: [['14 Sep', 24], ['25 Sep', 24], ['30 Sep', 26], ['5 Oct', 28], ['8 Oct', 31]] } } };
548
+ }
549
+ export function reportCheck(body) {
550
+ const names = (body.sections || []).map((s) => s.name);
551
+ const need = new Set(['decisions_needed', 'asks']);
552
+ return { ok: true, gate: 'block', reasons: [], warnings: ['2 sections need your input before this can go out.'], sections: names.map((n) => ({ name: n, traced: need.has(n) ? 0 : 1, stale_source: 0, needs_input: need.has(n) ? 1 : 0 })) };
553
+ }
554
+ export function outbox() {
555
+ const e = (id, recipient, channel, origin, body, ok) => ({ id, recipient, channel, origin, state: 'drafted', body, hash: 'a1b2c3d4e5f6a7b8c9d0' + id, claims: { ok, refused: ok ? [] : ['SSO is on track'] }, last_result: null });
556
+ return { ok: true, channels: ['jira-write', 'calendar-hold', 'customer-note'], entries: [
557
+ e('ob1', 'Marta', 'customer-note', 'agent', 'Hi Marta, SSO is on track for 14 Oct [fact]. SCIM follows in November (EN-412).', true),
558
+ e('ob2', 'Priya', 'jira-write', 'agent', 'Weekly status: SSO is on track', false),
559
+ ] };
560
+ }