create-pmos 0.46.0 → 0.48.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/AGENTS-HUMAN.md +1 -1
- package/payload/AGENTS.md +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/mods/pmos-cockpit/.claude-plugin/plugin.json +1 -1
- package/payload/mods/pmos-cockpit/README.md +13 -3
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +34 -6
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +22 -3
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +16 -3
- package/payload/mods/pmos-cockpit/hooks/lib/handover-run.mjs +33 -0
- package/payload/mods/pmos-cockpit/hooks/lib/handover.mjs +267 -0
- package/payload/mods/pmos-cockpit/hooks/register.tsx +189 -18
- package/payload/mods/pmos-cockpit/types/index.d.ts +16 -1
- package/payload/okf/core/concepts/frontmatter-format.md +6 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/pmos-log.sh +18 -8
- package/payload/scripts/pmos-model.js +233 -2
- package/payload/scripts/status-facts.js +204 -0
- package/payload/scripts/workflow-state.js +52 -0
- package/payload/skills/SKILL-FORMAT.md +1 -0
- package/payload/state/discoveries.md +1 -1
- package/workspace/README.md +3 -3
- package/workspace/bin/live-notes.js +133 -0
- package/workspace/design-parity.js +328 -21
- package/workspace/doc-category-bench.js +165 -0
- package/workspace/fixtures/doc-category-labelled.json +167 -0
- package/workspace/guide/initiatives-and-next-steps.md +16 -2
- package/workspace/guide/knowledge-graph.md +89 -2
- package/workspace/guide/layout.md +30 -2
- package/workspace/guide/proactive-engine.md +26 -4
- package/workspace/guide/scheduled-tasks-and-cli.md +1 -0
- package/workspace/guide/statuses.md +1 -0
- package/workspace/guide/validate-with-typesafe.md +17 -1
- package/workspace/lib/claims.js +51 -2
- package/workspace/lib/doc-category-corrections.js +113 -0
- package/workspace/lib/doc-category-writeback.js +110 -0
- package/workspace/lib/doc-category.js +225 -0
- package/workspace/lib/engine/pmos-model.js +233 -2
- package/workspace/lib/engine/status-facts.js +204 -0
- package/workspace/lib/engine/workflow-state.js +52 -0
- package/workspace/lib/jev-categorize.js +399 -0
- package/workspace/lib/jev-claims.js +59 -15
- package/workspace/lib/jev-route.js +129 -0
- package/workspace/lib/kg-cli.js +5 -2
- package/workspace/lib/kg-dangling-baseline.json +1 -0
- package/workspace/lib/kg-dangling-rules.json +1 -0
- package/workspace/lib/kg.js +26 -9
- package/workspace/lib/learn.js +282 -0
- package/workspace/lib/live-notes.js +890 -0
- package/workspace/lib/proactive-cli.js +49 -1
- package/workspace/lib/proactive.js +8 -7
- package/workspace/lib/typesafe-settings.js +10 -2
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +5 -4
- package/workspace/public/pm/app.js +148 -79
- package/workspace/public/pm/data.js +58 -0
- package/workspace/public/pm/fixture.js +40 -2
- package/workspace/public/pm/modes-live.js +267 -21
- package/workspace/public/pm/panel-cockpit.js +636 -0
- package/workspace/public/pm/panel-detail.js +210 -8
- package/workspace/public/pm/panel-initiatives.js +9 -9
- package/workspace/public/pm/panel-newsess.js +145 -86
- package/workspace/public/pm/panel-repos.js +34 -17
- package/workspace/public/pm/panel-tabs.js +145 -27
- package/workspace/public/pm/state.js +45 -4
- package/workspace/public/pm/tab-doc.js +244 -16
- package/workspace/public/pm/tab-term.js +11 -1
- package/workspace/public/pm/tokens.css +4 -3
- package/workspace/public/pm/view-addrepo.js +207 -0
- package/workspace/public/pm/view-files.js +197 -0
- package/workspace/public/pm/view-guide.js +141 -0
- package/workspace/public/pm/view-knowledge.js +225 -0
- package/workspace/public/pm/view-memory.js +122 -0
- package/workspace/public/pm/view-portfolio.js +153 -0
- package/workspace/public/pm/view-reports.js +190 -0
- package/workspace/public/pm/view-scheduled.js +161 -0
- package/workspace/public/pm/view-skills.js +216 -0
- package/workspace/public/pm/view-today.js +168 -0
- package/workspace/public/pm/view-validate.js +171 -0
- package/workspace/server.js +394 -10
- package/workspace/skills/live-notes.skill +58 -0
- package/workspace/skills/morning-brief.skill +1 -0
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { el, setChildren } from './dom.js';
|
|
30
|
-
import { state, setState, FIXTURE } from './state.js';
|
|
30
|
+
import { state, setState, FIXTURE, LEGACY_LAYOUT_FLAG } from './state.js';
|
|
31
|
+
import { isMobile } from './mobile.js';
|
|
31
32
|
import * as FX from './fixture.js';
|
|
32
33
|
import { openTab, openTerm as openTermTab, openDoc as openDocTab } from './panel-initiatives.js';
|
|
33
34
|
import { mountDoc, disposeDoc, docText } from './tab-doc.js';
|
|
@@ -41,7 +42,9 @@ import { boardOf, currentProduct } from './board-read.js';
|
|
|
41
42
|
* far smaller failure than a dead panel 4. When it lands, the panel repaints itself. */
|
|
42
43
|
let termMod = null;
|
|
43
44
|
let liveMod = null;
|
|
45
|
+
let cockpitMod = null;
|
|
44
46
|
/* Loaded lazily; a repaint is asked for only when a skill tab is already open (restored on reload) and waiting for it. */
|
|
47
|
+
import('./panel-cockpit.js').then((m) => { cockpitMod = m; if ((state.tabs || []).some((t) => t.kind === 'view')) setState({}); }).catch(() => { cockpitMod = null; });
|
|
45
48
|
import('./modes-live.js').then((m) => { liveMod = m; if ((state.tabs || []).some((t) => t.kind === 'skill' || t.kind === 'transcript')) setState({}); }).catch(() => { liveMod = null; });
|
|
46
49
|
import('./tab-term.js')
|
|
47
50
|
.then((m) => { termMod = m; setState({}); })
|
|
@@ -332,7 +335,17 @@ const CHIP_STYLE = 'flex:none;align-self:center;font-family:var(--mono);font-siz
|
|
|
332
335
|
const SPLIT_STYLE = 'flex:none;margin-left:auto;align-self:center;font-family:var(--mono);font-size:11px;color:oklch(0.85 0.01 214);padding:5px 12px;min-height:30px;background:oklch(1 0 0 / 6%);border:1px solid oklch(1 0 0 / 12%);white-space:nowrap';
|
|
333
336
|
const CHIP_HOVER = 'border-color:oklch(0.56 0.021 213.5)';
|
|
334
337
|
|
|
335
|
-
const tabBar = el('div', { style: BAR_STYLE });
|
|
338
|
+
const tabBar = el('div', { 'data-tabs': 'bar', style: BAR_STYLE });
|
|
339
|
+
|
|
340
|
+
/* ── the cockpit's tab strip (workspace-cockpit-nav Run 4, planning/design/cockpit-mockup/WsA.dc.html) ─────────────────────────
|
|
341
|
+
* On a desktop the strip is the mockup's: a 44px band whose tabs sit on its floor, the tab in front a card with rounded top corners,
|
|
342
|
+
* the others plain, each a dot and a title in 12px mono, and `+ session` at the right end. A phone and `?layout=legacy` keep the
|
|
343
|
+
* prototype's strip above. What the mockup does not draw is kept (R12): a tab's second line (its initiative and session) sits under
|
|
344
|
+
* its title in the tab's own lower padding, so the title stays where the mockup puts it; a tab that is not in front keeps its close
|
|
345
|
+
* control, in its right padding; the web, options and split controls sit to the left of `+ session`. */
|
|
346
|
+
const cockpitLook = () => !LEGACY_LAYOUT_FLAG && !isMobile();
|
|
347
|
+
const CK_BAR = 'flex:none;display:flex;align-items:flex-end;gap:4px;padding:0 10px;height:45px;border-bottom:1px solid var(--border);min-width:0;overflow-x:auto';
|
|
348
|
+
const CK_BTN = 'flex:none;margin:0 0 4px;height:30px;display:grid;place-items:center;font:500 12.5px var(--sans);line-height:normal;background:transparent;color:var(--foreground);border:1px solid var(--input);border-radius:8px;padding:0 12px;white-space:nowrap';
|
|
336
349
|
|
|
337
350
|
const newSessionBtn = el('button', {
|
|
338
351
|
onClick: () => newSession(), style: CHIP_STYLE, hover: CHIP_HOVER,
|
|
@@ -368,7 +381,7 @@ const emptyState = el('div', {
|
|
|
368
381
|
style: 'flex:1;display:grid;place-items:center;color:oklch(0.55 0.02 213);font-size:12.5px;padding:20px;text-align:center',
|
|
369
382
|
}, "nothing open — click a task's output or an artifact, or + session above");
|
|
370
383
|
|
|
371
|
-
const panesRow = el('div', { style: 'flex:1;display:flex;min-height:0;min-width:0;overflow:hidden' });
|
|
384
|
+
const panesRow = el('div', { 'data-tabs': 'panes', style: 'flex:1;display:flex;min-height:0;min-width:0;overflow:hidden' });
|
|
372
385
|
|
|
373
386
|
/* ── the scratchpad (lines 983–991) ──────────────────────────────────────────────────────────
|
|
374
387
|
* One textarea, created once. Its text is written back as a CHILD TEXT NODE as well as a value,
|
|
@@ -395,9 +408,16 @@ const SCRATCH_PLACEHOLDER = FIXTURE
|
|
|
395
408
|
? 'scratchpad — survives tab switches, dies with the page'
|
|
396
409
|
: 'scratchpad — one pad per session, saved on this machine';
|
|
397
410
|
|
|
411
|
+
/* workspace-cockpit-nav Run 3 (PRD R10) — the drawer takes the form of the cockpit board
|
|
412
|
+
* `planning/design/cockpit-mockup/ViewFilesPad.dc.html`: a labelled header with a fold control, the pad
|
|
413
|
+
* as a bordered field, and one row of buttons with a short note. The textarea stays a DIRECT child of
|
|
414
|
+
* `scratchBlock` (the height ceiling and pm-tabs-test measure through `parentElement`), and every
|
|
415
|
+
* behaviour below — the per-session pad, the autosave, the height handle — is unchanged. */
|
|
398
416
|
const scratchTa = el('textarea', {
|
|
399
417
|
placeholder: SCRATCH_PLACEHOLDER,
|
|
400
|
-
|
|
418
|
+
'data-scratch': 'text',
|
|
419
|
+
style: 'height:96px;flex:none;width:100%;border:1px solid var(--input);border-radius:8px;background:var(--background);'
|
|
420
|
+
+ 'color:var(--foreground);padding:10px 12px;font:12.5px/1.55 var(--mono)',
|
|
401
421
|
onInput: (e) => onScratchInput(e.target.value),
|
|
402
422
|
});
|
|
403
423
|
|
|
@@ -457,9 +477,10 @@ const scratchGrip = el('div', {
|
|
|
457
477
|
'aria-orientation': 'horizontal',
|
|
458
478
|
'aria-label': 'resize the scratchpad — drag, or ↑ / ↓',
|
|
459
479
|
title: 'drag to resize · ↑ / ↓ to nudge · double-click to reset',
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
480
|
+
'data-scratch': 'grip',
|
|
481
|
+
// full width across the drawer's padding, on its top edge
|
|
482
|
+
style: 'flex:none;height:7px;margin:-12px -18px 0;cursor:ns-resize;background:var(--sidebar);touch-action:none',
|
|
483
|
+
hover: 'background:var(--border)',
|
|
463
484
|
onPointerdown: (e) => {
|
|
464
485
|
dragFrom = { y: e.clientY, h: state.scratchH || SCRATCH_DEFAULT };
|
|
465
486
|
try { e.target.setPointerCapture(e.pointerId); } catch { /* not captured — the move still works */ }
|
|
@@ -564,6 +585,13 @@ if (typeof window !== 'undefined' && !FIXTURE) {
|
|
|
564
585
|
document.addEventListener('visibilitychange', () => { if (document.hidden) flushPad(); });
|
|
565
586
|
}
|
|
566
587
|
|
|
588
|
+
/* The board's two button forms, on the token system: `.btnp` (the one primary action) and `.btn`. */
|
|
589
|
+
const PAD_BTN = 'flex:none;font:500 12.5px var(--sans);border-radius:8px;padding:0 12px;height:28px;'
|
|
590
|
+
+ 'display:inline-grid;place-items:center;white-space:nowrap;';
|
|
591
|
+
const PAD_BTN_PRIMARY = PAD_BTN + 'background:var(--foreground);color:var(--background);border:0';
|
|
592
|
+
const PAD_BTN_OUTLINE = PAD_BTN + 'background:transparent;color:var(--foreground);border:1px solid var(--input)';
|
|
593
|
+
const PAD_BTN_OFF = PAD_BTN + 'background:transparent;color:var(--muted-foreground);border:1px solid var(--input)';
|
|
594
|
+
|
|
567
595
|
const scratchCopyLabel = iv('⧉ copy');
|
|
568
596
|
const scratchCopyBtn = el('button', {
|
|
569
597
|
onClick: () => {
|
|
@@ -571,24 +599,50 @@ const scratchCopyBtn = el('button', {
|
|
|
571
599
|
setState({ scratchCopied: true });
|
|
572
600
|
setTimeout(() => setState({ scratchCopied: false }), 1200);
|
|
573
601
|
},
|
|
574
|
-
|
|
575
|
-
|
|
602
|
+
'data-scratch': 'copy',
|
|
603
|
+
style: PAD_BTN_OUTLINE,
|
|
604
|
+
hover: 'background:var(--accent)',
|
|
576
605
|
}, scratchCopyLabel);
|
|
577
606
|
|
|
578
607
|
const scratchSendLabel = iv('open a terminal to send');
|
|
579
608
|
const scratchSendBtn = el('button', {
|
|
580
609
|
onClick: () => sendScratchToTerminal(),
|
|
581
|
-
|
|
582
|
-
|
|
610
|
+
'data-scratch': 'send',
|
|
611
|
+
style: PAD_BTN_OFF,
|
|
583
612
|
}, scratchSendLabel);
|
|
584
613
|
|
|
614
|
+
/* Clear — the board's second button. It empties the pad through the same path as typing, so the
|
|
615
|
+
* empty pad is saved for this session like any other edit. Disabled when there is nothing to clear. */
|
|
616
|
+
const scratchClearBtn = el('button', {
|
|
617
|
+
onClick: () => { if (state.scratchText) onScratchInput(''); },
|
|
618
|
+
'data-scratch': 'clear',
|
|
619
|
+
style: PAD_BTN_OUTLINE,
|
|
620
|
+
hover: 'background:var(--accent)',
|
|
621
|
+
}, 'clear');
|
|
622
|
+
|
|
623
|
+
const scratchHeadLabel = el('span', {
|
|
624
|
+
'data-scratch': 'label',
|
|
625
|
+
style: 'font:500 10px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)',
|
|
626
|
+
}, 'scratchpad');
|
|
627
|
+
const scratchFoldBtn = el('button', {
|
|
628
|
+
onClick: () => setState({ scratchOpen: false }),
|
|
629
|
+
'data-scratch': 'fold', 'aria-label': 'close the scratchpad', title: 'Close the scratchpad. The text is kept.',
|
|
630
|
+
style: 'flex:none;color:var(--muted-foreground);padding:0 4px', hover: 'color:var(--foreground)',
|
|
631
|
+
}, '▾');
|
|
632
|
+
const scratchNote = el('span', {
|
|
633
|
+
'data-scratch': 'note',
|
|
634
|
+
style: 'min-width:0;color:var(--muted-foreground);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap',
|
|
635
|
+
});
|
|
636
|
+
|
|
585
637
|
const scratchBlock = el('div', {
|
|
586
|
-
|
|
638
|
+
'data-scratch': 'pad',
|
|
639
|
+
style: 'display:flex;flex-direction:column;gap:8px;padding:12px 18px;border-top:1px solid var(--border);background:var(--sidebar)',
|
|
587
640
|
},
|
|
588
641
|
FIXTURE ? null : scratchGrip,
|
|
642
|
+
el('div', { style: 'display:flex;align-items:center;justify-content:space-between' }, scratchHeadLabel, scratchFoldBtn),
|
|
589
643
|
scratchTa,
|
|
590
|
-
el('div', { style: 'display:flex;
|
|
591
|
-
scratchCopyBtn,
|
|
644
|
+
el('div', { style: 'display:flex;align-items:center;gap:8px;min-width:0' },
|
|
645
|
+
scratchSendBtn, scratchCopyBtn, scratchClearBtn, scratchNote),
|
|
592
646
|
);
|
|
593
647
|
|
|
594
648
|
/* ── the bottom bar (lines 992–996) ─────────────────────────────────────────────────────────── */
|
|
@@ -598,6 +652,7 @@ const colTitleLabel = iv('scratchpad');
|
|
|
598
652
|
const copyLabel = iv('copy');
|
|
599
653
|
|
|
600
654
|
const bottomBar = el('div', {
|
|
655
|
+
'data-tabs': 'bottom',
|
|
601
656
|
style: 'display:flex;gap:10px;align-items:center;padding:5px 12px;border-top:1px solid oklch(1 0 0 / 10%);font-size:11px;color:oklch(0.6 0.02 213)',
|
|
602
657
|
},
|
|
603
658
|
el('button', {
|
|
@@ -833,6 +888,8 @@ function paneFor(tab) {
|
|
|
833
888
|
if (liveMod && typeof liveMod.mountSkillTab === 'function') liveMod.mountSkillTab(host, tab);
|
|
834
889
|
} else if (tab.kind === 'transcript') {
|
|
835
890
|
if (liveMod && typeof liveMod.mountTranscriptTab === 'function') liveMod.mountTranscriptTab(host, tab);
|
|
891
|
+
} else if (tab.kind === 'view') {
|
|
892
|
+
if (cockpitMod && typeof cockpitMod.mountView === 'function') cockpitMod.mountView(host, tab);
|
|
836
893
|
} else if (tab.kind === 'web') {
|
|
837
894
|
mountWeb(host, tab); // overhaul-r5 — never falls through to an empty doc tab
|
|
838
895
|
} else {
|
|
@@ -937,6 +994,42 @@ export function tabBranch(t, includeMain) {
|
|
|
937
994
|
return trees.length > 1 && main ? main.branch : null;
|
|
938
995
|
}
|
|
939
996
|
|
|
997
|
+
function cockpitTabNode(t, act, splitTab) {
|
|
998
|
+
const front = (act && act.id === t.id) || (splitTab && splitTab.id === t.id);
|
|
999
|
+
const dotColor = FX.TAB_COLORS.dot[t.kind] || FX.TAB_COLORS.dot.doc;
|
|
1000
|
+
const meta = t.kind === 'term'
|
|
1001
|
+
? ((!FIXTURE && t.sessionId) ? 'session ' + t.sessionId : 'session')
|
|
1002
|
+
: (t.kind === 'task' ? 'task' : t.kind === 'view' ? 'view' : t.kind === 'web' ? 'web' : t.kind === 'skill' ? 'skill' : t.kind === 'transcript' ? 'conversation' : t.role);
|
|
1003
|
+
const br = tabBranch(t);
|
|
1004
|
+
return el('div', {
|
|
1005
|
+
'data-tab': t.id, 'data-tab-active': front ? '1' : '0',
|
|
1006
|
+
style: 'position:relative;flex:none;display:flex;align-items:center;gap:8px;padding:0 14px;font:12px var(--mono);'
|
|
1007
|
+
+ (front ? 'height:37px;border:1px solid var(--border);border-bottom:0;border-radius:8px 8px 0 0;background:var(--card);color:var(--foreground)'
|
|
1008
|
+
: 'height:36px;color:var(--muted-foreground)'),
|
|
1009
|
+
},
|
|
1010
|
+
el('button', {
|
|
1011
|
+
onClick: () => setState({ active: t.id }),
|
|
1012
|
+
title: t.title + (t.slug ? '\n' + t.slug + ' · ' + meta : ''),
|
|
1013
|
+
style: 'display:flex;gap:8px;align-items:center;align-self:stretch;color:inherit',
|
|
1014
|
+
},
|
|
1015
|
+
el('span', { 'data-tab-dot': t.kind, style: 'width:8px;height:8px;border-radius:50%;display:inline-block;flex-shrink:0;background:' + dotColor + ';color:' + dotColor }),
|
|
1016
|
+
el('span', { style: 'display:flex;flex-direction:column;min-width:0;align-items:flex-start' },
|
|
1017
|
+
el('span', { style: 'max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(t.title)),
|
|
1018
|
+
el('span', {
|
|
1019
|
+
'data-tab-sub': '',
|
|
1020
|
+
// under the title, inside the tab's own width (the tab is its containing block), clipped to it
|
|
1021
|
+
style: 'position:absolute;left:30px;right:14px;bottom:1px;font:9px var(--mono);line-height:10px;color:oklch(0.52 0.02 213);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left',
|
|
1022
|
+
}, iv(t.slug), ' · ', iv(meta), br ? el('span', { 'data-r1': 'tab-branch' }, iv(' · ⑂ ' + br)) : null),
|
|
1023
|
+
),
|
|
1024
|
+
),
|
|
1025
|
+
el('button', {
|
|
1026
|
+
onClick: () => closeTab(t.id), 'data-tab-close': '', 'aria-label': 'close ' + t.title, title: 'close',
|
|
1027
|
+
style: 'color:var(--muted-foreground)' + (front ? '' : ';position:absolute;right:3px;top:50%;transform:translateY(-50%)'),
|
|
1028
|
+
hover: 'color:var(--foreground)',
|
|
1029
|
+
}, '×'),
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
940
1033
|
function tabNode(t, act, splitTab) {
|
|
941
1034
|
const selected = (act && act.id === t.id) || (splitTab && splitTab.id === t.id);
|
|
942
1035
|
const bg = selected ? FX.TAB_COLORS.activeBg : 'transparent';
|
|
@@ -949,7 +1042,7 @@ function tabNode(t, act, splitTab) {
|
|
|
949
1042
|
* fixture render keeps the design's literal — the comparator reads this text node. */
|
|
950
1043
|
const meta = t.kind === 'term'
|
|
951
1044
|
? ((!FIXTURE && t.sessionId) ? 'session ' + t.sessionId : 'session')
|
|
952
|
-
: (t.kind === 'task' ? 'task' : t.kind === 'web' ? 'web' : t.kind === 'skill' ? 'skill' : t.kind === 'transcript' ? 'conversation' : t.role);
|
|
1045
|
+
: (t.kind === 'task' ? 'task' : t.kind === 'view' ? 'view' : t.kind === 'web' ? 'web' : t.kind === 'skill' ? 'skill' : t.kind === 'transcript' ? 'conversation' : t.role);
|
|
953
1046
|
|
|
954
1047
|
return el('div', {
|
|
955
1048
|
style: 'flex:none;display:flex;align-items:center;gap:8px;padding:0 10px;background:' + bg
|
|
@@ -997,14 +1090,34 @@ export function renderTabs(host) {
|
|
|
997
1090
|
// ── tab bar ──
|
|
998
1091
|
const canSplit = visTabs.length >= 2 || !!state.splitId;
|
|
999
1092
|
splitLabel.textContent = state.splitId ? '⫽ unsplit' : '⫽ split';
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1093
|
+
const ck = cockpitLook();
|
|
1094
|
+
newSessionBtn.textContent = ck ? '+ session' : '+ session';
|
|
1095
|
+
emptyState.textContent = "nothing open — click a task's output or an artifact, or " + (ck ? '+' : '+') + ' session above';
|
|
1096
|
+
if (ck) {
|
|
1097
|
+
tabBar.setAttribute('style', CK_BAR);
|
|
1098
|
+
newSessionBtn.setAttribute('style', CK_BTN + ';margin-left:0');
|
|
1099
|
+
for (const b of [newWebBtn, newSessionOptsBtn, splitBtn]) b.setAttribute('style', CK_BTN);
|
|
1100
|
+
const extras = [
|
|
1101
|
+
FIXTURE ? null : newWebBtn,
|
|
1102
|
+
!FIXTURE && state.webNotice ? el('span', { 'data-r5': 'notice', style: 'align-self:center;font-size:11px;color:var(--destructive);padding:0 6px' }, state.webNotice) : null,
|
|
1103
|
+
newSessionOptsBtn,
|
|
1104
|
+
canSplit ? splitBtn : null,
|
|
1105
|
+
].filter(Boolean);
|
|
1106
|
+
extras[0].style.marginLeft = 'auto';
|
|
1107
|
+
setChildren(tabBar, [...visTabs.map((t) => cockpitTabNode(t, act, splitTab)), ...extras, newSessionBtn]);
|
|
1108
|
+
} else {
|
|
1109
|
+
tabBar.setAttribute('style', BAR_STYLE);
|
|
1110
|
+
newSessionBtn.setAttribute('style', CHIP_STYLE);
|
|
1111
|
+
newWebBtn.setAttribute('style', CHIP_STYLE); newSessionOptsBtn.setAttribute('style', CHIP_STYLE); splitBtn.setAttribute('style', SPLIT_STYLE);
|
|
1112
|
+
setChildren(tabBar, [
|
|
1113
|
+
...visTabs.map((t) => tabNode(t, act, splitTab)),
|
|
1114
|
+
newSessionBtn,
|
|
1115
|
+
FIXTURE ? null : newWebBtn,
|
|
1116
|
+
!FIXTURE && state.webNotice ? el('span', { 'data-r5': 'notice', style: 'align-self:center;font-size:11px;color:var(--destructive);padding:0 6px' }, state.webNotice) : null,
|
|
1117
|
+
newSessionOptsBtn,
|
|
1118
|
+
canSplit ? splitBtn : null,
|
|
1119
|
+
]);
|
|
1120
|
+
}
|
|
1008
1121
|
|
|
1009
1122
|
// ── panes ──
|
|
1010
1123
|
reapClosedPanes();
|
|
@@ -1031,10 +1144,15 @@ export function renderTabs(host) {
|
|
|
1031
1144
|
scratchCopyLabel.textContent = state.scratchCopied ? 'copied ✓' : '⧉ copy';
|
|
1032
1145
|
const canSend = !!(act && act.kind === 'term');
|
|
1033
1146
|
scratchSendLabel.textContent = canSend ? '⇥ send to terminal' : 'open a terminal to send';
|
|
1034
|
-
scratchSendBtn.setAttribute('style',
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1147
|
+
scratchSendBtn.setAttribute('style', canSend ? PAD_BTN_PRIMARY : PAD_BTN_OFF);
|
|
1148
|
+
scratchClearBtn.disabled = !scratchText;
|
|
1149
|
+
scratchClearBtn.style.opacity = scratchText ? '' : '0.5';
|
|
1150
|
+
/* The header names whose pad this is, and the note says what keeps it and what Send does. Both are
|
|
1151
|
+
* read from the same facts `padKey` uses, so they cannot claim a pad the drawer is not showing. */
|
|
1152
|
+
scratchHeadLabel.textContent = FIXTURE ? 'scratchpad'
|
|
1153
|
+
: (canSend ? 'scratchpad · this session' : 'scratchpad · this repository');
|
|
1154
|
+
scratchNote.textContent = FIXTURE ? 'Kept until the page closes.'
|
|
1155
|
+
: (canSend ? 'Kept for this session. Send also presses Enter.' : 'Kept for this repository. Open a terminal to send.');
|
|
1038
1156
|
|
|
1039
1157
|
// ── bottom bar ──
|
|
1040
1158
|
scratchToggleLabel.textContent = state.scratchOpen ? '▼ scratchpad' : '▲ scratchpad';
|
|
@@ -14,15 +14,47 @@
|
|
|
14
14
|
/* Fixture mode is decided from the URL, not from another module: the persistence bypass below is
|
|
15
15
|
* the whole reason the comparator can trust a run. Reading it here keeps state.js loadable on its
|
|
16
16
|
* own and stops a data-layer import from deciding whether the PM's layout is written to disk. */
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/* `?fixture=proto` is the prototype's own seed data; `?fixture=cockpit` is the cockpit mockup's (planning/design/cockpit-mockup/): the
|
|
18
|
+
* same fixture paths, a different seed. Both are the offline demo and the parity script's like-for-like content. */
|
|
19
|
+
const FIXTURE_KIND = (() => {
|
|
20
|
+
try { const f = new URLSearchParams(location.search).get('fixture'); return f === 'proto' || f === 'cockpit' ? f : null; }
|
|
21
|
+
catch { return null; }
|
|
20
22
|
})();
|
|
23
|
+
export const FIXTURE = FIXTURE_KIND !== null;
|
|
24
|
+
/** `?layout=legacy` draws the three-column shell on a desktop, so the suites of the panels that shell uses keep guarding them until
|
|
25
|
+
* Run 4 moves the phone onto the cockpit. Nothing offers it. */
|
|
26
|
+
export const LEGACY_LAYOUT_FLAG = (() => { try { return new URLSearchParams(location.search).get('layout') === 'legacy'; } catch { return false; } })();
|
|
27
|
+
export const FIXTURE_COCKPIT = FIXTURE_KIND === 'cockpit';
|
|
28
|
+
|
|
29
|
+
/** What the cockpit fixture starts with beyond its seed data: the derived statuses (so the list can group), the open tabs. */
|
|
30
|
+
const COCKPIT_START = {
|
|
31
|
+
statuses: {
|
|
32
|
+
microsites: {
|
|
33
|
+
'ooh-weather-expansion': { status: 'blocked' }, 'psle-paywall-revamp': { status: 'delivered' }, 'smrt-news-feed': { status: 'building' },
|
|
34
|
+
'lightbox-ga-tagging': { status: 'accepted' }, 'login-metric-tagging': { status: 'accepted' }, 'shortlink-redirects': { status: 'accepted' },
|
|
35
|
+
},
|
|
36
|
+
// five things wait on the person, as the mockup's "Today 5" says: two here, three in pmos (the tile with the dot), none in scarlet
|
|
37
|
+
pmos: { 'cockpit-terminal-mod': { status: 'blocked' }, 'workspace-cockpit-nav': { status: 'blocked' }, 'kit-publish': { status: 'delivered' } },
|
|
38
|
+
// the mockup's third tile carries no dot: nothing in that repository is waiting on the person
|
|
39
|
+
scarlet: { 'scarlet-onboarding': { status: 'building' } },
|
|
40
|
+
},
|
|
41
|
+
product: 'microsites', sel: 'ooh-weather-expansion',
|
|
42
|
+
tabs: [
|
|
43
|
+
{ id: 'term:ooh-weather-expansion:claude · merge PR 41', kind: 'term', title: 'claude · merge PR 41', slug: 'ooh-weather-expansion', repo: 'microsites', sess: 'claude · merge PR 41', state: 'working', role: 'TERM' },
|
|
44
|
+
{ id: 'doc:planning/prd/ooh-weather-expansion-prd.md', kind: 'doc', title: '…-prd.md', slug: 'ooh-weather-expansion', path: 'planning/prd/ooh-weather-expansion-prd.md', role: 'DOC' },
|
|
45
|
+
{ id: 'view:memory:microsites', kind: 'view', view: 'memory', title: 'tool · memory', slug: 'microsites', repo: 'microsites' },
|
|
46
|
+
],
|
|
47
|
+
active: 'term:ooh-weather-expansion:claude · merge PR 41',
|
|
48
|
+
};
|
|
21
49
|
|
|
22
50
|
const STORE_KEY = 'pmos.workspace.pm.v1';
|
|
23
51
|
|
|
24
52
|
/** The initial state — the prototype's `state = {…}` block, key for key. */
|
|
25
53
|
function initial() {
|
|
54
|
+
const base = initialBase();
|
|
55
|
+
return FIXTURE_COCKPIT ? { ...base, ...JSON.parse(JSON.stringify(COCKPIT_START)) } : base;
|
|
56
|
+
}
|
|
57
|
+
function initialBase() {
|
|
26
58
|
return {
|
|
27
59
|
theme: 'dark', sel: null, q: '', mode: 'product', collapsedGroups: {}, groupBy: 'objective', activeOnly: false, legendOpen: false, settingsOpen: false,
|
|
28
60
|
product: null, intgSynced: {},
|
|
@@ -35,6 +67,13 @@ function initial() {
|
|
|
35
67
|
newType: 'init', newName: '', newSlug: '', newAnchor: null, newFlow: 'adlc-feature',
|
|
36
68
|
userGroups: {}, userKrs: {}, addedInits: {}, userInits: {}, selTask: null, collapsedPhases: {},
|
|
37
69
|
detail: { kind: 'init' },
|
|
70
|
+
// the rail's menu (workspace-cockpit-nav): open or shut, never persisted
|
|
71
|
+
menuOpen: false,
|
|
72
|
+
paletteOpen: false, // Run 2: the ⌘K palette — never persisted
|
|
73
|
+
layoutFocus: false, // Run 2: the Focus layout — never persisted (distinct from `focus`, the focus-mode data)
|
|
74
|
+
todayScope: 'repo',
|
|
75
|
+
filesOpen: false, // Run 3: the Work list shows the repository's files — never persisted
|
|
76
|
+
todayDismissed: {},
|
|
38
77
|
tabs: [], active: null, splitId: null,
|
|
39
78
|
// overhaul-r1: reader measure per tab (R5), which repositories show their lines of work (R2.1),
|
|
40
79
|
// and the last remove/rename answer, shown on the row it is about (R3.3)
|
|
@@ -155,7 +194,9 @@ const TAB_FIELDS = ['id', 'kind', 'title', 'slug', 'repo', 'sess', 'sessionId',
|
|
|
155
194
|
// a skill tab's skill id — a reload reopens the same skill
|
|
156
195
|
'skillId',
|
|
157
196
|
// a conversation tab's session id — a reload reopens the same transcript
|
|
158
|
-
'convId'
|
|
197
|
+
'convId',
|
|
198
|
+
// a view tab's view key (workspace-cockpit-nav) — a reload reopens the same tool
|
|
199
|
+
'view'];
|
|
159
200
|
const MAX_TABS = 40;
|
|
160
201
|
|
|
161
202
|
function packTab(t) {
|