create-pmos 0.520.0 → 0.522.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.
Files changed (36) hide show
  1. package/bin/create-pmos.js +81 -42
  2. package/package.json +39 -31
  3. package/payload/KIT-VERSION +1 -1
  4. package/payload/planning/evals/eval-calibration.md +1 -1
  5. package/payload/planning/okrs/health-budgets.md +1 -1
  6. package/payload/state/discoveries.md +1 -1
  7. package/workspace/bin/pmos.js +2 -1
  8. package/workspace/board-publish-ui-test.js +1 -1
  9. package/workspace/changelog.json +38 -0
  10. package/workspace/guide/home.md +2 -2
  11. package/workspace/guide/next-prompt.md +4 -4
  12. package/workspace/guide/published-board.md +15 -3
  13. package/workspace/lib/board-pack.js +44 -1
  14. package/workspace/lib/board-publish-choices.js +22 -6
  15. package/workspace/lib/board-publish-parts.js +95 -0
  16. package/workspace/lib/boards-host.js +3 -3
  17. package/workspace/lib/boards-received.js +105 -0
  18. package/workspace/lib/kit-update.js +51 -21
  19. package/workspace/lib/routes-board-publish.js +42 -12
  20. package/workspace/lib/routes-next-prompt.js +12 -4
  21. package/workspace/package-lock.json +2 -2
  22. package/workspace/package.json +1 -1
  23. package/workspace/public/pm/bd-publish.css +3 -0
  24. package/workspace/public/pm/bd-publish.js +39 -9
  25. package/workspace/public/pm/bd-shared.css +5 -0
  26. package/workspace/public/pm/bd-shared.js +72 -10
  27. package/workspace/public/pm/chat-term.js +22 -0
  28. package/workspace/public/pm/cozy-board.css +10 -0
  29. package/workspace/public/pm/cozy-home.css +5 -5
  30. package/workspace/public/pm/home-chat.js +3 -0
  31. package/workspace/public/pm/next-prompt-ui.js +6 -3
  32. package/workspace/public/pm/premium-board.js +8 -2
  33. package/workspace/public/pm/premium-shell.js +2 -1
  34. package/workspace/public/pm/readonly-boot.js +4 -1
  35. package/workspace/public/pm/readonly.js +40 -5
  36. package/workspace/public/pm/term-view.js +39 -8
@@ -4,13 +4,28 @@
4
4
  * to the session over the page's WebSocket, as the sign-in terminal does (cozy-claude-setup.js signInTerminal), so keys typed in it reach the
5
5
  * session — a first-run prompt, a pasted sign-in code, an answer to Claude. Before, it was read only, and a new user whose Claude stopped at its
6
6
  * theme or trust-this-folder screen could not answer it.
7
+ * It fills the whole card and fits its grid to it, again whenever the card or the window changes size (batch 27 item 6), and only the keys
8
+ * the PM types reach the session: xterm.js's own answers to the queries in the replayed screen do not (chat-term.js typedOnly, item 5).
7
9
  * An ENDED session, or a page where the socket cannot attach, keeps the read-only view: GET /api/sessions/transcript?id=<sid>&raw=1 every 2 s,
8
10
  * drawn in xterm.js the size of the session's own, after the first read only what was printed since (since + gen). When xterm.js cannot load,
9
11
  * it falls back to the plain text (GET …&max=12000, ANSI taken out). */
10
12
  import { open, current, setKind } from './cozy-viewer.js';
11
13
  import { pmGet, pageNonce } from './http.js';
14
+ import { typedOnly } from './chat-term.js';
12
15
 
13
- let xtermP = null;
16
+ let xtermP = null, fitP = null;
17
+ /** window.FitAddon from /vendor/addon-fit.js, loaded once. Resolves null when it cannot load: the live terminal then keeps the session's grid. */
18
+ function loadFit() {
19
+ if (window.FitAddon) return Promise.resolve(window.FitAddon);
20
+ if (fitP) return fitP;
21
+ fitP = new Promise((done) => {
22
+ const s = document.createElement('script'); s.src = '/vendor/addon-fit.js';
23
+ s.onload = () => done(window.FitAddon || null);
24
+ s.onerror = () => { fitP = null; done(null); };
25
+ document.head.appendChild(s);
26
+ });
27
+ return fitP;
28
+ }
14
29
  /** window.Terminal from /vendor/xterm.js, loaded once with its stylesheet. Resolves null when it cannot load. */
15
30
  function loadXterm() {
16
31
  if (window.Terminal) return Promise.resolve(window.Terminal);
@@ -39,14 +54,15 @@ function look(el) {
39
54
 
40
55
  /** The live terminal: attach over the WebSocket and send what the PM types. Resolves true when it attached to a running session,
41
56
  * false when the session has ended or the socket did not answer (the caller then shows the read-only view). */
42
- function attachLive(Terminal, body, sid, live, keep) {
57
+ function attachLive(Terminal, Fit, body, sid, live, keep) {
43
58
  return new Promise((done) => {
59
+ let ro = null;
44
60
  let sock;
45
61
  try { sock = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/?pmos-nonce=' + encodeURIComponent(pageNonce())); } catch { done(false); return; }
46
62
  const send = (d) => { if (sock.readyState === WebSocket.OPEN) sock.send(JSON.stringify(d)); };
47
63
  let term = null, settled = false;
48
64
  const settle = (v) => { if (!settled) { settled = true; clearTimeout(t); done(v); } };
49
- const close = () => { try { send({ t: 'detach', id: sid }); sock.close(); } catch { /* gone */ } if (term) { try { term.dispose(); } catch { /* gone */ } term = null; } };
65
+ const close = () => { if (ro) { ro.disconnect(); ro = null; } try { send({ t: 'detach', id: sid }); sock.close(); } catch { /* gone */ } if (term) { try { term.dispose(); } catch { /* gone */ } term = null; } };
50
66
  const t = setTimeout(() => { close(); settle(false); }, 1500); // a session the server does not know never answers
51
67
  sock.addEventListener('open', () => send({ t: 'attach', id: sid }));
52
68
  sock.addEventListener('error', () => { close(); settle(false); });
@@ -60,14 +76,29 @@ function attachLive(Terminal, body, sid, live, keep) {
60
76
  term = new Terminal({ cols: m.cols || 80, rows: m.rows || 24, cursorBlink: true, scrollback: 5000, fontFamily: L.fontFamily, lineHeight: 1.25, theme: { ...L.theme, cursor: L.theme.foreground } });
61
77
  term.open(host);
62
78
  if (m.buf) term.write(m.buf, () => term && term.scrollToBottom());
63
- term.onData((d) => send({ t: 'input', id: sid, data: d }));
64
- // drawn at the session's own grid (as the read-only view is), so opening the card never reflows a running agent
79
+ // only the keys the PM types reach the session: the terminal's own answers to the queries in the replayed screen stay here (chat-term.js typedOnly)
80
+ term.onData((d) => { const k = typedOnly(d); if (k) send({ t: 'input', id: sid, data: k }); });
81
+ // the terminal fills the whole card and fits its lines to it, again on every resize (batch 27 item 6, Wawan 2026-11-04: "Terminal
82
+ // should occupy the whole width and height"). The session has one grid, so the agent redraws at the card's size.
83
+ if (Fit) {
84
+ const fit = new Fit.FitAddon(); term.loadAddon(fit);
85
+ let t0 = null, sent = { cols: term.cols, rows: term.rows };
86
+ const refit = () => {
87
+ if (!term || !host.isConnected) return;
88
+ try { fit.fit(); } catch { return; }
89
+ if (term.cols !== sent.cols || term.rows !== sent.rows) { sent = { cols: term.cols, rows: term.rows }; send({ t: 'resize', id: sid, cols: term.cols, rows: term.rows }); }
90
+ };
91
+ refit();
92
+ ro = new ResizeObserver(() => { clearTimeout(t0); t0 = setTimeout(refit, 80); });
93
+ ro.observe(host);
94
+ }
65
95
  keep(close);
66
96
  term.focus();
67
97
  settle(true);
68
98
  }
69
99
  if (m.t === 'data' && m.id === sid && term) { const b = term.buffer.active, atEnd = b.viewportY >= b.baseY; term.write(m.data, () => { if (atEnd && term) term.scrollToBottom(); }); }
70
- if (m.t === 'size' && m.id === sid && term) { try { term.resize(m.cols, m.rows); } catch { /* keep */ } }
100
+ // another screen resized the session: this card takes the size back at its next fit (a resize of the card or the window)
101
+ if (m.t === 'size' && m.id === sid && term && !ro) { try { term.resize(m.cols, m.rows); } catch { /* keep */ } }
71
102
  if (m.t === 'error' && !term) { close(); settle(false); }
72
103
  });
73
104
  });
@@ -117,9 +148,9 @@ export function openTermView(sid, o = {}) {
117
148
  };
118
149
 
119
150
  (async () => {
120
- const Terminal = await loadXterm();
151
+ const [Terminal, Fit] = await Promise.all([loadXterm(), loadFit()]);
121
152
  if (!Terminal) plain = true;
122
- else if (await attachLive(Terminal, body, sid, live, (c) => { closeLive = c; })) return; // a running session: the real terminal
153
+ else if (await attachLive(Terminal, Fit, body, sid, live, (c) => { closeLive = c; })) return; // a running session: the real terminal
123
154
  if (!live()) return;
124
155
  // an ended session (or no socket): the read-only view, and the card says so
125
156
  setKind(id, 'Terminal · read only');