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,41 @@
1
+ /* pm/c3-terminal-icons.js — the icon language of the agent surfaces, and the few glyphs the console3 sprite does not hold.
2
+ *
3
+ * Two glyphs, never swapped (the mockups draw them apart: agent-surfaces.html and terminal-layout.html):
4
+ * AGENT `pulse` (the mockup's i-pulse) the agent surfaces: sessions that need you, results to accept. Header button (with the needs-you badge) and the
5
+ * dock's "Agent surfaces" button. It also means "the agent acts" everywhere else on the page (swipe right, suggested move).
6
+ * TERMINAL `term` (`>_`) a real session's terminal: the dock rail button, a session's "Open the session" link, a session row's mark.
7
+ * AGENT = "the interface that reads sessions for you". TERMINAL = "the raw CLI session". A button shows one or the other, with its own tooltip.
8
+ *
9
+ * The extra symbols are the mockup's own paths (agent-surfaces.html lines 168 to 184), added once under the c3i- ids c3ic() reads. A symbol
10
+ * another module already added is not added again. */
11
+ import { c3ic } from './pm-work-view.js';
12
+
13
+ export const ICON = { agent: 'pulse', terminal: 'term' }; // the mockup's header Sessions pill draws the pulse glyph; the rail draws the terminal glyph
14
+ export const TIPS = {
15
+ agentHeader: (n) => (n ? 'Agent surfaces: ' + n + (n === 1 ? ' session needs you' : ' sessions need you') : 'Agent surfaces: sessions, results to accept, questions for you'),
16
+ agentDock: 'Agent surfaces: the full page of sessions that need you',
17
+ terminalRail: 'Sessions dock (Ctrl J): the terminals of your branches',
18
+ terminalOpen: 'Open the session: its real terminal, in the main app',
19
+ };
20
+
21
+ const NS = 'http://www.w3.org/2000/svg';
22
+ const PATHS = {
23
+ edit: 'M4 20h4L19 9l-4-4L4 16zM13 7l4 4', book: 'M5 4h10a3 3 0 0 1 3 3v13H8a3 3 0 0 1-3-3zM5 17a3 3 0 0 1 3-3h10', flip: 'M4 8h13l-3-3M20 16H7l3 3',
24
+ run: 'M12 3a9 9 0 1 0 9 9', pulse: 'M3 12h4l3-7 4 14 3-7h4', chev: 'm9 6 6 6-6 6', chevl: 'm15 6-6 6 6 6', chat: 'M4 5h16v11H9l-5 4z',
25
+ };
26
+ let done = false;
27
+ export function ensureTermIcons() {
28
+ if (done || typeof document === 'undefined') return;
29
+ done = true;
30
+ const missing = Object.keys(PATHS).filter((k) => !document.getElementById('c3i-' + k));
31
+ if (!missing.length) return;
32
+ const svg = document.createElementNS(NS, 'svg');
33
+ svg.setAttribute('width', '0'); svg.setAttribute('height', '0'); svg.setAttribute('aria-hidden', 'true'); svg.style.position = 'absolute';
34
+ for (const k of missing) {
35
+ const sym = document.createElementNS(NS, 'symbol'); sym.setAttribute('id', 'c3i-' + k); sym.setAttribute('viewBox', '0 0 24 24');
36
+ const p = document.createElementNS(NS, 'path'); p.setAttribute('d', PATHS[k]); sym.appendChild(p); svg.appendChild(sym);
37
+ }
38
+ document.body.appendChild(svg);
39
+ }
40
+ /** An icon element (the c3 sprite plus the extras above). */
41
+ export function ic(name, cls) { ensureTermIcons(); return c3ic(name, cls); }
@@ -0,0 +1,141 @@
1
+ /* pm/c3-terminal-results.js — what a finished run shows, by result type (agent-surfaces.html: claims, hero, fileBox, deck, table, diagram, draft).
2
+ * resultNodes(result, ctx) result = the contract's folded result ({ type, ...value }); returns the nodes under the headline.
3
+ * Everything from a run goes in as a text node (el() never takes HTML). An svg is never inserted: a diagram is drawn from its source.
4
+ * ctx = { ui, redraw, canShare }: ui is the per-run state kept outside the render (the deck slide, the table sort), redraw repaints the card. */
5
+ import { el } from './dom.js';
6
+ import { ic } from './c3-terminal-icons.js';
7
+ import { normalizeCase, slideFor } from './c3-case.js';
8
+
9
+ const NS = 'http://www.w3.org/2000/svg';
10
+ const LAB = { fact: ['l-fact', 'fact'], estimate: ['l-est', 'estimate'], assumption: ['l-ass', 'assumption'], reported: ['l-rep', 'reported'] };
11
+ const NO_CHANNEL = 'No channel is connected, so this stays a draft. Nothing is sent.';
12
+
13
+ /** The label chip of one claim: its basis, or "unlabelled" when the contract marked it not ok. */
14
+ export function labelChip(c) {
15
+ const l = c && c.ok !== false && LAB[c.basis] ? LAB[c.basis] : ['l-unl', 'unlabelled'];
16
+ return el('span', { class: 'lab ' + l[0], title: c && c.ok === false ? 'No valid label. It cannot back a message.' : null }, l[1]);
17
+ }
18
+ export function claimsList(list) {
19
+ return el('ul', { class: 'c3t-claims', role: 'list' }, (list || []).map((c) => el('li', null, labelChip(c), el('span', null, c.text, (c.source || c.range) ? el('span', { class: 'dim mono c3t-src' }, ' ' + (c.source || c.range)) : null))));
20
+ }
21
+
22
+ /** The hero of a card.update (the mockup's three: a promise-versus-forecast line, a quote, a CI strip). Anything else is not drawn. */
23
+ export function heroNode(h) {
24
+ if (!h || typeof h !== 'object') return null;
25
+ if (h.t === 'tl' && Number.isFinite(+h.p) && Number.isFinite(+h.f)) {
26
+ const pos = (v) => ((v - 1) / 29 * 100).toFixed(1) + '%';
27
+ return el('div', { class: 'c3t-tl', role: 'img', 'aria-label': 'Promise ' + h.p + ' Nov, forecast ' + h.f + ' Nov' }, el('div', { class: 'axis' }),
28
+ +h.f > +h.p ? el('div', { class: 'gap', style: 'left:' + pos(+h.p) + ';width:' + (((+h.f - +h.p) / 29) * 100).toFixed(1) + '%' }) : null,
29
+ el('div', { class: 'mk pr', style: 'left:' + pos(+h.p) }, el('span', null, 'promise ' + h.p + ' Nov')), el('div', { class: 'mk fc', style: 'left:' + pos(+h.f) }, el('span', null, 'forecast ' + h.f + ' Nov')));
30
+ }
31
+ if (h.t === 'q') return el('blockquote', { class: 'c3t-q' }, '“' + String(h.x || '') + '”', el('cite', null, String(h.by || '')));
32
+ if (h.t === 'ci' && Array.isArray(h.p)) return el('div', { class: 'c3t-ci' }, ...h.p.map((s) => el('i', { class: ['ok', 'bad', 'run'].includes(s) ? s : '' })), el('span', { class: 'dim mono' }, String(h.l || '')));
33
+ return null;
34
+ }
35
+
36
+ /** The mockup's md(): # and ## headings, "- " bullets, **bold**, paragraphs. Built as nodes. */
37
+ export function mdNode(text) {
38
+ const out = []; let ul = null;
39
+ const bold = (s) => String(s).split(/(\*\*[^*]+\*\*)/).map((p) => (/^\*\*[^*]+\*\*$/.test(p) ? el('b', null, p.slice(2, -2)) : p));
40
+ for (const l of String(text || '').split('\n')) {
41
+ if (l.startsWith('- ')) { if (!ul) { ul = el('ul'); out.push(ul); } ul.append(el('li', null, ...bold(l.slice(2)))); continue; }
42
+ ul = null;
43
+ if (l.startsWith('## ') || l.startsWith('# ')) out.push(el('h4', null, ...bold(l.replace(/^#+ /, '')))); else if (l.trim()) out.push(el('p', null, ...bold(l)));
44
+ }
45
+ return el('div', { class: 'c3t-md' }, ...out);
46
+ }
47
+
48
+ function fileNode(r, ctx) {
49
+ const body = ctx.fileBody && ctx.fileBody(r.path);
50
+ return el('div', { class: 'c3t-file' },
51
+ el('div', { class: 'fh' }, ic('doc', 'sm'), el('b', null, r.title), el('span', { class: 'chip2' }, r.media + ' · ' + (r.render || ''))),
52
+ body ? el('div', { class: 'fb' }, mdNode(body)) : el('div', { class: 'fb dim mono' }, 'The page does not read run files. Open the session to read it.'),
53
+ el('div', { class: 'dim mono ft' }, r.path + (r.sha256 ? ' · checksum ' + r.sha256 : ' · no checksum')));
54
+ }
55
+
56
+ function draftNode(r) {
57
+ const c = normalizeCase({ id: 'draft', outward: true, outboxReason: NO_CHANNEL, options: [{ id: 'send', l: 'Send to ' + r.to, rec: 1, outward: true }] });
58
+ const s = slideFor(c, 'send', { outbox: false });
59
+ return el('div', { class: 'c3t-draft' },
60
+ el('div', { class: 'draft' }, r.body),
61
+ claimsList(r.claims),
62
+ r.can_send === false ? el('div', { class: 'dim mono', style: 'color:var(--destructive)' }, 'Send is blocked: a claim in this draft has no label. Ask for evidence or retry.')
63
+ : el('div', { class: 'dim mono' }, ic('next', 'sm'), ' Queued for ' + r.to + '. The slide sends exactly this text (hash ' + r.hash + ', computed by the workspace, not the agent). The session cannot send.'),
64
+ el('div', { class: 'slt off', 'aria-disabled': 'true', 'data-draft-slide': '', style: '--tc:var(--muted-foreground)' }, el('span', { class: 'lb' }, s.label + ' ›››'),
65
+ el('button', { type: 'button', class: 'th', disabled: true, 'aria-label': 'Send: not available. ' + s.reason }, ic('lock'))),
66
+ el('div', { class: 'slt-why', role: 'note' }, ic('lock', 'sm'), s.reason));
67
+ }
68
+
69
+ function optionsNode(r) {
70
+ return el('div', { class: 'c3t-opts' }, (r.options || []).map((o) => el('div', { class: 'opt-r' + (o.rec ? ' rec' : '') }, el('b', null, o.label), el('span', { class: 'dim mono' }, o.when || ''), el('span', { class: 'dim mono' }, (o.cost || '') + (o.rec ? ' · suggested' : '') + (r.close_call ? ' · a close call' : '')))));
71
+ }
72
+
73
+ function deckNode(r, ctx) {
74
+ const ui = ctx.ui; const sl = r.slides || []; const i = Math.max(0, Math.min(sl.length - 1, ui.slide || 0)); const s = sl[i];
75
+ if (!s) return null;
76
+ const go = (d) => { ui.slide = Math.max(0, Math.min(sl.length - 1, i + d)); ctx.redraw(); };
77
+ const box = el('div', { class: 'c3t-dk', 'data-c3t-deck': '' },
78
+ el('div', { class: 'sl' }, el('h4', null, s.title), ...(s.bullets || []).map((b) => el('div', { class: 'c3t-bul' }, labelChip(b), el('span', null, b.text)))),
79
+ el('div', { class: 'nv' }, el('button', { type: 'button', class: 'ib sm', 'aria-label': 'Previous slide', disabled: i === 0, onclick: () => go(-1) }, ic('chevl', 'sm')), el('span', { class: 'dim mono' }, (i + 1) + ' / ' + sl.length + ' · swipe or press'),
80
+ el('button', { type: 'button', class: 'ib sm', 'aria-label': 'Next slide', disabled: i === sl.length - 1, onclick: () => go(1) }, ic('chev', 'sm'))));
81
+ let x0 = null;
82
+ box.addEventListener('pointerdown', (e) => { if (!e.target.closest('button')) x0 = e.clientX; });
83
+ box.addEventListener('pointerup', (e) => { if (x0 === null) return; const dx = e.clientX - x0; x0 = null; if (Math.abs(dx) > 50) go(dx < 0 ? 1 : -1); });
84
+ return el('div', { class: 'c3t-deck' }, box, el('div', { class: 'foot' },
85
+ r.unlabelled ? el('span', { style: 'color:var(--destructive)' }, r.unlabelled + ' bullet' + (r.unlabelled > 1 ? 's' : '') + ' without a label. Cannot be shared.') : el('span', { class: 'dim mono' }, 'All bullets labelled.'),
86
+ el('button', { type: 'button', class: 'pill', disabled: true, title: 'A pptx export is not built on this page. Nothing leaves the machine.' }, ic('doc', 'sm'), 'Export as pptx')));
87
+ }
88
+
89
+ function tableNode(r, ctx) {
90
+ const ui = ctx.ui; const cols = r.columns || []; ui.col = ui.col == null ? 1 : ui.col; ui.dir = ui.dir || -1;
91
+ const rows = (r.rows || []).slice().sort((a, b) => { const x = a[ui.col], y = b[ui.col]; return (typeof x === 'number' ? x - y : String(x).localeCompare(String(y))) * ui.dir; });
92
+ const tog = el('div', { class: 'chips2' }, el('button', { type: 'button', class: 'pill', 'aria-pressed': String(!ui.cards), onclick: () => { ui.cards = false; ctx.redraw(); } }, 'Table'), el('button', { type: 'button', class: 'pill', 'aria-pressed': String(!!ui.cards), onclick: () => { ui.cards = true; ctx.redraw(); } }, 'Cards'),
93
+ el('span', { class: 'dim mono' }, 'Showing ' + rows.length + ' of ' + (r.total || rows.length) + ' · tap a heading to sort'));
94
+ if (ui.cards) return el('div', { class: 'c3t-tbl' }, tog, el('div', { class: 'c3t-opts' }, rows.map((row) => el('div', { class: 'opt-r' }, el('b', null, String(row[0])), el('span', { class: 'dim mono' }, row.slice(1, 2).join('') + (cols[1] ? ' ' + cols[1].label : '')), el('span', { class: 'dim mono' }, row.slice(2).join(' · '))))));
95
+ const head = el('tr', null, cols.map((c, i) => el('th', { class: c.type === 'number' ? 'n' : '', 'aria-sort': ui.col === i ? (ui.dir > 0 ? 'ascending' : 'descending') : 'none' },
96
+ el('button', { type: 'button', class: 'c3t-th', onclick: () => { ui.dir = ui.col === i ? -ui.dir : 1; ui.col = i; ctx.redraw(); } }, c.label + (ui.col === i ? (ui.dir > 0 ? ' ▲' : ' ▼') : '')))));
97
+ return el('div', { class: 'c3t-tbl' }, tog, el('div', { class: 'tw' }, el('table', { class: 'tbl' }, head, rows.map((row) => el('tr', null, row.map((v, i) => el('td', { class: cols[i] && cols[i].type === 'number' ? 'n' : '' }, v == null ? '' : String(v))))))));
98
+ }
99
+
100
+ /** A mermaid "graph LR" of edges, drawn as layers (the longest chain in the accent). Anything it cannot read stays as source text. */
101
+ function diagramNode(r) {
102
+ const src = String(r.source || '');
103
+ const edges = []; const names = [];
104
+ for (const part of src.replace(/^\s*(graph|flowchart)\s+\w+\s*;?/i, '').split(/[;\n]/)) {
105
+ const m = /^\s*([\w-]+)\s*-->\s*([\w-]+)\s*$/.exec(part);
106
+ if (m) { edges.push([m[1], m[2]]); for (const n of [m[1], m[2]]) if (!names.includes(n)) names.push(n); }
107
+ }
108
+ const text = el('details', { class: 'c3t-src' }, el('summary', { class: 'chips2 dim' }, ic('chev', 'sm'), 'Source (' + r.kind + ')'), el('div', { class: 'raw' }, src));
109
+ if (r.kind !== 'mermaid' || !edges.length) return el('div', { class: 'c3t-dg' }, el('div', { class: 'dim mono' }, 'The diagram is shown as its source: this page draws only edge lists.'), text);
110
+ const depth = {}; const longest = {};
111
+ const lay = (n, seen = []) => { if (depth[n] != null) return depth[n]; if (seen.includes(n)) return 0; const ins = edges.filter((e) => e[1] === n).map((e) => e[0]); depth[n] = ins.length ? 1 + Math.max(...ins.map((x) => lay(x, seen.concat(n)))) : 0; return depth[n]; };
112
+ names.forEach((n) => lay(n));
113
+ const maxD = Math.max(...Object.values(depth));
114
+ const end = names.find((n) => depth[n] === maxD); let cur = end; longest[cur] = 1;
115
+ while (cur) { const prev = edges.map((e) => e).filter((e) => e[1] === cur && depth[e[0]] === depth[cur] - 1)[0]; cur = prev && prev[0]; if (cur) longest[cur] = 1; }
116
+ const rowOf = {}; const cnt = {};
117
+ names.forEach((n) => { cnt[depth[n]] = (cnt[depth[n]] || 0); rowOf[n] = longest[n] ? 0 : (cnt[depth[n]] += 1); });
118
+ const W = 120, H = 40, GX = 150, GY = 62; const maxRow = Math.max(...Object.values(rowOf));
119
+ const pt = (n) => ({ x: 10 + depth[n] * GX, y: 10 + rowOf[n] * GY });
120
+ const svg = document.createElementNS(NS, 'svg'); svg.setAttribute('viewBox', '0 0 ' + (20 + maxD * GX + W) + ' ' + (20 + maxRow * GY + H)); svg.setAttribute('role', 'img'); svg.setAttribute('aria-label', r.title || 'Diagram');
121
+ const mk = (tag, a, txt) => { const n = document.createElementNS(NS, tag); for (const k of Object.keys(a)) n.setAttribute(k, a[k]); if (txt) n.textContent = txt; svg.appendChild(n); return n; };
122
+ for (const [a, b] of edges) { const p = pt(a), q = pt(b); const cp = longest[a] && longest[b] && depth[b] === depth[a] + 1; mk('path', { d: 'M' + (p.x + W) + ' ' + (p.y + H / 2) + 'L' + q.x + ' ' + (q.y + H / 2), class: cp ? 'ec' : 'ed' }); }
123
+ for (const n of names) { const p = pt(n); mk('rect', { x: p.x, y: p.y, width: W, height: H, rx: 8, class: 'nd' + (longest[n] ? ' cp' : '') }); mk('text', { x: p.x + W / 2, y: p.y + H / 2 + 4, 'text-anchor': 'middle' }, n); }
124
+ return el('div', { class: 'c3t-dg' }, el('div', { class: 'dg' }, svg), text, el('div', { class: 'dim mono' }, 'The longest chain is blue. Drawn here from its source; the page loads nothing from the network.'));
125
+ }
126
+
127
+ export function resultNodes(r, ctx) {
128
+ if (!r) return [];
129
+ switch (r.type) {
130
+ case 'card.update': return [r.hero ? heroNode(r.hero) : null, claimsList(r.claims), r.chip ? el('div', { class: 'chips2' }, el('span', { class: 'chip2 ok' }, r.chip)) : null];
131
+ case 'evidence': return [claimsList(r.claims)];
132
+ case 'options': return [optionsNode(r)];
133
+ case 'draft': return [draftNode(r)];
134
+ case 'note': return [el('p', { class: 'c3t-note' }, r.note)];
135
+ case 'file': return [fileNode(r, ctx)];
136
+ case 'deck': return [deckNode(r, ctx)];
137
+ case 'table': return [tableNode(r, ctx)];
138
+ case 'diagram': return [diagramNode(r)];
139
+ default: return [el('p', { class: 'dim mono' }, 'A result of type ' + String(r.type) + ' that this page does not draw.')];
140
+ }
141
+ }
@@ -0,0 +1,52 @@
1
+ /* pm/c3-terminal.js — the Terminal page: the agent surfaces of agent-surfaces.html, opened by the header's agent button (spark, with the needs-you badge).
2
+ * openTerminalPage({ tab }) closeTerminalPage()
3
+ * One wide sheet (c3-sheet.js: Esc closes, focus is kept inside, a bottom sheet on a phone) with the mockup's two views:
4
+ * Sessions On a card, Started by me, Past (7 days, with the observed / reported / you timeline), Automatic (hidden until switched on): sessions-panel.js,
5
+ * every run a card (run-card.js) with its strip, its questions, its permission cards, its result and next moves.
6
+ * Edge cases the mockup's cases, one card each: c3-terminal-edge.js (example data, also in real mode).
7
+ * The mockup's third view, "Structure", is the design note (how a session is read); it is not drawn on the page.
8
+ * Real mode reads GET /api/runs and records presses; demo mode (the sample content) answers from c3-demo-term.js. Nothing here sends anything outward. */
9
+ import { el } from './dom.js';
10
+ import { openSheet, closeSheet } from './c3-sheet.js';
11
+ import { ic } from './c3-terminal-icons.js';
12
+ import { source, isDemo } from './c3-terminal-data.js';
13
+ import { mountSessions, disposeSessions } from './sessions-panel.js';
14
+ import { mountEdge, disposeEdge } from './c3-terminal-edge.js';
15
+
16
+ const TABS = [['sessions', 'Sessions', 'pulse'], ['edge', 'Edge cases', 'alert']];
17
+ const NEXT = [['', 'replies normally'], ['noresult', 'ends with no result'], ['rejected', 'is unreadable'], ['quiet', 'goes quiet'], ['unlabelled', 'has an unlabelled claim'], ['prose', 'answers in plain text'], ['attention', 'needs a sign-in'], ['crashed', 'crashes']];
18
+ let sheet = null;
19
+
20
+ export function closeTerminalPage() { if (sheet) closeSheet(); }
21
+
22
+ export async function openTerminalPage(o = {}) {
23
+ const src = source();
24
+ const st = { tab: o.tab === 'edge' ? 'edge' : 'sessions' };
25
+ const host = el('div', { class: 'c3t-host', 'data-c3t': 'host' });
26
+ const seg = el('div', { class: 'seg', role: 'group', 'aria-label': 'Agent surfaces view', 'data-c3t': 'tabs' });
27
+ const note = el('div', { class: 'dim mono c3t-note', 'data-c3t': 'note' });
28
+ const demoBar = src.demo ? el('div', { class: 'chips2 c3t-demo', 'data-c3t': 'demo-controls' },
29
+ el('label', { class: 'dim mono', for: 'c3t-next' }, 'Demo: next reply'),
30
+ el('select', { id: 'c3t-next', 'aria-label': 'How the next reply ends', onchange: (e) => src.setNext(e.target.value) }, NEXT.map(([v, l]) => el('option', { value: v }, l))),
31
+ el('button', { type: 'button', class: 'pill', 'data-c3t': 'fire', onclick: () => src.fire() }, ic('bolt', 'sm'), 'Fire a detector')) : null;
32
+ let fx = null;
33
+ const draw = async () => {
34
+ seg.replaceChildren(...TABS.map(([id, label, icon]) => el('button', { type: 'button', 'aria-pressed': String(st.tab === id), 'data-c3t-tab': id, onclick: () => { if (st.tab !== id) { st.tab = id; draw(); } } }, ic(icon, 'sm'), label)));
35
+ disposeSessions(host); disposeEdge(host);
36
+ if (demoBar) demoBar.hidden = st.tab !== 'sessions';
37
+ if (st.tab === 'sessions') {
38
+ note.textContent = isDemo() ? 'Example data. A swipe right accepts a finished result, a swipe left opens the reply tray. Tap the corner icon to flip a card.' : 'Your runs, read from this workspace. A run is a real CLI session; the page reads its replies and records your presses. Nothing is sent outward.';
39
+ mountSessions(host, {});
40
+ } else {
41
+ note.textContent = '';
42
+ if (!fx) { try { fx = (await import('./c3-demo-term.js')).FX; } catch { fx = null; } }
43
+ if (st.tab !== 'edge') return;
44
+ if (fx) mountEdge(host, fx); else host.replaceChildren(el('p', { class: 'dim mono', role: 'status' }, 'The examples could not be loaded.'));
45
+ }
46
+ };
47
+ const body = [el('div', { class: 'c3 c3e c3t c3t-page', 'data-c3t': 'page' }, el('div', { class: 'chips2', style: 'justify-content:space-between' }, seg, demoBar), note, host)];
48
+ const s = openSheet({ title: 'Agent surfaces', icon: 'pulse', dw: true, body, onClose: () => { disposeSessions(host); disposeEdge(host); sheet = null; } });
49
+ sheet = s;
50
+ await draw();
51
+ return s;
52
+ }
@@ -0,0 +1,163 @@
1
+ /* pm/c3-tickets.js — the Tickets sub-view of Initiatives on /c3.html (console3.html patch at lines 3764 to 3860 and 4488: tkRow, list20, the
2
+ * "Open points by owner" card, raciCard and the agent-drafts card).
3
+ * Tickets are JIRA ONLY and their status is MIRRORED, read-only: the server never reads Jira (D53). The rows come from the cached Jira
4
+ * read the My work route serves: GET /api/my-work, the items with source 'jira' (id jira:KEY, key, title, status, category todo|doing|done,
5
+ * due, estimate, blocks, open, initiative). Nothing on this view writes.
6
+ * Real: key, title, status, category, due, estimate (XS 1, S 2, M 3, L 5, XL 8 points), blocks, initiative; "late" is an overdue date.
7
+ * Optional fields the real read does not carry (owner, pts, forecast_due, blocker, critical, in_work) and the cards `raci` and `drafts`
8
+ * are drawn only when present: the demo's fixtures carry them, a real read does not, so real mode never shows an invented owner or draft.
9
+ * Late or at risk = a forecast past the due date, an overdue date, or a named blocker. */
10
+ import { el } from './dom.js';
11
+ import { pmGet, pmPost } from './http.js';
12
+ import { c3ic } from './pm-work-view.js';
13
+ import { makeView, frame, hBars, card, listCard, emptyRow, dayLabel, short, rowShell, caseNode, markDone, say } from './c3-init-extras.js';
14
+
15
+ const SIZE_PTS = { XS: 1, S: 2, M: 3, L: 5, XL: 8 };
16
+ const OWN_COL = ['var(--acc)', 'var(--success)', 'var(--purple)', 'var(--warning)', 'var(--muted-foreground)', 'var(--destructive)'];
17
+ const dayNum = (iso) => Math.round(Date.parse(String(iso).slice(0, 10) + 'T00:00:00Z') / 86400000);
18
+
19
+ /** One ticket the view draws: the my-work item plus the derived state (late days, late or at risk). */
20
+ export function ticketOf(it, today) {
21
+ const due = it.due || null; const fc = it.forecast_due || due;
22
+ const slip = due && fc ? Math.max(0, dayNum(fc) - dayNum(due)) : 0;
23
+ const over = !slip && due && today && it.category !== 'done' && dayNum(today) > dayNum(due) ? dayNum(today) - dayNum(due) : 0;
24
+ const risk = it.category !== 'done' && (it.blocker || (it.blocks > 0) || (it.open && it.open.length));
25
+ const bad = it.category !== 'done' && (slip > 0 || over > 0 || !!it.blocker);
26
+ return { ...it, due, fc, slip, over, bad, risk: !!risk, owner: it.owner || null, pts: it.pts != null ? it.pts : (SIZE_PTS[it.estimate] || null) };
27
+ }
28
+ export const jiraTickets = (j) => ((j && j.items) || []).filter((i) => i.source === 'jira').map((i) => ticketOf(i, j.today));
29
+
30
+ const goTab = (V, k) => { const b = V.ctx.links && V.ctx.links.querySelector && V.ctx.links.querySelector('[data-c3-link=' + k + ']'); if (b) b.click(); };
31
+ const mineish = (t) => !t.owner || /^(wawan|you)$/i.test(t.owner);
32
+
33
+ /** The case file of a ticket (console3.html genTk, line 3770). READ-ONLY: Jira owns status, dates and owners; this page never changes them.
34
+ * "Take it into today" is the page's own Work list (a page press); a nudge goes outside, so its slide is disabled until a channel is connected. */
35
+ function caseOf(V, t) {
36
+ const chg = [];
37
+ if (t.blocker) chg.push(['alert', t.blocker, 'Jira', 'bad']);
38
+ if (t.slip > 0) chg.push(['clock', 'Forecast ' + dayLabel(t.fc) + ', ' + t.slip + ' day' + (t.slip === 1 ? '' : 's') + ' after plan', 'timeline', 'warn']);
39
+ else if (t.over > 0) chg.push(['clock', 'Overdue by ' + t.over + ' day' + (t.over === 1 ? '' : 's') + ', due ' + dayLabel(t.due), 'Jira', 'bad']);
40
+ if (t.critical) chg.push(['flag', 'On the critical path: it moves the ship date', 'path']);
41
+ if (t.in_work) chg.push(['check', 'Also in your Work list', 'Work']);
42
+ if (!chg.length) chg.push(['check', t.due ? 'On plan, due ' + dayLabel(t.fc || t.due) : 'No date in Jira', 'timeline', t.due ? 'ok' : '']);
43
+ chg.push(['lock', 'Status, dates and owner are Jira’s: ' + (t.status || 'no status read') + (t.owner ? ', ' + t.owner : ''), 'Jira, read-only']);
44
+ const opts = [];
45
+ if (!t.in_work && t.category !== 'done') opts.push({ id: 'tk', l: 'Take it into today', rec: mineish(t), fx: [['check', 'Appears in your Work list', 'ok']] });
46
+ if (t.category !== 'done' && !mineish(t)) opts.push({ id: 'nd', l: 'Ask ' + t.owner + ' for an update', rec: t.slip > 0 || !!t.blocker, outward: true, fx: [['reply', 'A draft to ' + t.owner, 'ok'], ['out', 'Goes out only by your slide', 'warn']] });
47
+ opts.push({ id: 'ok', l: 'Leave it', fx: [] });
48
+ const model = { sit: t.why || 'Ticket ' + t.key + (t.initiative ? ' in ' + (V.ctx.rows || []).reduce((n, r) => (r.id === t.initiative ? r.title : n), t.initiative) : '') + (t.owner ? ', owned by ' + t.owner : '') + '. Jira owns it: a change here is only ever a proposal.',
49
+ conf: Math.round(72 + (t.slip > 0 ? 6 : 10)), chg, opts, cta: 'Slide to confirm', outboxReason: 'This message goes outside. No channel is connected, so it stays a draft and nothing is sent.' };
50
+ return caseNode(V, 'tk:' + t.key, model, { onConfirm: (o) => decide(V, t, o) });
51
+ }
52
+
53
+ async function takeIntoToday(V, t) {
54
+ const r = await pmPost('/api/pm-items/add', { line: t.title + ' (' + t.key + ') today' });
55
+ if (!r || !r.ok) { say((r && r.error) || 'Not added to Work.'); return null; }
56
+ const id = r.item && r.item.id;
57
+ return () => (id ? pmPost('/api/pm-items/remove', { id }) : null);
58
+ }
59
+ async function decide(V, t, o) {
60
+ if (o.id === 'tk') {
61
+ const undo = await takeIntoToday(V, t); if (undo === null) return;
62
+ say('In your Work list for today.', undo); markDone(V, 'tk:' + t.key, 'Added to your Work list for today.', undo);
63
+ } else markDone(V, 'tk:' + t.key, 'Left as it is. No change.');
64
+ }
65
+
66
+ function row(V, t) {
67
+ const col = t.category === 'done' ? 'var(--success)' : t.bad ? 'var(--destructive)' : t.risk ? 'var(--warning)' : 'var(--success)';
68
+ const word = t.category === 'done' ? 'Done' : t.slip ? 'Delayed +' + t.slip + ' d' : t.over ? 'Overdue ' + t.over + ' d' : t.risk ? 'At risk' : 'On time';
69
+ const wcol = t.category === 'done' ? 'var(--success)' : t.slip || t.over ? 'var(--destructive)' : t.risk ? 'var(--warning)' : 'var(--success)';
70
+ const icon = t.category === 'done' ? 'check' : t.bad ? 'clock' : 'check';
71
+ /* the swipe (console3.html line 3879): right takes it into today, left drafts a nudge to the owner. A nudge goes outside: no channel, so nothing is sent. */
72
+ const sw = {
73
+ labelRight: 'take it into today', labelLeft: t.owner && !mineish(t) ? 'draft a nudge to ' + t.owner : 'nothing to nudge', iconRight: 'sun', iconLeft: 'bolt',
74
+ onRight: async () => { if (t.in_work || t.category === 'done') { say('Already in Work'); return; } const undo = await takeIntoToday(V, t); if (undo) { say('In your Work list for today', undo); markDone(V, 'tk:' + t.key, 'Added to your Work list for today.', undo); } },
75
+ onLeft: () => say(t.owner && !mineish(t) ? (window.__c3demo ? 'Nudge drafted for ' + t.owner : 'A nudge to ' + t.owner + ' goes outside. No channel is connected, so nothing was drafted or sent.') : 'Nothing to nudge: the ticket is yours or has no owner.'),
76
+ };
77
+ return rowShell(V, { key: 'tk:' + t.key, attrs: { 'data-in-ticket': t.key, 'data-id': t.key, 'data-tk': '1' }, icon, iconColor: col, sw, xpContent: caseOf(V, t),
78
+ titleNode: el('span', { class: 'tt' }, t.critical ? el('span', { class: 'st bad', title: 'On the critical path', style: 'margin-right:6px' }, c3ic('flag', 'sm')) : null, t.title),
79
+ chips: [el('span', { class: 'chip2 mono' }, t.key),
80
+ el('span', { class: 'chip2', style: 'color:' + wcol }, word),
81
+ t.owner ? el('span', { class: 'chip2' }, el('span', { class: 'av' }, t.owner.slice(0, 1)), t.owner) : null,
82
+ t.fc ? el('span', { class: 'chip2' }, c3ic('cal', 'sm'), dayLabel(t.fc)) : null,
83
+ t.blocker && t.category !== 'done' ? el('span', { class: 'st warn' }, c3ic('alert', 'sm'), t.blocker) : null,
84
+ t.in_work ? el('button', { type: 'button', class: 'chip2 lk', 'data-gowork': t.key, onclick: () => goTab(V, 'work') }, c3ic('check', 'sm'), 'In Work') : null,
85
+ t.status && !t.owner ? el('span', { class: 'chip2' }, t.status) : null,
86
+ el('span', { class: 'tag' }, 'Jira')] });
87
+ }
88
+
89
+ function raciCard(R) {
90
+ const cell = (v) => el('span', { class: 'c ' + v }, v);
91
+ return card('org', 'Who does what', 'responsible, accountable, consulted, informed',
92
+ el('div', { class: 'rcg' }, el('span'), ...R.people.map((p) => el('span', { class: 'h' }, p.slice(0, 3))),
93
+ ...R.rows.flatMap((r) => [el('span', null, r.title), ...r.cells.map(cell)])),
94
+ el('ul', { class: 'chgl' }, R.notes.map((n) => el('li', { class: n.tone }, c3ic('alert', 'sm'), el('span', null, n.text)))));
95
+ }
96
+
97
+ function load(V) {
98
+ pmGet('/api/my-work').then((j) => {
99
+ if (V.disposed) return;
100
+ V.data = { j: j && j.ok ? j : null, err: j && j.ok ? null : (j && j.error) || 'The Jira read could not be reached.' };
101
+ V.render();
102
+ }, () => { V.data = { j: null, err: 'The Jira read could not be reached.' }; V.render(); });
103
+ }
104
+
105
+ function render(V) {
106
+ const D = V.data;
107
+ const all = D && D.j ? jiraTickets(D.j) : [];
108
+ const T = V.scope ? all.filter((t) => t.initiative === V.scope) : all;
109
+ const bad = T.filter((t) => t.bad || t.risk);
110
+ const sorted = (f) => T.filter(f).sort((a, b) => String(a.fc || '9').localeCompare(String(b.fc || '9')));
111
+ const late = sorted((t) => t.category !== 'done' && (t.bad || t.risk));
112
+ const doing = sorted((t) => t.category === 'doing' && !late.includes(t));
113
+ const todo = sorted((t) => t.category === 'todo' && !late.includes(t));
114
+ const done = sorted((t) => t.category === 'done');
115
+ const reading = !D;
116
+ const none = !reading && !all.length;
117
+ const head = reading ? { icon: 'check', h: 'Reading the Jira tickets', s: 'Tickets live in Jira. The agent proposes, you accept.', ring: [0, 1] }
118
+ : none ? { icon: 'check', h: 'No Jira tickets read yet', s: D.err || 'The agent has not read Jira yet. A read from My work in the main app fills this view.', ring: [0, 1] }
119
+ : { icon: 'check', h: T.length + ' Jira ticket' + (T.length === 1 ? '' : 's') + ', ' + bad.length + ' late or at risk', s: 'Tickets live in Jira. The agent proposes, you accept.', ring: [done.length, T.length] };
120
+
121
+ const left = [];
122
+ if (reading) left.push(listCard('check', 'Tickets', null, '', [emptyRow('clock', 'Reading the Jira tickets.', { role: 'status' })]));
123
+ else if (none) left.push(listCard('check', 'Tickets', 0, 'Jira only, read-only', [emptyRow('moon', 'The agent has not read Jira yet, so there are no tickets to show. Nothing here is invented.', { 'data-in-empty': '' })]));
124
+ else if (!T.length) left.push(listCard('check', 'Tickets', 0, 'Jira only, read-only', [emptyRow('check', 'No Jira ticket belongs to this initiative.', { 'data-in-empty': '' })]));
125
+ else {
126
+ if (late.length) left.push(listCard('alert', 'Late or at risk', late.length, '', late.map((t) => row(V, t)), { 'data-in-list': 'late' }));
127
+ if (doing.length) left.push(listCard('clock', 'Doing', doing.length, '', doing.map((t) => row(V, t)), { 'data-in-list': 'doing' }));
128
+ if (todo.length) left.push(listCard('moon', 'To do', todo.length, '', todo.map((t) => row(V, t)), { 'data-in-list': 'todo' }));
129
+ if (done.length) left.push(listCard('check', 'Done', done.length, '', done.map((t) => row(V, t)), { 'data-in-list': 'done' }));
130
+ }
131
+
132
+ const right = [];
133
+ if (T.length) {
134
+ const by = {};
135
+ T.forEach((t) => { if (t.category !== 'done') { const o = t.owner || 'You'; by[o] = (by[o] || 0) + (t.pts || 0); } });
136
+ const names = Object.keys(by).sort((a, b) => by[b] - by[a]);
137
+ const mx = Math.max(1, ...names.map((n) => by[n]));
138
+ if (names.some((n) => by[n] > 0)) right.push(card('user', 'Open points by owner', '', hBars(names.map((n, i) => [n, by[n] / mx, OWN_COL[i % OWN_COL.length], by[n] + ' pts']))));
139
+ }
140
+ if (D && D.j && D.j.raci) right.push(raciCard(D.j.raci));
141
+ if (D && D.j && Array.isArray(D.j.drafts) && D.j.drafts.length) {
142
+ right.push(card('plus', 'Agent drafts', 'propose only, you accept',
143
+ el('ul', { class: 'chgl' }, D.j.drafts.map((t) => el('li', { class: 'warn' }, c3ic('plus', 'sm'), el('span', null, t.title + ' · ' + t.pts + ' pts')))),
144
+ el('div', { class: 'dim', style: 'font-size:var(--fs-2)' }, 'Nothing reaches Jira until you accept. Slide the card in the deck.')));
145
+ }
146
+ if (!right.length) right.push(card('info', 'Read-only mirror', '', el('ul', { class: 'chgl' },
147
+ el('li', null, c3ic('lock', 'sm'), el('span', null, 'Status, dates and owners are Jira’s. This view never writes to Jira.')),
148
+ el('li', null, c3ic('clock', 'sm'), el('span', null, 'The agent reads Jira with your connection and the server keeps the last read.')))));
149
+ const readAt = D && D.j && D.j.read_at;
150
+ if (readAt) right.push(el('p', { class: 'dim mono', style: 'font-size:var(--fs-2)', 'data-in-readat': '' }, 'Last read of Jira: ' + short(readAt, 25)));
151
+ frame(V, { head, left, right });
152
+ }
153
+
154
+ const view = makeView('tickets', { init: load, render });
155
+ export const mount = view.mount;
156
+ export const dispose = view.dispose;
157
+
158
+ /** The count on the Tickets button: the Jira tickets that are late or at risk. */
159
+ export async function badge(ctx) {
160
+ const j = await pmGet('/api/my-work' + (ctx && ctx.ws ? '?ws=' + encodeURIComponent(ctx.ws) : ''));
161
+ if (!j || !j.ok) return null;
162
+ return jiraTickets(j).filter((t) => t.category !== 'done' && (t.bad || t.risk)).length;
163
+ }