create-pmos 0.88.0 → 0.89.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 +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/layout.md +2 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +42 -4
- package/workspace/public/pm/components.css +10 -0
- package/workspace/public/pm/dock.js +53 -13
- package/workspace/public/pm/frame-copy.js +6 -0
- package/workspace/public/pm/panel-tabs.js +11 -8
- package/workspace/public/pm/primitives.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T17:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:12:14Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T17:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:12:14Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-07T17:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-07T17:12:14Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -57,9 +57,9 @@ The dock holds your terminals, documents, conversations, skills and web pages. I
|
|
|
57
57
|
- Press **⋯** on a row to change the name, the initiative or the type of work.
|
|
58
58
|
- Press **✕** on a row to end the session. The row asks "End “name”?". Press **End** or **Cancel**. When you end the last running session on its own working copy, the message offers **Remove working copy**. This removes the folder only. The branch stays. If the folder has unsaved work, the workspace keeps it and tells you why.
|
|
59
59
|
- **+ Session** at the top of the dock, or **+** on an initiative, opens one sheet. Type a name, or leave it empty to name the session after the skill and the initiative. **For** is the initiative. **On** is where the session runs. **Automatic** uses the initiative's own working copy on `initiative/<id>` (made the first time), or the main tree when there is no initiative. You can also choose a branch that has a working copy, or **New branch…**. Then press **Blank session** (the CLI and model from Settings) or a skill. The session starts at once.
|
|
60
|
-
- The icons at the top of the session list, under **+ Session**,
|
|
60
|
+
- The icons at the top of the session list, under **+ Session**, switch the sessions as a strip on top or a list at the side, set the dock size, and fold the dock. They stay in view when you scroll the list. On top, a select chooses the group, and each session of the group is a button. **+** starts a session, and **✕** ends the session in front.
|
|
61
61
|
- To make the dock wider or narrower, drag its left edge. With the edge focused, the left and right arrow keys change the width in steps. Double-click the edge to go back to the default width. The width is kept in this browser.
|
|
62
|
-
- **
|
|
62
|
+
- **Dock size** opens a menu of four sizes: Normal (the default width), Half the window, Two-thirds, and Whole window (focus). Choose one to set the dock to that size. The size is remembered in this browser. Dragging the edge clears the chosen size and sets a pixel width instead. Whole window (focus) gives the dock the entire screen; the place and the detail are hidden, and the dock stays full width on every place. Press Whole window again, or ⌘⇧J (Ctrl-Shift-J), to show the place. Folding the dock also ends focus.
|
|
63
63
|
- **Scratchpad** in the bar at the bottom of the dock opens and closes the scratchpad. The pad keeps one note for each terminal session, and one for the repository when no terminal is in front. **Send to terminal** types the note into the session in front and presses Enter. It is available only when a terminal is in front. **Copy** and **Clear** act on the note.
|
|
64
64
|
- The same bottom bar shows the title of the tab in front and a copy button that names what it copies: **Copy document** for a document, **Copy attach command** for a terminal.
|
|
65
65
|
- A document tab has one bar. The words at its start say whether the file is saved on disk or has unsaved changes. The copy button beside them shows the file's Confluence or Google Docs copy (see [Edit a document in place](#edit-a-document-in-place)). **Changes**, **Revert** and **Save** show when there are unsaved changes. **Markdown** shows the whole file as markdown in one box; press it again to go back. A file that you cannot edit has **Source** in its place. **⋯** holds the rest: the reading width (full width, or lines of 72 characters), **Normalize line breaks** (it joins hard-wrapped paragraphs into one line each in the buffer; read the result in Changes, then save) and **Hide the outline and connections**. **Run scripts** shows on the bar for an HTML page that can run its own scripts.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.89.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -414,7 +414,7 @@ function readFrame() {
|
|
|
414
414
|
}
|
|
415
415
|
const fr = (() => {
|
|
416
416
|
const s = readFrame();
|
|
417
|
-
return { dest: FRAME_DESTS.includes(s.dest) ? s.dest : 'today', scope: s.scope === 'all' ? 'all' : 'repo', params: {}, dock: {}, dockMax: false, dockW: null, syncing: false, reg: null, gAt: 0, needs: 0 };
|
|
417
|
+
return { dest: FRAME_DESTS.includes(s.dest) ? s.dest : 'today', scope: s.scope === 'all' ? 'all' : 'repo', params: {}, dock: {}, dockMax: false, dockW: null, dockSize: null, syncing: false, reg: null, gAt: 0, needs: 0 };
|
|
418
418
|
})();
|
|
419
419
|
function writeFrame() {
|
|
420
420
|
try { localStorage.setItem(FRAME_KEY, JSON.stringify({ dest: fr.dest, scope: fr.scope })); } catch { /* private mode: the default stands */ }
|
|
@@ -444,6 +444,34 @@ function toggleFocus() {
|
|
|
444
444
|
if (fr.dockMax) fr.dock[fr.dest] = true;
|
|
445
445
|
setState({});
|
|
446
446
|
}
|
|
447
|
+
/* Dock size: set the dock to one of four preset sizes, or to focus mode.
|
|
448
|
+
* Normal: default width (no CSS override). Half: 50vw. Two-thirds: 66.667vw (the body spans the window).
|
|
449
|
+
* Focus: the dock takes the whole window (same as toggleFocus). */
|
|
450
|
+
const DOCK_SIZE_KEY = 'pmos.workspace.pm.dock-size';
|
|
451
|
+
const DOCK_SIZES = ['normal', 'half', 'two-thirds'];
|
|
452
|
+
function readDockSize() {
|
|
453
|
+
if (!FRAME) return null;
|
|
454
|
+
let v = null;
|
|
455
|
+
try { v = localStorage.getItem(DOCK_SIZE_KEY) || null; } catch { v = null; }
|
|
456
|
+
return v === 'half' || v === 'two-thirds' ? v : null;
|
|
457
|
+
}
|
|
458
|
+
function setDockSize(next) {
|
|
459
|
+
if (!FRAME) return;
|
|
460
|
+
if (next === 'whole') {
|
|
461
|
+
if (!fr.dockMax) toggleFocus();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (!DOCK_SIZES.includes(next)) return;
|
|
465
|
+
// A preset replaces a dragged pixel width, and leaving focus returns to the size chosen here.
|
|
466
|
+
fr.dockW = null;
|
|
467
|
+
try { localStorage.removeItem(DOCK_W_KEY); } catch { /* private mode */ }
|
|
468
|
+
fr.dockSize = next === 'normal' ? null : next;
|
|
469
|
+
try { if (fr.dockSize) localStorage.setItem(DOCK_SIZE_KEY, fr.dockSize); else localStorage.removeItem(DOCK_SIZE_KEY); } catch { /* private mode */ }
|
|
470
|
+
fr.dockMax = false;
|
|
471
|
+
fr.dock[fr.dest] = true;
|
|
472
|
+
paintDockWidth();
|
|
473
|
+
setState({});
|
|
474
|
+
}
|
|
447
475
|
/* The dock's width, chosen with the grip (0.53.1). Kept in this browser like the dock's other per-window choices;
|
|
448
476
|
* null is the default width. */
|
|
449
477
|
const DOCK_W_KEY = 'pmos.workspace.pm.dock-width';
|
|
@@ -457,12 +485,21 @@ const dockWidth = {
|
|
|
457
485
|
get: () => fr.dockW,
|
|
458
486
|
set: (px) => {
|
|
459
487
|
fr.dockW = Number.isFinite(px) && px > 0 ? Math.round(px) : null;
|
|
488
|
+
fr.dockSize = null; // dragging the grip, or a double-click, replaces a preset
|
|
460
489
|
try { if (fr.dockW) localStorage.setItem(DOCK_W_KEY, String(fr.dockW)); else localStorage.removeItem(DOCK_W_KEY); } catch { /* private mode */ }
|
|
490
|
+
if (!fr.dockSize) try { localStorage.removeItem(DOCK_SIZE_KEY); } catch { /* private mode */ }
|
|
461
491
|
paintDockWidth();
|
|
462
492
|
},
|
|
463
493
|
};
|
|
464
494
|
function paintDockWidth() {
|
|
465
|
-
|
|
495
|
+
let v = '';
|
|
496
|
+
if (fr.dockW) {
|
|
497
|
+
v = fr.dockW + 'px';
|
|
498
|
+
} else if (fr.dockSize === 'half') {
|
|
499
|
+
v = '50vw';
|
|
500
|
+
} else if (fr.dockSize === 'two-thirds') {
|
|
501
|
+
v = '66.667vw';
|
|
502
|
+
}
|
|
466
503
|
if (bodyGrid.style.getPropertyValue('--fr-dock-w') !== v) {
|
|
467
504
|
if (v) bodyGrid.style.setProperty('--fr-dock-w', v); else bodyGrid.style.removeProperty('--fr-dock-w');
|
|
468
505
|
}
|
|
@@ -574,6 +611,7 @@ function setupFrame() {
|
|
|
574
611
|
document.addEventListener('pointerdown', (e) => { fr.downAt = e.target; }, true);
|
|
575
612
|
dockRail = createDockRail({ open: openDock, resize: dockWidth });
|
|
576
613
|
fr.dockW = readDockW();
|
|
614
|
+
fr.dockSize = readDockSize();
|
|
577
615
|
frDetail = el('div', { class: 'fr-detail', 'data-fr': 'detail' },
|
|
578
616
|
el('div', { class: 'fr-detail__bar' }, iconButton({ icon: 'x', ariaLabel: FC.detail.close, onClick: closeDetail, attrs: { 'data-fr': 'detail-close' } })),
|
|
579
617
|
hostDetail);
|
|
@@ -712,9 +750,9 @@ function frameKey(e) {
|
|
|
712
750
|
|
|
713
751
|
/* What the suites and the phone chrome reach the frame by. */
|
|
714
752
|
const frameApi = {
|
|
715
|
-
on: true, go, toggleDock, foldDock, openDock, toggleFocus, dockWidth, chooseRepo, chooseAll, openPalette, repoMenu, openBranchesSheet, closeDetail, sync: runSync,
|
|
753
|
+
on: true, go, toggleDock, foldDock, openDock, toggleFocus, setDockSize, dockWidth, chooseRepo, chooseAll, openPalette, repoMenu, openBranchesSheet, closeDetail, sync: runSync,
|
|
716
754
|
get unsynced() { return !!fr.unsynced; },
|
|
717
|
-
get dest() { return fr.dest; }, get scope() { return fr.scope; }, get dockOpen() { return dockOpen(); }, get dockMax() { return !!fr.dockMax; },
|
|
755
|
+
get dest() { return fr.dest; }, get scope() { return fr.scope; }, get dockOpen() { return dockOpen(); }, get dockMax() { return !!fr.dockMax; }, get dockSize() { return fr.dockSize || 'normal'; },
|
|
718
756
|
get detailOpen() { return !!state.col2; }, get syncing() { return fr.syncing; }, get needs() { return fr.needs || 0; },
|
|
719
757
|
};
|
|
720
758
|
|
|
@@ -652,6 +652,16 @@
|
|
|
652
652
|
white-space: nowrap;
|
|
653
653
|
cursor: pointer;
|
|
654
654
|
}
|
|
655
|
+
/* The shortcut hint sits at a row's end; a checked row (a menuitemradio) is marked. */
|
|
656
|
+
.pmc-menu__hint {
|
|
657
|
+
margin-left: auto;
|
|
658
|
+
padding-left: 16px;
|
|
659
|
+
color: var(--muted-foreground);
|
|
660
|
+
font: 11px var(--mono);
|
|
661
|
+
}
|
|
662
|
+
.pmc-menu__item[aria-checked="true"] {
|
|
663
|
+
background: var(--muted);
|
|
664
|
+
}
|
|
655
665
|
.pmc-menu__item--danger {
|
|
656
666
|
color: var(--destructive);
|
|
657
667
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* createDockRail({ open, resize }) the 44px rail: one control that opens the dock. While the dock is open the rail
|
|
10
10
|
* is a thin grip on the dock's left edge that resizes it (0.53.1)
|
|
11
11
|
* foldButton({ fold }) the control in the open dock's strip that folds it back to the rail
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* dockSizeButton({ setSize, getState }) / paintDockSize(b, size, inFocus)
|
|
13
|
+
* Dock size menu: Normal, Half, Two-thirds, Whole window (focus; 0.53.1, folded into the menu later)
|
|
14
14
|
* sessionMenuButton({ open, ws }) "+ Session": opens the new-session sheet (dock-sessions.js): a blank
|
|
15
15
|
* session or a skill to start from (R10; one list since 0.55.1)
|
|
16
16
|
* sessionDefaults() / setSessionDefaults(patch)
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { el } from './dom.js';
|
|
24
24
|
import { FIXTURE } from './state.js';
|
|
25
25
|
import { pmGet } from './http.js';
|
|
26
|
-
import { button, iconButton } from './primitives.js';
|
|
26
|
+
import { button, iconButton, menu } from './primitives.js';
|
|
27
27
|
import { icon } from './icons.js';
|
|
28
28
|
import { COPY } from './frame-copy.js';
|
|
29
29
|
|
|
@@ -103,19 +103,59 @@ const endDrag = () => { if (drag) { drag.g.removeAttribute('data-dragging'); dra
|
|
|
103
103
|
window.addEventListener('pointerup', endDrag);
|
|
104
104
|
window.addEventListener('pointercancel', endDrag);
|
|
105
105
|
|
|
106
|
-
/*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
/* Dock size menu: Normal, Half, Two-thirds, or Whole window (focus). It replaces the old Focus button; focus is its last row
|
|
107
|
+
* and keeps ⌘⇧J. The button shows the current size; the menu is built on each press, so it always shows the current choice. */
|
|
108
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
109
|
+
const SIZE_SHARE = { normal: 7, half: 9, 'two-thirds': 12 }; // width of the filled part of the glyph, out of 18
|
|
110
|
+
/** A small window glyph with the dock's share filled on the right. Own SVG: the vendored sprite has no such symbol. */
|
|
111
|
+
function sizeGlyph(size) {
|
|
112
|
+
const svg = icon('square'); // the primitive's own <svg> (class, size, aria-hidden); its symbol is replaced below
|
|
113
|
+
svg.replaceChildren();
|
|
114
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
115
|
+
svg.setAttribute('fill', 'none');
|
|
116
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
117
|
+
svg.setAttribute('stroke-width', '2');
|
|
118
|
+
svg.setAttribute('data-icon', 'dock-' + size);
|
|
119
|
+
const frame = document.createElementNS(SVG_NS, 'rect');
|
|
120
|
+
for (const [k, v] of Object.entries({ x: 3, y: 5, width: 18, height: 14, rx: 2 })) frame.setAttribute(k, String(v));
|
|
121
|
+
const fill = document.createElementNS(SVG_NS, 'rect');
|
|
122
|
+
const w = SIZE_SHARE[size] || SIZE_SHARE.normal;
|
|
123
|
+
for (const [k, v] of Object.entries({ x: 21 - w, y: 5, width: w, height: 14 })) fill.setAttribute(k, String(v));
|
|
124
|
+
fill.setAttribute('fill', 'currentColor');
|
|
125
|
+
fill.setAttribute('stroke', 'none');
|
|
126
|
+
svg.append(frame, fill);
|
|
127
|
+
return svg;
|
|
110
128
|
}
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
const sizeNode = (size, inFocus) => (inFocus ? icon('minimize-2') : sizeGlyph(size || 'normal'));
|
|
130
|
+
|
|
131
|
+
export function dockSizeButton({ setSize, getState } = {}) {
|
|
132
|
+
return iconButton({ icon: 'maximize-2', ariaLabel: COPY.dock.dockSize,
|
|
133
|
+
attrs: { class: 'fr-dock-size', 'data-fr': 'dock-size', 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-keyshortcuts': 'Meta+Shift+J Control+Shift+J' },
|
|
134
|
+
onClick: (e) => {
|
|
135
|
+
const b = e.currentTarget;
|
|
136
|
+
const st = (getState && getState()) || {};
|
|
137
|
+
const cur = st.inFocus ? 'whole' : (st.size || 'normal');
|
|
138
|
+
const row = (id, label, hint, node) => ({
|
|
139
|
+
label, iconNode: node, hint, role: 'menuitemradio',
|
|
140
|
+
attrs: { 'data-size': id, 'aria-checked': cur === id ? 'true' : 'false' },
|
|
141
|
+
onSelect: () => setSize && setSize(id),
|
|
142
|
+
});
|
|
143
|
+
menu({ anchor: b, ariaLabel: COPY.dock.dockSizeMenu, items: [
|
|
144
|
+
row('normal', COPY.dock.sizeNormal, '', sizeGlyph('normal')),
|
|
145
|
+
row('half', COPY.dock.sizeHalf, '', sizeGlyph('half')),
|
|
146
|
+
row('two-thirds', COPY.dock.sizeTwoThirds, '', sizeGlyph('two-thirds')),
|
|
147
|
+
row('whole', COPY.dock.sizeWhole, '⌘⇧J', icon('maximize-2')),
|
|
148
|
+
] });
|
|
149
|
+
} });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Show the button's glyph and name for the current size. */
|
|
153
|
+
export function paintDockSize(b, size, inFocus) {
|
|
113
154
|
if (!b) return;
|
|
114
|
-
const
|
|
155
|
+
const want = inFocus ? 'minimize-2' : 'dock-' + (size || 'normal');
|
|
115
156
|
const cur = b.querySelector('[data-icon]');
|
|
116
|
-
if (!cur || cur.getAttribute('data-icon') !==
|
|
117
|
-
|
|
118
|
-
const label = on ? COPY.dock.unfocusTitle : COPY.dock.focusTitle;
|
|
157
|
+
if (!cur || cur.getAttribute('data-icon') !== want) b.replaceChildren(sizeNode(size, inFocus));
|
|
158
|
+
const label = inFocus ? COPY.dock.unfocusTitle : COPY.dock.dockSize;
|
|
119
159
|
b.setAttribute('aria-label', label);
|
|
120
160
|
b.title = label;
|
|
121
161
|
}
|
|
@@ -62,6 +62,12 @@ export const COPY = {
|
|
|
62
62
|
unfocus: 'Exit focus',
|
|
63
63
|
focusTitle: 'Give the dock the whole window (⌘⇧J)',
|
|
64
64
|
unfocusTitle: 'Show the page beside the dock again (⌘⇧J)',
|
|
65
|
+
dockSize: 'Dock size',
|
|
66
|
+
dockSizeMenu: 'Choose dock size',
|
|
67
|
+
sizeNormal: 'Normal',
|
|
68
|
+
sizeHalf: 'Half the window',
|
|
69
|
+
sizeTwoThirds: 'Two-thirds',
|
|
70
|
+
sizeWhole: 'Whole window (focus)',
|
|
65
71
|
resize: 'Resize the agent dock. Drag, or use the left and right arrow keys. Double-click resets the width.',
|
|
66
72
|
session: 'Session',
|
|
67
73
|
sessionMenu: 'Start a session',
|
|
@@ -1152,11 +1152,14 @@ function frameDock() {
|
|
|
1152
1152
|
open: () => (dsMod ? dsMod.openNewSessionSheet(currentProduct(), { initiative: null }) : newSession()),
|
|
1153
1153
|
}),
|
|
1154
1154
|
fold: dockParts ? dockParts.fold : dockMod.foldButton({ fold: () => window.pmApp.frame.foldDock() }),
|
|
1155
|
-
|
|
1155
|
+
size: dockParts ? dockParts.size : dockMod.dockSizeButton({
|
|
1156
|
+
setSize: (sz) => window.pmApp.frame.setDockSize(sz),
|
|
1157
|
+
getState: () => ({ size: window.pmApp.frame.dockSize, inFocus: !!window.pmApp.frame.dockMax }),
|
|
1158
|
+
}),
|
|
1156
1159
|
};
|
|
1157
1160
|
}
|
|
1158
|
-
if (isMobile()) return { session: dockParts.session, fold: null,
|
|
1159
|
-
dockMod.
|
|
1161
|
+
if (isMobile()) return { session: dockParts.session, fold: null, size: null, orient: null };
|
|
1162
|
+
dockMod.paintDockSize(dockParts.size, window.pmApp.frame.dockSize, !!window.pmApp.frame.dockMax);
|
|
1160
1163
|
// the tabs-on-top / tabs-at-side switch, icon only in the frame (0.55.1); drawn per paint because its icon follows the mode
|
|
1161
1164
|
// one element across paints, so the keyboard focus stays on it; only its icon and name follow the mode
|
|
1162
1165
|
const v = !!state.tabsVertical;
|
|
@@ -1170,7 +1173,7 @@ function frameDock() {
|
|
|
1170
1173
|
const olabel = v ? COPY.dock.topTabs : COPY.dock.sideTabs;
|
|
1171
1174
|
dockOrient.setAttribute('aria-label', olabel);
|
|
1172
1175
|
dockOrient.title = olabel;
|
|
1173
|
-
return { session: dockParts.session, fold: dockParts.fold,
|
|
1176
|
+
return { session: dockParts.session, fold: dockParts.fold, size: dockParts.size, orient: dockOrient };
|
|
1174
1177
|
}
|
|
1175
1178
|
|
|
1176
1179
|
/**
|
|
@@ -1395,7 +1398,7 @@ export function renderTabs(host) {
|
|
|
1395
1398
|
newSessionOptsBtn,
|
|
1396
1399
|
canSplit ? splitBtn : null,
|
|
1397
1400
|
orientBtn,
|
|
1398
|
-
fr ? fr.
|
|
1401
|
+
fr ? fr.size : null,
|
|
1399
1402
|
fr ? fr.fold : null),
|
|
1400
1403
|
]);
|
|
1401
1404
|
} else if (ck) {
|
|
@@ -1410,7 +1413,7 @@ export function renderTabs(host) {
|
|
|
1410
1413
|
].filter(Boolean);
|
|
1411
1414
|
extras[0].style.marginLeft = 'auto';
|
|
1412
1415
|
// The switch sits after `+ session`, so the strip still ends the mockup's way in the fixture, which does not draw it.
|
|
1413
|
-
setChildren(tabBar, [...visTabs.map((t) => cockpitTabNode(t, act, splitTab)), ...extras, fr ? fr.session : newSessionBtn, FIXTURE ? null : orientBtn, fr ? fr.
|
|
1416
|
+
setChildren(tabBar, [...visTabs.map((t) => cockpitTabNode(t, act, splitTab)), ...extras, fr ? fr.session : newSessionBtn, FIXTURE ? null : orientBtn, fr ? fr.size : null, fr ? fr.fold : null]);
|
|
1414
1417
|
} else {
|
|
1415
1418
|
tabBar.setAttribute('style', BAR_STYLE);
|
|
1416
1419
|
newSessionBtn.setAttribute('style', CHIP_STYLE);
|
|
@@ -1437,11 +1440,11 @@ export function renderTabs(host) {
|
|
|
1437
1440
|
...dsMod.railNodes(ds, others.map((t) => dockTabRow(t, (act && act.id === t.id) || (splitTab && splitTab.id === t.id)))),
|
|
1438
1441
|
] : [
|
|
1439
1442
|
...dsMod.stripNodes(ds, others.map((t) => dockTabChip(t, (act && act.id === t.id) || (splitTab && splitTab.id === t.id)))),
|
|
1440
|
-
state.splitId ? splitBtn : null, fr.orient, fr.
|
|
1443
|
+
state.splitId ? splitBtn : null, fr.orient, fr.size, fr.fold,
|
|
1441
1444
|
]);
|
|
1442
1445
|
}
|
|
1443
1446
|
|
|
1444
|
-
if (ck && ds && vertical) setChildren(dockHead, [fr.session, state.splitId ? splitBtn : null, fr.orient, fr.
|
|
1447
|
+
if (ck && ds && vertical) setChildren(dockHead, [fr.session, state.splitId ? splitBtn : null, fr.orient, fr.size, fr.fold]);
|
|
1445
1448
|
if (tabBar.scrollLeft !== keepX) tabBar.scrollLeft = keepX;
|
|
1446
1449
|
if (tabBar.scrollTop !== keepY) tabBar.scrollTop = keepY;
|
|
1447
1450
|
// When the tab in front changes, the bar scrolls just enough to show it, so a tab opened at the far end of a crowded strip or a
|
|
@@ -354,9 +354,9 @@ export function menu({ anchor, items, ariaLabel } = {}) {
|
|
|
354
354
|
if (it.sep) { node.appendChild(el('div', { class: 'pmc-menu__sep', role: 'separator' })); continue; }
|
|
355
355
|
const b = el('button', {
|
|
356
356
|
...(it.attrs || {}),
|
|
357
|
-
type: 'button', role: 'menuitem', tabindex: '-1', class: cx('pmc-menu__item', it.danger && 'pmc-menu__item--danger'),
|
|
357
|
+
type: 'button', role: it.role || 'menuitem', tabindex: '-1', class: cx('pmc-menu__item', it.danger && 'pmc-menu__item--danger'),
|
|
358
358
|
onClick: (e) => { e.stopPropagation(); close(false); if (it.onSelect) it.onSelect(); },
|
|
359
|
-
}, it.icon ? icon(it.icon) : null, el('span', null, it.label));
|
|
359
|
+
}, it.iconNode || (it.icon ? icon(it.icon) : null), el('span', null, it.label), it.hint ? el('kbd', { class: 'pmc-menu__hint' }, it.hint) : null);
|
|
360
360
|
btns.push(b); node.appendChild(b);
|
|
361
361
|
}
|
|
362
362
|
node.addEventListener('toggle', (e) => { if (e.newState === 'closed') close(false); });
|