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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.63.1",
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": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@4cf3dc9
1
+ kernel-source: pmos@586dc2f
2
2
  assembled: 2026-10-06
3
3
  mode: vendor
@@ -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-06T22:58:51Z"}
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-06T22:58:51Z"}
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-06T22:58:51Z"}
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.63.1",
3
+ "version": "0.63.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.63.1",
3
+ "version": "0.63.2",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -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.fit === 'function') { try { t.fit(); return; } catch { /* fall through */ } }
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', refit);
519
- window.visualViewport.addEventListener('scroll', refit);
547
+ window.visualViewport.addEventListener('resize', fitViewport);
548
+ window.visualViewport.addEventListener('scroll', fitViewport);
520
549
  }
521
- window.addEventListener('orientationchange', () => setTimeout(refit, 250));
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
- syncSize(rec);
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(() => syncSize(rec, true));
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);