create-pmos 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/mods/pmos-cockpit/README.md +13 -0
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
- package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/plan-items-stage.js +271 -0
- package/payload/scripts/pmos-log.sh +18 -7
- package/payload/scripts/workflow-state.js +28 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/bin/pmos.js +15 -1
- package/workspace/cut-stub.js +40 -0
- package/workspace/first-paint-audit.js +145 -0
- package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
- package/workspace/fixtures/initiatives-api/list.json +176 -0
- package/workspace/fixtures/initiatives-api/post-403.json +5 -0
- package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
- package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
- package/workspace/fixtures/initiatives-api/sheet.json +112 -0
- package/workspace/fixtures/report-api/draft.json +177 -0
- package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
- package/workspace/fixtures/today-api/provider-rows.json +204 -0
- package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
- package/workspace/guide/board.md +81 -0
- package/workspace/guide/breakdown.md +154 -0
- package/workspace/guide/command-palette.md +75 -0
- package/workspace/guide/index.md +27 -17
- package/workspace/guide/initiatives-roadmap.md +130 -0
- package/workspace/guide/initiatives-screen.md +89 -0
- package/workspace/guide/initiatives-timeline.md +57 -0
- package/workspace/guide/integrated-work.md +94 -0
- package/workspace/guide/knowledge-graph.md +3 -68
- package/workspace/guide/layout.md +98 -32
- package/workspace/guide/live-notes.md +3 -3
- package/workspace/guide/pm-only-acts.md +89 -0
- package/workspace/guide/pm-only-lines.md +61 -0
- package/workspace/guide/reports-page.md +80 -0
- package/workspace/guide/reports.md +73 -0
- package/workspace/guide/timeline.md +70 -0
- package/workspace/guide/today.md +85 -0
- package/workspace/guide/validate-with-typesafe.md +1 -1
- package/workspace/lib/actor.js +91 -0
- package/workspace/lib/board-cache.js +32 -2
- package/workspace/lib/board-cli.js +108 -0
- package/workspace/lib/board-fold.js +232 -0
- package/workspace/lib/breakdown-export.js +80 -0
- package/workspace/lib/capture.js +98 -0
- package/workspace/lib/engine/plan-items-stage.js +271 -0
- package/workspace/lib/engine/workflow-state.js +28 -2
- package/workspace/lib/factory-join.js +158 -0
- package/workspace/lib/grant-mint.js +113 -0
- package/workspace/lib/health-rules-factory.js +92 -0
- package/workspace/lib/health-rules-plan.js +164 -0
- package/workspace/lib/health.js +79 -0
- package/workspace/lib/initiative-cli.js +222 -0
- package/workspace/lib/initiative-display.js +183 -0
- package/workspace/lib/initiative-needs.js +127 -0
- package/workspace/lib/initiative-rows.js +206 -0
- package/workspace/lib/initiative-writer.js +141 -0
- package/workspace/lib/issues.js +206 -0
- package/workspace/lib/item-cli.js +195 -0
- package/workspace/lib/item-spans.js +137 -0
- package/workspace/lib/measure-cli.js +100 -0
- package/workspace/lib/measurements.js +189 -0
- package/workspace/lib/mobile-nav.js +57 -53
- package/workspace/lib/needs-providers.js +120 -0
- package/workspace/lib/placements.js +440 -0
- package/workspace/lib/plan-items.js +922 -0
- package/workspace/lib/plan-ledgers.js +85 -0
- package/workspace/lib/plan-rules.js +72 -0
- package/workspace/lib/plan-tree.js +367 -0
- package/workspace/lib/pm-guard.js +174 -0
- package/workspace/lib/pm-lines-needs.js +331 -0
- package/workspace/lib/pmos-verbs.js +66 -0
- package/workspace/lib/proactive.js +7 -4
- package/workspace/lib/report-cli.js +152 -0
- package/workspace/lib/report-draft.js +579 -0
- package/workspace/lib/report-needs.js +213 -0
- package/workspace/lib/route-modules.js +128 -0
- package/workspace/lib/routes-board.js +91 -0
- package/workspace/lib/routes-capture.js +52 -0
- package/workspace/lib/routes-initiatives.js +371 -0
- package/workspace/lib/routes-reports.js +163 -0
- package/workspace/lib/routes-timeline.js +197 -0
- package/workspace/lib/routes-today.js +32 -0
- package/workspace/lib/today-cli.js +99 -0
- package/workspace/lib/today.js +333 -0
- package/workspace/lib/triage.js +163 -0
- package/workspace/lib/verify-work-cli.js +81 -0
- package/workspace/lib/work-check-needs.js +99 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +4 -5
- package/workspace/public/index.html +29 -19
- package/workspace/public/pm/app.js +299 -30
- package/workspace/public/pm/bd-board.js +113 -0
- package/workspace/public/pm/bd-drag.js +59 -0
- package/workspace/public/pm/bd-model.js +152 -0
- package/workspace/public/pm/bd-timeline.js +233 -0
- package/workspace/public/pm/bd-tree.js +244 -0
- package/workspace/public/pm/board-copy.js +57 -0
- package/workspace/public/pm/breakdown-api.js +58 -0
- package/workspace/public/pm/breakdown-copy.js +138 -0
- package/workspace/public/pm/components.css +3054 -0
- package/workspace/public/pm/cycle-review.js +125 -0
- package/workspace/public/pm/data.js +40 -187
- package/workspace/public/pm/dest-board.js +438 -0
- package/workspace/public/pm/dest-initiatives.js +134 -0
- package/workspace/public/pm/dest-reports.js +270 -0
- package/workspace/public/pm/dest-settings.js +217 -0
- package/workspace/public/pm/dest-today.js +176 -0
- package/workspace/public/pm/dialogs.js +88 -0
- package/workspace/public/pm/display-word.js +56 -0
- package/workspace/public/pm/dock.js +105 -0
- package/workspace/public/pm/fixture.js +8 -227
- package/workspace/public/pm/frame-copy.js +94 -0
- package/workspace/public/pm/handoff-box.js +82 -0
- package/workspace/public/pm/http.js +88 -0
- package/workspace/public/pm/icons.js +53 -0
- package/workspace/public/pm/initiative-list.js +218 -0
- package/workspace/public/pm/initiative-sheet.js +261 -0
- package/workspace/public/pm/initiatives-api.js +59 -0
- package/workspace/public/pm/initiatives-copy.js +215 -0
- package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
- package/workspace/public/pm/initiatives-timeline.js +410 -0
- package/workspace/public/pm/initiatives-ui.js +123 -0
- package/workspace/public/pm/item-sheet.js +251 -0
- package/workspace/public/pm/keep-card.js +204 -0
- package/workspace/public/pm/lifecycle.js +7 -1
- package/workspace/public/pm/mobile.css +49 -20
- package/workspace/public/pm/mobile.js +157 -29
- package/workspace/public/pm/modes-live.js +7 -883
- package/workspace/public/pm/modes.js +32 -1024
- package/workspace/public/pm/new-initiative-sheet.js +74 -0
- package/workspace/public/pm/next-step-ui.js +3 -2
- package/workspace/public/pm/palette-copy.js +51 -0
- package/workspace/public/pm/palette.js +312 -0
- package/workspace/public/pm/panel-breakdown.js +702 -0
- package/workspace/public/pm/panel-cockpit.js +63 -295
- package/workspace/public/pm/panel-detail.js +25 -172
- package/workspace/public/pm/panel-initiatives.js +1 -19
- package/workspace/public/pm/panel-repos.js +31 -81
- package/workspace/public/pm/panel-tabs.js +197 -110
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/portfolio-report.js +61 -0
- package/workspace/public/pm/primitives.js +656 -0
- package/workspace/public/pm/print.css +33 -0
- package/workspace/public/pm/provider-cards.js +136 -0
- package/workspace/public/pm/registry.js +194 -0
- package/workspace/public/pm/release-card.js +110 -0
- package/workspace/public/pm/report-view.js +309 -0
- package/workspace/public/pm/reports-copy.js +114 -0
- package/workspace/public/pm/roadmap.js +271 -0
- package/workspace/public/pm/sig-watch.js +41 -0
- package/workspace/public/pm/state.js +8 -4
- package/workspace/public/pm/status-banner.js +13 -5
- package/workspace/public/pm/tab-doc.js +2 -4
- package/workspace/public/pm/tab-term.js +12 -2
- package/workspace/public/pm/tab-web.js +3 -2
- package/workspace/public/pm/timeline-copy.js +48 -0
- package/workspace/public/pm/today-cards.js +244 -0
- package/workspace/public/pm/today-copy.js +97 -0
- package/workspace/public/pm/tokens.css +16 -18
- package/workspace/public/pm/topbar.js +102 -0
- package/workspace/public/pm/triage-card.js +60 -0
- package/workspace/public/pm/undo-hold.js +103 -0
- package/workspace/public/pm/view-addrepo.js +2 -1
- package/workspace/public/pm/view-livenotes.js +3 -1
- package/workspace/public/pm/view-today.js +7 -162
- package/workspace/public/vendor/Sortable.min.js +2 -0
- package/workspace/public/vendor/lucide-sprite.js +79 -0
- package/workspace/replaced-tests.json +2864 -0
- package/workspace/run-tests.js +56 -5
- package/workspace/server.js +312 -36
- package/workspace/sheet-answer.js +61 -0
- package/workspace/skills/measure.skill +41 -0
- package/workspace/skills/report-biweekly.skill +23 -25
- package/workspace/skills/tracker-refresh.skill +50 -0
- package/workspace/test-files.json +19 -3
- package/workspace/test-lists/base.json +6 -0
- package/workspace/test-lists/run2-a.json +6 -0
- package/workspace/test-lists/run2-b.json +13 -0
- package/workspace/test-lists/run2-c.json +19 -0
- package/workspace/test-lists/run2-d.json +22 -0
- package/workspace/test-lists/run3-0.json +5 -0
- package/workspace/test-lists/run3-a.json +5 -0
- package/workspace/test-lists/run3-b.json +6 -0
- package/workspace/test-lists/run3-c.json +15 -0
- package/workspace/test-lists/run3-d.json +6 -0
- package/workspace/test-lists/run4-a.json +8 -0
- package/workspace/test-lists/run4-b.json +6 -0
- package/workspace/test-lists/run4-c.json +13 -0
- package/workspace/test-lists/run4-d.json +9 -0
- package/workspace/test-lists/run5-a.json +6 -0
- package/workspace/test-lists/run5-b.json +6 -0
- package/workspace/test-lists/run5-c.json +15 -0
- package/workspace/test-run.js +27 -1
- package/workspace/third-party/README.md +44 -0
- package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
- package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
- package/workspace/design-parity.js +0 -673
- package/workspace/public/pm/tab-task.js +0 -86
- package/workspace/public/pm/view-guide.js +0 -141
- package/workspace/public/pm/view-knowledge.js +0 -225
- package/workspace/public/pm/view-memory.js +0 -122
- package/workspace/public/pm/view-portfolio.js +0 -153
- package/workspace/public/pm/view-reports.js +0 -190
- package/workspace/public/pm/view-scheduled.js +0 -161
- package/workspace/public/pm/view-skills.js +0 -216
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/* pm/mobile.js — the phone surface of the four-panel workspace (≤760px).
|
|
2
2
|
*
|
|
3
|
-
* Owns three things and nothing else: the top bar (back · the screen's name · the ⌘K
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Owns three things and nothing else: the top bar (back · Repository · the screen's name · the ⌘K
|
|
4
|
+
* palette · Settings; the old shell shows theme in place of Repository and Settings), the five-tab
|
|
5
|
+
* bottom nav (Today · Initiatives · Board · Reports · Agent, pm-os-absorb Run 3, slice 3A), and the
|
|
6
|
+
* terminal control-key accessory bar that writes real PTY sequences. Its markup ships in
|
|
7
|
+
* public/index.html; its styling in pm/mobile.css. In the frame, a tab is the frame's place (Agent is
|
|
8
|
+
* the dock) and Settings is pushed over the current tab.
|
|
6
9
|
*
|
|
7
10
|
* ── ONE renderer, two shells ──────────────────────────────────────────────────────────────────
|
|
8
11
|
* Nothing here draws a phone-shaped copy of a panel. The four panel modules render exactly what
|
|
@@ -33,6 +36,9 @@
|
|
|
33
36
|
*/
|
|
34
37
|
|
|
35
38
|
import { state, setState, writeCount, FIXTURE } from './state.js';
|
|
39
|
+
import { COPY as FC } from './frame-copy.js';
|
|
40
|
+
import { ensureComponentsCss } from './primitives.js';
|
|
41
|
+
import { ensureSprite, icon } from './icons.js';
|
|
36
42
|
|
|
37
43
|
/* ── the breakpoint ─────────────────────────────────────────────────────────────────────────── */
|
|
38
44
|
|
|
@@ -50,19 +56,17 @@ if (!NAV) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/** kind → which of the four panels draws it. The reducer owns the map; this is only its fallback. */
|
|
53
|
-
const PANEL = { more: 0, list: 1, detail: 2, terminal: 3, doc: 3 };
|
|
59
|
+
const PANEL = { dest: 0, more: 0, list: 1, detail: 2, terminal: 3, doc: 3, agent: 3 };
|
|
54
60
|
const panelOf = (kind) =>
|
|
55
61
|
(NAV && typeof NAV.panelOf === 'function') ? NAV.panelOf(kind)
|
|
56
|
-
: (Object.prototype.hasOwnProperty.call(PANEL, kind) ? PANEL[kind] :
|
|
62
|
+
: (Object.prototype.hasOwnProperty.call(PANEL, kind) ? PANEL[kind] : 0);
|
|
57
63
|
|
|
58
|
-
/*
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* retired app.js's MODES exactly and a mode invented for a different surface would read as drift. */
|
|
65
|
-
const EXTRA_DEST = { connect: 'more', focus: 'board', portfolio: 'more', guide: 'more' };
|
|
64
|
+
/* The old shell (fixture mode, ?layout=legacy) on a phone: a tab's root is the list panel in the mode it shows. Agent
|
|
65
|
+
* has no mode: it is the tabs panel. In the frame a tab is the frame's place and no mode is set. */
|
|
66
|
+
const DEST_MODE = { today: 'product', initiatives: 'product', board: 'product', reports: 'product' };
|
|
67
|
+
/* Modes that postdate the reducer's map. They are named here rather than added to `DEST_FOR_MODE`, because MOBNAV-P2
|
|
68
|
+
* iterates that map and a mode invented for a different surface would read as drift. */
|
|
69
|
+
const EXTRA_DEST = {}; // no mode outside the nav's own table reaches a phone destination since the R29 cuts
|
|
66
70
|
|
|
67
71
|
const destForMode = (m) => (NAV && NAV.destForMode(m)) || EXTRA_DEST[m] || null;
|
|
68
72
|
|
|
@@ -70,9 +74,11 @@ const destForMode = (m) => (NAV && NAV.destForMode(m)) || EXTRA_DEST[m] || null;
|
|
|
70
74
|
|
|
71
75
|
const $ = (id) => document.getElementById(id);
|
|
72
76
|
|
|
73
|
-
const DEST_STORE_KEY = 'pmos.workspace.pm.
|
|
77
|
+
const DEST_STORE_KEY = 'pmos.workspace.pm.mtab';
|
|
78
|
+
/* The retired five-tab nav's key: read once, through the reducer's migration, never written. */
|
|
79
|
+
const OLD_DEST_KEY = 'pmos.workspace.pm.mdest';
|
|
74
80
|
|
|
75
|
-
let ctx = null; // { bodyGrid, render } — handed over by app.js at boot
|
|
81
|
+
let ctx = null; // { bodyGrid, render, frame } — handed over by app.js at boot (frame: pmApp.frame, or null)
|
|
76
82
|
let nav = null; // the reducer's state
|
|
77
83
|
let seen = null; // the app-state fingerprint the last screen was derived from
|
|
78
84
|
let selfDriven = false; // the next state change is one this module caused — do not read it as navigation
|
|
@@ -86,6 +92,17 @@ function readDest() {
|
|
|
86
92
|
if (FIXTURE) return null;
|
|
87
93
|
try { return localStorage.getItem(DEST_STORE_KEY); } catch { return null; }
|
|
88
94
|
}
|
|
95
|
+
/** The tab a phone opens on: the frame's place when it is a tab, else the stored tab, else the old key migrated. */
|
|
96
|
+
function firstDest() {
|
|
97
|
+
const f = ctx && ctx.frame;
|
|
98
|
+
if (f && NAV.ROOTS[f.dest]) return f.dest;
|
|
99
|
+
const d = readDest();
|
|
100
|
+
if (d && NAV.ROOTS[d]) return d;
|
|
101
|
+
if (FIXTURE) return NAV.migrateView(null);
|
|
102
|
+
let old = null;
|
|
103
|
+
try { old = localStorage.getItem(OLD_DEST_KEY); } catch { old = null; }
|
|
104
|
+
return NAV.migrateView(old);
|
|
105
|
+
}
|
|
89
106
|
function writeDest(d) {
|
|
90
107
|
if (FIXTURE) return;
|
|
91
108
|
try { localStorage.setItem(DEST_STORE_KEY, d); } catch { /* quota / private mode */ }
|
|
@@ -199,6 +216,7 @@ function onCaptureKeyDown(e) {
|
|
|
199
216
|
|
|
200
217
|
function fingerprint() {
|
|
201
218
|
const d = state.detail || {};
|
|
219
|
+
const f = ctx && ctx.frame;
|
|
202
220
|
return {
|
|
203
221
|
mode: state.mode,
|
|
204
222
|
sel: state.sel,
|
|
@@ -209,6 +227,9 @@ function fingerprint() {
|
|
|
209
227
|
* screen even though the value does not move. The counter is state.js's write log; watching
|
|
210
228
|
* the value alone is what left the terminal screen unreachable once you had navigated off it. */
|
|
211
229
|
activeAsk: writeCount('active'),
|
|
230
|
+
// the frame: the place on screen and whether the detail is open (state.col2 is the frame's detail column)
|
|
231
|
+
fdest: f ? f.dest : null,
|
|
232
|
+
col2: !!state.col2,
|
|
212
233
|
};
|
|
213
234
|
}
|
|
214
235
|
|
|
@@ -219,6 +240,32 @@ function go(screen) {
|
|
|
219
240
|
nav = NAV.push(base, screen);
|
|
220
241
|
}
|
|
221
242
|
|
|
243
|
+
const frameOn = () => !!(ctx && ctx.frame);
|
|
244
|
+
const tabScreen = (tab, id) => ({ kind: (tab && (tab.kind === 'doc' || tab.kind === 'breakdown' || tab.kind === 'dest')) ? 'doc' : 'terminal', id, title: (tab && (tab.title || tab.name)) || String(id) });
|
|
245
|
+
|
|
246
|
+
/* The frame on a phone: the PM's own moves through the panels, the palette or Settings, read back as screens. A place
|
|
247
|
+
* opened from elsewhere (⌘K, Settings › Show its initiatives) is a tab switch; Settings is pushed on top; an open
|
|
248
|
+
* detail is pushed and a closed one popped; a session or document opened is pushed. */
|
|
249
|
+
function syncFromFrame(cur) {
|
|
250
|
+
const f = ctx.frame;
|
|
251
|
+
if (cur.active && (cur.active !== seen.active || cur.activeAsk !== seen.activeAsk)) {
|
|
252
|
+
go(tabScreen((state.tabs || []).find((t) => t && t.id === cur.active), cur.active));
|
|
253
|
+
} else if (cur.col2 && !seen.col2) {
|
|
254
|
+
const dk = (state.detail && state.detail.kind) || 'detail';
|
|
255
|
+
go({ kind: 'detail', dk, id: (state.detail && state.detail.id) || cur.sel });
|
|
256
|
+
} else if (!cur.col2 && seen.col2 && NAV.current(nav).kind === 'detail') {
|
|
257
|
+
nav = NAV.pop(nav);
|
|
258
|
+
} else if (cur.fdest !== seen.fdest) {
|
|
259
|
+
if (cur.fdest === 'settings') { if (NAV.current(nav).id !== 'settings') go({ kind: 'dest', id: 'settings' }); }
|
|
260
|
+
else if (NAV.ROOTS[cur.fdest]) { nav = NAV.switchDest(nav, cur.fdest); writeDest(nav.dest); }
|
|
261
|
+
}
|
|
262
|
+
if (f && NAV.current(nav).kind === 'dest' && NAV.current(nav).id !== f.dest) {
|
|
263
|
+
// the screen names a place the frame is not showing (a pop, say): put the frame on it
|
|
264
|
+
selfDriven = true;
|
|
265
|
+
f.go(NAV.current(nav).id);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
222
269
|
/**
|
|
223
270
|
* Read the app's state for a navigation the PM made through a panel, and push the screen it means.
|
|
224
271
|
* Exactly one transition is taken per render, most specific first: opening a session is a terminal
|
|
@@ -227,10 +274,10 @@ function go(screen) {
|
|
|
227
274
|
function syncFromState() {
|
|
228
275
|
const cur = fingerprint();
|
|
229
276
|
if (!seen || selfDriven) { selfDriven = false; seen = cur; return; }
|
|
277
|
+
if (frameOn()) { syncFromFrame(cur); seen = cur; return; }
|
|
230
278
|
|
|
231
279
|
if (cur.active && (cur.active !== seen.active || cur.activeAsk !== seen.activeAsk)) {
|
|
232
|
-
|
|
233
|
-
go({ kind: (tab && tab.kind === 'doc') ? 'doc' : 'terminal', id: cur.active, title: (tab && (tab.title || tab.name)) || String(cur.active) });
|
|
280
|
+
go(tabScreen((state.tabs || []).find((t) => t && t.id === cur.active), cur.active));
|
|
234
281
|
} else if (cur.detail !== seen.detail || (cur.sel && cur.sel !== seen.sel)) {
|
|
235
282
|
const dk = (state.detail && state.detail.kind) || '';
|
|
236
283
|
if (dk && dk !== 'none') go({ kind: 'detail', dk, id: (state.detail && state.detail.id) || cur.sel });
|
|
@@ -249,6 +296,12 @@ function syncFromState() {
|
|
|
249
296
|
|
|
250
297
|
/* ── applying it ────────────────────────────────────────────────────────────────────────────── */
|
|
251
298
|
|
|
299
|
+
/** The panel a screen shows. The frame's grid is the reducer's; the old shell draws a tab's root in its list panel. */
|
|
300
|
+
function panelFor(kind) {
|
|
301
|
+
if (!frameOn() && kind === 'dest') return 1;
|
|
302
|
+
return panelOf(kind);
|
|
303
|
+
}
|
|
304
|
+
|
|
252
305
|
/**
|
|
253
306
|
* Bring the chrome and the visible panel in line with the current screen. Called at the end of
|
|
254
307
|
* every shell render, and on every breakpoint change.
|
|
@@ -268,15 +321,17 @@ export function applyMobile() {
|
|
|
268
321
|
return;
|
|
269
322
|
}
|
|
270
323
|
|
|
271
|
-
if (!nav) nav = NAV.init(
|
|
324
|
+
if (!nav) nav = NAV.init(firstDest());
|
|
272
325
|
syncFromState();
|
|
273
326
|
|
|
274
327
|
const s = NAV.current(nav);
|
|
328
|
+
const f = ctx.frame;
|
|
275
329
|
|
|
276
330
|
// The host is chosen FIRST, so a renderer that measures itself (the terminal's fit) sees the box
|
|
277
331
|
// it is actually going to occupy rather than the one it is leaving.
|
|
278
|
-
ctx.bodyGrid.dataset.mpanel = String(
|
|
279
|
-
|
|
332
|
+
ctx.bodyGrid.dataset.mpanel = String(panelFor(s.kind));
|
|
333
|
+
const act = (state.tabs || []).find((t) => t && t.id === state.active);
|
|
334
|
+
if (menu) menu.dataset.view = s.kind === 'agent' ? (act && act.kind === 'term' ? 'terminal' : 'agent') : s.kind;
|
|
280
335
|
|
|
281
336
|
const title = $('mobTitle');
|
|
282
337
|
if (title) title.textContent = NAV.titleOf(s) || '';
|
|
@@ -285,15 +340,50 @@ export function applyMobile() {
|
|
|
285
340
|
const back = $('mobBack');
|
|
286
341
|
if (back) back.hidden = !deep; // absent at a destination root, never present-and-inert
|
|
287
342
|
const brand = $('mobBrand');
|
|
288
|
-
if (brand) brand.hidden = deep;
|
|
343
|
+
if (brand) brand.hidden = deep || !!f; // the lockup yields to the back control, and to Repository in the frame
|
|
289
344
|
|
|
290
345
|
const theme = $('mobTheme');
|
|
291
|
-
if (theme)
|
|
346
|
+
if (theme) {
|
|
347
|
+
const want = state.theme === 'dark' ? 'moon' : 'sun';
|
|
348
|
+
if (theme.getAttribute('data-icon') !== want) { theme.setAttribute('data-icon', want); theme.replaceChildren(icon(want)); }
|
|
349
|
+
theme.hidden = !!f;
|
|
350
|
+
}
|
|
351
|
+
// the frame's own controls: Repository and Settings (the theme is in Settings)
|
|
352
|
+
const repo = $('mobRepo');
|
|
353
|
+
if (repo) {
|
|
354
|
+
repo.hidden = !f || deep;
|
|
355
|
+
if (f) {
|
|
356
|
+
const label = f.scope === 'all' ? FC.repo.all : (state.product || FC.repo.none);
|
|
357
|
+
if (repo.textContent !== label) repo.textContent = label;
|
|
358
|
+
repo.setAttribute('aria-label', FC.repo.open(label));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Sync, as the desktop bar has it: only while the current repository is not in sync (never in All repositories)
|
|
362
|
+
const sync = $('mobSync');
|
|
363
|
+
if (sync) {
|
|
364
|
+
const want = !!f && !deep && f.scope !== 'all' && (f.unsynced || f.syncing);
|
|
365
|
+
sync.hidden = !want;
|
|
366
|
+
if (want) {
|
|
367
|
+
const label = f.syncing ? FC.sync.busy : FC.sync.label;
|
|
368
|
+
if (sync.textContent !== label) sync.textContent = label;
|
|
369
|
+
sync.disabled = !!f.syncing;
|
|
370
|
+
sync.title = FC.sync.title;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const set = $('mobSettings');
|
|
374
|
+
if (set) { set.hidden = !f; set.setAttribute('aria-current', f && f.dest === 'settings' ? 'page' : 'false'); }
|
|
375
|
+
const count = $('mobTodayCount');
|
|
376
|
+
if (count) {
|
|
377
|
+
const n = f ? Math.max(0, Number(f.needs) || 0) : 0;
|
|
378
|
+
count.hidden = n === 0;
|
|
379
|
+
count.textContent = String(n);
|
|
380
|
+
count.setAttribute('aria-label', FC.needs(n));
|
|
381
|
+
}
|
|
292
382
|
|
|
293
383
|
const buttons = menu ? menu.querySelectorAll('#mobNav [data-dest]') : [];
|
|
294
384
|
for (const b of buttons) b.setAttribute('aria-current', b.dataset.dest === nav.dest ? 'page' : 'false');
|
|
295
385
|
|
|
296
|
-
if (s.kind !== 'terminal' && ctrlArmed) setCtrlArmed(false);
|
|
386
|
+
if (s.kind !== 'terminal' && s.kind !== 'agent' && ctrlArmed) setCtrlArmed(false);
|
|
297
387
|
}
|
|
298
388
|
|
|
299
389
|
/* ── the chrome's own controls ──────────────────────────────────────────────────────────────── */
|
|
@@ -302,16 +392,30 @@ export function applyMobile() {
|
|
|
302
392
|
function setDest(dest) {
|
|
303
393
|
nav = NAV.switchDest(nav, dest);
|
|
304
394
|
writeDest(nav.dest);
|
|
305
|
-
const mode = DEST_MODE[dest];
|
|
306
395
|
selfDriven = true;
|
|
307
|
-
|
|
396
|
+
const f = ctx && ctx.frame;
|
|
397
|
+
if (f) {
|
|
398
|
+
// a tab shows its place (Agent shows the dock over the current one); a detail left open does not follow
|
|
399
|
+
if (dest !== 'agent') f.go(dest);
|
|
400
|
+
setState({ col2: false });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const mode = DEST_MODE[dest];
|
|
404
|
+
if (mode) setState({ mode });
|
|
308
405
|
}
|
|
309
406
|
|
|
310
407
|
/** Back: pop exactly one screen and put the app back in the state that screen describes. */
|
|
311
408
|
function goBack() {
|
|
409
|
+
const was = NAV.current(nav);
|
|
312
410
|
nav = NAV.pop(nav);
|
|
313
411
|
const s = NAV.current(nav);
|
|
314
412
|
selfDriven = true;
|
|
413
|
+
const f = ctx && ctx.frame;
|
|
414
|
+
if (f) {
|
|
415
|
+
if (s.kind === 'dest' && s.id !== f.dest) f.go(s.id);
|
|
416
|
+
setState(was.kind === 'detail' ? { col2: false } : {});
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
315
419
|
setState(s.kind === 'list' && s.mode ? { mode: s.mode } : {});
|
|
316
420
|
}
|
|
317
421
|
|
|
@@ -325,10 +429,11 @@ function focusSearch() {
|
|
|
325
429
|
/**
|
|
326
430
|
* The jump palette. It is a jump control for EVERY destination, so it lives in the always-visible
|
|
327
431
|
* top bar; the control bar below scrolls, and a control that scrolls out of view at 390px is the
|
|
328
|
-
* hover-only trap in another costume.
|
|
329
|
-
* screen is the one on screen before it hands focus over.
|
|
432
|
+
* hover-only trap in another costume. In the frame it opens ⌘K; in the old shell search lives in
|
|
433
|
+
* panel 2, so the palette makes sure a list screen is the one on screen before it hands focus over.
|
|
330
434
|
*/
|
|
331
435
|
function openPalette() {
|
|
436
|
+
if (ctx && ctx.frame) { ctx.frame.openPalette(); return; }
|
|
332
437
|
if (NAV.current(nav).kind !== 'list') go({ kind: 'list', mode: state.mode });
|
|
333
438
|
selfDriven = true;
|
|
334
439
|
setState({ col1: true }, focusSearch);
|
|
@@ -340,6 +445,25 @@ function toggleTheme() {
|
|
|
340
445
|
setState((s) => ({ theme: s.theme === 'dark' ? 'light' : 'dark' }));
|
|
341
446
|
}
|
|
342
447
|
|
|
448
|
+
/** The frame's Settings, pushed over the current tab so Back returns to it. */
|
|
449
|
+
function openSettings() {
|
|
450
|
+
const f = ctx && ctx.frame;
|
|
451
|
+
if (!f) return;
|
|
452
|
+
if (NAV.current(nav).id !== 'settings') go({ kind: 'dest', id: 'settings' });
|
|
453
|
+
selfDriven = true;
|
|
454
|
+
f.go('settings');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* The frame's bar controls (Repository, Sync, Settings) are reached through one listener on the document: the bar is
|
|
458
|
+
* static markup. */
|
|
459
|
+
function onChromeClick(e) {
|
|
460
|
+
const t = e.target && e.target.closest ? e.target.closest('#mobRepo, #mobSync, #mobSettings') : null;
|
|
461
|
+
if (!t || !ctx || !ctx.frame) return;
|
|
462
|
+
if (t.id === 'mobSettings') openSettings();
|
|
463
|
+
else if (t.id === 'mobSync') ctx.frame.sync();
|
|
464
|
+
else ctx.frame.repoMenu(t);
|
|
465
|
+
}
|
|
466
|
+
|
|
343
467
|
/* The terminal has to re-measure when the soft keyboard shows or hides and after a rotation, or
|
|
344
468
|
* xterm keeps the row count it had and the bottom of the session is clipped. The fit itself belongs
|
|
345
469
|
* to the terminal panel; this only tells it that the box changed. */
|
|
@@ -353,12 +477,15 @@ function refit() {
|
|
|
353
477
|
|
|
354
478
|
/**
|
|
355
479
|
* Wire the phone chrome up, once, at boot.
|
|
356
|
-
* `context` is `{ bodyGrid, render }` — the body grid whose children are the
|
|
480
|
+
* `context` is `{ bodyGrid, render, frame }` — the body grid whose children are the hosts, the frame's surface
|
|
481
|
+
* (window.pmApp.frame; null in the old shell), and
|
|
357
482
|
* the shell's render function, so a breakpoint change can repaint the panels the phone needs.
|
|
358
483
|
*/
|
|
359
484
|
export function mountMobile(context) {
|
|
360
485
|
ctx = context || null;
|
|
361
486
|
if (!ctx || !ctx.bodyGrid || !NAV) return;
|
|
487
|
+
ensureComponentsCss(); // the phone chrome's icons are the lucide sprite's, in every shell
|
|
488
|
+
ensureSprite();
|
|
362
489
|
|
|
363
490
|
for (const b of document.querySelectorAll('#ctrlBar [data-seq]')) {
|
|
364
491
|
b.addEventListener('click', () => sendSeq(b.dataset.seq));
|
|
@@ -376,6 +503,7 @@ export function mountMobile(context) {
|
|
|
376
503
|
if (pal) pal.addEventListener('click', openPalette);
|
|
377
504
|
const theme = $('mobTheme');
|
|
378
505
|
if (theme) theme.addEventListener('click', toggleTheme);
|
|
506
|
+
document.addEventListener('click', onChromeClick);
|
|
379
507
|
|
|
380
508
|
if (window.visualViewport) {
|
|
381
509
|
window.visualViewport.addEventListener('resize', refit);
|
|
@@ -387,7 +515,7 @@ export function mountMobile(context) {
|
|
|
387
515
|
// phone shell exists at all AND repaint the panels — a shell left mounted above 760px is a second
|
|
388
516
|
// navigation system on the desktop, and panels left unpainted below it are three empty screens.
|
|
389
517
|
const onMQ = () => {
|
|
390
|
-
if (isMobile()) nav = nav || NAV.init(
|
|
518
|
+
if (isMobile()) nav = nav || NAV.init(firstDest());
|
|
391
519
|
if (ctx && typeof ctx.render === 'function') ctx.render();
|
|
392
520
|
else applyMobile();
|
|
393
521
|
refit();
|