create-pmos 0.51.0 → 0.53.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 (212) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/mods/pmos-cockpit/README.md +13 -0
  4. package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
  5. package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
  6. package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
  7. package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
  8. package/payload/planning/evals/eval-calibration.md +1 -1
  9. package/payload/planning/okrs/health-budgets.md +1 -1
  10. package/payload/scripts/plan-items-stage.js +271 -0
  11. package/payload/scripts/pmos-log.sh +18 -7
  12. package/payload/scripts/workflow-state.js +28 -2
  13. package/payload/state/discoveries.md +1 -1
  14. package/payload/state/log.md +1 -1
  15. package/workspace/bin/pmos.js +15 -1
  16. package/workspace/cut-stub.js +40 -0
  17. package/workspace/first-paint-audit.js +145 -0
  18. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  19. package/workspace/fixtures/initiatives-api/list.json +176 -0
  20. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  21. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  22. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  23. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  24. package/workspace/fixtures/report-api/draft.json +177 -0
  25. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  26. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  27. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  28. package/workspace/guide/board.md +81 -0
  29. package/workspace/guide/breakdown.md +154 -0
  30. package/workspace/guide/command-palette.md +75 -0
  31. package/workspace/guide/index.md +27 -17
  32. package/workspace/guide/initiatives-roadmap.md +130 -0
  33. package/workspace/guide/initiatives-screen.md +89 -0
  34. package/workspace/guide/initiatives-timeline.md +57 -0
  35. package/workspace/guide/integrated-work.md +94 -0
  36. package/workspace/guide/knowledge-graph.md +3 -68
  37. package/workspace/guide/layout.md +98 -32
  38. package/workspace/guide/live-notes.md +3 -3
  39. package/workspace/guide/pm-only-acts.md +89 -0
  40. package/workspace/guide/pm-only-lines.md +61 -0
  41. package/workspace/guide/reports-page.md +80 -0
  42. package/workspace/guide/reports.md +73 -0
  43. package/workspace/guide/timeline.md +70 -0
  44. package/workspace/guide/today.md +85 -0
  45. package/workspace/guide/validate-with-typesafe.md +1 -1
  46. package/workspace/lib/actor.js +91 -0
  47. package/workspace/lib/board-cache.js +32 -2
  48. package/workspace/lib/board-cli.js +108 -0
  49. package/workspace/lib/board-fold.js +232 -0
  50. package/workspace/lib/breakdown-export.js +80 -0
  51. package/workspace/lib/capture.js +98 -0
  52. package/workspace/lib/engine/plan-items-stage.js +271 -0
  53. package/workspace/lib/engine/workflow-state.js +28 -2
  54. package/workspace/lib/factory-join.js +158 -0
  55. package/workspace/lib/grant-mint.js +113 -0
  56. package/workspace/lib/health-rules-factory.js +92 -0
  57. package/workspace/lib/health-rules-plan.js +164 -0
  58. package/workspace/lib/health.js +79 -0
  59. package/workspace/lib/initiative-cli.js +222 -0
  60. package/workspace/lib/initiative-display.js +183 -0
  61. package/workspace/lib/initiative-needs.js +127 -0
  62. package/workspace/lib/initiative-rows.js +206 -0
  63. package/workspace/lib/initiative-writer.js +141 -0
  64. package/workspace/lib/issues.js +206 -0
  65. package/workspace/lib/item-cli.js +195 -0
  66. package/workspace/lib/item-spans.js +137 -0
  67. package/workspace/lib/measure-cli.js +100 -0
  68. package/workspace/lib/measurements.js +189 -0
  69. package/workspace/lib/mobile-nav.js +57 -53
  70. package/workspace/lib/needs-providers.js +120 -0
  71. package/workspace/lib/placements.js +440 -0
  72. package/workspace/lib/plan-items.js +922 -0
  73. package/workspace/lib/plan-ledgers.js +85 -0
  74. package/workspace/lib/plan-rules.js +72 -0
  75. package/workspace/lib/plan-tree.js +367 -0
  76. package/workspace/lib/pm-guard.js +174 -0
  77. package/workspace/lib/pm-lines-needs.js +331 -0
  78. package/workspace/lib/pmos-verbs.js +66 -0
  79. package/workspace/lib/proactive.js +7 -4
  80. package/workspace/lib/report-cli.js +152 -0
  81. package/workspace/lib/report-draft.js +579 -0
  82. package/workspace/lib/report-needs.js +213 -0
  83. package/workspace/lib/route-modules.js +128 -0
  84. package/workspace/lib/routes-board.js +91 -0
  85. package/workspace/lib/routes-capture.js +52 -0
  86. package/workspace/lib/routes-initiatives.js +371 -0
  87. package/workspace/lib/routes-reports.js +163 -0
  88. package/workspace/lib/routes-timeline.js +197 -0
  89. package/workspace/lib/routes-today.js +32 -0
  90. package/workspace/lib/today-cli.js +99 -0
  91. package/workspace/lib/today.js +333 -0
  92. package/workspace/lib/triage.js +163 -0
  93. package/workspace/lib/verify-work-cli.js +81 -0
  94. package/workspace/lib/work-check-needs.js +99 -0
  95. package/workspace/package-lock.json +1 -1
  96. package/workspace/package.json +5 -5
  97. package/workspace/public/index.html +29 -19
  98. package/workspace/public/pm/app.js +299 -30
  99. package/workspace/public/pm/bd-board.js +113 -0
  100. package/workspace/public/pm/bd-drag.js +59 -0
  101. package/workspace/public/pm/bd-model.js +152 -0
  102. package/workspace/public/pm/bd-timeline.js +233 -0
  103. package/workspace/public/pm/bd-tree.js +244 -0
  104. package/workspace/public/pm/board-copy.js +57 -0
  105. package/workspace/public/pm/breakdown-api.js +58 -0
  106. package/workspace/public/pm/breakdown-copy.js +138 -0
  107. package/workspace/public/pm/components.css +3054 -0
  108. package/workspace/public/pm/cycle-review.js +125 -0
  109. package/workspace/public/pm/data.js +40 -187
  110. package/workspace/public/pm/dest-board.js +438 -0
  111. package/workspace/public/pm/dest-initiatives.js +134 -0
  112. package/workspace/public/pm/dest-reports.js +270 -0
  113. package/workspace/public/pm/dest-settings.js +217 -0
  114. package/workspace/public/pm/dest-today.js +176 -0
  115. package/workspace/public/pm/dialogs.js +88 -0
  116. package/workspace/public/pm/display-word.js +56 -0
  117. package/workspace/public/pm/dock.js +105 -0
  118. package/workspace/public/pm/fixture.js +8 -227
  119. package/workspace/public/pm/frame-copy.js +94 -0
  120. package/workspace/public/pm/handoff-box.js +82 -0
  121. package/workspace/public/pm/http.js +88 -0
  122. package/workspace/public/pm/icons.js +53 -0
  123. package/workspace/public/pm/initiative-list.js +218 -0
  124. package/workspace/public/pm/initiative-sheet.js +261 -0
  125. package/workspace/public/pm/initiatives-api.js +59 -0
  126. package/workspace/public/pm/initiatives-copy.js +215 -0
  127. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  128. package/workspace/public/pm/initiatives-timeline.js +410 -0
  129. package/workspace/public/pm/initiatives-ui.js +123 -0
  130. package/workspace/public/pm/item-sheet.js +251 -0
  131. package/workspace/public/pm/keep-card.js +204 -0
  132. package/workspace/public/pm/lifecycle.js +7 -1
  133. package/workspace/public/pm/mobile.css +49 -20
  134. package/workspace/public/pm/mobile.js +157 -29
  135. package/workspace/public/pm/modes-live.js +7 -883
  136. package/workspace/public/pm/modes.js +32 -1024
  137. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  138. package/workspace/public/pm/next-step-ui.js +3 -2
  139. package/workspace/public/pm/palette-copy.js +51 -0
  140. package/workspace/public/pm/palette.js +312 -0
  141. package/workspace/public/pm/panel-breakdown.js +702 -0
  142. package/workspace/public/pm/panel-cockpit.js +63 -295
  143. package/workspace/public/pm/panel-detail.js +25 -172
  144. package/workspace/public/pm/panel-initiatives.js +1 -19
  145. package/workspace/public/pm/panel-repos.js +31 -81
  146. package/workspace/public/pm/panel-tabs.js +197 -110
  147. package/workspace/public/pm/panel-trees.js +5 -2
  148. package/workspace/public/pm/portfolio-report.js +61 -0
  149. package/workspace/public/pm/primitives.js +656 -0
  150. package/workspace/public/pm/print.css +33 -0
  151. package/workspace/public/pm/provider-cards.js +136 -0
  152. package/workspace/public/pm/registry.js +194 -0
  153. package/workspace/public/pm/release-card.js +110 -0
  154. package/workspace/public/pm/report-view.js +309 -0
  155. package/workspace/public/pm/reports-copy.js +114 -0
  156. package/workspace/public/pm/roadmap.js +271 -0
  157. package/workspace/public/pm/sig-watch.js +41 -0
  158. package/workspace/public/pm/state.js +8 -4
  159. package/workspace/public/pm/status-banner.js +13 -5
  160. package/workspace/public/pm/tab-doc.js +2 -4
  161. package/workspace/public/pm/tab-term.js +12 -2
  162. package/workspace/public/pm/tab-web.js +3 -2
  163. package/workspace/public/pm/timeline-copy.js +48 -0
  164. package/workspace/public/pm/today-cards.js +244 -0
  165. package/workspace/public/pm/today-copy.js +97 -0
  166. package/workspace/public/pm/tokens.css +16 -18
  167. package/workspace/public/pm/topbar.js +102 -0
  168. package/workspace/public/pm/triage-card.js +60 -0
  169. package/workspace/public/pm/undo-hold.js +103 -0
  170. package/workspace/public/pm/view-addrepo.js +2 -1
  171. package/workspace/public/pm/view-livenotes.js +3 -1
  172. package/workspace/public/pm/view-today.js +7 -162
  173. package/workspace/public/vendor/Sortable.min.js +2 -0
  174. package/workspace/public/vendor/lucide-sprite.js +79 -0
  175. package/workspace/replaced-tests.json +2864 -0
  176. package/workspace/run-tests.js +117 -6
  177. package/workspace/server.js +312 -36
  178. package/workspace/sheet-answer.js +61 -0
  179. package/workspace/skills/measure.skill +41 -0
  180. package/workspace/skills/report-biweekly.skill +23 -25
  181. package/workspace/skills/tracker-refresh.skill +50 -0
  182. package/workspace/test-files.json +20 -4
  183. package/workspace/test-lists/base.json +6 -0
  184. package/workspace/test-lists/run2-a.json +6 -0
  185. package/workspace/test-lists/run2-b.json +13 -0
  186. package/workspace/test-lists/run2-c.json +19 -0
  187. package/workspace/test-lists/run2-d.json +22 -0
  188. package/workspace/test-lists/run3-0.json +5 -0
  189. package/workspace/test-lists/run3-a.json +5 -0
  190. package/workspace/test-lists/run3-b.json +6 -0
  191. package/workspace/test-lists/run3-c.json +15 -0
  192. package/workspace/test-lists/run3-d.json +6 -0
  193. package/workspace/test-lists/run4-a.json +8 -0
  194. package/workspace/test-lists/run4-b.json +6 -0
  195. package/workspace/test-lists/run4-c.json +13 -0
  196. package/workspace/test-lists/run4-d.json +9 -0
  197. package/workspace/test-lists/run5-a.json +6 -0
  198. package/workspace/test-lists/run5-b.json +6 -0
  199. package/workspace/test-lists/run5-c.json +15 -0
  200. package/workspace/test-run.js +27 -1
  201. package/workspace/third-party/README.md +44 -0
  202. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  203. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  204. package/workspace/design-parity.js +0 -673
  205. package/workspace/public/pm/tab-task.js +0 -86
  206. package/workspace/public/pm/view-guide.js +0 -141
  207. package/workspace/public/pm/view-knowledge.js +0 -225
  208. package/workspace/public/pm/view-memory.js +0 -122
  209. package/workspace/public/pm/view-portfolio.js +0 -153
  210. package/workspace/public/pm/view-reports.js +0 -190
  211. package/workspace/public/pm/view-scheduled.js +0 -161
  212. package/workspace/public/pm/view-skills.js +0 -216
@@ -1,98 +1,37 @@
1
- /* pm/modes.js — the seven secondary modes behind panel 1's "settings & tools" drawer.
1
+ /* pm/modes.js — what is left of the secondary modes after the R29 cuts (pm-os-absorb Run 3, slice 3D).
2
2
  *
3
- * A PORT of the prototype's non-`product` branches: the panel-2 lists (template lines 130–178 and
4
- * 227–320) and the panel-3 bodies (template lines 450–663, 771–835), with their derivations
5
- * (lines 1467–1570, 1683–1697, 1809–1961). Every inline `style` string below is copied from that
6
- * file character for character and every literal is the prototype's literal — the acceptance bar
7
- * is a mechanical diff, so a reworded label or a tidied padding is a defect, not an improvement.
3
+ * The settings drawer and its modes are gone: Focus, Portfolio, Memory, Knowledge, the Guide, Skills, Scheduled, Agents,
4
+ * Connect and the morning brief. Memory, Knowledge and the Guide are ⌘K search scopes (palette.js); Skills start from the
5
+ * dock's + Session menu; the schedules live in Reports; "All repositories" is a choice in Repository ▾. This file keeps
6
+ * the two things that stay:
8
7
  *
9
- * ── What "mode" means here ──────────────────────────────────────────────────────────────────
10
- * `state.mode` switches panels 2 AND 3 together. The drawer offers exactly seven:
8
+ * validate the Validate page's list (modes-live.js validateList), reached from Settings
9
+ * prof the Claude profiles in the detail column, reached from Settings
11
10
  *
12
- * this repository agent · memory · knowledge
13
- * all repositories focus · skills · connect · scheduled
11
+ * ── The entry points ─────────────────────────────────────────────────────────────────────────
12
+ * renderMode(hostList, hostDetail, mode) — the body for `mode`, APPENDED to `hostList`.
13
+ * detailBody(kind, detail) — the nodes the detail column puts BELOW its header for a secondary
14
+ * detail kind (`prof`).
14
15
  *
15
- * `workflow` is an eighth mode that exists in the prototype's code and is deliberately absent
16
- * from the drawer (a workflow is decided by the agent at runtime, not configured — PRD, panel 1).
17
- * Its list and its `flow` detail are ported anyway, because they are reachable programmatically
18
- * and a half-ported mode is a trap for whoever re-enables it.
19
- *
20
- * ── The two entry points, and who owns what ─────────────────────────────────────────────────
21
- * renderMode(hostList, hostDetail, mode) — panel 2's body for `mode`, APPENDED to `hostList`.
22
- * panel-initiatives.js has already put its sticky
23
- * header and its search box there and every mode
24
- * keeps both, so this appends and never clears.
25
- * `hostDetail` is optional and normally null: panel 3
26
- * is painted by its own owner, which asks this module
27
- * for a body through `detailBody`.
28
- * detailBody(kind, detail) — the nodes panel 3 puts BELOW its sticky header for
29
- * a secondary detail kind (`flow · sched · skill ·
30
- * know · agent · mem · prof · intg`).
31
- *
32
- * ── Where the data comes from ───────────────────────────────────────────────────────────────
33
- * The four editable lists — workflows, scheduled tasks, agents, memory — live on the state keys
34
- * the prototype used (`flowsMap`, `schedList`, `agentList`, `memory`), each `null` until edited.
35
- * `null` resolves to the FIXTURE seed in fixture mode and to an EMPTY list otherwise: seed rows
36
- * rendering in the real app is "the beautiful shell" the PRD names as a failure mode. The data
37
- * layer publishes real values into those same keys, plus `skills`, `know`, `profiles` and
38
- * `intgs`; until it does, a mode renders its section labels and one honest italic line rather
39
- * than a blank panel. In fixture mode every list is non-empty, so that line never renders and the
40
- * comparator sees the prototype's markup exactly.
41
- *
42
- * No network, no derived state cached (D33): every list, count, colour and active row below is
43
- * computed at read time, on every render.
16
+ * No network, no derived state cached (D33): every list, count and colour below is computed at read time, on every render.
44
17
  */
45
18
 
46
19
  import { el, setChildren } from './dom.js';
47
- import { state, setState, FIXTURE } from './state.js';
20
+ import { state, FIXTURE } from './state.js';
48
21
  import * as FX from './fixture.js';
49
- import { openDoc, openTerm } from './panel-initiatives.js';
50
- import { boardOf, records, currentProduct } from './board-read.js';
51
22
  import * as LIVE from './modes-live.js'; // overhaul-r4: server-backed renderers, live mode only
52
23
 
53
- /* ── the prototype's small helpers ─────────────────────────────────────────────────────────── */
54
-
55
24
  /** `{{ expr }}` — an interpolated value, wrapped the way the prototype's runtime wraps it. */
56
25
  const iv = (v) => el('span', null, v === null || v === undefined ? '' : String(v));
57
26
 
58
- /** selRow (line 1402) — an active row is washed and gets the 2px brand left border. */
59
- const selRow = (on) => ({
60
- bg: on ? 'var(--accent)' : 'transparent',
61
- bl: on ? 'var(--brand)' : 'transparent',
62
- });
63
-
64
- /** setDetail (line 1401) — open panel 3 on a detail kind, un-collapsing it if the PM folded it. */
65
- const setDetail = (d) => setState({ detail: d, col2: true });
66
-
67
27
  /** The detail descriptor, never undefined. */
68
28
  const detOf = () => state.detail || { kind: 'init' };
69
29
 
70
- const C = FX.C;
30
+ /* ── style constants ─────────────────────────────────────────────────────────────────────── */
71
31
 
72
- /* ── style constants that repeat verbatim across modes ─────────────────────────────────────── */
73
-
74
- // Section label, 9px top padding (focus's five, connections' second, memory's second).
75
- const LABEL9 = 'padding:9px 14px 4px;font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground);border-bottom:1px solid var(--border)';
76
- // Section label, 7px top padding (knowledge groups, connections' first, memory's first).
77
- const LABEL7 = 'padding:7px 14px 4px;font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground);border-bottom:1px solid var(--border)';
78
- // The shared secondary button. Panel 3 uses it bare; panel 2's list actions add the margin.
79
- const SEC_BTN = 'padding:5px 12px;min-height:30px;background:var(--secondary);border:1px solid var(--border);font-size:12px';
80
- const ACTION_BTN = 'margin:10px 14px;' + SEC_BTN;
81
- const ACTION_HOVER = 'border-color:var(--ring)';
82
- // A list row: 8px padding (skills, knowledge, agents, connections, workflows) …
83
- const row8 = (r) => 'display:block;width:100%;padding:8px 14px;min-height:44px;border-bottom:1px solid oklch(1 0 0 / 5%);border-left:2px solid ' + r.bl + ';background:' + r.bg;
84
- // … and 7px padding (scheduled, focus queue, memory).
85
- const row7 = (r) => 'display:block;width:100%;padding:7px 14px;min-height:44px;border-bottom:1px solid oklch(1 0 0 / 5%);border-left:2px solid ' + r.bl + ';background:' + r.bg;
86
- const ROW_HOVER = 'background:var(--accent)';
87
- // The prototype's brand-filled button (▶ run now, ▶ delegate, ↥ promote …).
88
- const BRAND_BTN = 'padding:5px 12px;min-height:30px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-size:12px;font-weight:500';
89
- const DESTRUCTIVE_BTN = 'padding:5px 12px;min-height:30px;border:1px solid var(--destructive);color:var(--destructive);font-size:12px';
90
- const DESTRUCTIVE_HOVER = 'background:color-mix(in oklab, var(--destructive) 10%, transparent)';
91
32
  // The two-column field grid every secondary detail uses; `pt` is the label's top padding.
92
33
  const FIELD_ROW = 'display:grid;grid-template-columns:76px 1fr;gap:10px;padding:8px 14px;border-bottom:1px solid var(--border)';
93
- const FIELD_ROW10 = 'display:grid;grid-template-columns:76px 1fr;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border)';
94
34
  const fieldLabel = (pt) => 'font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground);padding-top:' + pt + 'px';
95
- const SECTION_LABEL = 'padding:10px 14px 4px;font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground)';
96
35
  const FOOTER = 'margin-top:auto;padding:8px 14px;border-top:1px solid var(--border);font-family:var(--mono);font-size:10px;color:var(--muted-foreground)';
97
36
  const HEAD_BLOCK = 'padding:13px 14px 11px;border-bottom:1px solid var(--border)';
98
37
  const HEAD_NAME = 'font-family:var(--mono);font-size:15px;font-weight:600';
@@ -100,866 +39,15 @@ const HEAD_SUB = 'font-size:12px;color:var(--muted-foreground);margin-top:2px';
100
39
  const ITALIC_EMPTY = 'font-size:11.5px;color:var(--muted-foreground);font-style:italic';
101
40
  const PILL = 'font-family:var(--mono);font-size:10px;padding:2px 6px;background:var(--secondary);border:1px solid var(--border)';
102
41
 
103
- /**
104
- * The one line a mode shows when its list is genuinely empty. It is the prototype's own copy for
105
- * "nothing is filed" — a read that succeeded and found nothing is not an error, and it is not a
106
- * spinner either. In fixture mode no list is empty, so this never renders into the diff.
107
- */
108
- const emptyLine = () => el('div', {
109
- style: 'padding:5px 14px 6px;font-size:11px;color:var(--muted-foreground);font-style:italic',
110
- }, 'nothing filed yet — a successful read of nothing');
111
-
112
- /* ── the state-backed lists ────────────────────────────────────────────────────────────────── */
113
-
114
- const seedOr = (seed, empty) => (FIXTURE ? seed() : empty);
115
-
116
- /** flowsMap() (line 1054). */
117
- const flowsMap = () => state.flowsMap || seedOr(() => FX.FLOWS, {});
118
- /** schedList() (lines 1055–1061). */
119
- const schedList = () => state.schedList || seedOr(FX.defaultSched, []);
120
- /** agentList() (lines 1062–1067). */
121
- const agentList = () => state.agentList || seedOr(FX.defaultAgents, []);
122
- /** memory() (lines 1068–1081). */
123
- const memoryOf = () => state.memory || seedOr(FX.defaultMemory, { short: [], long: [] });
124
- /** SKILLS (lines 1491–1496). */
125
- const skillList = () => state.skills || seedOr(() => FX.SKILLS, []);
126
- /** KNOW (line 1502) — the base decisions with anything a repo ingest proposed layered on. */
127
- const knowMap = () => ({ ...(state.know || seedOr(() => FX.KNOW, {})), ...(state.extraKnow || {}) });
128
- /** PROFILES / INTG (line 1684). */
129
- const profileList = () => state.profiles || seedOr(() => FX.PROFILES, []);
130
- const intgList = () => state.intgs || seedOr(() => FX.INTG, []);
131
-
132
- /* ── the board, read the way panels 2 and 3 read it (board-read.js — one derivation) ───────── */
133
-
134
- /** The initiative a mode acts on when it starts a session (line 1397). */
135
- function selectedSlug() {
136
- if (state.sel !== null && state.sel !== undefined) return state.sel;
137
- const board = boardOf();
138
- const g0 = (board[currentProduct()] || [])[0];
139
- return g0 && g0.slugs && g0.slugs.length ? g0.slugs[0] : null;
140
- }
141
-
142
- /** allProds (line 1431), as far as this module needs it — focus's objective lines. */
143
- function productTuples() {
144
- if (FIXTURE && !state.board) return FX.productsList(state.addedProducts || []);
145
- const added = (state.addedProducts || []).map((x) => [x.id, x.sub, x.kind || 'github']);
146
- return [...Object.keys(boardOf()).map((id) => [id, '', 'github']), ...added];
147
- }
148
-
149
- /** jump (line 1542) — pick a repository AND an initiative, without leaving the current mode. */
150
- const jump = (prod2, slug) => setState({ product: prod2, sel: slug, detail: { kind: 'init' }, col2: true });
151
-
152
- /** Start a session against an initiative and open its terminal tab (lines 1558–1559, 1863–1864). */
153
- function startSession(slug, sess) {
154
- if (!slug) return;
155
- setState(
156
- (s) => ({ extraSess: { ...s.extraSess, [slug]: [...(s.extraSess[slug] || []), sess] } }),
157
- () => openTerm(slug, sess),
158
- );
159
- }
160
-
161
- /** profChips (lines 1685–1689) — the claude-work / claude-personal picker, used by 2 details. */
162
- function profChips(cur, onPick) {
163
- return profileList().map((x) => ({
164
- l: x.id.replace('claude-', ''),
165
- on: () => onPick(x.id),
166
- bg: cur === x.id ? 'var(--secondary)' : 'transparent',
167
- bc: cur === x.id ? 'var(--input)' : 'var(--border)',
168
- color: cur === x.id ? 'var(--foreground)' : 'var(--muted-foreground)',
169
- }));
170
- }
171
-
172
- function profChipNode(pb) {
173
- return el('button', {
174
- onClick: pb.on,
175
- style: 'font-family:var(--mono);font-size:10.5px;padding:4px 8px;min-height:26px;background:' + pb.bg + ';border:1px solid ' + pb.bc + ';color:' + pb.color,
176
- hover: ACTION_HOVER,
177
- }, iv(pb.l));
178
- }
179
-
180
- /* ══════════════════════════════════════════════════════════════════════════════════════════════
181
- * PANEL 2 — one list per mode (template lines 130–178, 227–320)
182
- * ════════════════════════════════════════════════════════════════════════════════════════════ */
183
-
184
- /* ── focus (lines 130–178, derivation 1541–1570) ───────────────────────────────────────────── */
185
-
186
- function focusList() {
187
- const det = detOf();
188
- const selSlug = state.sel;
189
- const fRow = (f) => ({
190
- ...f,
191
- on: () => jump(f.prod2, f.slug),
192
- ...selRow(selSlug === f.slug && det.kind === 'init'),
193
- });
194
- /* The queue is a JUDGEMENT about what is blocked on the PM, so it is never invented here. In
195
- * fixture mode it is the prototype's four ranked items; in real mode it is whatever the data
196
- * layer published on `state.focus` (loadFocus's adapted `/api/console?name=focus` view —
197
- * {first, waiting, syn, saw}, or {readError} for a failed read); with neither, each section
198
- * renders its label and one honest empty line. */
199
- const F = state.focus || (FIXTURE
200
- ? { first: FX.FOCUS_FIRST, waiting: FX.FOCUS_WAITING, syn: FX.FOCUS_SYN }
201
- : { first: [], waiting: [], syn: [] });
202
- const first = (F.first || []).map(fRow);
203
- const waiting = (F.waiting || []).map(fRow);
204
- const MEM = memoryOf();
205
-
206
- const syn = (F.syn || []).map((f) => ({
207
- ...f,
208
- on: () => {
209
- /* The delegate spawn is the FIXTURE's own seed session. A real-mode row never carries this
210
- * action (data.js renders the server's `starts` without a button — a button would claim a
211
- * spawn this surface has no runner for), so the gate is stated, not assumed. */
212
- if (FIXTURE && f.action === 'delegate-billing-stories') {
213
- const d = FX.DELEGATE_SESSION;
214
- startSession(d.slug, { ...d.sess });
215
- }
216
- },
217
- }));
218
-
219
- /* Upkeep is data-driven like the other sections: the fixture seed renders ONLY in fixture mode;
220
- * in real mode the rows are the server's own upkeep view, adapted by data.js. Seed rows behind a
221
- * published `state.focus` were "the beautiful shell" waiting to activate. */
222
- const saw = (FIXTURE ? FX.focusSaw(MEM) : (F.saw || [])).map((f) => ({
223
- ...f,
224
- on: () => {
225
- if (f.action === 'mode:memory') { setState({ mode: 'memory' }); return; }
226
- if (f.action && f.action.startsWith('jump:')) {
227
- const [prod2, slug] = f.action.slice(5).split('|');
228
- jump(prod2, slug);
229
- }
230
- },
231
- }));
232
-
233
- const board = boardOf();
234
- const ends = productTuples().map(([id]) => {
235
- const g0 = (board[id] || [])[0];
236
- return g0 ? { prod: id, obj: g0.gid + ' · ' + g0.gname } : null;
237
- }).filter(Boolean);
238
-
239
- /* A failed read renders as a failed read, and an in-flight one says so — never "a successful
240
- * read of nothing" for either. Used by the four sections the focus payload feeds; the objectives
241
- * section derives from the board and keeps its own empty line. */
242
- const emptyOr = () => {
243
- if (F.readError) {
244
- return el('div', { style: 'padding:5px 14px 6px;font-size:11px;color:var(--destructive)' },
245
- iv('read failed — ' + F.readError));
246
- }
247
- if (F.reading) {
248
- return el('div', { style: 'padding:5px 14px 6px;font-size:11px;color:var(--muted-foreground);font-style:italic' },
249
- 'reading the focus queue…');
250
- }
251
- return emptyLine();
252
- };
253
-
254
- const out = [];
255
- out.push(el('div', { style: 'padding:8px 14px 4px;font-size:11.5px;color:var(--muted-foreground);line-height:1.5' },
256
- 'This list holds only items where ',
257
- el('span', { style: 'color:var(--foreground)' }, 'you'),
258
- ' are the blocker. Everything else is running on an agent or waiting on another team.'));
259
-
260
- out.push(el('div', { style: LABEL9 }, 'objectives · what this quarter is for'));
261
- for (const fe of ends) {
262
- out.push(el('div', { style: 'display:flex;gap:8px;align-items:baseline;padding:6px 14px;border-bottom:1px solid oklch(1 0 0 / 5%)' },
263
- el('span', { style: 'flex:none;font-family:var(--mono);font-size:10.5px;color:var(--brand)' }, iv(fe.prod)),
264
- el('span', { style: 'font-size:11px;color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(fe.obj))));
265
- }
266
- if (!ends.length) out.push(emptyLine());
267
-
268
- out.push(el('div', { style: LABEL9 }, 'your queue · ranked — do #1 first'));
269
- out.push(el('div', { style: 'padding:5px 14px 3px;font-size:10.5px;color:var(--muted-foreground)' },
270
- 'each item shows what was read before it asks you to act'));
271
- for (const ff of first) {
272
- out.push(el('button', { onClick: ff.on, style: row7(ff), hover: ROW_HOVER },
273
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
274
- el('span', { style: 'flex:none;font-family:var(--mono);font-size:11px;color:var(--warning)' }, iv(ff.n)),
275
- el('span', { style: 'font-family:var(--mono);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(ff.slug)),
276
- el('span', { style: 'margin-left:auto;flex:none;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(ff.prod2))),
277
- el('span', { style: 'display:block;font-size:11px;margin-top:1px' }, iv(ff.why)),
278
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(ff.read))));
279
- }
280
- if (!first.length) out.push(emptyOr());
42
+ /* ── the Claude profiles (the detail Settings opens) ─────────────────────────────────────────── */
281
43
 
282
- out.push(el('div', { style: LABEL9 }, 'handoffs · waiting on other teams'));
283
- for (const fw of waiting) {
284
- out.push(el('button', { onClick: fw.on, style: row7(fw), hover: ROW_HOVER },
285
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
286
- el('span', { style: 'font-family:var(--mono);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(fw.slug)),
287
- el('span', { style: 'margin-left:auto;flex:none;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(fw.prod2))),
288
- el('span', { style: 'display:block;font-size:11px;color:var(--muted-foreground);margin-top:1px' }, iv(fw.why)),
289
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(fw.read))));
290
- }
291
- /* `waitingNote` is the server's own sentence for a tracker that is NOT CONNECTED — a different
292
- * fact from a successful read that found nothing, so it gets the server's words, not the calm
293
- * empty line. */
294
- if (!waiting.length) {
295
- out.push(F.waitingNote
296
- ? el('div', { style: 'padding:5px 14px 6px;font-size:11px;color:var(--muted-foreground);font-style:italic' },
297
- iv(F.waitingNote))
298
- : emptyOr());
299
- }
300
-
301
- out.push(el('div', { style: LABEL9 }, 'delegated · agents in parallel'));
302
- for (const fy of syn) {
303
- out.push(el('div', { style: 'display:flex;gap:8px;align-items:center;padding:6px 14px;border-bottom:1px solid oklch(1 0 0 / 5%)' },
304
- el('span', { style: 'font-size:11.5px;color:var(--muted-foreground);flex:1;min-width:0;line-height:1.45' }, iv(fy.t)),
305
- fy.hasBtn && el('button', {
306
- onClick: fy.on,
307
- style: 'flex:none;font-family:var(--mono);font-size:10.5px;padding:4px 9px;min-height:26px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500',
308
- }, iv(fy.btn))));
309
- }
310
- if (!syn.length) out.push(emptyOr());
311
-
312
- out.push(el('div', { style: LABEL9 }, 'upkeep · keep the system sharp'));
313
- for (const fs2 of saw) {
314
- out.push(el('button', {
315
- onClick: fs2.on,
316
- style: 'display:block;width:100%;padding:7px 14px;min-height:40px;border-bottom:1px solid oklch(1 0 0 / 5%)',
317
- hover: ROW_HOVER,
318
- }, el('span', { style: 'display:block;font-size:11.5px;color:var(--muted-foreground);line-height:1.45' }, iv(fs2.t))));
319
- }
320
- if (!saw.length) out.push(emptyOr());
321
- return out;
322
- }
323
-
324
- /* ── scheduled (lines 227–237, derivation 1467–1477) ───────────────────────────────────────── */
325
-
326
- function schedRows() {
327
- const det = detOf();
328
- return schedList().map((t) => {
329
- const on = det.kind === 'sched' && det.id === t.name;
330
- const last = (t.log || [])[0];
331
- return {
332
- name: t.name, when: t.when,
333
- st: t.enabled ? (last ? last.ts + ' — ' + last.t : 'never run yet') : 'disabled',
334
- c: t.enabled ? (last ? last.c : C.mut) : C.mut,
335
- ...selRow(on),
336
- on: () => setDetail({ kind: 'sched', id: t.name }),
337
- };
338
- });
339
- }
340
-
341
- function addSched() {
342
- const list = schedList();
343
- const name = 'custom task ' + (list.filter((t) => t.name.startsWith('custom task')).length + 1);
344
- setState({ schedList: [...list, { name, ...FX.NEW_SCHED, log: [] }] },
345
- () => setDetail({ kind: 'sched', id: name }));
346
- }
347
-
348
- function scheduledList() {
349
- const rows = schedRows();
350
- const kids = [];
351
- for (const sr of rows) {
352
- kids.push(el('button', { onClick: sr.on, style: row7(sr), hover: ROW_HOVER },
353
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
354
- el('span', { style: 'font-size:12.5px' }, iv(sr.name)),
355
- el('span', { style: 'margin-left:auto;font-family:var(--mono);font-size:10.5px;color:var(--muted-foreground)' }, iv(sr.when))),
356
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10.5px;color:' + sr.c }, iv(sr.st))));
357
- }
358
- if (!rows.length) kids.push(emptyLine());
359
- kids.push(el('button', { onClick: addSched, style: ACTION_BTN, hover: ACTION_HOVER }, '+ schedule a task'));
360
- return [el('div', { style: 'padding:6px 0' }, kids)];
361
- }
362
-
363
- /* ── workflow (lines 239–249, derivation 1478–1489) ────────────────────────────────────────── */
364
-
365
- /** flowUse (line 1480) — which initiatives declare each workflow. */
366
- function flowUse() {
367
- const use = {};
368
- const DATA = records();
369
- Object.keys(DATA).forEach((slug) => {
370
- const i = DATA[slug];
371
- if (i && i.flow) (use[i.flow] = use[i.flow] || []).push(slug);
372
- });
373
- return use;
374
- }
375
-
376
- function addFlow() {
377
- const map = flowsMap();
378
- let n = 1; while (map['custom-' + n]) n++;
379
- const name = 'custom-' + n;
380
- setState({ flowsMap: { ...map, [name]: [...FX.NEW_FLOW_STAGES] } },
381
- () => setDetail({ kind: 'flow', id: name }));
382
- }
383
-
384
- function workflowList() {
385
- const det = detOf();
386
- const use = flowUse();
387
- const map = flowsMap();
388
- const out = [];
389
- out.push(el('div', { style: 'padding:8px 14px 4px;font-size:11.5px;color:var(--muted-foreground);line-height:1.5' },
390
- 'Workflow templates — an initiative picks one; its ladder renders from it. Keep this list short: templates dictate how agents work.'));
391
- const names = Object.keys(map);
392
- for (const name of names) {
393
- const on = det.kind === 'flow' && det.id === name;
394
- const fl = {
395
- name, chain: map[name].join(' → '), used: (use[name] || []).length + ' initiatives', ...selRow(on),
396
- };
397
- out.push(el('button', {
398
- onClick: () => setDetail({ kind: 'flow', id: name }), style: row8(fl), hover: ROW_HOVER,
399
- },
400
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
401
- el('span', { style: 'font-family:var(--mono);font-size:12px;color:var(--foreground)' }, iv(fl.name)),
402
- el('span', { style: 'margin-left:auto;font-size:10.5px;color:var(--muted-foreground)' }, iv(fl.used))),
403
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--info);margin-top:2px;overflow-wrap:anywhere' }, iv(fl.chain))));
404
- }
405
- if (!names.length) out.push(emptyLine());
406
- out.push(el('button', { onClick: addFlow, style: ACTION_BTN, hover: ACTION_HOVER }, '+ new workflow'));
407
- out.push(el('div', { style: 'padding:0 14px 10px;font-size:11px;line-height:1.5;color:var(--muted-foreground)' },
408
- 'Creating a template is the easy part. Applying one is the commitment — an initiative opts in by setting ',
409
- el('span', { style: 'font-family:var(--mono)' }, 'flow:'),
410
- ' in its record, and its ladder re-derives from the new stages.'));
411
- return out;
412
- }
413
-
414
- /* ── skills (lines 251–258, derivation 1490–1500) ──────────────────────────────────────────── */
415
-
416
- function skillsList() {
417
- const det = detOf();
418
- const list = skillList();
419
- const out = list.map((sk) => {
420
- const r = selRow(det.kind === 'skill' && det.id === sk.name);
421
- return el('button', {
422
- onClick: () => setDetail({ kind: 'skill', id: sk.name }), style: row8(r), hover: ROW_HOVER,
423
- },
424
- el('span', { style: 'display:block;font-family:var(--mono);font-size:12px' }, iv(sk.name)),
425
- el('span', { style: 'display:block;font-size:11px;color:var(--muted-foreground)' }, iv(sk.d)));
426
- });
427
- if (!list.length) out.push(emptyLine());
428
- return out;
429
- }
430
-
431
- /* ── knowledge (lines 260–273, derivation 1501–1510) ───────────────────────────────────────── */
432
-
433
- function knowledgeList() {
434
- const det = detOf();
435
- const KNOW = knowMap();
436
- const kGroups = {};
437
- Object.keys(KNOW).forEach((id) => {
438
- const k = KNOW[id];
439
- (kGroups[k.grp] = kGroups[k.grp] || []).push([id, k]);
440
- });
441
-
442
- const out = [];
443
- out.push(el('div', { style: 'padding:8px 14px 4px;font-size:11.5px;color:var(--muted-foreground)' },
444
- 'Decisions, grouped by domain — related decisions link to each other.'));
445
- const names = Object.keys(kGroups);
446
- for (const name of names) {
447
- const rows = kGroups[name].map(([id, k]) => {
448
- const r = selRow(det.kind === 'know' && det.id === id);
449
- const idColor = k.badge === 'superseded' ? 'var(--muted-foreground)' : 'var(--brand)';
450
- return el('button', {
451
- onClick: () => setDetail({ kind: 'know', id }),
452
- style: 'display:block;width:100%;padding:8px 14px 8px 20px;min-height:44px;border-bottom:1px solid oklch(1 0 0 / 5%);border-left:2px solid ' + r.bl + ';background:' + r.bg,
453
- hover: ROW_HOVER,
454
- },
455
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
456
- el('span', { style: 'font-family:var(--mono);font-size:12px;color:' + idColor }, iv(id)),
457
- el('span', { style: 'font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(k.badge))),
458
- el('span', { style: 'display:block;font-size:11.5px;color:var(--muted-foreground)' }, iv(k.d)));
459
- });
460
- out.push(el('div', null, el('div', { style: LABEL7 }, iv(name)), rows));
461
- }
462
- if (!names.length) out.push(emptyLine());
463
- return out;
464
- }
465
-
466
- /* ── agents (lines 275–283, derivation 1511–1527) ──────────────────────────────────────────── */
467
-
468
- function addAgent() {
469
- const list = agentList();
470
- let n = 1; while (list.find((a) => a.name === 'agent-' + n)) n++;
471
- const name = 'agent-' + n;
472
- setState({ agentList: [...list, { name, ...FX.NEW_AGENT, sessions: [] }] },
473
- () => setDetail({ kind: 'agent', id: name }));
474
- }
475
-
476
- function agentsList() {
477
- const det = detOf();
478
- const list = agentList();
479
- const out = list.map((a) => {
480
- const r = selRow(det.kind === 'agent' && det.id === a.name);
481
- const working = (a.sessions || []).filter((s) => s.st === 'working' || s.st === 'needs').length;
482
- const st = a.paused ? 'paused — takes no new work'
483
- : (working ? working + ' session' + (working > 1 ? 's' : '') + ' working' : 'idle');
484
- const c = a.paused ? C.mut : (working ? C.suc : C.mut);
485
- return el('button', {
486
- onClick: () => setDetail({ kind: 'agent', id: a.name }), style: row8(r), hover: ROW_HOVER,
487
- },
488
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
489
- el('span', { style: 'flex:none;font-size:10px;color:' + c }, '●'),
490
- el('span', { style: 'font-family:var(--mono);font-size:12px' }, iv(a.name)),
491
- el('span', { style: 'margin-left:auto;font-size:10.5px;color:var(--muted-foreground)' }, iv(a.role))),
492
- el('span', { style: 'display:block;font-size:11px;color:var(--muted-foreground);padding-left:18px' }, iv(st)));
493
- });
494
- if (!list.length) out.push(emptyLine());
495
- out.push(el('button', { onClick: addAgent, style: ACTION_BTN, hover: ACTION_HOVER }, '+ register agent'));
496
- return out;
497
- }
498
-
499
- /* ── connections (lines 285–301, derivation 1683–1697) ─────────────────────────────────────── */
500
-
501
- function connectList() {
502
- const det = detOf();
503
- const out = [];
504
- out.push(el('div', { style: 'padding:8px 14px 4px;font-size:11.5px;color:var(--muted-foreground);line-height:1.5' },
505
- 'Claude profiles and MCP connections. Workspace MCP — Jira, Confluence, Slack, Google Workspace — is only reachable on claude-work.'));
506
-
507
- out.push(el('div', { style: LABEL7 }, 'claude profiles'));
508
- const profs = profileList();
509
- for (const x of profs) {
510
- const r = selRow(det.kind === 'prof' && det.id === x.id);
511
- const mcpLine = x.mcp.length ? 'mcp: ' + x.mcp.join(' · ') : 'mcp: none';
512
- const mcpColor = x.mcp.length ? C.inf : C.mut;
513
- out.push(el('button', {
514
- onClick: () => setDetail({ kind: 'prof', id: x.id }), style: row8(r), hover: ROW_HOVER,
515
- },
516
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
517
- el('span', { style: 'font-family:var(--mono);font-size:12px' }, iv(x.id)),
518
- el('span', { style: 'margin-left:auto;font-size:10.5px;color:var(--muted-foreground)' }, iv(x.account))),
519
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:' + mcpColor }, iv(mcpLine))));
520
- }
521
- if (!profs.length) out.push(emptyLine());
522
-
523
- out.push(el('div', { style: LABEL9 }, 'connections · via claude-work'));
524
- const conns = intgList();
525
- for (const g of conns) {
526
- const r = selRow(det.kind === 'intg' && det.id === g.id);
527
- const st = g.st + ' · synced ' + ((state.intgSynced && state.intgSynced[g.id]) || g.synced);
528
- out.push(el('button', {
529
- onClick: () => setDetail({ kind: 'intg', id: g.id }), style: row8(r), hover: ROW_HOVER,
530
- },
531
- el('span', { style: 'display:flex;gap:8px;align-items:baseline' },
532
- el('span', { style: 'flex:none;font-size:10px;color:var(--success)' }, '●'),
533
- el('span', { style: 'font-family:var(--mono);font-size:12px' }, iv(g.name)),
534
- el('span', { style: 'margin-left:auto;font-size:10.5px;color:var(--muted-foreground)' }, iv(g.scope))),
535
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--muted-foreground);padding-left:18px' }, iv(st))));
536
- }
537
- // PMOS holds no credential and makes no outbound request (PRD, "External sync"). With nothing
538
- // configured the panel says exactly that instead of listing a connection that does not exist.
539
- if (!conns.length) {
540
- out.push(el('div', { style: 'padding:5px 14px 6px;font-size:11px;color:var(--muted-foreground);font-style:italic' },
541
- 'no connector configured — nothing was read, and nothing will be written'));
542
- }
543
- return out;
544
- }
545
-
546
- /* ── memory (lines 303–320, derivation 1528–1540) ──────────────────────────────────────────── */
547
-
548
- function addMem() {
549
- const MEM = memoryOf();
550
- let n = 1; while (MEM.long.find((m) => m.id === 'ln' + n)) n++;
551
- const m = { id: 'ln' + n, ...FX.NEW_MEMORY_NOTE };
552
- setState({ memory: { ...MEM, long: [...MEM.long, m] } },
553
- () => setDetail({ kind: 'mem', id: m.id, scope: 'long' }));
554
- }
555
-
556
- function memoryList() {
557
- const det = detOf();
558
- const MEM = memoryOf();
559
- const rowOf = (m, scope) => ({
560
- ...m, pin: m.pinned ? '⚑ pinned' : '',
561
- ...selRow(det.kind === 'mem' && det.id === m.id),
562
- on: () => setDetail({ kind: 'mem', id: m.id, scope }),
563
- });
564
-
565
- const out = [];
566
- out.push(el('div', { style: 'padding:8px 14px 4px;font-size:11.5px;color:var(--muted-foreground);line-height:1.5' },
567
- 'Agent memory — short-term dies with the session; long-term survives and is injected into every run.'));
568
-
569
- out.push(el('div', { style: LABEL7 }, 'short-term · session context'));
570
- const shorts = MEM.short.map((m) => rowOf(m, 'short'));
571
- for (const me of shorts) {
572
- out.push(el('button', { onClick: me.on, style: row7(me), hover: ROW_HOVER },
573
- el('span', { style: 'display:block;font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(me.text)),
574
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' },
575
- iv(me.src), ' · ', iv(me.ts))));
576
- }
577
- if (!shorts.length) out.push(emptyLine());
578
-
579
- out.push(el('div', { style: LABEL9 }, 'long-term · durable'));
580
- const longs = MEM.long.map((m) => rowOf(m, 'long'));
581
- for (const me of longs) {
582
- out.push(el('button', { onClick: me.on, style: row7(me), hover: ROW_HOVER },
583
- el('span', { style: 'display:flex;gap:6px;align-items:baseline' },
584
- el('span', { style: 'font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(me.text)),
585
- el('span', { style: 'margin-left:auto;flex:none;font-family:var(--mono);font-size:10px;color:var(--warning)' }, iv(me.pin))),
586
- el('span', { style: 'display:block;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(me.src))));
587
- }
588
- if (!longs.length) out.push(emptyLine());
589
- out.push(el('button', { onClick: addMem, style: ACTION_BTN, hover: ACTION_HOVER }, '+ add a long-term note'));
590
- return out;
591
- }
592
-
593
- /* ══════════════════════════════════════════════════════════════════════════════════════════════
594
- * PANEL 3 — one body per secondary detail kind (template lines 450–663, 771–835)
595
- * ════════════════════════════════════════════════════════════════════════════════════════════ */
596
-
597
- /* ── `dFlow` (lines 450–489, derivation 1809–1833) ─────────────────────────────────────────── */
598
-
599
- function flowBody(det) {
600
- const map = flowsMap();
601
- const use = flowUse();
602
- let fd = { name: '', used: '', stages: [], usedBy: [], unused: false, canDelete: false, noDelete: false, onDelete: () => {}, onNewStage: () => {} };
603
- if (map[det.id]) {
604
- const name = det.id, stages = map[det.id], ub = use[name] || [];
605
- const setStages = (st2) => setState({ flowsMap: { ...map, [name]: st2 } });
606
- fd = {
607
- name, used: ub.length + ' initiatives use it',
608
- stages: stages.map((k, idx) => ({
609
- n: String(idx + 1).padStart(2, '0'), k,
610
- onUp: () => {
611
- if (idx === 0) return;
612
- const s2 = [...stages];
613
- const tmp = s2[idx - 1]; s2[idx - 1] = s2[idx]; s2[idx] = tmp;
614
- setStages(s2);
615
- },
616
- onDel: () => setStages(stages.filter((_, j) => j !== idx)),
617
- })),
618
- onNewStage: (e) => {
619
- if (e.key !== 'Enter') return;
620
- const v = e.target.value.trim().toLowerCase().replace(/\s+/g, '-');
621
- if (!v) return;
622
- e.target.value = '';
623
- setStages([...stages, v]);
624
- },
625
- usedBy: ub.map((slug) => ({ slug, on: () => setState({ sel: slug, mode: 'product', detail: { kind: 'init' }, col2: true }) })),
626
- unused: ub.length === 0,
627
- canDelete: ub.length === 0, noDelete: ub.length > 0,
628
- onDelete: () => {
629
- const m2 = { ...map }; delete m2[det.id];
630
- setState({ flowsMap: m2, detail: { kind: 'init' } });
631
- },
632
- };
633
- }
634
-
635
- const out = [];
636
- out.push(el('div', { style: HEAD_BLOCK },
637
- el('div', { style: HEAD_NAME }, iv(fd.name)),
638
- el('div', { style: HEAD_SUB }, 'workflow template · ', iv(fd.used))));
639
- out.push(el('div', { style: SECTION_LABEL }, 'stages'));
640
- out.push(el('div', { style: 'display:flex;flex-direction:column;gap:2px;padding:4px 14px 6px' },
641
- fd.stages.map((fs) => el('div', { style: 'display:flex;gap:9px;align-items:center;min-height:34px;padding:0 8px;background:var(--background);border:1px solid var(--border)' },
642
- el('span', { style: 'flex:none;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(fs.n)),
643
- el('span', { style: 'font-family:var(--mono);font-size:11.5px;letter-spacing:.06em' }, iv(fs.k)),
644
- el('button', { onClick: fs.onUp, style: 'margin-left:auto;color:var(--muted-foreground);padding:3px 5px;font-size:11px', hover: 'color:var(--foreground)' }, '↑'),
645
- el('button', { onClick: fs.onDel, style: 'color:var(--muted-foreground);padding:3px 5px;font-size:12px', hover: 'color:var(--destructive)' }, '×')))));
646
- out.push(el('div', { style: 'display:flex;gap:6px;padding:2px 14px 10px' },
647
- el('input', {
648
- onKeyDown: fd.onNewStage, placeholder: 'new stage — Enter to add',
649
- style: 'flex:1;min-width:0;font-family:var(--mono);font-size:11.5px;padding:6px 9px;border:1px dashed var(--input);min-height:32px',
650
- })));
651
- out.push(el('div', { style: 'border-top:1px solid var(--border);display:grid;grid-template-columns:76px 1fr;gap:10px;padding:8px 14px' },
652
- el('span', { style: fieldLabel(2) }, 'used by'),
653
- el('div', { style: 'display:flex;flex-direction:column;gap:2px;min-width:0' },
654
- fd.usedBy.map((ub) => el('button', {
655
- onClick: ub.on, style: 'font-family:var(--mono);font-size:11px;color:var(--brand);padding:3px 0;min-height:26px', hover: 'color:var(--foreground)',
656
- }, iv(ub.slug))),
657
- fd.unused && el('span', { style: ITALIC_EMPTY }, 'no initiatives use this yet'))));
658
- out.push(el('div', { style: 'padding:10px 14px;border-top:1px solid var(--border)' },
659
- fd.canDelete && el('button', { onClick: fd.onDelete, style: DESTRUCTIVE_BTN, hover: DESTRUCTIVE_HOVER }, 'delete workflow'),
660
- fd.noDelete && el('span', { style: ITALIC_EMPTY }, 'in use — reassign its initiatives before deleting')));
661
- out.push(el('div', { style: FOOTER }, 'planning/workflows/', iv(fd.name), '.yaml · edits apply to new ladders'));
662
- return out;
663
- }
664
-
665
- /* ── `dSched` (lines 491–537, derivation 1835–1855) ────────────────────────────────────────── */
666
-
667
- function schedBody(det) {
668
- const list = schedList();
669
- let sd = { name: '', when: '', runCmd: '', scope: '', enabledLabel: '', toggleLabel: '', log: [], profBtns: [], profNote: '', onWhen: () => {}, onRunCmd: () => {}, onScope: () => {}, onRun: () => {}, onToggle: () => {}, onDelete: () => {} };
670
- const t = list.find((x) => x.name === det.id);
671
- if (t) {
672
- const save = (t2) => setState({ schedList: list.map((x) => (x.name === det.id ? t2 : x)) });
673
- const prof = t.profile || 'claude-work';
674
- sd = {
675
- name: t.name, when: t.when, runCmd: t.runCmd || '', scope: t.scope || '',
676
- enabledLabel: t.enabled ? 'enabled' : 'disabled',
677
- profBtns: profChips(prof, (id) => save({ ...t, profile: id })),
678
- profNote: prof === 'claude-work'
679
- ? 'runs with workspace MCP — can read Jira and Confluence'
680
- : 'runs without workspace MCP — local files only',
681
- toggleLabel: t.enabled ? '‖ disable' : '▷ enable',
682
- log: (t.log || []).length ? t.log : [{ ...FX.SCHED_NEVER_RUN }],
683
- onWhen: (e) => save({ ...t, when: e.target.value }),
684
- onRunCmd: (e) => save({ ...t, runCmd: e.target.value }),
685
- onScope: (e) => save({ ...t, scope: e.target.value }),
686
- /* The green "ok — ran … 0 failures" line is the FIXTURE's seed log. In real mode nothing
687
- * executes on this press — no runner is wired to this surface — so writing it there would be
688
- * a fabricated execution success (the watermelon the honesty contract forbids). The real-mode
689
- * line says exactly what happened: nothing. */
690
- onRun: () => save({
691
- ...t,
692
- log: [FIXTURE ? {
693
- ts: '12:41 today',
694
- t: t.runCmd ? 'ok — ran `' + t.runCmd + '` manually, 0 failures' : 'nothing ran — no command defined yet',
695
- c: t.runCmd ? 'var(--success)' : 'var(--warning)',
696
- } : {
697
- ts: '—',
698
- t: 'nothing ran — no runner is wired to this surface yet',
699
- c: 'var(--warning)',
700
- }, ...(t.log || [])],
701
- }),
702
- onToggle: () => save({ ...t, enabled: !t.enabled }),
703
- onDelete: () => setState({ schedList: list.filter((x) => x.name !== det.id), detail: { kind: 'init' } }),
704
- };
705
- }
706
-
707
- const INPUT = 'font-family:var(--mono);font-size:11.5px;padding:6px 9px;border:1px solid var(--input);min-height:32px';
708
- const out = [];
709
- out.push(el('div', { style: HEAD_BLOCK },
710
- el('div', { style: HEAD_NAME }, iv(sd.name)),
711
- el('div', { style: HEAD_SUB }, 'scheduled task · ', iv(sd.enabledLabel))));
712
- out.push(el('div', { style: FIELD_ROW10 },
713
- el('span', { style: fieldLabel(8) }, 'cadence'),
714
- el('input', { onChange: sd.onWhen, value: sd.when, style: INPUT })));
715
- out.push(el('div', { style: FIELD_ROW10 },
716
- el('span', { style: fieldLabel(8) }, 'runs'),
717
- el('div', { style: 'min-width:0' },
718
- el('input', { onChange: sd.onRunCmd, value: sd.runCmd, placeholder: 'pmos run <skill> …', style: 'width:100%;' + INPUT }),
719
- el('div', { style: 'font-size:11px;color:var(--muted-foreground);margin-top:4px' },
720
- 'the command this task executes on schedule — any pmos skill or shell command'))));
721
- out.push(el('div', { style: FIELD_ROW10 },
722
- el('span', { style: fieldLabel(8) }, 'scope'),
723
- el('input', { onChange: sd.onScope, value: sd.scope, placeholder: 'all initiatives, or a slug', style: INPUT })));
724
- out.push(el('div', { style: FIELD_ROW10 },
725
- el('span', { style: fieldLabel(8) }, 'profile'),
726
- el('div', { style: 'min-width:0' },
727
- el('div', { style: 'display:flex;gap:4px' }, sd.profBtns.map(profChipNode)),
728
- el('div', { style: 'font-size:11px;color:var(--muted-foreground);margin-top:4px;line-height:1.5' }, iv(sd.profNote)))));
729
- out.push(el('div', { style: 'display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border)' },
730
- el('button', { onClick: sd.onRun, style: BRAND_BTN }, '▶ run now'),
731
- el('button', { onClick: sd.onToggle, style: SEC_BTN, hover: ACTION_HOVER }, iv(sd.toggleLabel)),
732
- el('button', { onClick: sd.onDelete, style: 'margin-left:auto;' + DESTRUCTIVE_BTN, hover: DESTRUCTIVE_HOVER }, 'delete')));
733
- out.push(el('div', { style: SECTION_LABEL }, 'run log'));
734
- out.push(el('div', { style: 'display:flex;flex-direction:column;padding:4px 14px 12px' },
735
- sd.log.map((lg) => el('div', { style: 'display:flex;gap:9px;align-items:baseline;padding:5px 0;border-bottom:1px solid oklch(1 0 0 / 5%)' },
736
- el('span', { style: 'flex:none;font-family:var(--mono);font-size:10.5px;color:var(--muted-foreground)' }, iv(lg.ts)),
737
- el('span', { style: 'font-size:12px;color:' + lg.c }, iv(lg.t))))));
738
- out.push(el('div', { style: FOOTER }, 'planning/scheduled/', iv(sd.name), '.yaml'));
739
- return out;
740
- }
741
-
742
- /* ── `dSkill` (lines 539–561, derivation 1857–1868) ────────────────────────────────────────── */
743
-
744
- function skillBody(det) {
745
- const selSlug = selectedSlug();
746
- let kd = { name: '', d: '', reads: '', writes: '', usedIn: '', onRun: () => {}, onOpen: () => {} };
747
- const sk = skillList().find((x) => x.name === det.id);
748
- if (sk) {
749
- kd = {
750
- ...sk,
751
- onRun: () => startSession(selSlug, { name: sk.name + ' run', st: 'working', label: 'working' }),
752
- onOpen: () => openDoc(null, 'skills/' + sk.name + '/SKILL.md', 'SKILL'),
753
- };
754
- }
755
-
756
- const out = [];
757
- out.push(el('div', { style: HEAD_BLOCK },
758
- el('div', { style: HEAD_NAME }, iv(kd.name)),
759
- el('div', { style: HEAD_SUB }, 'skill · ', iv(kd.d))));
760
- const wrap = 'font-family:var(--mono);font-size:11px;color:var(--muted-foreground);overflow-wrap:anywhere';
761
- out.push(el('div', { style: FIELD_ROW },
762
- el('span', { style: fieldLabel(2) }, 'reads'), el('span', { style: wrap }, iv(kd.reads))));
763
- out.push(el('div', { style: FIELD_ROW },
764
- el('span', { style: fieldLabel(2) }, 'writes'), el('span', { style: wrap }, iv(kd.writes))));
765
- out.push(el('div', { style: FIELD_ROW },
766
- el('span', { style: fieldLabel(2) }, 'used in'),
767
- el('span', { style: 'font-size:12px;color:var(--muted-foreground)' }, iv(kd.usedIn))));
768
- out.push(el('div', { style: 'display:flex;gap:6px;padding:12px 14px' },
769
- el('button', { onClick: kd.onRun, style: BRAND_BTN }, '▶ run in a session'),
770
- el('button', { onClick: kd.onOpen, style: SEC_BTN, hover: ACTION_HOVER }, 'open definition')));
771
- out.push(el('div', { style: FOOTER }, 'skills/', iv(kd.name), '/SKILL.md'));
772
- return out;
773
- }
774
-
775
- /* ── `dKnow` (lines 563–589, derivation 1870–1882) ─────────────────────────────────────────── */
776
-
777
- function knowBody(det) {
778
- const KNOW = knowMap();
779
- let nd = { id: '', d: '', badge: '', badgeColor: C.mut, idColor: C.brand, body: '', rels: [], noRels: false, path: '', onOpen: () => {} };
780
- if (KNOW[det.id]) {
781
- const k = KNOW[det.id];
782
- nd = {
783
- id: det.id, d: k.d, badge: k.badge,
784
- badgeColor: k.badge === 'active' ? C.suc : C.wrn,
785
- idColor: k.badge === 'superseded' ? 'var(--muted-foreground)' : 'var(--brand)',
786
- body: k.body, path: k.path,
787
- rels: (k.rels || []).map(([kind, id2]) => ({
788
- kind, id: id2, d: KNOW[id2] ? KNOW[id2].d : '',
789
- on: () => setDetail({ kind: 'know', id: id2 }),
790
- })),
791
- noRels: (k.rels || []).length === 0,
792
- onOpen: () => openDoc(null, k.path, 'DECISION'),
793
- };
794
- }
795
-
796
- const out = [];
797
- out.push(el('div', { style: HEAD_BLOCK },
798
- el('div', { style: 'display:flex;gap:9px;align-items:baseline' },
799
- el('span', { style: 'font-family:var(--mono);font-size:15px;font-weight:600;color:' + nd.idColor }, iv(nd.id)),
800
- el('span', { style: PILL + ';color:' + nd.badgeColor }, iv(nd.badge))),
801
- el('div', { style: HEAD_SUB }, iv(nd.d))));
802
- out.push(el('div', { style: 'padding:12px 14px;border-bottom:1px solid var(--border);font-size:13px;line-height:1.55' }, iv(nd.body)));
803
- out.push(el('div', { style: SECTION_LABEL }, 'relationships'));
804
- out.push(el('div', { style: 'display:flex;flex-direction:column;gap:2px;padding:4px 14px 10px' },
805
- nd.rels.map((rl) => el('button', {
806
- onClick: rl.on,
807
- style: 'display:flex;gap:9px;align-items:center;min-height:34px;padding:0 9px;background:var(--background);border:1px solid var(--border)',
808
- hover: ACTION_HOVER,
809
- },
810
- el('span', { style: 'flex:none;width:86px;font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)' }, iv(rl.kind)),
811
- el('span', { style: 'font-family:var(--mono);font-size:11.5px;color:var(--brand)' }, iv(rl.id)),
812
- el('span', { style: 'font-size:11px;color:var(--muted-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(rl.d)))),
813
- nd.noRels && el('span', { style: ITALIC_EMPTY }, 'stands alone — no linked decisions')));
814
- out.push(el('div', { style: 'padding:2px 14px 12px' },
815
- el('button', { onClick: nd.onOpen, style: SEC_BTN, hover: ACTION_HOVER }, 'open file')));
816
- out.push(el('div', { style: FOOTER }, iv(nd.path)));
817
- return out;
818
- }
819
-
820
- /* ── `dAgent` (lines 591–642, derivation 1884–1907) ────────────────────────────────────────── */
821
-
822
- function agentBody(det) {
823
- const list = agentList();
824
- const MEM = memoryOf();
825
- const selSlug = selectedSlug();
826
- let ad = { name: '', role: '', model: '', st: '', c: C.mut, sessions: [], noSess: false, memLine: '', toggleLabel: '', profBtns: [], profNote: '', onToggle: () => {}, onStart: () => {}, onMem: () => {} };
827
- const a = list.find((x) => x.name === det.id);
828
- if (a) {
829
- const sessions = a.sessions || [];
830
- const working = sessions.filter((s) => s.st === 'working' || s.st === 'needs').length;
831
- const prof = a.profile || 'claude-work';
832
- ad = {
833
- name: a.name, role: a.role, model: a.model,
834
- profBtns: profChips(prof, (id) => setState({ agentList: list.map((x) => (x.name === a.name ? { ...x, profile: id } : x)) })),
835
- profNote: prof === 'claude-work'
836
- ? 'claude-work — this agent reaches Jira, Confluence, Slack, Google Workspace over MCP'
837
- : 'claude-personal — no workspace MCP; this agent cannot touch Jira or Confluence',
838
- st: a.paused ? 'paused — takes no new work' : (working ? working + ' sessions working' : 'idle'),
839
- c: a.paused ? C.mut : (working ? C.suc : C.mut),
840
- sessions: sessions.map((s) => {
841
- const dot = FX.SDOT[s.st] || FX.SDOT.exited;
842
- return {
843
- name: s.name, slug: s.slug, dot: dot[0], color: dot[1],
844
- on: () => openTerm(s.slug, { name: s.name, st: s.st }),
845
- };
846
- }),
847
- noSess: sessions.length === 0,
848
- memLine: MEM.short.length + ' short-term entries · ' + MEM.long.length + ' long-term',
849
- toggleLabel: a.paused ? '▷ resume' : '‖ pause',
850
- onToggle: () => setState({ agentList: list.map((x) => (x.name === a.name ? { ...x, paused: !x.paused } : x)) }),
851
- onStart: () => {
852
- const s2 = { name: a.name + ' session', slug: selSlug, st: 'working', profile: prof };
853
- setState(
854
- { agentList: list.map((x) => (x.name === a.name ? { ...x, sessions: [...(x.sessions || []), s2] } : x)) },
855
- () => openTerm(selSlug, { name: s2.name, st: 'working' }),
856
- );
857
- },
858
- onMem: () => setState({ mode: 'memory' }),
859
- };
860
- }
861
-
862
- const out = [];
863
- out.push(el('div', { style: HEAD_BLOCK },
864
- el('div', { style: 'display:flex;gap:9px;align-items:baseline' },
865
- el('span', { style: 'flex:none;font-size:10px;color:' + ad.c }, '●'),
866
- el('span', { style: HEAD_NAME }, iv(ad.name)),
867
- el('span', { style: 'margin-left:auto;' + PILL + ';color:var(--muted-foreground)' }, iv(ad.role))),
868
- el('div', { style: HEAD_SUB }, iv(ad.st))));
869
- out.push(el('div', { style: FIELD_ROW },
870
- el('span', { style: fieldLabel(2) }, 'model'),
871
- el('span', { style: 'font-family:var(--mono);font-size:11.5px' }, iv(ad.model))));
872
- out.push(el('div', { style: FIELD_ROW },
873
- el('span', { style: fieldLabel(2) }, 'profile'),
874
- el('div', { style: 'min-width:0' },
875
- el('div', { style: 'display:flex;gap:4px' }, ad.profBtns.map(profChipNode)),
876
- el('div', { style: 'font-size:11px;color:var(--muted-foreground);margin-top:4px;line-height:1.5' }, iv(ad.profNote)))));
877
- out.push(el('div', { style: FIELD_ROW },
878
- el('span', { style: fieldLabel(2) }, 'sessions'),
879
- el('div', { style: 'display:flex;flex-direction:column;gap:2px;min-width:0' },
880
- ad.sessions.map((s) => el('button', {
881
- onClick: s.on, style: 'display:flex;gap:8px;align-items:center;padding:5px 6px;margin:0 -6px;min-height:34px', hover: ROW_HOVER,
882
- },
883
- el('span', { style: 'flex:none;font-size:10px;color:' + s.color }, iv(s.dot)),
884
- el('span', { style: 'font-size:12.5px' }, iv(s.name)),
885
- el('span', { style: 'margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(s.slug)))),
886
- ad.noSess && el('span', { style: ITALIC_EMPTY }, 'no sessions attached'))));
887
- out.push(el('div', { style: FIELD_ROW },
888
- el('span', { style: fieldLabel(2) }, 'memory'),
889
- el('div', { style: 'min-width:0' },
890
- el('div', { style: 'font-size:12px;color:var(--muted-foreground)' }, iv(ad.memLine)),
891
- el('button', {
892
- onClick: ad.onMem,
893
- style: 'margin-top:3px;font-family:var(--mono);font-size:11px;color:var(--brand);padding:2px 0;min-height:24px',
894
- hover: 'color:var(--foreground)',
895
- }, 'open memory →'))));
896
- out.push(el('div', { style: 'display:flex;gap:6px;padding:12px 14px' },
897
- el('button', { onClick: ad.onToggle, style: SEC_BTN, hover: ACTION_HOVER }, iv(ad.toggleLabel)),
898
- el('button', { onClick: ad.onStart, style: BRAND_BTN }, '▶ new session')));
899
- out.push(el('div', { style: FOOTER }, 'agents/', iv(ad.name), '.yaml'));
900
- return out;
901
- }
902
-
903
- /* ── `dMem` (lines 644–663, derivation 1909–1935) ──────────────────────────────────────────── */
904
-
905
- function memBody(det) {
906
- const MEM = memoryOf();
907
- let md = { scope: '', scopeColor: C.inf, src: '', text: '', note: '', canPromote: false, canPin: false, pinLabel: '', path: '', onPromote: () => {}, onPin: () => {}, onForget: () => {} };
908
- const list = det.scope === 'short' ? MEM.short : MEM.long;
909
- const m = list.find((x) => x.id === det.id);
910
- if (m) {
911
- md = {
912
- scope: det.scope === 'short' ? 'short-term' : 'long-term',
913
- scopeColor: det.scope === 'short' ? C.inf : C.suc,
914
- src: m.src + (m.ts ? ' · ' + m.ts : ''), text: m.text,
915
- note: det.scope === 'short'
916
- ? 'Dies when its session exits. Promote it to keep it — the agent will see it in every future run.'
917
- : 'Injected into every agent run. Pinned entries survive compaction; unpinned ones may be summarized away.',
918
- canPromote: det.scope === 'short', canPin: det.scope === 'long',
919
- pinLabel: m.pinned ? '⚑ unpin' : '⚑ pin',
920
- path: det.scope === 'short' ? 'runtime — session context' : 'memory/long-term.jsonl',
921
- onPromote: () => {
922
- const nm = { id: 'p' + m.id, text: m.text, src: 'promoted just now', pinned: false };
923
- setState({ memory: { short: MEM.short.filter((x) => x.id !== m.id), long: [...MEM.long, nm] } },
924
- () => setDetail({ kind: 'mem', id: nm.id, scope: 'long' }));
925
- },
926
- onPin: () => setState({ memory: { ...MEM, long: MEM.long.map((x) => (x.id === m.id ? { ...x, pinned: !x.pinned } : x)) } }),
927
- onForget: () => setState({
928
- memory: det.scope === 'short'
929
- ? { ...MEM, short: MEM.short.filter((x) => x.id !== m.id) }
930
- : { ...MEM, long: MEM.long.filter((x) => x.id !== m.id) },
931
- detail: { kind: 'init' },
932
- }),
933
- };
934
- }
935
-
936
- const out = [];
937
- out.push(el('div', { style: HEAD_BLOCK },
938
- el('div', { style: 'display:flex;gap:8px;align-items:baseline' },
939
- el('span', { style: PILL + ';color:' + md.scopeColor }, iv(md.scope)),
940
- el('span', { style: 'font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(md.src)))));
941
- out.push(el('div', { style: 'padding:12px 14px;border-bottom:1px solid var(--border);font-size:13.5px;line-height:1.55' }, iv(md.text)));
942
- out.push(el('div', { style: 'padding:10px 14px;font-size:11.5px;line-height:1.55;color:var(--muted-foreground)' }, iv(md.note)));
943
- out.push(el('div', { style: 'display:flex;gap:6px;padding:2px 14px 12px;flex-wrap:wrap' },
944
- md.canPromote && el('button', { onClick: md.onPromote, style: BRAND_BTN }, '↥ promote to long-term'),
945
- md.canPin && el('button', { onClick: md.onPin, style: SEC_BTN, hover: ACTION_HOVER }, iv(md.pinLabel)),
946
- el('button', { onClick: md.onForget, style: DESTRUCTIVE_BTN, hover: DESTRUCTIVE_HOVER }, 'forget')));
947
- out.push(el('div', { style: FOOTER }, iv(md.path)));
948
- return out;
949
- }
950
-
951
- /* ── `dProf` (lines 771–800, derivation 1937–1946) ─────────────────────────────────────────── */
44
+ /** The registered profiles: the live data layer's, else (fixture mode only) the seed's. */
45
+ const profileList = () => state.profiles || (FIXTURE ? FX.PROFILES : []);
952
46
 
953
47
  function profBody(det) {
954
- let pf = { id: '', account: '', mcp: [], noMcp: false, usedBy: [], unused: false, note: '' };
48
+ let pf = { id: '', account: '', mcp: [], noMcp: false, note: '' };
955
49
  const x = profileList().find((z) => z.id === det.id);
956
- if (x) {
957
- const usedBy = [
958
- ...agentList().filter((a) => (a.profile || 'claude-work') === x.id).map((a) => ({ t: a.name, k: 'agent' })),
959
- ...schedList().filter((t) => (t.profile || 'claude-work') === x.id).map((t) => ({ t: t.name, k: 'scheduled task' })),
960
- ];
961
- pf = { ...x, noMcp: x.mcp.length === 0, usedBy, unused: usedBy.length === 0 };
962
- }
50
+ if (x) pf = { ...x, noMcp: x.mcp.length === 0 };
963
51
 
964
52
  const out = [];
965
53
  out.push(el('div', { style: HEAD_BLOCK },
@@ -970,116 +58,36 @@ function profBody(det) {
970
58
  el('div', { style: 'display:flex;gap:4px;flex-wrap:wrap;min-width:0' },
971
59
  pf.mcp.map((mc) => el('span', { style: PILL + ';color:var(--info)' }, iv(mc))),
972
60
  pf.noMcp && el('span', { style: ITALIC_EMPTY }, 'none — workspace MCP unavailable on this profile'))));
973
- out.push(el('div', { style: FIELD_ROW },
974
- el('span', { style: fieldLabel(2) }, 'used by'),
975
- el('div', { style: 'display:flex;flex-direction:column;gap:2px;min-width:0' },
976
- pf.usedBy.map((ub) => el('span', { style: 'font-size:12px' },
977
- el('span', { style: 'font-family:var(--mono)' }, iv(ub.t)),
978
- ' ',
979
- el('span', { style: 'color:var(--muted-foreground)' }, '· ', iv(ub.k)))),
980
- pf.unused && el('span', { style: ITALIC_EMPTY }, 'nothing runs on this profile right now'))));
981
61
  out.push(el('div', { style: 'padding:10px 14px;font-size:11.5px;line-height:1.55;color:var(--muted-foreground)' }, iv(pf.note)));
982
62
  out.push(el('div', { style: FOOTER }, '~/.claude/profiles/', iv(pf.id), '.json'));
983
63
  return out;
984
64
  }
985
65
 
986
- /* ── `dIntg` (lines 802–835, derivation 1948–1961) ─────────────────────────────────────────── */
987
-
988
- function intgBody(det) {
989
- let ig = { id: '', name: '', scope: '', st: '', synced: '', does: '', hasPreview: false, previewLabel: '', onPreview: () => {}, onSync: () => {} };
990
- const x = intgList().find((z) => z.id === det.id);
991
- if (x) {
992
- const pv = FX.INTG_PREVIEWS[x.id];
993
- ig = {
994
- ...x,
995
- synced: (state.intgSynced && state.intgSynced[x.id]) || x.synced,
996
- hasPreview: !!pv, previewLabel: pv ? pv[1] : '',
997
- onPreview: () => { if (pv) openDoc(null, pv[0], x.name.toUpperCase()); },
998
- onSync: () => setState((s) => ({ intgSynced: { ...s.intgSynced, [x.id]: '12:41 today' } })),
999
- };
1000
- }
1001
-
1002
- const out = [];
1003
- out.push(el('div', { style: HEAD_BLOCK },
1004
- el('div', { style: 'display:flex;gap:9px;align-items:baseline' },
1005
- el('span', { style: 'flex:none;font-size:10px;color:var(--success)' }, '●'),
1006
- el('span', { style: HEAD_NAME }, iv(ig.name)),
1007
- el('span', { style: 'margin-left:auto;' + PILL + ';color:var(--success)' }, iv(ig.st))),
1008
- el('div', { style: HEAD_SUB }, iv(ig.scope))));
1009
- out.push(el('div', { style: FIELD_ROW },
1010
- el('span', { style: fieldLabel(2) }, 'profile'),
1011
- el('div', { style: 'min-width:0' },
1012
- el('span', { style: 'font-family:var(--mono);font-size:11.5px' }, 'claude-work'),
1013
- el('div', { style: 'font-size:11px;color:var(--muted-foreground);margin-top:2px;line-height:1.5' },
1014
- 'the MCP server runs under claude-work — sessions on claude-personal cannot see it'))));
1015
- out.push(el('div', { style: FIELD_ROW },
1016
- el('span', { style: fieldLabel(2) }, 'synced'),
1017
- el('div', { style: 'display:flex;gap:8px;align-items:center;min-width:0' },
1018
- el('span', { style: 'font-family:var(--mono);font-size:11.5px' }, iv(ig.synced)),
1019
- el('button', {
1020
- onClick: ig.onSync,
1021
- style: 'font-family:var(--mono);font-size:10.5px;padding:4px 10px;min-height:26px;background:var(--secondary);border:1px solid var(--border)',
1022
- hover: ACTION_HOVER,
1023
- }, '⟳ sync now'))));
1024
- out.push(el('div', { style: FIELD_ROW },
1025
- el('span', { style: fieldLabel(2) }, 'syncs'),
1026
- el('span', { style: 'font-size:12px;line-height:1.55;color:var(--muted-foreground)' }, iv(ig.does))));
1027
- if (ig.hasPreview) {
1028
- out.push(el('div', { style: 'padding:12px 14px' },
1029
- el('button', { onClick: ig.onPreview, style: SEC_BTN, hover: ACTION_HOVER }, iv(ig.previewLabel))));
1030
- }
1031
- out.push(el('div', { style: FOOTER }, 'mcp/', iv(ig.id), '.json · via claude-work'));
1032
- return out;
1033
- }
1034
-
1035
- /* ══════════════════════════════════════════════════════════════════════════════════════════════
1036
- * the two entry points
1037
- * ════════════════════════════════════════════════════════════════════════════════════════════ */
66
+ /* ═════════════════════════════════════════════════════════════════════════════════════════════
67
+ * the entry points
68
+ * ═════════════════════════════════════════════════════════════════════════════════════════════ */
1038
69
 
1039
70
  const LISTS = {
1040
- focus: focusList,
1041
- // overhaul-r4: in live mode these three render their server routes; the fixture keeps the prototype's
1042
- scheduled: () => (FIXTURE ? scheduledList() : LIVE.scheduledLive()),
1043
- workflow: workflowList,
1044
- skills: () => (FIXTURE ? skillsList() : LIVE.skillsLive()),
1045
- knowledge: () => (FIXTURE ? knowledgeList() : LIVE.knowledgeLive()),
1046
- portfolio: () => (FIXTURE ? [] : LIVE.portfolioList()),
1047
- guide: () => (FIXTURE ? [] : LIVE.guideList()),
71
+ // live mode only: the Validate page's list. The fixture has none.
1048
72
  validate: () => (FIXTURE ? [] : LIVE.validateList()),
1049
- today: () => (FIXTURE ? [] : LIVE.todayList()),
1050
- agent: agentsList,
1051
- connect: connectList,
1052
- memory: () => (FIXTURE ? memoryList() : LIVE.memoryLive()),
1053
73
  };
1054
74
 
1055
75
  const BODIES = {
1056
- flow: flowBody,
1057
- sched: schedBody,
1058
- skill: (det) => (FIXTURE ? skillBody(det) : LIVE.skillDetailLive(det)),
1059
- know: knowBody,
1060
- agent: agentBody,
1061
- mem: memBody,
1062
76
  prof: profBody,
1063
- intg: intgBody,
1064
77
  };
1065
78
 
1066
- /* WHERE EACH THING OPENS (column-layout). A list or a directory (memory, skills, knowledge, scheduled tasks, focus,
1067
- * portfolio, the guide, the initiatives themselves) is drawn in the navigation column, under the repository list. A
1068
- * detail (an initiative, a skill, a schedule, a memory item) is drawn in the detail column. This table is the rule;
1069
- * a mode or a detail kind that is not in it is a failing test, so a new one has to say which column it opens in. */
79
+ /* WHERE EACH THING OPENS. A list (the initiatives, the Validate page) is drawn in the navigation column; a detail (an
80
+ * initiative, a profile) is drawn in the detail column. This table is the rule; a mode or a detail kind that is not in
81
+ * it is a failing test, so a new one has to say which column it opens in. */
1070
82
  export const MODE_PLACE = {
1071
- lists: { product: 'navigation', focus: 'navigation', scheduled: 'navigation', workflow: 'navigation', skills: 'navigation',
1072
- knowledge: 'navigation', portfolio: 'navigation', guide: 'navigation', validate: 'navigation', today: 'navigation', agent: 'navigation', connect: 'navigation', memory: 'navigation' },
1073
- details: { init: 'detail', flow: 'detail', sched: 'detail', skill: 'detail', know: 'detail', agent: 'detail', mem: 'detail',
1074
- prof: 'detail', intg: 'detail', addprod: 'detail', bind: 'detail', new: 'detail', none: 'detail' },
83
+ lists: { product: 'navigation', validate: 'navigation' },
84
+ details: { init: 'detail', prof: 'detail', addprod: 'detail', bind: 'detail', new: 'detail', none: 'detail' },
1075
85
  };
1076
- /* WHAT A ROW IN EACH LIST OPENS, as the live app does it today. An initiative (from the board, the portfolio or the focus
1077
- * queue) opens in the detail column. A file-like row (a skill, a knowledge note, a guide page) opens in a TAB, like any
1078
- * other document, so it can sit beside a terminal. A schedule and a memory entry are acted on inside the list itself. */
86
+ /* WHAT A ROW IN EACH LIST OPENS. An initiative opens in the detail column; the Validate page opens in a tab, like any other
87
+ * document, so it can sit beside a terminal. */
1079
88
  export const ROW_OPENS = {
1080
- product: 'detail', portfolio: 'detail', focus: 'detail',
1081
- skills: 'tab', knowledge: 'tab', guide: 'tab', validate: 'tab',
1082
- scheduled: 'list', memory: 'list', today: 'list',
89
+ product: 'detail',
90
+ validate: 'tab',
1083
91
  };
1084
92
  /** The column a list mode opens in: always `navigation`. `null` for a mode this table does not know. */
1085
93
  export const placeOfMode = (mode) => MODE_PLACE.lists[mode] || null;