create-pmos 0.63.1 → 0.63.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/guide/layout.md +2 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/mobile.css +17 -0
- package/workspace/public/pm/mobile.js +36 -5
- package/workspace/public/pm/tab-term.js +18 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.2",
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-06T23:17:22Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-06T23:17:22Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-06T23:17:22Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -119,6 +119,8 @@ Every row has a visible ⋯ button. Every control is at least 44 pixels high. No
|
|
|
119
119
|
|
|
120
120
|
You can have the same session open on your phone and your computer. A session has one size, so the screen you type in or tap last sets it. The other screen keeps showing the session and takes the size back as soon as you type in it.
|
|
121
121
|
|
|
122
|
+
On an iPhone or an iPad, the terminal gets smaller when the keyboard opens, so the agent's prompt stays above the keyboard. Tapping the terminal does not zoom the page, and dragging at its top or bottom does not pull or reload the page. A swipe from the left edge of the screen is Safari's Back gesture and leaves the workspace. To avoid that, add the workspace to your Home Screen and open it from there.
|
|
123
|
+
|
|
122
124
|
## The older layout
|
|
123
125
|
|
|
124
126
|
The address options `?layout=legacy` and `?fixture=cockpit` keep the older layouts. The rest of this page describes them. In the cockpit, the workspace has four columns: the repository rail, the list, the detail and the tabs. With `?layout=legacy` it has three columns: navigation, detail and tabs.
|
package/workspace/package.json
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
border-left: 1px solid var(--border);
|
|
44
44
|
border-right: 1px solid var(--border);
|
|
45
45
|
}
|
|
46
|
+
html.pm-vv body { height: var(--fr-vvh, 100vh); }
|
|
46
47
|
|
|
47
48
|
/* Explicit placement, never auto-placement: app.js appends its root LAST, so source order would
|
|
48
49
|
* put the screen underneath the bottom nav. */
|
|
@@ -363,3 +364,19 @@
|
|
|
363
364
|
.mob[hidden], .mobmenu[hidden], .mob-back[hidden], .mob-brand[hidden] { display: none !important; }
|
|
364
365
|
.mob-repo[hidden], .mob-set[hidden], .mob-sync[hidden], .mob-theme[hidden], .mobnav-n[hidden] { display: none !important; }
|
|
365
366
|
.mob-deck[hidden], .mob-pager[hidden], .mob-deck__views[hidden] { display: none !important; }
|
|
367
|
+
|
|
368
|
+
/* ── a touch screen: the visible height, no page bounce, no zoom on focus ─────────────────────
|
|
369
|
+
* pm/mobile.js sets `pm-vv` and `--fr-vvh` (the visual viewport's height) on a touch screen. The
|
|
370
|
+
* page is that tall, so the soft keyboard sits under the terminal and does not cover it. Above the
|
|
371
|
+
* breakpoint (an iPad) that is app.js's root; at the breakpoint and below it is <body>, in the phone rules above.
|
|
372
|
+
* A drag at the top or bottom of the terminal does not pull the page or reload it. Every rule here
|
|
373
|
+
* needs `pm-vv`, so a desktop with a mouse is untouched. */
|
|
374
|
+
html.pm-vv, html.pm-vv body { overscroll-behavior: none; }
|
|
375
|
+
html.pm-vv { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
|
|
376
|
+
@media (min-width: 761px) {
|
|
377
|
+
html.pm-vv body > [data-pm-theme] { height: var(--fr-vvh, 100vh) !important; }
|
|
378
|
+
}
|
|
379
|
+
/* xterm types through a hidden textarea and gives it the terminal's 12.5px font. iOS zooms the
|
|
380
|
+
* page in on a focused field under 16px, which pushes the terminal off the side of the screen. The
|
|
381
|
+
* textarea is invisible, so its size changes nothing else. */
|
|
382
|
+
html.pm-vv .xterm .xterm-helper-textarea { font-size: 16px !important; }
|
|
@@ -475,13 +475,42 @@ function onChromeClick(e) {
|
|
|
475
475
|
|
|
476
476
|
/* The terminal has to re-measure when the soft keyboard shows or hides and after a rotation, or
|
|
477
477
|
* xterm keeps the row count it had and the bottom of the session is clipped. The fit itself belongs
|
|
478
|
-
* to the terminal panel; this only tells it that the box changed.
|
|
478
|
+
* to the terminal panel; this only tells it that the box changed. It calls the terminal's refit
|
|
479
|
+
* directly: a synthetic window `resize` also re-lays the card deck and the report slides. */
|
|
479
480
|
function refit() {
|
|
480
481
|
const t = window.pmTerm;
|
|
481
|
-
if (t && typeof t.
|
|
482
|
+
if (t && typeof t.refitVisible === 'function') { try { t.refitVisible(); return; } catch { /* fall through */ } }
|
|
482
483
|
window.dispatchEvent(new Event('resize'));
|
|
483
484
|
}
|
|
484
485
|
|
|
486
|
+
/* ── the visible height, on a touch screen ───────────────────────────────────────────────────
|
|
487
|
+
* iOS does not shrink the page when the soft keyboard opens. `100vh` stays the full screen, the
|
|
488
|
+
* keyboard covers its bottom, and that bottom is where the agent's prompt and the control keys
|
|
489
|
+
* are. Safari then scrolls the page to show the focused field, so the screen jumps. And with
|
|
490
|
+
* Safari's toolbar shown, `100vh` is already taller than what is visible.
|
|
491
|
+
*
|
|
492
|
+
* So on a touch screen the page height is the VISUAL viewport's: `--fr-vvh` on <html>, read by
|
|
493
|
+
* pm/mobile.css, with the page held at the top. A pinch zoom also changes the visual viewport, and
|
|
494
|
+
* that is not the page getting smaller, so the height is in layout pixels (height × scale). */
|
|
495
|
+
const TOUCH = window.matchMedia('(hover: none) and (pointer: coarse)');
|
|
496
|
+
let vvh = 0;
|
|
497
|
+
|
|
498
|
+
function fitViewport() {
|
|
499
|
+
const vv = window.visualViewport;
|
|
500
|
+
const root = document.documentElement;
|
|
501
|
+
if (!vv || !TOUCH.matches) {
|
|
502
|
+
if (root.classList.contains('pm-vv')) { root.classList.remove('pm-vv'); vvh = 0; refit(); }
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
root.classList.add('pm-vv');
|
|
506
|
+
if (window.scrollY || window.scrollX) window.scrollTo(0, 0);
|
|
507
|
+
const h = Math.round(vv.height * (vv.scale || 1));
|
|
508
|
+
if (!h || h === vvh) return;
|
|
509
|
+
vvh = h;
|
|
510
|
+
root.style.setProperty('--fr-vvh', h + 'px');
|
|
511
|
+
refit();
|
|
512
|
+
}
|
|
513
|
+
|
|
485
514
|
/* ── mount ──────────────────────────────────────────────────────────────────────────────────── */
|
|
486
515
|
|
|
487
516
|
/**
|
|
@@ -515,10 +544,12 @@ export function mountMobile(context) {
|
|
|
515
544
|
document.addEventListener('click', onChromeClick);
|
|
516
545
|
|
|
517
546
|
if (window.visualViewport) {
|
|
518
|
-
window.visualViewport.addEventListener('resize',
|
|
519
|
-
window.visualViewport.addEventListener('scroll',
|
|
547
|
+
window.visualViewport.addEventListener('resize', fitViewport);
|
|
548
|
+
window.visualViewport.addEventListener('scroll', fitViewport);
|
|
520
549
|
}
|
|
521
|
-
|
|
550
|
+
if (TOUCH.addEventListener) TOUCH.addEventListener('change', fitViewport);
|
|
551
|
+
window.addEventListener('orientationchange', () => setTimeout(() => { fitViewport(); refit(); }, 250));
|
|
552
|
+
fitViewport();
|
|
522
553
|
|
|
523
554
|
// Crossing the breakpoint (rotate a tablet, drag a window narrow) has to re-decide whether the
|
|
524
555
|
// phone shell exists at all AND repaint the panels — a shell left mounted above 760px is a second
|
|
@@ -699,6 +699,18 @@ function syncSize(rec, claim) {
|
|
|
699
699
|
if (send({ t: 'resize', id: rec.sessionId, cols, rows })) { rec.sentCols = cols; rec.sentRows = rows; }
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
+
/**
|
|
703
|
+
* State a grid that a refit changed, once the box stops moving. A phone's soft keyboard and a
|
|
704
|
+
* rotation move the box several times in a few hundred milliseconds, and every grid the PTY hears
|
|
705
|
+
* makes the agent redraw its whole frame. Typing still sends at once: `onData` calls syncSize
|
|
706
|
+
* before the keystroke, which states the latest grid and makes the queued call a no-op.
|
|
707
|
+
*/
|
|
708
|
+
const SIZE_SETTLE_MS = 120;
|
|
709
|
+
function queueSize(rec) {
|
|
710
|
+
clearTimeout(rec.sizeTimer);
|
|
711
|
+
rec.sizeTimer = setTimeout(() => { rec.sizeTimer = null; syncSize(rec, true); }, SIZE_SETTLE_MS);
|
|
712
|
+
}
|
|
713
|
+
|
|
702
714
|
/** Write a status line into the terminal itself — once per distinct message, dimmed. */
|
|
703
715
|
function note(rec, line, force) {
|
|
704
716
|
if (!rec.term) return;
|
|
@@ -726,8 +738,9 @@ export function refitVisible() {
|
|
|
726
738
|
try { rec.fit.fit(); } catch { /* a terminal mid-teardown */ }
|
|
727
739
|
// …and state the resulting grid to the PTY. `fit()` only fires xterm's `onResize` when the
|
|
728
740
|
// grid MOVES, so a pane that was measured before it had a session would otherwise never tell
|
|
729
|
-
// the shell how wide it is. See the `attached` handler.
|
|
730
|
-
|
|
741
|
+
// the shell how wide it is. See the `attached` handler. A grid change the fit just queued
|
|
742
|
+
// (see queueSize) goes out when the box settles, not here.
|
|
743
|
+
if (!rec.sizeTimer) syncSize(rec);
|
|
731
744
|
}
|
|
732
745
|
};
|
|
733
746
|
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(run);
|
|
@@ -891,12 +904,12 @@ function buildReal(tab) {
|
|
|
891
904
|
term.attachCustomKeyEventHandler((e) => !((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')));
|
|
892
905
|
|
|
893
906
|
const rec = {
|
|
894
|
-
kind: 'real', tab, host, term, fit, sessionId: null, attachedId: null, noted: null, ro: null,
|
|
907
|
+
kind: 'real', tab, host, term, fit, sessionId: null, attachedId: null, noted: null, ro: null, sizeTimer: null,
|
|
895
908
|
};
|
|
896
909
|
// Typing, focusing or re-sizing THIS pane takes the session's grid back from another screen, and
|
|
897
910
|
// the grid goes out before the keystroke so the agent answers it at this pane's size.
|
|
898
911
|
term.onData((d) => { if (rec.sessionId) { syncSize(rec, true); send({ t: 'input', id: rec.sessionId, data: d }); } });
|
|
899
|
-
term.onResize(() =>
|
|
912
|
+
term.onResize(() => queueSize(rec));
|
|
900
913
|
host.addEventListener('focusin', () => syncSize(rec, true));
|
|
901
914
|
if (typeof ResizeObserver === 'function') {
|
|
902
915
|
rec.ro = new ResizeObserver(() => refitVisible());
|
|
@@ -951,6 +964,7 @@ export function disposeTerm(tab) {
|
|
|
951
964
|
* pane for the id and detaches from the orphan instead. */
|
|
952
965
|
if (rec.kind !== 'real') return;
|
|
953
966
|
if (rec.ro) { try { rec.ro.disconnect(); } catch { /* ignore */ } }
|
|
967
|
+
clearTimeout(rec.sizeTimer);
|
|
954
968
|
if (rec.sessionId) {
|
|
955
969
|
send({ t: 'detach', id: rec.sessionId });
|
|
956
970
|
SOCK.attached.delete(rec.sessionId);
|