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
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ── What this panel is ──────────────────────────────────────────────────────────────────────
|
|
10
10
|
* Panel 3 answers one question about one initiative: what is the state of the work, and what can
|
|
11
|
-
* I open? Top to bottom: the slug + tag + pills header,
|
|
12
|
-
*
|
|
13
|
-
* files), ARTIFACTS (repo files, each optionally linked to an external destination with a
|
|
11
|
+
* I open? Top to bottom: the slug + tag + pills header, LINKS (external references that are deliberately
|
|
12
|
+
* NOT repo files), ARTIFACTS (repo files, each optionally linked to an external destination with a
|
|
14
13
|
* repo-is-source sync panel), and the derived footer line.
|
|
15
14
|
*
|
|
16
15
|
* ── Two structural facts the port has to honour ─────────────────────────────────────────────
|
|
@@ -22,11 +21,10 @@
|
|
|
22
21
|
* reproduces the wrapper; bare text is used exactly where the template has bare text — `+`,
|
|
23
22
|
* `⇄`, `·`, `→ repo`, `adopt`, `link`, `next`, the note lines and the two headings.
|
|
24
23
|
*
|
|
25
|
-
* 2. NOTHING DERIVABLE IS STORED (D33).
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* lies the moment a task moves.
|
|
24
|
+
* 2. NOTHING DERIVABLE IS STORED (D33). Link lists and the footer line are all computed at read time,
|
|
25
|
+
* every render, from the initiative record plus the state maps the prototype keeps (`initLinks`,
|
|
26
|
+
* `artLinks`, `artSyncOpen`/`artSyncState`). The work itself (items, their state) is the Breakdown's;
|
|
27
|
+
* the phases with synthetic rung tasks are cut (R29), and so is the Task tab that opened them.
|
|
30
28
|
*
|
|
31
29
|
* ── Where the data comes from ───────────────────────────────────────────────────────────────
|
|
32
30
|
* Fixture mode reads `fixture.js` directly. Normal mode is the same code path with the optional
|
|
@@ -53,12 +51,15 @@ import { el, setChildren } from './dom.js';
|
|
|
53
51
|
import { state, setState, FIXTURE, LEGACY_LAYOUT_FLAG } from './state.js';
|
|
54
52
|
import { isMobile } from './mobile.js';
|
|
55
53
|
import * as FX from './fixture.js';
|
|
56
|
-
import { openDoc,
|
|
54
|
+
import { openDoc, openTerm, fileNewModel } from './panel-initiatives.js';
|
|
57
55
|
import { addProductBody } from './panel-repos.js';
|
|
58
56
|
import { lifecycleRow } from './lifecycle.js';
|
|
59
57
|
import { statusBanner, lifecycleNotes } from './status-banner.js';
|
|
60
58
|
import { stepActions } from './next-step-ui.js';
|
|
61
59
|
import { boardOf, records, currentProduct } from './board-read.js';
|
|
60
|
+
import { breakdownEntry } from './panel-breakdown.js';
|
|
61
|
+
import { askConfirm } from './dialogs.js';
|
|
62
|
+
import { button } from './primitives.js';
|
|
62
63
|
|
|
63
64
|
/** The cockpit's own look of this panel (workspace-cockpit-nav): the mockup's header, chips and phase rows. A phone and `?layout=legacy`
|
|
64
65
|
* keep the prototype's look, which the older suites and the phone still read. */
|
|
@@ -92,13 +93,6 @@ function sibling(key, path) {
|
|
|
92
93
|
return null;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
/** Open a task tab: panel 4's own opener when it is reachable, the shared one otherwise. */
|
|
96
|
-
function openTask(tab) {
|
|
97
|
-
const tabs = sibling('tabs', './panel-tabs.js');
|
|
98
|
-
if (tabs && typeof tabs.openTask === 'function') { tabs.openTask(tab); return; }
|
|
99
|
-
openTab(tab);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
96
|
/**
|
|
103
97
|
* Record a link declaration in file-mode state, if the data layer offers that write. A link is a
|
|
104
98
|
* DECLARATION — `{artifact_path, kind, target}` — never an outbound call: PMOS holds no credential
|
|
@@ -310,8 +304,10 @@ function lanePicker(slug, rec) {
|
|
|
310
304
|
onChange: async (e) => {
|
|
311
305
|
const next = e.target.value;
|
|
312
306
|
if (next === rec.lane) return;
|
|
313
|
-
const ok =
|
|
314
|
-
:
|
|
307
|
+
const ok = await askConfirm({
|
|
308
|
+
title: 'Change “' + slug + '” to the ' + next + ' lane?', confirmLabel: 'Change lane',
|
|
309
|
+
body: 'Change “' + slug + '” from the ' + rec.lane + ' lane to the ' + next + ' lane?\n\nThe lane decides which steps the workflow ladder requires (spec, rubric, explainer, …). Only the lane: line of the record changes; the steps are re-derived and some may show as missing.',
|
|
310
|
+
});
|
|
315
311
|
if (!ok) { e.target.value = rec.lane; return; }
|
|
316
312
|
const data = sibling('data', './data.js');
|
|
317
313
|
const out = data && data.setInitiativeLane ? await data.setInitiativeLane(slug, next) : null;
|
|
@@ -369,16 +365,6 @@ function noLadderBlock(ansBg, ansC, ansText, noLadderText) {
|
|
|
369
365
|
];
|
|
370
366
|
}
|
|
371
367
|
|
|
372
|
-
/* ── 4 · TASK PHASES (lines 357–378, derivation 1719–1756) ──────────────────────────────────
|
|
373
|
-
* The phase set is the initiative's workflow — one phase per engine rung. Its tasks are the
|
|
374
|
-
* record's authored `tasks[<phase>]` when present, otherwise the three derived from the rung's own
|
|
375
|
-
* evidence (`synthTasks`). Nothing is invented per initiative: a rung with no evidence reads
|
|
376
|
-
* `todo`, never `done`.
|
|
377
|
-
*
|
|
378
|
-
* Phase status is derived from its tasks, in this order: any blocked → blocked; any doing → doing;
|
|
379
|
-
* all done → done; some done → doing; else todo. Collapse is per phase PER INITIATIVE (`pkey`), so
|
|
380
|
-
* folding `build` on one initiative does not fold it on the next.
|
|
381
|
-
*/
|
|
382
368
|
/**
|
|
383
369
|
* What the documents say, beside the ladder and never in it (status-from-artifacts, Run 1). One plain
|
|
384
370
|
* line each, with its basis. A flag colours its own dot only; no phase, count or total reads it.
|
|
@@ -397,55 +383,6 @@ function factsBlock(rec) {
|
|
|
397
383
|
)));
|
|
398
384
|
}
|
|
399
385
|
|
|
400
|
-
function phaseBlock(slug, rec) {
|
|
401
|
-
const phases = FX.phasesFor(rec, slug);
|
|
402
|
-
if (!phases.length) return null;
|
|
403
|
-
const collP = state.collapsedPhases || {};
|
|
404
|
-
const repo = repoOf(slug);
|
|
405
|
-
|
|
406
|
-
return el('div', { style: 'padding:2px 0 6px' }, phases.map((ph) => {
|
|
407
|
-
const pkey = slug + '|' + ph.key;
|
|
408
|
-
const open = !collP[pkey];
|
|
409
|
-
const [phDot, phColor] = FX.TDOT[ph.status];
|
|
410
|
-
|
|
411
|
-
const head = el('button', {
|
|
412
|
-
onClick: () => setState((s) => ({
|
|
413
|
-
collapsedPhases: { ...s.collapsedPhases, [pkey]: !((s.collapsedPhases || {})[pkey]) },
|
|
414
|
-
})),
|
|
415
|
-
style: 'display:flex;align-items:center;gap:8px;width:100%;padding:9px 14px 8px',
|
|
416
|
-
hover: 'background:var(--accent)',
|
|
417
|
-
},
|
|
418
|
-
el('span', { style: 'flex:none;width:10px;font-size:9px;color:var(--muted-foreground)' }, iv(open ? '▾' : '▸')),
|
|
419
|
-
el('span', { style: 'flex:none;width:13px;text-align:center;font-size:11px;color:' + phColor }, iv(phDot)),
|
|
420
|
-
el('span', { style: 'font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--foreground)' }, iv(ph.label)),
|
|
421
|
-
el('span', { style: 'margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(ph.count)),
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
const rows = open ? ph.tasks.map((t, ti) => {
|
|
425
|
-
const tid = FX.taskTabId(slug, ph.key, ti);
|
|
426
|
-
const active = state.active === tid;
|
|
427
|
-
const [dot, color] = FX.TDOT[t.status] || FX.TDOT.todo;
|
|
428
|
-
return el('button', {
|
|
429
|
-
onClick: () => openTask({
|
|
430
|
-
id: tid, kind: 'task', role: 'TASK', title: t.title, slug, repo, phase: ph.key,
|
|
431
|
-
status: t.status, instruction: t.instruction, input: t.input, expect: t.expect,
|
|
432
|
-
evalNote: t.evalNote, output: t.output,
|
|
433
|
-
}),
|
|
434
|
-
style: 'display:flex;align-items:center;gap:9px;width:100%;padding:6px 14px 6px 32px;min-height:34px;border-top:1px solid oklch(1 0 0 / 4%);background:'
|
|
435
|
-
+ (active ? 'var(--accent)' : 'transparent')
|
|
436
|
-
+ ';border-left:2px solid ' + (active ? 'var(--brand)' : 'transparent'),
|
|
437
|
-
hover: 'background:var(--accent)',
|
|
438
|
-
},
|
|
439
|
-
el('span', { style: 'flex:none;width:13px;text-align:center;font-size:11px;color:' + color }, iv(dot)),
|
|
440
|
-
el('span', { style: 'flex:1;min-width:0;font-size:12.5px;color:var(--foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(t.title)),
|
|
441
|
-
el('span', { style: 'flex:none;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, '↗'),
|
|
442
|
-
);
|
|
443
|
-
}) : [];
|
|
444
|
-
|
|
445
|
-
return el('div', { style: 'border-bottom:1px solid oklch(1 0 0 / 5%)' }, head, rows);
|
|
446
|
-
}));
|
|
447
|
-
}
|
|
448
|
-
|
|
449
386
|
/* ── 5a · LINKS (lines 380–398, derivation 1757–1766, 1797–1798) ────────────────────────────
|
|
450
387
|
* External references — NOT repo files.
|
|
451
388
|
*
|
|
@@ -633,10 +570,7 @@ function linksRow(slug) {
|
|
|
633
570
|
style: 'width:100%;font-family:var(--mono);font-size:11.5px;padding:6px 8px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
|
|
634
571
|
}),
|
|
635
572
|
el('div', { style: 'display:flex;gap:6px;margin-top:8px;flex-wrap:wrap' },
|
|
636
|
-
|
|
637
|
-
onClick: () => submitTyped(openSrc, read()),
|
|
638
|
-
style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500',
|
|
639
|
-
}, 'record this reference'),
|
|
573
|
+
button({ label: 'record this reference', variant: 'primary', size: 'sm', onClick: () => submitTyped(openSrc, read()) }),
|
|
640
574
|
resolver && el('button', {
|
|
641
575
|
onClick: () => askSessionToResolve(slug, openSrc, resolver, read()),
|
|
642
576
|
style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
|
|
@@ -829,10 +763,7 @@ function artifactRow(slug, a, noHead) {
|
|
|
829
763
|
style: 'width:100%;font-family:var(--mono);font-size:11px;padding:5px 7px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
|
|
830
764
|
}),
|
|
831
765
|
el('div', { style: 'display:flex;gap:6px;margin-top:7px;flex-wrap:wrap' },
|
|
832
|
-
|
|
833
|
-
onClick: () => submit(kind),
|
|
834
|
-
style: 'font-family:var(--mono);font-size:10px;padding:4px 9px;min-height:26px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500',
|
|
835
|
-
}, 'record'),
|
|
766
|
+
button({ label: 'record', variant: 'primary', size: 'sm', onClick: () => submit(kind) }),
|
|
836
767
|
el('button', {
|
|
837
768
|
onClick: () => setState((s) => ({ artLinkForm: { ...s.artLinkForm, [key]: null } })),
|
|
838
769
|
style: 'font-family:var(--mono);font-size:10px;padding:4px 9px;min-height:26px;background:transparent;border:1px solid var(--border);color:var(--muted-foreground)',
|
|
@@ -917,10 +848,7 @@ function artifactRow(slug, a, noHead) {
|
|
|
917
848
|
el('div', { style: 'font-size:12px;line-height:1.5;color:var(--muted-foreground)' },
|
|
918
849
|
iv(live ? FX.artDiffText(dest) : NO_CONNECTOR)),
|
|
919
850
|
el('div', { style: 'display:flex;gap:6px;margin-top:8px;flex-wrap:wrap' },
|
|
920
|
-
|
|
921
|
-
onClick: decide(FX.artSyncPushed), disabled: inert,
|
|
922
|
-
style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500' + off,
|
|
923
|
-
}, 'repo is source → push'),
|
|
851
|
+
button({ label: 'repo is source → push', variant: 'primary', size: 'sm', disabled: inert, onClick: decide(FX.artSyncPushed) }),
|
|
924
852
|
el('button', {
|
|
925
853
|
onClick: decide(FX.artSyncPulled), disabled: inert,
|
|
926
854
|
style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)' + off,
|
|
@@ -972,14 +900,7 @@ function realSyncPanel(slug, a, key, dest) {
|
|
|
972
900
|
})));
|
|
973
901
|
};
|
|
974
902
|
|
|
975
|
-
const btn = (label, primary, onClick) =>
|
|
976
|
-
onClick,
|
|
977
|
-
style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;'
|
|
978
|
-
+ (primary
|
|
979
|
-
? 'background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500'
|
|
980
|
-
: 'background:var(--secondary);border:1px solid var(--border);color:var(--foreground)'),
|
|
981
|
-
hover: primary ? null : 'border-color:var(--ring)',
|
|
982
|
-
}, label);
|
|
903
|
+
const btn = (label, primary, onClick) => button({ label, variant: primary ? 'primary' : 'outline', size: 'sm', onClick });
|
|
983
904
|
|
|
984
905
|
return el('div', { style: 'margin-top:6px;padding:9px 11px;border:1px solid var(--border);background:var(--background)' },
|
|
985
906
|
el('div', { style: 'font-size:12px;line-height:1.5;color:var(--muted-foreground)' },
|
|
@@ -1177,58 +1098,6 @@ function cockpitTitle(name, tag, pills) {
|
|
|
1177
1098
|
];
|
|
1178
1099
|
}
|
|
1179
1100
|
|
|
1180
|
-
const CK_PH_DOT = { done: 'var(--success)', blocked: 'var(--destructive)', doing: 'var(--info)', todo: 'var(--muted-foreground)' };
|
|
1181
|
-
|
|
1182
|
-
/** The phases as the mockup draws them: a row per phase with its count, its tasks beneath while it is open. */
|
|
1183
|
-
function phaseBlockCockpit(slug, rec) {
|
|
1184
|
-
const phases = FX.phasesFor(rec, slug);
|
|
1185
|
-
if (!phases.length) return null;
|
|
1186
|
-
const collP = state.collapsedPhases || {};
|
|
1187
|
-
const repo = repoOf(slug);
|
|
1188
|
-
/* Run 4 (the mockup): only the phase the initiative is at opens by itself — the same pointer the breadcrumb uses (the first
|
|
1189
|
-
* blocked step, else the current one). Every other phase shows its row and its count, and opens on a click. A phase the PM has
|
|
1190
|
-
* opened or folded keeps that choice (`collapsedPhases` holds true or false for it). */
|
|
1191
|
-
const stepAt = Array.isArray(rec.steps) ? FX.targetStepIndex(rec.steps) : -1;
|
|
1192
|
-
const atKey = stepAt >= 0 && rec.steps[stepAt] ? rec.steps[stepAt].k : null;
|
|
1193
|
-
const anyTaskTab = (state.tabs || []).some((t) => t.id === state.active && t.kind === 'task' && t.slug === slug);
|
|
1194
|
-
return el('div', { 'data-cockpit': 'phases', style: 'display:flex;flex-direction:column;gap:2px' }, phases.map((ph) => {
|
|
1195
|
-
const pkey = slug + '|' + ph.key;
|
|
1196
|
-
const open = Object.prototype.hasOwnProperty.call(collP, pkey) ? !collP[pkey] : ph.key === atKey;
|
|
1197
|
-
// the task the initiative is at: the first blocked (else in-progress) task of that phase, marked while no task tab is in front
|
|
1198
|
-
const atTask = ph.key === atKey && !anyTaskTab ? ph.tasks.findIndex((t) => t.status === 'blocked') : -1;
|
|
1199
|
-
const atIdx = atTask >= 0 ? atTask : (ph.key === atKey && !anyTaskTab ? ph.tasks.findIndex((t) => t.status === 'doing') : -1);
|
|
1200
|
-
const head = el('button', {
|
|
1201
|
-
onClick: () => setState((s) => ({ collapsedPhases: { ...s.collapsedPhases, [pkey]: open } })),
|
|
1202
|
-
style: 'display:flex;align-items:center;gap:8px;min-height:32px;font:500 11px var(--mono);letter-spacing:.08em;text-transform:uppercase;text-align:left;width:100%',
|
|
1203
|
-
},
|
|
1204
|
-
el('span', null, open ? '▾' : '▸'),
|
|
1205
|
-
el('span', { style: 'width:8px;height:8px;border-radius:50%;display:inline-block;flex-shrink:0;background:' + (CK_PH_DOT[ph.status] || CK_PH_DOT.todo) }),
|
|
1206
|
-
ph.label.charAt(0).toUpperCase() + ph.label.slice(1),
|
|
1207
|
-
el('span', { style: 'margin-left:auto;color:var(--muted-foreground)' }, iv(ph.count)),
|
|
1208
|
-
);
|
|
1209
|
-
const rows = open ? ph.tasks.map((t, ti) => {
|
|
1210
|
-
const tid = FX.taskTabId(slug, ph.key, ti);
|
|
1211
|
-
const active = state.active === tid || ti === atIdx;
|
|
1212
|
-
const [glyph, color] = FX.TDOT[t.status] || FX.TDOT.todo;
|
|
1213
|
-
return el('button', {
|
|
1214
|
-
onClick: () => openTask({
|
|
1215
|
-
id: tid, kind: 'task', role: 'TASK', title: t.title, slug, repo, phase: ph.key,
|
|
1216
|
-
status: t.status, instruction: t.instruction, input: t.input, expect: t.expect,
|
|
1217
|
-
evalNote: t.evalNote, output: t.output,
|
|
1218
|
-
}),
|
|
1219
|
-
'aria-current': ti === atIdx ? 'step' : null,
|
|
1220
|
-
style: 'display:flex;align-items:center;gap:10px;min-height:34px;padding:0 8px 0 24px;border-radius:8px;font:400 13px var(--sans);text-align:left;width:100%;background:' + (active ? 'var(--accent)' : 'transparent'),
|
|
1221
|
-
hover: 'background:var(--accent)',
|
|
1222
|
-
},
|
|
1223
|
-
el('span', { style: 'color:' + color }, glyph),
|
|
1224
|
-
el('span', { style: 'flex-grow:1' }, iv(t.title)),
|
|
1225
|
-
el('span', { style: 'color:var(--muted-foreground)' }, '↗'),
|
|
1226
|
-
);
|
|
1227
|
-
}) : [];
|
|
1228
|
-
return [head, rows];
|
|
1229
|
-
}));
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
1101
|
/** The blocker card (workspace-cockpit-nav): drawn only when a rung of the ladder is blocking, in the engine's own words. The cockpit
|
|
1233
1102
|
* draws it as the mockup's row: the words on the left, and on the right the slot of the Grant button, which stays empty until the
|
|
1234
1103
|
* grants ledger behind it exists (PRD Q3) — the slot keeps the card's height, so nothing moves when the button arrives. */
|
|
@@ -1275,6 +1144,7 @@ function initBody(slug) {
|
|
|
1275
1144
|
out.push(headerBlock(slug, rec.tag || '', rec.pills || [], rec.crumb || null, lanePicker(slug, rec)));
|
|
1276
1145
|
if (rec.blocker) out.push(el('div', { style: 'margin:12px 14px 0' }, blockerCard(rec.blocker)));
|
|
1277
1146
|
}
|
|
1147
|
+
{ const bd = breakdownEntry(slug, rec, repoOf(slug)); if (bd) out.push(bd); }
|
|
1278
1148
|
{ const sb = statusBanner(repoOf(slug), slug, rec); if (sb) out.push(sb); }
|
|
1279
1149
|
{ const lc = lifecycleRow(repoOf(slug), slug); if (lc) { out.push(lc); const ln = lifecycleNotes(rec); if (ln) out.push(ln); } }
|
|
1280
1150
|
|
|
@@ -1291,8 +1161,6 @@ function initBody(slug) {
|
|
|
1291
1161
|
const nextB = nextStepsBlock(slug, rec);
|
|
1292
1162
|
if (nextB) out.push(nextB);
|
|
1293
1163
|
|
|
1294
|
-
if (!ck) { const phases = phaseBlock(slug, rec); if (phases) out.push(phases); }
|
|
1295
|
-
|
|
1296
1164
|
{ const fb = factsBlock(rec); if (fb) out.push(fb); }
|
|
1297
1165
|
|
|
1298
1166
|
out.push(el('div', { style: 'border-top:1px solid var(--border)' },
|
|
@@ -1312,10 +1180,9 @@ function initBody(slug) {
|
|
|
1312
1180
|
* column, so they keep it (`FULL`). */
|
|
1313
1181
|
const FULL = 'margin:0 -16px';
|
|
1314
1182
|
function cockpitInitBody(slug, rec) {
|
|
1315
|
-
const ph = phaseBlockCockpit(slug, rec);
|
|
1316
1183
|
const out = [...cockpitTitle(slug, rec.tag || '', rec.pills || [])];
|
|
1317
1184
|
if (rec.blocker) out.push(blockerCard(rec.blocker));
|
|
1318
|
-
if (
|
|
1185
|
+
{ const bd = breakdownEntry(slug, rec, repoOf(slug)); if (bd) out.push(bd); }
|
|
1319
1186
|
const more = [crumbBlock(rec.crumb || null), lanePicker(slug, rec)].filter(Boolean);
|
|
1320
1187
|
if (more.length) out.push(el('div', null, more));
|
|
1321
1188
|
const wide = [];
|
|
@@ -1567,10 +1434,7 @@ function fileNewBody() {
|
|
|
1567
1434
|
}
|
|
1568
1435
|
|
|
1569
1436
|
out.push(el('div', { style: 'padding:8px 14px 12px;border-bottom:1px solid var(--border)' },
|
|
1570
|
-
|
|
1571
|
-
onClick: nw.onCreate,
|
|
1572
|
-
style: 'padding:5px 12px;min-height:30px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-size:12px;font-weight:500',
|
|
1573
|
-
}, iv(nw.createLabel)),
|
|
1437
|
+
button({ label: nw.createLabel, variant: 'primary', onClick: nw.onCreate }),
|
|
1574
1438
|
));
|
|
1575
1439
|
|
|
1576
1440
|
out.push(el('div', { style: 'margin-top:auto;padding:8px 14px;border-top:1px solid var(--border);font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' },
|
|
@@ -1625,25 +1489,14 @@ export function renderDetail(host) {
|
|
|
1625
1489
|
else if (kind === 'none') body = [noneBody()];
|
|
1626
1490
|
else body = modeDetailBody(kind);
|
|
1627
1491
|
|
|
1628
|
-
// the cockpit draws an initiative without the panel's own title bar (the top bar's breadcrumb names it)
|
|
1492
|
+
// the cockpit draws an initiative without the panel's own title bar (the top bar's breadcrumb names it)
|
|
1629
1493
|
const bare = cockpitLook() && kind === 'init';
|
|
1630
|
-
/* The cockpit's column
|
|
1631
|
-
* gap between sections. The legacy shell
|
|
1632
|
-
const ckCol = cockpitLook()
|
|
1494
|
+
/* The cockpit's column is the mockup's: the page's own background, and for an initiative 16px of padding and a 12px
|
|
1495
|
+
* gap between sections. The legacy shell and a phone keep the panel's sidebar fill and draw their own padding. */
|
|
1496
|
+
const ckCol = cockpitLook();
|
|
1633
1497
|
host.style.background = ckCol ? 'transparent' : 'var(--sidebar)';
|
|
1634
1498
|
host.style.padding = ckCol && bare ? '16px' : '';
|
|
1635
1499
|
host.style.gap = ckCol && bare ? '12px' : '';
|
|
1636
|
-
if (bare && state.layoutFocus) {
|
|
1637
|
-
// Focus (Run 2): the detail is a 360px slice with a sticky bar that names what is asked of the PM and leads back to the lists
|
|
1638
|
-
const slug = selectedSlug();
|
|
1639
|
-
const rows = (state.statuses && state.statuses[state.product]) || {};
|
|
1640
|
-
const key = window.PMOSStatusGroups ? window.PMOSStatusGroups.keyOf(rows[slug]) : 'unknown';
|
|
1641
|
-
const ask = key === 'needs-you' ? 'Needs you' : key === 'awaiting-acceptance' ? 'Awaiting your acceptance' : 'Nothing is asked of you';
|
|
1642
|
-
const bar = el('div', { 'data-focus': 'bar', style: 'position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--sidebar)' },
|
|
1643
|
-
el('span', { style: 'font:500 12px var(--sans);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, ask),
|
|
1644
|
-
el('button', { 'data-focus': 'navigate', onClick: () => setState({ layoutFocus: false }), style: 'margin-left:auto;font:500 11px var(--sans);border:1px solid var(--border);border-radius:6px;padding:2px 8px', hover: 'background:var(--accent)' }, 'Navigate'));
|
|
1645
|
-
return setChildren(host, [bar, ...body]);
|
|
1646
|
-
}
|
|
1647
1500
|
/* The cockpit's initiative has no title bar, so its fold control floats at the column's top right. The column folds to the 36px
|
|
1648
1501
|
* strip app.js draws ("initiative ⟩"), which opens it again. Nothing in the column moves (design-parity names the control). */
|
|
1649
1502
|
host.style.position = bare ? 'relative' : '';
|
|
@@ -866,27 +866,9 @@ export function renderInitiatives(host) {
|
|
|
866
866
|
chip('objective', !byStatus, () => setState({ groupBy: 'objective' }), 'by-objective'),
|
|
867
867
|
chip('status', byStatus, () => setState({ groupBy: 'status' }), 'by-status'),
|
|
868
868
|
byStatus ? chip('active only', !!state.activeOnly, () => setState({ activeOnly: !state.activeOnly }), 'active-only') : null,
|
|
869
|
-
SG ? chip('what do these mean?', !!state.legendOpen, () => setState({ legendOpen: !state.legendOpen }), 'legend-toggle') : null,
|
|
870
869
|
byStatus && statusErr ? el('span', { 'data-r16': 'status-error', style: 'flex-basis:100%;font-size:10.5px;color:var(--destructive)' }, 'status could not be read — ' + statusErr + ' (every initiative is under Unknown)') : null);
|
|
871
870
|
|
|
872
|
-
|
|
873
|
-
* says and where a row lands cannot disagree: what it means, the fact it is read from, who acts, what moves it,
|
|
874
|
-
* and what the PM can do by hand. */
|
|
875
|
-
const OWNER_WORD = { pm: 'you', agent: 'the agent', system: 'nobody (derived)' };
|
|
876
|
-
const legend = !FIXTURE && SG && state.legendOpen ? el('div', { 'data-r18': 'legend',
|
|
877
|
-
style: 'display:flex;flex-direction:column;gap:8px;margin:0 12px 8px;padding:8px 10px;border:1px solid var(--border);background:var(--secondary)' },
|
|
878
|
-
el('div', { style: 'font-size:11px;color:var(--muted-foreground);line-height:1.5' },
|
|
879
|
-
'A status is never typed. It is worked out again each time from the ledgers and git. You can only hold, close, cancel or reopen an initiative by hand.'),
|
|
880
|
-
SG.GROUPS.map((g) => el('div', { 'data-r18': 'legend-row', 'data-key': g.key, style: 'display:flex;flex-direction:column;gap:2px;padding-top:6px;border-top:1px solid var(--border)' },
|
|
881
|
-
el('div', { style: 'display:flex;gap:7px;align-items:baseline' },
|
|
882
|
-
el('span', { style: 'font-size:12px;font-weight:600;color:' + (STATUS_TONE[g.key] || 'var(--foreground)') }, g.label),
|
|
883
|
-
el('span', { style: 'font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)' }, 'acts: ' + (OWNER_WORD[g.owner] || g.owner))),
|
|
884
|
-
el('div', { style: 'font-size:11.5px;line-height:1.5;color:var(--foreground)' }, g.meaning),
|
|
885
|
-
el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'read from: ' + g.source),
|
|
886
|
-
el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'moves: ' + g.moves),
|
|
887
|
-
el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'you can: ' + g.pmCan)))) : null;
|
|
888
|
-
|
|
889
|
-
const kids = [header, searchBox, groupBar, legend];
|
|
871
|
+
const kids = [header, searchBox, groupBar];
|
|
890
872
|
/* THE WORKING SET, pinned above the Objectives.
|
|
891
873
|
*
|
|
892
874
|
* It is `groupNode` — the same element, the same collapse key, the same forced-open-on-search
|
|
@@ -28,9 +28,11 @@ import { syncButton } from './lifecycle.js';
|
|
|
28
28
|
import { treesBlock, branchForm, openBranchForm, treeNoteFor } from './panel-trees.js';
|
|
29
29
|
import { state, setState, FIXTURE } from './state.js';
|
|
30
30
|
import {
|
|
31
|
-
productsList, repoRow, firstSlugOf,
|
|
31
|
+
productsList, repoRow, firstSlugOf, ADDABLE_PRODUCTS,
|
|
32
32
|
INGESTED_KNOW, INGEST_LOG_GITHUB, INGEST_LOG_LOCAL, INGEST_ALREADY,
|
|
33
33
|
} from './fixture.js';
|
|
34
|
+
import { askConfirm, askFields } from './dialogs.js';
|
|
35
|
+
import { toast } from './primitives.js';
|
|
34
36
|
|
|
35
37
|
/** An interpolated value, in the inline span the prototype's runtime puts it in. */
|
|
36
38
|
const ip = (v) => el('span', null, v);
|
|
@@ -161,69 +163,6 @@ export function selectRepo(id) {
|
|
|
161
163
|
}).catch(() => { /* an unreadable repository still selects; panel 3 reports the read */ });
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
/* ── settings & tools ────────────────────────────────────────────────────────────────────────
|
|
165
|
-
* EXACTLY two groups, and no `workflow` entry. The prototype's `modeMeta` still carries `workflow`
|
|
166
|
-
* because the mode exists in code, but it is deliberately absent from the drawer: a workflow is
|
|
167
|
-
* decided by the agent at runtime, not configured by hand (PRD, panel-1 scope). The filter below is
|
|
168
|
-
* belt and braces — it makes the absence a property of THIS module, so it cannot regress by someone
|
|
169
|
-
* adding an id to the seed list.
|
|
170
|
-
*/
|
|
171
|
-
/* overhaul-r1 R6.1 — in LIVE mode, a mode with no server behind it is not offered. `agents`,
|
|
172
|
-
* `memory` and `connections` start empty and every edit is a client-only setState that no server
|
|
173
|
-
* receives (modes.js `seedOr`), so they looked broken because they were. They come back in a later
|
|
174
|
-
* release wired or replaced. Fixture mode keeps them: it is the prototype's recorded state, and the
|
|
175
|
-
* design comparison reads it. Exported so the live-mode test can assert the list, not a screenshot. */
|
|
176
|
-
export const UNBACKED_MODES = ['agent', 'connect']; // mode ids (labels: agents · connections). `memory` left this list in R9 (D72): the repository's ledger backs it.
|
|
177
|
-
/* overhaul-r4 — the PORTFOLIO mode joins "all repositories" in live mode only (the fixture drawer is the
|
|
178
|
-
* prototype's, and the design comparison reads it). Its meta lives here, not in fixture.js, for the
|
|
179
|
-
* same reason. */
|
|
180
|
-
const META = Object.assign({}, MODE_META, { portfolio: ['▦', 'portfolio'], guide: ['?', 'guide'], validate: ['◉', 'validate'], today: ['☀', 'today'] });
|
|
181
|
-
const LIVE_EXTRA = { 'all repositories': ['today', 'portfolio', 'guide', 'validate'] };
|
|
182
|
-
const DRAWER_GROUPS = SETTINGS_GROUPS.map((g) => ({
|
|
183
|
-
label: g.label,
|
|
184
|
-
items: (FIXTURE ? [] : LIVE_EXTRA[g.label] || []).concat(
|
|
185
|
-
g.items.filter((id) => id !== 'workflow' && MODE_META[id] && (FIXTURE || !UNBACKED_MODES.includes(id)))),
|
|
186
|
-
})).filter((g) => g.items.length);
|
|
187
|
-
|
|
188
|
-
function drawerButton(id) {
|
|
189
|
-
const [glyph, label] = META[id];
|
|
190
|
-
const on = state.mode === id;
|
|
191
|
-
return el('button', {
|
|
192
|
-
onClick: () => setState({ mode: id, settingsOpen: false, col1: true }),
|
|
193
|
-
style: 'display:flex;align-items:center;gap:9px;width:100%;padding:7px 14px;min-height:38px;border-left:2px solid '
|
|
194
|
-
+ (on ? 'var(--brand)' : 'transparent') + ';background:' + (on ? 'var(--accent)' : 'transparent')
|
|
195
|
-
+ ';color:' + (on ? 'var(--foreground)' : 'var(--muted-foreground)'),
|
|
196
|
-
hover: 'background:var(--accent)',
|
|
197
|
-
},
|
|
198
|
-
el('span', { style: 'flex:none;width:16px;text-align:center;font-size:13px;line-height:1' }, ip(glyph)),
|
|
199
|
-
el('span', { style: 'font-size:12px' }, ip(label)),
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function drawer() {
|
|
204
|
-
const kids = [];
|
|
205
|
-
if (state.settingsOpen) {
|
|
206
|
-
const groups = [];
|
|
207
|
-
for (const g of DRAWER_GROUPS) {
|
|
208
|
-
groups.push(el('div', {
|
|
209
|
-
style: 'padding:8px 14px 3px;font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground)',
|
|
210
|
-
}, ip(g.label)));
|
|
211
|
-
for (const id of g.items) groups.push(drawerButton(id));
|
|
212
|
-
}
|
|
213
|
-
kids.push(el('div', { style: 'max-height:44vh;overflow-y:auto;border-bottom:1px solid var(--border)' }, groups));
|
|
214
|
-
}
|
|
215
|
-
kids.push(el('button', {
|
|
216
|
-
onClick: () => setState((s) => ({ settingsOpen: !s.settingsOpen })),
|
|
217
|
-
style: 'display:flex;align-items:center;gap:9px;width:100%;padding:11px 14px;min-height:44px;color:var(--muted-foreground)',
|
|
218
|
-
hover: 'color:var(--foreground);background:var(--accent)',
|
|
219
|
-
},
|
|
220
|
-
el('span', { style: 'flex:none;width:14px;text-align:center;font-size:14px' }, '⚙'),
|
|
221
|
-
el('span', { style: 'font-size:12px' }, 'settings & tools'),
|
|
222
|
-
el('span', { style: 'margin-left:auto;font-size:10px' }, ip(state.settingsOpen ? '▾' : '▸')),
|
|
223
|
-
));
|
|
224
|
-
return el('div', { style: 'border-top:1px solid var(--border);flex:none' }, kids);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
166
|
/* ── the rows ────────────────────────────────────────────────────────────────────────────────── */
|
|
228
167
|
|
|
229
168
|
function repoButton(tuple, product) {
|
|
@@ -259,17 +198,29 @@ export function treesOf(name) {
|
|
|
259
198
|
return dataMod && typeof dataMod.productTrees === 'function' ? dataMod.productTrees(name) : [];
|
|
260
199
|
}
|
|
261
200
|
|
|
201
|
+
/* A refusal is told twice: the legacy list draws repoNotice, and the frame's Settings page, which draws no notice, gets the toast. */
|
|
202
|
+
function refused(name, text) {
|
|
203
|
+
setState({ repoNotice: { name, tone: 'bad', text } });
|
|
204
|
+
toast({ text, tone: 'error' });
|
|
205
|
+
}
|
|
206
|
+
|
|
262
207
|
export async function renameRepo(name) {
|
|
263
|
-
|
|
264
|
-
const label = win ? win.prompt('Rename “' + name + '” to:', name) : null;
|
|
265
|
-
if (label === null || !String(label).trim()) return;
|
|
266
|
-
// the collapsed rail shows an abbreviation; empty keeps (or restores) the one derived from the name
|
|
208
|
+
// the legacy rail shows an abbreviation; empty keeps (or restores) the one derived from the name
|
|
267
209
|
const cur = dataMod.repoInitials ? dataMod.repoInitials(name) : null;
|
|
268
|
-
const
|
|
269
|
-
|
|
210
|
+
const answer = await askFields({
|
|
211
|
+
title: 'Rename “' + name + '”', confirmLabel: 'Rename',
|
|
212
|
+
fields: [
|
|
213
|
+
{ key: 'label', label: 'Name', value: name },
|
|
214
|
+
{ key: 'ini', label: 'Short name for the legacy rail: up to 3 letters or digits. Leave it empty to use the first letters of the name.', value: cur || '' },
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
if (!answer) return;
|
|
218
|
+
const label = answer.label;
|
|
219
|
+
const ini = answer.ini;
|
|
220
|
+
if (!String(label).trim()) return;
|
|
270
221
|
if (String(label).trim() === name && String(ini).trim() === (cur || '')) return;
|
|
271
222
|
const out = await dataMod.renameRepository(name, String(label).trim(), String(ini).trim());
|
|
272
|
-
if (!out || out.ok === false) {
|
|
223
|
+
if (!out || out.ok === false) { refused(name, (out && out.error) || 'The rename was refused.'); return; }
|
|
273
224
|
await connectData();
|
|
274
225
|
setState({ product: String(label).trim(), repoNotice: null });
|
|
275
226
|
}
|
|
@@ -312,19 +263,21 @@ async function switchProfile(name) {
|
|
|
312
263
|
? 'Switch “' + name + '” to document work?\n\nLanes offered: ' + lanesText(name, 'pm-artifacts') + '.\nThe ladder becomes route, anchor, brief, draft, review, approved, handed off — no contract, build, eval or explainer.'
|
|
313
264
|
: 'Switch “' + name + '” to code work?\n\nLanes offered: ' + lanesText(name, 'product-code') + '.\nThe ladder returns to spec, contract, build, eval, explainer, acceptance.';
|
|
314
265
|
const tail = '\n\nAn initiative whose lane is not in the new set is treated as the strictest lane (' + (toDocs ? 'a governed prd' : 'feature') + '). No initiative record is edited.\n\nThis ' + (toDocs ? 'writes' : 'removes') + ' the file .pmos-profile in the repository. It is a change for you to commit.';
|
|
315
|
-
const ok =
|
|
266
|
+
const ok = await askConfirm({ title: toDocs ? 'Switch “' + name + '” to document work?' : 'Switch “' + name + '” to code work?', body: msg + tail, confirmLabel: 'Switch' });
|
|
316
267
|
if (!ok) return;
|
|
317
268
|
const out = await dataMod.setRepoProfile(name, toDocs ? 'pm-artifacts' : 'product-code');
|
|
318
269
|
if (!out || out.ok === false) { setState({ repoNotice: { name, tone: 'bad', text: (out && out.error) || 'the profile was not changed' } }); return; }
|
|
319
270
|
setState({ repoNotice: { name, tone: null, text: 'now ' + (toDocs ? 'document work (pm-artifacts)' : 'code work (product-code)') + ' — .pmos-profile ' + (toDocs ? 'written' : 'removed') + '; commit it' } });
|
|
320
271
|
}
|
|
321
272
|
|
|
322
|
-
async function removeRepo(name) {
|
|
323
|
-
const ok =
|
|
324
|
-
:
|
|
273
|
+
export async function removeRepo(name) {
|
|
274
|
+
const ok = await askConfirm({
|
|
275
|
+
title: 'Remove “' + name + '” from PMOS?', confirmLabel: 'Remove', danger: true,
|
|
276
|
+
body: 'This forgets the repository and its worktree rows. No folder is deleted.',
|
|
277
|
+
});
|
|
325
278
|
if (!ok) return;
|
|
326
279
|
const out = await dataMod.removeRepository(name);
|
|
327
|
-
if (!out || out.ok === false) {
|
|
280
|
+
if (!out || out.ok === false) { refused(name, (out && out.error) || 'The removal was refused.'); return; }
|
|
328
281
|
realRepos = (realRepos || []).filter((t) => t[0] !== name);
|
|
329
282
|
setState({ repoNotice: null });
|
|
330
283
|
}
|
|
@@ -502,7 +455,7 @@ export function renderRepos(host) {
|
|
|
502
455
|
),
|
|
503
456
|
);
|
|
504
457
|
|
|
505
|
-
setChildren(host, [header, scroll
|
|
458
|
+
setChildren(host, [header, scroll]);
|
|
506
459
|
return host;
|
|
507
460
|
}
|
|
508
461
|
|
|
@@ -521,7 +474,7 @@ export function renderRepos(host) {
|
|
|
521
474
|
const inputStyle = 'font-family:var(--mono);font-size:11.5px;padding:6px 9px;border:1px solid var(--input);min-height:32px';
|
|
522
475
|
const labelStyle = 'font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground);padding-top:8px';
|
|
523
476
|
const rowStyle = 'display:grid;grid-template-columns:76px 1fr;gap:10px;padding:10px 14px';
|
|
524
|
-
const goStyle = 'padding:5px 12px;min-height:30px;background:var(--
|
|
477
|
+
const goStyle = 'padding:5px 12px;min-height:30px;background:var(--primary);color:var(--primary-foreground);font:500 12px var(--sans)';
|
|
525
478
|
|
|
526
479
|
/**
|
|
527
480
|
* A form field. `oninput` writes the value straight onto the state object WITHOUT a render — a
|
|
@@ -786,6 +739,3 @@ export function renderAddProduct(host) {
|
|
|
786
739
|
return host;
|
|
787
740
|
}
|
|
788
741
|
|
|
789
|
-
/* Exposed for the browser suite: the drawer's exact shape is an acceptance criterion (two groups,
|
|
790
|
-
* no `workflow`), and an assertion should be able to read it without scraping the DOM. */
|
|
791
|
-
export const settingsGroups = () => DRAWER_GROUPS.map((g) => ({ label: g.label, items: [...g.items] }));
|