create-pmos 0.52.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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/mods/pmos-cockpit/README.md +13 -0
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
- package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/plan-items-stage.js +271 -0
- package/payload/scripts/pmos-log.sh +18 -7
- package/payload/scripts/workflow-state.js +28 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/bin/pmos.js +15 -1
- package/workspace/cut-stub.js +40 -0
- package/workspace/first-paint-audit.js +145 -0
- package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
- package/workspace/fixtures/initiatives-api/list.json +176 -0
- package/workspace/fixtures/initiatives-api/post-403.json +5 -0
- package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
- package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
- package/workspace/fixtures/initiatives-api/sheet.json +112 -0
- package/workspace/fixtures/report-api/draft.json +177 -0
- package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
- package/workspace/fixtures/today-api/provider-rows.json +204 -0
- package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
- package/workspace/guide/board.md +81 -0
- package/workspace/guide/breakdown.md +154 -0
- package/workspace/guide/command-palette.md +75 -0
- package/workspace/guide/index.md +27 -17
- package/workspace/guide/initiatives-roadmap.md +130 -0
- package/workspace/guide/initiatives-screen.md +89 -0
- package/workspace/guide/initiatives-timeline.md +57 -0
- package/workspace/guide/integrated-work.md +94 -0
- package/workspace/guide/knowledge-graph.md +3 -68
- package/workspace/guide/layout.md +98 -32
- package/workspace/guide/live-notes.md +3 -3
- package/workspace/guide/pm-only-acts.md +89 -0
- package/workspace/guide/pm-only-lines.md +61 -0
- package/workspace/guide/reports-page.md +80 -0
- package/workspace/guide/reports.md +73 -0
- package/workspace/guide/timeline.md +70 -0
- package/workspace/guide/today.md +85 -0
- package/workspace/guide/validate-with-typesafe.md +1 -1
- package/workspace/lib/actor.js +91 -0
- package/workspace/lib/board-cache.js +32 -2
- package/workspace/lib/board-cli.js +108 -0
- package/workspace/lib/board-fold.js +232 -0
- package/workspace/lib/breakdown-export.js +80 -0
- package/workspace/lib/capture.js +98 -0
- package/workspace/lib/engine/plan-items-stage.js +271 -0
- package/workspace/lib/engine/workflow-state.js +28 -2
- package/workspace/lib/factory-join.js +158 -0
- package/workspace/lib/grant-mint.js +113 -0
- package/workspace/lib/health-rules-factory.js +92 -0
- package/workspace/lib/health-rules-plan.js +164 -0
- package/workspace/lib/health.js +79 -0
- package/workspace/lib/initiative-cli.js +222 -0
- package/workspace/lib/initiative-display.js +183 -0
- package/workspace/lib/initiative-needs.js +127 -0
- package/workspace/lib/initiative-rows.js +206 -0
- package/workspace/lib/initiative-writer.js +141 -0
- package/workspace/lib/issues.js +206 -0
- package/workspace/lib/item-cli.js +195 -0
- package/workspace/lib/item-spans.js +137 -0
- package/workspace/lib/measure-cli.js +100 -0
- package/workspace/lib/measurements.js +189 -0
- package/workspace/lib/mobile-nav.js +57 -53
- package/workspace/lib/needs-providers.js +120 -0
- package/workspace/lib/placements.js +440 -0
- package/workspace/lib/plan-items.js +922 -0
- package/workspace/lib/plan-ledgers.js +85 -0
- package/workspace/lib/plan-rules.js +72 -0
- package/workspace/lib/plan-tree.js +367 -0
- package/workspace/lib/pm-guard.js +174 -0
- package/workspace/lib/pm-lines-needs.js +331 -0
- package/workspace/lib/pmos-verbs.js +66 -0
- package/workspace/lib/proactive.js +7 -4
- package/workspace/lib/report-cli.js +152 -0
- package/workspace/lib/report-draft.js +579 -0
- package/workspace/lib/report-needs.js +213 -0
- package/workspace/lib/route-modules.js +128 -0
- package/workspace/lib/routes-board.js +91 -0
- package/workspace/lib/routes-capture.js +52 -0
- package/workspace/lib/routes-initiatives.js +371 -0
- package/workspace/lib/routes-reports.js +163 -0
- package/workspace/lib/routes-timeline.js +197 -0
- package/workspace/lib/routes-today.js +32 -0
- package/workspace/lib/today-cli.js +99 -0
- package/workspace/lib/today.js +333 -0
- package/workspace/lib/triage.js +163 -0
- package/workspace/lib/verify-work-cli.js +81 -0
- package/workspace/lib/work-check-needs.js +99 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +4 -5
- package/workspace/public/index.html +29 -19
- package/workspace/public/pm/app.js +299 -30
- package/workspace/public/pm/bd-board.js +113 -0
- package/workspace/public/pm/bd-drag.js +59 -0
- package/workspace/public/pm/bd-model.js +152 -0
- package/workspace/public/pm/bd-timeline.js +233 -0
- package/workspace/public/pm/bd-tree.js +244 -0
- package/workspace/public/pm/board-copy.js +57 -0
- package/workspace/public/pm/breakdown-api.js +58 -0
- package/workspace/public/pm/breakdown-copy.js +138 -0
- package/workspace/public/pm/components.css +3054 -0
- package/workspace/public/pm/cycle-review.js +125 -0
- package/workspace/public/pm/data.js +40 -187
- package/workspace/public/pm/dest-board.js +438 -0
- package/workspace/public/pm/dest-initiatives.js +134 -0
- package/workspace/public/pm/dest-reports.js +270 -0
- package/workspace/public/pm/dest-settings.js +217 -0
- package/workspace/public/pm/dest-today.js +176 -0
- package/workspace/public/pm/dialogs.js +88 -0
- package/workspace/public/pm/display-word.js +56 -0
- package/workspace/public/pm/dock.js +105 -0
- package/workspace/public/pm/fixture.js +8 -227
- package/workspace/public/pm/frame-copy.js +94 -0
- package/workspace/public/pm/handoff-box.js +82 -0
- package/workspace/public/pm/http.js +88 -0
- package/workspace/public/pm/icons.js +53 -0
- package/workspace/public/pm/initiative-list.js +218 -0
- package/workspace/public/pm/initiative-sheet.js +261 -0
- package/workspace/public/pm/initiatives-api.js +59 -0
- package/workspace/public/pm/initiatives-copy.js +215 -0
- package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
- package/workspace/public/pm/initiatives-timeline.js +410 -0
- package/workspace/public/pm/initiatives-ui.js +123 -0
- package/workspace/public/pm/item-sheet.js +251 -0
- package/workspace/public/pm/keep-card.js +204 -0
- package/workspace/public/pm/lifecycle.js +7 -1
- package/workspace/public/pm/mobile.css +49 -20
- package/workspace/public/pm/mobile.js +157 -29
- package/workspace/public/pm/modes-live.js +7 -883
- package/workspace/public/pm/modes.js +32 -1024
- package/workspace/public/pm/new-initiative-sheet.js +74 -0
- package/workspace/public/pm/next-step-ui.js +3 -2
- package/workspace/public/pm/palette-copy.js +51 -0
- package/workspace/public/pm/palette.js +312 -0
- package/workspace/public/pm/panel-breakdown.js +702 -0
- package/workspace/public/pm/panel-cockpit.js +63 -295
- package/workspace/public/pm/panel-detail.js +25 -172
- package/workspace/public/pm/panel-initiatives.js +1 -19
- package/workspace/public/pm/panel-repos.js +31 -81
- package/workspace/public/pm/panel-tabs.js +197 -110
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/portfolio-report.js +61 -0
- package/workspace/public/pm/primitives.js +656 -0
- package/workspace/public/pm/print.css +33 -0
- package/workspace/public/pm/provider-cards.js +136 -0
- package/workspace/public/pm/registry.js +194 -0
- package/workspace/public/pm/release-card.js +110 -0
- package/workspace/public/pm/report-view.js +309 -0
- package/workspace/public/pm/reports-copy.js +114 -0
- package/workspace/public/pm/roadmap.js +271 -0
- package/workspace/public/pm/sig-watch.js +41 -0
- package/workspace/public/pm/state.js +8 -4
- package/workspace/public/pm/status-banner.js +13 -5
- package/workspace/public/pm/tab-doc.js +2 -4
- package/workspace/public/pm/tab-term.js +12 -2
- package/workspace/public/pm/tab-web.js +3 -2
- package/workspace/public/pm/timeline-copy.js +48 -0
- package/workspace/public/pm/today-cards.js +244 -0
- package/workspace/public/pm/today-copy.js +97 -0
- package/workspace/public/pm/tokens.css +16 -18
- package/workspace/public/pm/topbar.js +102 -0
- package/workspace/public/pm/triage-card.js +60 -0
- package/workspace/public/pm/undo-hold.js +103 -0
- package/workspace/public/pm/view-addrepo.js +2 -1
- package/workspace/public/pm/view-livenotes.js +3 -1
- package/workspace/public/pm/view-today.js +7 -162
- package/workspace/public/vendor/Sortable.min.js +2 -0
- package/workspace/public/vendor/lucide-sprite.js +79 -0
- package/workspace/replaced-tests.json +2864 -0
- package/workspace/run-tests.js +56 -5
- package/workspace/server.js +312 -36
- package/workspace/sheet-answer.js +61 -0
- package/workspace/skills/measure.skill +41 -0
- package/workspace/skills/report-biweekly.skill +23 -25
- package/workspace/skills/tracker-refresh.skill +50 -0
- package/workspace/test-files.json +19 -3
- package/workspace/test-lists/base.json +6 -0
- package/workspace/test-lists/run2-a.json +6 -0
- package/workspace/test-lists/run2-b.json +13 -0
- package/workspace/test-lists/run2-c.json +19 -0
- package/workspace/test-lists/run2-d.json +22 -0
- package/workspace/test-lists/run3-0.json +5 -0
- package/workspace/test-lists/run3-a.json +5 -0
- package/workspace/test-lists/run3-b.json +6 -0
- package/workspace/test-lists/run3-c.json +15 -0
- package/workspace/test-lists/run3-d.json +6 -0
- package/workspace/test-lists/run4-a.json +8 -0
- package/workspace/test-lists/run4-b.json +6 -0
- package/workspace/test-lists/run4-c.json +13 -0
- package/workspace/test-lists/run4-d.json +9 -0
- package/workspace/test-lists/run5-a.json +6 -0
- package/workspace/test-lists/run5-b.json +6 -0
- package/workspace/test-lists/run5-c.json +15 -0
- package/workspace/test-run.js +27 -1
- package/workspace/third-party/README.md +44 -0
- package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
- package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
- package/workspace/design-parity.js +0 -673
- package/workspace/public/pm/tab-task.js +0 -86
- package/workspace/public/pm/view-guide.js +0 -141
- package/workspace/public/pm/view-knowledge.js +0 -225
- package/workspace/public/pm/view-memory.js +0 -122
- package/workspace/public/pm/view-portfolio.js +0 -153
- package/workspace/public/pm/view-reports.js +0 -190
- package/workspace/public/pm/view-scheduled.js +0 -161
- package/workspace/public/pm/view-skills.js +0 -216
|
@@ -1,98 +1,37 @@
|
|
|
1
|
-
/* pm/modes.js — the
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
|
|
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,
|
|
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
|
-
/*
|
|
987
|
-
|
|
988
|
-
|
|
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
|
-
|
|
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
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
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',
|
|
1072
|
-
|
|
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
|
|
1077
|
-
*
|
|
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',
|
|
1081
|
-
|
|
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;
|