create-pmos 0.53.0 → 0.53.2
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/README.md +2 -0
- package/workspace/REMOTE.md +107 -65
- package/workspace/bin/pmos.js +4 -2
- package/workspace/first-paint-audit.js +6 -2
- package/workspace/fixtures/initiatives-api/list.json +13 -0
- package/workspace/fixtures/initiatives-api/roadmap.json +13 -0
- package/workspace/fixtures/report-api/draft.json +105 -0
- package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +12 -0
- package/workspace/guide/command-palette.md +1 -1
- package/workspace/guide/index.md +3 -1
- package/workspace/guide/initiatives-roadmap.md +1 -0
- package/workspace/guide/initiatives-screen.md +1 -0
- package/workspace/guide/launcher.md +51 -0
- package/workspace/guide/layout.md +7 -2
- package/workspace/guide/proactive-engine.md +21 -5
- package/workspace/guide/report-slides.md +28 -0
- package/workspace/guide/reports.md +2 -0
- package/workspace/lib/auto-sync.js +83 -0
- package/workspace/lib/board-fold.js +2 -2
- package/workspace/lib/breakdown-export.js +1 -6
- package/workspace/lib/brief.js +20 -2
- package/workspace/lib/goals.js +165 -0
- package/workspace/lib/initiative-rows.js +7 -3
- package/workspace/lib/launcher-cli.js +207 -0
- package/workspace/lib/plan-tree.js +15 -2
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/proactive-cli.js +58 -2
- package/workspace/lib/proactive.js +2 -2
- package/workspace/lib/qr.js +293 -0
- package/workspace/lib/remote-access.js +184 -12
- package/workspace/lib/report-draft.js +34 -6
- package/workspace/lib/report-slides-data.js +141 -0
- package/workspace/lib/routes-initiatives.js +35 -2
- package/workspace/lib/routes-reports.js +3 -3
- package/workspace/lib/routes-timeline.js +1 -1
- package/workspace/lib/sync.js +61 -6
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/icons/apple-touch-icon.png +0 -0
- package/workspace/public/icons/icon-192.png +0 -0
- package/workspace/public/icons/icon-512.png +0 -0
- package/workspace/public/index.html +11 -0
- package/workspace/public/pm/app.js +55 -9
- package/workspace/public/pm/components.css +865 -1
- package/workspace/public/pm/dest-reports.js +8 -2
- package/workspace/public/pm/dest-today.js +8 -2
- package/workspace/public/pm/dock.js +73 -3
- package/workspace/public/pm/frame-copy.js +5 -0
- package/workspace/public/pm/initiative-list.js +18 -1
- package/workspace/public/pm/initiatives-copy.js +4 -0
- package/workspace/public/pm/panel-tabs.js +14 -12
- package/workspace/public/pm/print.css +11 -0
- package/workspace/public/pm/report-slides.js +438 -0
- package/workspace/public/pm/report-view.js +8 -2
- package/workspace/public/pm/reports-copy.js +3 -0
- package/workspace/public/pm/roadmap.js +23 -0
- package/workspace/public/pm/slides-copy.js +52 -0
- package/workspace/public/pm/today-copy.js +29 -0
- package/workspace/public/pm/today-goals.js +172 -0
- package/workspace/public/pm/tokens.css +13 -0
- package/workspace/public/pm/topbar.js +5 -2
- package/workspace/replaced-tests.json +36 -1
- package/workspace/server.js +165 -15
- package/workspace/skills/morning-brief.skill +15 -9
- package/workspace/skills/report-biweekly.skill +1 -0
- package/workspace/test-files.json +4 -0
- package/workspace/test-lists/run6.json +9 -0
- package/workspace/test-lists/workspace-launcher.json +5 -0
- package/workspace/test-lists/workspace-phone-app.json +5 -0
- package/workspace/test-lists/workspace-public-host.json +5 -0
|
@@ -23,7 +23,8 @@ import { pmGet, pmPost } from './http.js';
|
|
|
23
23
|
import { watchSig } from './sig-watch.js';
|
|
24
24
|
import { wsOf } from './data.js';
|
|
25
25
|
import { COPY, cadenceName } from './reports-copy.js';
|
|
26
|
-
import {
|
|
26
|
+
import { openIssueSheet, openNotesSheet, draftNow, printReport, copyMarkdown, clearPrint, ensurePrintCss } from './report-view.js';
|
|
27
|
+
import { openSlides, slideCards } from './report-slides.js';
|
|
27
28
|
|
|
28
29
|
const SHOWN = 4; // the reports listed before "Show older"
|
|
29
30
|
const insts = new WeakMap(); // host -> instance
|
|
@@ -227,6 +228,10 @@ function viewerBlock(inst) {
|
|
|
227
228
|
const bar = el('div', { class: 'rp-toolbar', role: 'toolbar', 'aria-label': COPY.reportName(r.cadence, r.slot) });
|
|
228
229
|
bar.appendChild(el('h3', { class: 'rp-section__title rp-toolbar__title' }, COPY.reportName(r.cadence, r.slot)));
|
|
229
230
|
const acts = el('div', { class: 'rp-toolbar__acts' });
|
|
231
|
+
// Present: the slides full screen (Run 6, R22.7); it reads the file and writes nothing
|
|
232
|
+
const present = button({ label: COPY.present, variant: 'outline', size: 'sm', icon: 'columns-2', attrs: { 'data-rp-act': 'present', 'data-rp-key': 'present' }, disabled: !doc,
|
|
233
|
+
onClick: () => doc && openSlides({ md: doc.markdown, label: COPY.reportName(r.cadence, r.slot), printable: !!r.issued, returnFocus: present }) });
|
|
234
|
+
acts.appendChild(present);
|
|
230
235
|
if (r.issued) {
|
|
231
236
|
acts.appendChild(button({ label: COPY.print, variant: 'outline', size: 'sm', attrs: { 'data-rp-act': 'print', 'data-rp-key': 'print' }, disabled: !doc, onClick: () => doc && printReport(doc.markdown) }));
|
|
232
237
|
acts.appendChild(button({ label: COPY.copyMd, variant: 'outline', size: 'sm', attrs: { 'data-rp-act': 'copy', 'data-rp-key': 'copy' }, disabled: !doc, onClick: () => doc && copyMarkdown(doc.markdown) }));
|
|
@@ -246,7 +251,8 @@ function viewerBlock(inst) {
|
|
|
246
251
|
bar.appendChild(acts);
|
|
247
252
|
sec.appendChild(bar);
|
|
248
253
|
if (r.issued) sec.appendChild(el('p', { class: 'rp-viewer__state' }, r.edited ? COPY.editedWhy : COPY.frozen));
|
|
249
|
-
|
|
254
|
+
// the report is read as its slides (Run 6): the PM-OS deck, scaled to the column
|
|
255
|
+
sec.appendChild(doc ? slideCards(doc.markdown) : el('p', { class: 'rp-section__empty' }, COPY.loading));
|
|
250
256
|
return sec;
|
|
251
257
|
}
|
|
252
258
|
|
|
@@ -21,6 +21,7 @@ import { wsOf } from './data.js';
|
|
|
21
21
|
import { state, setState, FIXTURE } from './state.js';
|
|
22
22
|
import { COPY } from './today-copy.js';
|
|
23
23
|
import { todayCard, acceptCard, rankNodes, scopeCtx, ensureFeed, feedOf, feedErrorOf, bannerOf, onFeed } from './today-cards.js';
|
|
24
|
+
import { goalsSection, goalsSig } from './today-goals.js';
|
|
24
25
|
|
|
25
26
|
const SHOWN = 3; // cards drawn before "Show more": the first paint holds at most 17 controls (first-paint-today.test.js)
|
|
26
27
|
let expanded = false;
|
|
@@ -117,7 +118,10 @@ function build(ctx0) {
|
|
|
117
118
|
? button({ label: expanded ? COPY.fewer : COPY.more(nodes.length - SHOWN), variant: 'ghost', attrs: { 'data-today': 'more' }, onClick: () => { expanded = !expanded; setState({}); } })
|
|
118
119
|
: null;
|
|
119
120
|
nodes.forEach((node) => node.setAttribute('role', 'listitem'));
|
|
120
|
-
|
|
121
|
+
// today-goals (G6): the PM's goals sit above what is owed; they are joined to the cards of the first repository in scope
|
|
122
|
+
const goalsWs = sc.repos.length ? wsOf(sc.repos[0]) : null;
|
|
123
|
+
const goals = goalsSection({ ws: goalsWs, setState: ctx0.setState || setState });
|
|
124
|
+
return { children: [head, goals, health, list, more], sig: JSON.stringify([sc.all, sc.repo, n, ready, expanded, banner]) + goalsSig(goalsWs) + nodes.map((x) => x.outerHTML).join('') };
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
/** The key of the control that has the focus inside the board, so it can be focused again after a rebuild. */
|
|
@@ -128,6 +132,7 @@ function focusKey(root) {
|
|
|
128
132
|
const attr = ['data-today-act', 'data-today-more', 'data-today-add-note', 'data-today-accept', 'data-today-return'].find((k) => a.hasAttribute(k));
|
|
129
133
|
if (card && attr) return { id: card.getAttribute('data-today-id'), attr };
|
|
130
134
|
if (a.closest('[data-today-note]')) return { id: card && card.getAttribute('data-today-id'), note: true };
|
|
135
|
+
if (a.getAttribute('data-goals') === 'input') return { goalsInput: true, caret: a.selectionStart };
|
|
131
136
|
if (a.hasAttribute('data-today-scope')) return { scope: a.getAttribute('data-today-scope') };
|
|
132
137
|
if (a.hasAttribute('data-today') && a.getAttribute('data-today') === 'more') return { more: true };
|
|
133
138
|
return null;
|
|
@@ -138,9 +143,10 @@ function refocus(root, k) {
|
|
|
138
143
|
let t = null;
|
|
139
144
|
if (k.note && card) t = card.querySelector('[data-today-note] input, [data-today-note] textarea, input[data-today-note], textarea[data-today-note]');
|
|
140
145
|
else if (k.attr && card) t = card.querySelector('[' + k.attr + ']');
|
|
146
|
+
else if (k.goalsInput) t = root.querySelector('[data-goals="input"]');
|
|
141
147
|
else if (k.scope) t = root.querySelector('[data-today-scope="' + k.scope + '"]');
|
|
142
148
|
else if (k.more) t = root.querySelector('[data-today="more"]');
|
|
143
|
-
if (t && !t.hidden) { try { t.focus({ preventScroll: true }); } catch { /* not focusable */ } }
|
|
149
|
+
if (t && !t.hidden) { try { t.focus({ preventScroll: true }); if (k.goalsInput && typeof k.caret === 'number') t.setSelectionRange(k.caret, k.caret); } catch { /* not focusable */ } }
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
/** Draw Today into `host`. Safe to call on every render: nothing changes unless what Today shows changed. */
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* the dock is a rail waits in the dock until the PM opens it.
|
|
7
7
|
*
|
|
8
8
|
* This module holds the parts of the dock that are new in the frame:
|
|
9
|
-
* createDockRail({ open,
|
|
9
|
+
* createDockRail({ open, resize }) the 44px rail: one control that opens the dock. While the dock is open the rail
|
|
10
|
+
* is a thin grip on the dock's left edge that resizes it (0.53.1)
|
|
10
11
|
* foldButton({ fold }) the control in the open dock's strip that folds it back to the rail
|
|
12
|
+
* focusButton({ toggle }) / paintFocus(b, on)
|
|
13
|
+
* Focus: the dock takes the whole window, and Exit focus gives the page back (0.53.1)
|
|
11
14
|
* sessionMenuButton({ newSession, withOptions, openSkill, ws })
|
|
12
15
|
* "+ Session": New session, Session with options, then the skills to start
|
|
13
16
|
* from (R10: Skills start from the dock's +session menu; the Skills view goes)
|
|
@@ -47,9 +50,76 @@ export function setSessionDefaults(patch) {
|
|
|
47
50
|
|
|
48
51
|
/* ── the rail and the fold control ────────────────────────────────────────────────────────────────────────────────── */
|
|
49
52
|
|
|
50
|
-
export function createDockRail({ open } = {}) {
|
|
53
|
+
export function createDockRail({ open, resize } = {}) {
|
|
51
54
|
const b = iconButton({ icon: 'bot', ariaLabel: COPY.dock.open, onClick: () => open && open(), attrs: { 'data-fr': 'dock-open', 'aria-keyshortcuts': 'Meta+J Control+J' } });
|
|
52
|
-
return el('div', { class: 'fr-dock-rail', 'data-fr': 'dock-rail' }, b);
|
|
55
|
+
return el('div', { class: 'fr-dock-rail', 'data-fr': 'dock-rail' }, b, resize ? dockGrip(resize) : null);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ── the dock's width (0.53.1: the PM found the terminal too small) ───────────────────────────────────────────────────
|
|
59
|
+
* The grip sits in the rail's place while the dock is open, so the body keeps its four children and the dock stays the
|
|
60
|
+
* fourth. `resize` = { get() → px | null, set(px | null) }: null is the default width (the CSS clamp). The grip measures
|
|
61
|
+
* from the body's right edge, so the dock is as wide as the distance from the pointer to the window's edge. */
|
|
62
|
+
export const DOCK_MIN = 320;
|
|
63
|
+
export const DOCK_STEP = 32;
|
|
64
|
+
/** The widest the dock may be in a body `bodyW` wide: the page keeps 280px beside it. */
|
|
65
|
+
export const dockMax = (bodyW) => Math.max(DOCK_MIN, Math.round(bodyW) - 280);
|
|
66
|
+
export const clampDock = (px, bodyW) => Math.min(dockMax(bodyW), Math.max(DOCK_MIN, Math.round(px)));
|
|
67
|
+
|
|
68
|
+
function dockGrip(resize) {
|
|
69
|
+
const g = el('div', {
|
|
70
|
+
class: 'fr-dock-grip', 'data-fr': 'dock-grip', role: 'separator', 'aria-orientation': 'vertical',
|
|
71
|
+
'aria-label': COPY.dock.resize, title: COPY.dock.resize, tabindex: '0',
|
|
72
|
+
});
|
|
73
|
+
grips.set(g, resize);
|
|
74
|
+
g.addEventListener('dblclick', () => resize.set(null));
|
|
75
|
+
g.addEventListener('keydown', (e) => {
|
|
76
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
const b = g.closest('.fr-body');
|
|
79
|
+
const d = b && b.querySelector(':scope > .fr-dock');
|
|
80
|
+
const now = resize.get() || (d ? d.getBoundingClientRect().width : 0);
|
|
81
|
+
resize.set(clampDock(now + (e.key === 'ArrowLeft' ? DOCK_STEP : -DOCK_STEP), b ? b.getBoundingClientRect().width : window.innerWidth));
|
|
82
|
+
});
|
|
83
|
+
return g;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* The drag is read on the document, once: a press on a grip starts it, and the moves and the release are the window's,
|
|
87
|
+
* so a drag that leaves the grip (it is 6px wide) keeps going. The grip itself is a separator with keys (above). */
|
|
88
|
+
const grips = new WeakMap(); // grip -> resize
|
|
89
|
+
let drag = null; // { g, resize, right }
|
|
90
|
+
document.addEventListener('pointerdown', (e) => {
|
|
91
|
+
const g = e.button === 0 && e.target && e.target.closest ? e.target.closest('[data-fr="dock-grip"]') : null;
|
|
92
|
+
const resize = g && grips.get(g);
|
|
93
|
+
const b = g && g.closest('.fr-body');
|
|
94
|
+
if (!resize || !b) return;
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
drag = { g, resize, body: b, right: b.getBoundingClientRect().right };
|
|
97
|
+
g.setAttribute('data-dragging', '');
|
|
98
|
+
});
|
|
99
|
+
window.addEventListener('pointermove', (e) => {
|
|
100
|
+
if (drag) drag.resize.set(clampDock(drag.right - e.clientX, drag.body.getBoundingClientRect().width));
|
|
101
|
+
});
|
|
102
|
+
const endDrag = () => { if (drag) { drag.g.removeAttribute('data-dragging'); drag = null; } };
|
|
103
|
+
window.addEventListener('pointerup', endDrag);
|
|
104
|
+
window.addEventListener('pointercancel', endDrag);
|
|
105
|
+
|
|
106
|
+
const focusLabels = new WeakMap(); // the Focus control -> its label node
|
|
107
|
+
export function focusButton({ toggle } = {}) {
|
|
108
|
+
const label = el('span', null, COPY.dock.focus);
|
|
109
|
+
const b = button({ label, variant: 'ghost', size: 'sm',
|
|
110
|
+
attrs: { class: 'fr-dock-focus', 'data-fr': 'dock-focus', 'aria-pressed': 'false', title: COPY.dock.focusTitle, 'aria-keyshortcuts': 'Meta+Shift+J Control+Shift+J' },
|
|
111
|
+
onClick: () => toggle && toggle() });
|
|
112
|
+
focusLabels.set(b, label);
|
|
113
|
+
return b;
|
|
114
|
+
}
|
|
115
|
+
/** Show the Focus control's state: pressed while the dock has the whole window. */
|
|
116
|
+
export function paintFocus(b, on) {
|
|
117
|
+
if (!b) return;
|
|
118
|
+
const label = focusLabels.get(b);
|
|
119
|
+
const text = on ? COPY.dock.unfocus : COPY.dock.focus;
|
|
120
|
+
if (label && label.textContent !== text) label.textContent = text;
|
|
121
|
+
b.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
122
|
+
b.title = on ? COPY.dock.unfocusTitle : COPY.dock.focusTitle;
|
|
53
123
|
}
|
|
54
124
|
|
|
55
125
|
export function foldButton({ fold } = {}) {
|
|
@@ -36,6 +36,11 @@ export const COPY = {
|
|
|
36
36
|
dock: {
|
|
37
37
|
open: 'Open the agent dock (⌘J)',
|
|
38
38
|
fold: 'Fold the agent dock (⌘J)',
|
|
39
|
+
focus: 'Focus',
|
|
40
|
+
unfocus: 'Exit focus',
|
|
41
|
+
focusTitle: 'Give the dock the whole window (⌘⇧J)',
|
|
42
|
+
unfocusTitle: 'Show the page beside the dock again (⌘⇧J)',
|
|
43
|
+
resize: 'Resize the agent dock. Drag, or use the left and right arrow keys. Double-click resets the width.',
|
|
39
44
|
session: 'Session',
|
|
40
45
|
sessionMenu: 'Start a session',
|
|
41
46
|
newSession: 'New session',
|
|
@@ -23,6 +23,7 @@ import { watchSig } from './sig-watch.js';
|
|
|
23
23
|
import { COPY, shortDate } from './initiatives-copy.js';
|
|
24
24
|
import { getList, sigKey, place, placed, leaveNow, setLifecycleBulk } from './initiatives-api.js';
|
|
25
25
|
import { healthSignal, say, confirmSheet, checkBySheet, onContextMenu } from './initiatives-ui.js';
|
|
26
|
+
import { openDoc } from './panel-initiatives.js';
|
|
26
27
|
|
|
27
28
|
const ORDER = { now: 0, next: 1, later: 2 };
|
|
28
29
|
const MAX_BULK = 20;
|
|
@@ -143,6 +144,20 @@ export function mountList(host, env) {
|
|
|
143
144
|
el('h3', { class: 'in-group__head' }, head), g.anchors.map(anchorEl));
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
// The units with documents and no record (routes-initiatives.js unrecordedOf), last: they have no anchor to group by.
|
|
148
|
+
function unrecordedEl(list) {
|
|
149
|
+
return el('section', { class: 'in-group', 'data-in-group': 'unrecorded' },
|
|
150
|
+
el('h3', { class: 'in-group__head' }, el('span', { class: 'in-group__name' }, COPY.noRecordGroup)),
|
|
151
|
+
el('div', { class: 'in-rows', role: 'list' }, list.map((u) => {
|
|
152
|
+
const doc = u.artifacts.find((a) => a.role === 'PRD') || u.artifacts[0];
|
|
153
|
+
return row({
|
|
154
|
+
title: u.title, meta: [COPY.noRecord],
|
|
155
|
+
onOpen: () => openDoc(null, doc.path, doc.role || 'DOC'), openLabel: COPY.aria.open(u.title),
|
|
156
|
+
attrs: { class: 'in-row', 'data-in-unrecorded-row': u.id }, openAttrs: { 'data-in-unrecorded-open': u.id },
|
|
157
|
+
});
|
|
158
|
+
})));
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
function bulkBar() {
|
|
147
162
|
const n = picked.size;
|
|
148
163
|
const can = env.grants().includes('lifecycle-bulk');
|
|
@@ -167,12 +182,14 @@ export function mountList(host, env) {
|
|
|
167
182
|
return;
|
|
168
183
|
}
|
|
169
184
|
const kids = [];
|
|
170
|
-
|
|
185
|
+
const unrecorded = data.unrecorded || [];
|
|
186
|
+
if (!allRows().length && !unrecorded.length) kids.push(empty({ text: COPY.emptyAll }));
|
|
171
187
|
else {
|
|
172
188
|
kids.push(el('p', { class: 'in-note in-hint', 'data-in-hint': '' }, COPY.menuHintRow));
|
|
173
189
|
kids.push(el('div', { class: 'in-toolbar' }, selecting ? bulkBar() : button({ label: COPY.select, variant: 'ghost', size: 'sm', onClick: () => { selecting = true; render(); }, attrs: { 'data-in-select-mode': '' } })));
|
|
174
190
|
kids.push(...data.groups.map(groupEl));
|
|
175
191
|
}
|
|
192
|
+
if (unrecorded.length) kids.push(unrecordedEl(unrecorded));
|
|
176
193
|
root.replaceChildren(...kids);
|
|
177
194
|
if (active) { const t = root.querySelector('[data-in-open="' + CSS.escape(active) + '"], [data-in-select="' + CSS.escape(active) + '"]'); if (t) t.focus({ preventScroll: true }); }
|
|
178
195
|
}
|
|
@@ -41,6 +41,10 @@ export const COPY = {
|
|
|
41
41
|
emptyColumn: { now: 'Nothing in Now.', next: 'Nothing in Next.', later: 'Nothing in Later.' },
|
|
42
42
|
dormant: (n) => `${n} dormant`,
|
|
43
43
|
hideDormant: (n) => `Hide ${n} dormant`,
|
|
44
|
+
unrecorded: (n) => `${n} with no record`,
|
|
45
|
+
hideUnrecorded: (n) => `Hide ${n} with no record`,
|
|
46
|
+
noRecord: 'no record yet',
|
|
47
|
+
noRecordGroup: 'No record yet',
|
|
44
48
|
unconfirmed: 'unconfirmed',
|
|
45
49
|
checkBy: (d) => `check-by ${d}`,
|
|
46
50
|
waitingYou: 'waiting on you',
|
|
@@ -719,13 +719,11 @@ const bottomBar = el('div', { 'data-tabs': 'bottom', 'data-term-chrome': '', cla
|
|
|
719
719
|
button({ label: copyLabel, variant: 'outline', size: 'sm', onClick: () => copyActive() }),
|
|
720
720
|
);
|
|
721
721
|
|
|
722
|
-
/* pm-os-absorb Run 3 (R11)
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
const [, bottomTitle, bottomCopy] = bottomBar.children;
|
|
726
|
-
|
|
727
|
-
/* The drawer had its toggle in the bar and has none now: a stored "open" is dropped once, at load, so no capture box comes back. */
|
|
728
|
-
if (!FIXTURE && state.scratchOpen) setState({ scratchOpen: false });
|
|
722
|
+
/* pm-os-absorb Run 3 (R11) took the scratchpad toggle out of the bar, and with it the only way to the pad on a desktop.
|
|
723
|
+
* 0.53.1 puts it back: paintBottom lays the bar out as the scratchpad toggle, the session's name and `copy`, or, on a
|
|
724
|
+
* phone, the terminal's compose line (the same per-session pad). Fixture mode keeps the prototype's bar. */
|
|
725
|
+
const [bottomToggle, bottomTitle, bottomCopy] = bottomBar.children;
|
|
726
|
+
bottomToggle.setAttribute('data-scratch', 'toggle');
|
|
729
727
|
|
|
730
728
|
/* The compose line: one message field and Send on the SAME per-session pad as the drawer (padKey), so the text is kept
|
|
731
729
|
* on this machine and never crosses sessions. On a desktop ⌘K "Ask <session>" types to a session (pm/palette.js). */
|
|
@@ -751,7 +749,7 @@ function sendCompose() {
|
|
|
751
749
|
onScratchInput('');
|
|
752
750
|
}
|
|
753
751
|
|
|
754
|
-
/** The bar in real mode:
|
|
752
|
+
/** The bar in real mode: the scratchpad toggle, the title and copy; a phone's terminal gets the compose line instead. */
|
|
755
753
|
function paintBottom(act) {
|
|
756
754
|
const compose = isMobile() && !!act && act.kind === 'term';
|
|
757
755
|
bottomTitle.textContent = act ? act.title : '';
|
|
@@ -761,7 +759,7 @@ function paintBottom(act) {
|
|
|
761
759
|
if (composeInput.control.value !== text) composeInput.control.value = text;
|
|
762
760
|
composeSend.disabled = !text.trim();
|
|
763
761
|
}
|
|
764
|
-
setChildren(bottomBar, compose ? [composeLine] : (act ? [bottomTitle, bottomCopy] : [bottomTitle]));
|
|
762
|
+
setChildren(bottomBar, compose ? [composeLine] : (act ? [bottomToggle, bottomTitle, bottomCopy] : [bottomToggle, bottomTitle]));
|
|
765
763
|
}
|
|
766
764
|
|
|
767
765
|
/* ── clipboard + scratchpad actions (lines 2098–2118) ───────────────────────────────────────── */
|
|
@@ -1101,9 +1099,12 @@ function frameDock() {
|
|
|
1101
1099
|
openSkill: (sk) => openTab({ id: 'skill:' + sk.id, kind: 'skill', title: sk.label || sk.skill || sk.id, skillId: sk.id, repo: state.product || null, slug: null, role: 'SKILL' }),
|
|
1102
1100
|
}),
|
|
1103
1101
|
fold: dockParts ? dockParts.fold : dockMod.foldButton({ fold: () => window.pmApp.frame.foldDock() }),
|
|
1102
|
+
focus: dockParts ? dockParts.focus : dockMod.focusButton({ toggle: () => window.pmApp.frame.toggleFocus() }),
|
|
1104
1103
|
};
|
|
1105
1104
|
}
|
|
1106
|
-
return { session: dockParts.session, fold:
|
|
1105
|
+
if (isMobile()) return { session: dockParts.session, fold: null, focus: null };
|
|
1106
|
+
dockMod.paintFocus(dockParts.focus, !!window.pmApp.frame.dockMax);
|
|
1107
|
+
return { session: dockParts.session, fold: dockParts.fold, focus: dockParts.focus };
|
|
1107
1108
|
}
|
|
1108
1109
|
|
|
1109
1110
|
/**
|
|
@@ -1290,6 +1291,7 @@ export function renderTabs(host) {
|
|
|
1290
1291
|
newSessionOptsBtn,
|
|
1291
1292
|
canSplit ? splitBtn : null,
|
|
1292
1293
|
orientBtn,
|
|
1294
|
+
fr ? fr.focus : null,
|
|
1293
1295
|
fr ? fr.fold : null),
|
|
1294
1296
|
]);
|
|
1295
1297
|
} else if (ck) {
|
|
@@ -1304,7 +1306,7 @@ export function renderTabs(host) {
|
|
|
1304
1306
|
].filter(Boolean);
|
|
1305
1307
|
extras[0].style.marginLeft = 'auto';
|
|
1306
1308
|
// The switch sits after `+ session`, so the strip still ends the mockup's way in the fixture, which does not draw it.
|
|
1307
|
-
setChildren(tabBar, [...visTabs.map((t) => cockpitTabNode(t, act, splitTab)), ...extras, fr ? fr.session : newSessionBtn, FIXTURE ? null : orientBtn, fr ? fr.fold : null]);
|
|
1309
|
+
setChildren(tabBar, [...visTabs.map((t) => cockpitTabNode(t, act, splitTab)), ...extras, fr ? fr.session : newSessionBtn, FIXTURE ? null : orientBtn, fr ? fr.focus : null, fr ? fr.fold : null]);
|
|
1308
1310
|
} else {
|
|
1309
1311
|
tabBar.setAttribute('style', BAR_STYLE);
|
|
1310
1312
|
newSessionBtn.setAttribute('style', CHIP_STYLE);
|
|
@@ -1363,7 +1365,7 @@ export function renderTabs(host) {
|
|
|
1363
1365
|
newSessionBlock(),
|
|
1364
1366
|
visTabs.length === 0 && !state.startPrompt && !state.newSess ? emptyState : null,
|
|
1365
1367
|
panesRow,
|
|
1366
|
-
state.scratchOpen ? scratchBlock : null,
|
|
1368
|
+
state.scratchOpen && (FIXTURE || !isMobile()) ? scratchBlock : null,
|
|
1367
1369
|
bottomBar,
|
|
1368
1370
|
]);
|
|
1369
1371
|
}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
@page {
|
|
6
6
|
margin: 16mm;
|
|
7
7
|
}
|
|
8
|
+
/* the slides (Run 6): each slide is one 1280 × 720 px page (13.333in × 7.5in), 16:9, with no margin */
|
|
9
|
+
@page slides {
|
|
10
|
+
size: 13.3333in 7.5in;
|
|
11
|
+
margin: 0;
|
|
12
|
+
}
|
|
8
13
|
@media print {
|
|
9
14
|
body > *:not(.rp-print) {
|
|
10
15
|
display: none !important;
|
|
@@ -30,4 +35,10 @@
|
|
|
30
35
|
.rp-print .rp-doc__note {
|
|
31
36
|
break-inside: avoid;
|
|
32
37
|
}
|
|
38
|
+
.rp-print--slides .rp-slide {
|
|
39
|
+
page: slides;
|
|
40
|
+
break-after: page;
|
|
41
|
+
print-color-adjust: exact;
|
|
42
|
+
-webkit-print-color-adjust: exact;
|
|
43
|
+
}
|
|
33
44
|
}
|