create-pmos 0.182.0 → 0.183.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.182.0",
3
+ "version": "0.183.0",
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@1400593
1
+ kernel-source: pmos@ab3a131
2
2
  assembled: 2026-10-13
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-13T03:18:01Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T03:53:55Z"}
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-13T03:18:01Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T03:53:55Z"}
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-13T03:18:01Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T03:53:55Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -11,7 +11,9 @@
11
11
  * meeting a Later today row and the morning card open the meeting's sheet with its brief (GET /api/meeting-focus)
12
12
  * record the red button shows the recording line and the notes being taken (just heard, decisions); Stop brings the bar back
13
13
  * debrief 11:25 shows the decisions and actions of the recorded Checkout sync; a meeting with no recording asks you to tell PMOS
14
- * desk Full console shows the desktop grid and survives a reload; More > Phone companion brings it back
14
+ * nodesk there is no Full console; a phone that chose it before 2026-10-13 shows the companion again
15
+ * focus the meeting companion opens over the companion and Back to your day closes it
16
+ * narrow a long title and a long list of people never widen the companion past the screen
15
17
  * desktop at 1280 wide no companion is drawn
16
18
  * 620 the companion shows up to 640 wide, where the phone styling starts
17
19
  * No script error may be raised.
@@ -131,6 +133,15 @@ const writes = (p) => (window.__c3demoWrites || []).filter((w) => w.path === p |
131
133
  await page.click('[data-c3ph-rec-meeting]');
132
134
  ok('moments: Record starts recording Checkout sync', await waitFor(page, () => /Checkout sync/.test(document.querySelector('[data-c3ph-recbar]')?.textContent || ''), null, 8000));
133
135
  ok('moments: while recording, the card opens the meeting companion', await waitFor(page, () => !!document.querySelector('[data-c3ph-moment] [data-c3ph-focus]'), null, 5000));
136
+ const fbtn = await waitFor(page, () => !!document.querySelector('[data-c3ph-focus]'), null, 8000);
137
+ ok('focus: the Open the meeting companion button is found', fbtn);
138
+ if (fbtn) {
139
+ await page.click('[data-c3ph-focus]');
140
+ ok('focus: the compact focus page shows with Back to your day', await waitFor(page, () => !!document.querySelector('[data-c3focus][data-fc-pop] [data-fc="leave"]') && !document.querySelector('[data-c3focus] [data-fc="pop-open"]'), null, 8000));
141
+ await page.click('[data-c3focus] [data-fc="pop-head"] [data-fc="leave"]');
142
+ ok('focus: Back to your day closes it and the companion shows', await waitFor(page, () => !document.querySelector('[data-c3focus]') && !!document.querySelector('[data-c3ph-moment]'), null, 5000));
143
+ }
144
+
134
145
  if (await page.$('[data-c3ph-stop]')) await page.click('[data-c3ph-stop]');
135
146
 
136
147
  await page.close();
@@ -145,19 +156,20 @@ const writes = (p) => (window.__c3demoWrites || []).filter((w) => w.path === p |
145
156
  await page.close();
146
157
  page = await open(pctx, '/c3.html?demo=1&at=10:45');
147
158
 
148
- /* ── full console and back ── */
149
- await page.click('[data-c3ph-desk]');
150
- ok('desk: Full console shows the desktop grid', await waitFor(page, () => { const g = document.querySelector('.c3grid'); return !!g && getComputedStyle(g).display !== 'none' && !document.querySelector('[data-c3ph]'); }, null, 5000));
159
+ /* ── the meeting companion and back ── */
160
+ ok('nodesk: the companion offers no Full console', await page.evaluate(() => !document.querySelector('[data-c3ph-desk]') && !/Full console/.test(document.querySelector('[data-c3ph]').textContent)));
161
+ /* ── a long title and a long list of people stay inside the screen ── */
162
+ ok('narrow: long text does not widen the companion', await page.evaluate(() => {
163
+ const long = 'SC-01 Technical blueprint and estimate: ideal build versus the minimum viable path for the partner programme, MTGA41, Victor, Ho, Jasmine, Cheng, Cara, Chai, Tan, Lee';
164
+ document.querySelectorAll('[data-c3ph] .c3ph-big, [data-c3ph] .c3ph-tt, [data-c3ph] .pmc-title__text, [data-c3ph] .pmc-row__meta > span').forEach((n) => { n.textContent = long; });
165
+ const host = document.querySelector('[data-c3ph]');
166
+ return host.scrollWidth <= innerWidth + 1 && document.documentElement.scrollWidth <= innerWidth + 1;
167
+ }));
168
+
169
+ /* ── a phone that chose Full console before it was dropped shows the companion again ── */
170
+ await page.evaluate(() => localStorage.setItem('pmos.c3.phone', 'desk'));
151
171
  await page.reload({ waitUntil: 'load' });
152
- await waitFor(page, () => !!document.querySelector('nav.c3tabs [role="tab"]'));
153
- ok('desk: the choice survives a reload', await page.evaluate(() => !document.querySelector('[data-c3ph]')));
154
- const more = await page.$('[data-c3pg="more"]');
155
- if (more) {
156
- await more.click();
157
- const item = await page.$('[data-a="phcomp"]');
158
- ok('desk: More offers Phone companion', !!item);
159
- if (item) { await item.click(); ok('desk: Phone companion brings the companion back', await waitFor(page, () => !!document.querySelector('[data-c3ph-moment]'), null, 5000)); }
160
- } else ok('desk: the More button is found', false);
172
+ ok('nodesk: an old Full console choice is cleared and the companion shows', await waitFor(page, () => !!document.querySelector('[data-c3ph-moment]') && !localStorage.getItem('pmos.c3.phone'), null, 8000));
161
173
  await page.close();
162
174
  await pctx.close();
163
175
 
@@ -9,7 +9,7 @@
9
9
  * more Backlog, Releases, Docs and Sprints show only after More; each still opens
10
10
  * calendar one Layers button shows and hides the layer chips
11
11
  * reports no deck is drawn
12
- * phone at 400 wide with Full console chosen, nothing changes: the title shows and no dot is drawn (c3-phone-test.js tests the companion)
12
+ * phone at 400 wide on the desktop layout (lib/c3-full-console.js), nothing changes: the title shows and no dot is drawn (c3-phone-test.js tests the companion)
13
13
  * No script error may be raised.
14
14
  */
15
15
  const path = require('path');
@@ -46,7 +46,9 @@ The red button records a conversation or a meeting with the same recorder as **R
46
46
 
47
47
  ## The full console
48
48
 
49
- **Full console** at the top shows the desktop pages on this device, and the device keeps this choice. To go back, open **More** and choose **Phone companion**. In a window wider than 640 pixels the console always shows the desktop pages.
49
+ A phone always shows the companion. There is no way to the desktop pages on a phone: use your computer for them. In a window wider than 640 pixels the console always shows the desktop pages.
50
+
51
+ **Open the meeting companion** opens the meeting helper over the companion. To go back, press **Back to your day** at the top or the bottom. The meeting continues to record.
50
52
 
51
53
  ## Limits
52
54
 
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
  /* lib/c3-full-console.js — for browser suites that test the console's desktop layout at phone width.
3
- * Since c3-phone-companion, console3 at 640px or less shows the phone companion. The desktop layout stays reachable there with
4
- * "Full console", which stores pmos.c3.phone = 'desk' on the device. fullConsole(browser) makes every new context start with
5
- * that choice, so a suite that resizes to 400 wide still tests the desktop layout. c3-phone-test.js tests the companion itself. */
3
+ * Since c3-phone-companion, console3 at 640px or less shows the phone companion. The PM has no way to the desktop layout there
4
+ * (Full console was dropped on 2026-10-13); pmos.c3.testDesk = 'desk' keeps it, for suites only. fullConsole(browser) makes every
5
+ * new context start with it, so a suite that resizes to 400 wide still tests the desktop layout. c3-phone-test.js tests the companion itself. */
6
6
  function fullConsole(browser) {
7
7
  const make = browser.newContext.bind(browser);
8
8
  browser.newContext = async (...args) => {
9
9
  const ctx = await make(...args);
10
- await ctx.addInitScript(() => { try { localStorage.setItem('pmos.c3.phone', 'desk'); } catch { /* no storage */ } });
10
+ await ctx.addInitScript(() => { try { localStorage.setItem('pmos.c3.testDesk', 'desk'); } catch { /* no storage */ } });
11
11
  return ctx;
12
12
  };
13
13
  return browser;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.182.0",
3
+ "version": "0.183.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.182.0",
9
+ "version": "0.183.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.182.0",
3
+ "version": "0.183.0",
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",
@@ -1,5 +1,5 @@
1
1
  /* pm/c3-focus.js — the Meeting focus page of console3 (initiative c3-meeting-focus; the mockup the PM approved on 2026-10-12, version 7).
2
- * openFocus({ event, date, pop, meet, mtitle }) closeFocus()
2
+ * openFocus({ event, date, pop, back, meet, mtitle }) closeFocus()
3
3
  * One calm full-screen page over console3, inside #c3-root: it covers the header, the tabs and the dock (they stay in the DOM, set inert) and Leave
4
4
  * focus gives them back. Recording is not touched by leaving. It reads GET /api/meeting-focus every 3 seconds while it is open:
5
5
  * header title, time, people, initiative; the clock (starts in, or elapsed and left); REC with Start recording or End meeting (the note dock);
@@ -12,7 +12,8 @@
12
12
  * In demo mode (?demo=1) c3-demo-focus.js answers the reads with the mockup's Partner API review.
13
13
  * Compact mode (?pop=1, initiative c3-meet-extension): the page the Chrome extension frames over a Google Meet call. Only a head (PMOS, the topic or "Starts in",
14
14
  * the time left, a fold button), What's being said, the conversation and a foot (Open focus in PMOS, only you see this). The stage, the bar of the talk, the now line and
15
- * the captured items are not drawn, and there is no Leave focus. ?focus=now first asks GET /api/meeting-focus/now (meet = the Meet code, mtitle = the tab's title) which
15
+ * the captured items are not drawn, and there is no Leave focus. With back (the phone companion opens it so) the head and the foot have
16
+ * "Back to your day" in place of Open focus in PMOS, which closes the page as Leave focus does. ?focus=now first asks GET /api/meeting-focus/now (meet = the Meet code, mtitle = the tab's title) which
16
17
  * meeting the call is; when none fits, the page says so and lists today's meetings to pick from. Fold shows the head only and tells the frame's parent with one
17
18
  * message, { pmosFocus: 'fold' | 'open' }, that carries no data. */
18
19
  import { el } from './dom.js';
@@ -80,7 +81,7 @@ export function closeFocus() {
80
81
  if (f.opener && document.contains(f.opener) && typeof f.opener.focus === 'function') { try { f.opener.focus({ preventScroll: true }); } catch { /* not focusable any more */ } }
81
82
  }
82
83
 
83
- export function openFocus({ event, date, pop, meet, mtitle } = {}) {
84
+ export function openFocus({ event, date, pop, back, meet, mtitle } = {}) {
84
85
  closeFocus();
85
86
  const ev = event || {};
86
87
  const want = String(ev.id || '');
@@ -102,7 +103,8 @@ export function openFocus({ event, date, pop, meet, mtitle } = {}) {
102
103
  el('div', { class: 'fc-popbody', 'data-fc': 'pop-body' },
103
104
  r.band = el('div', { class: 'fc-bandwrap' }), convo,
104
105
  r.foot = el('div', { class: 'fc-foot', 'data-fc': 'pop-foot' },
105
- lk({ 'data-fc': 'pop-open', title: 'Opens the full focus page in a new tab' }, icon('out', 'sm'), 'Open focus in PMOS'), el('span', { class: 'dim mono' }, 'only you see this'))))
106
+ back ? pbtn('ghost', 'Back to your day', 'prev', { 'data-fc': 'leave', title: 'Back to the companion. The meeting keeps recording.' })
107
+ : lk({ 'data-fc': 'pop-open', title: 'Opens the full focus page in a new tab' }, icon('out', 'sm'), 'Open focus in PMOS'), el('span', { class: 'dim mono' }, 'only you see this'))))
106
108
  : el('div', { class: 'fc-page' },
107
109
  r.top = el('header', { class: 'card fc-top', 'aria-label': 'Meeting' }),
108
110
  r.now = el('div', { class: 'fc-nowl', 'data-fc': 'now-line' }),
@@ -112,7 +114,7 @@ export function openFocus({ event, date, pop, meet, mtitle } = {}) {
112
114
  el('aside', { class: 'fc-w', 'aria-label': 'Conversation' }, convo)));
113
115
  root.append(page);
114
116
  F = { id, date: day, ev0: ev, root, r, sig: {}, data: null, offset: 0, ui: { open: new Set(), folded: new Set(), pulled: [], stage: 0, picker: false, newTopic: false, addTopic: false, heldOpen: false, pending: new Map(), failed: new Map(), pill: false },
115
- pop: !!pop, meet: String(meet || ''), mtitle: String(mtitle || ''), choices: null,
117
+ pop: !!pop, back: !!back, meet: String(meet || ''), mtitle: String(mtitle || ''), choices: null,
116
118
  timers: {}, askTimers: new Map(), sheet: null, sheetOpen: false, status: 'reading', error: '',
117
119
  opener: document.activeElement && document.activeElement !== document.body ? document.activeElement : null, seenNotes: 0 };
118
120
  // a fixed box inside an element with container-type is placed against that element, not the screen: lift it while the page is open
@@ -232,7 +234,7 @@ function drawPopHead(d, ph) {
232
234
  : ph === 'after' ? (e.title || 'Meeting') + ' has ended' : cur ? cur.text : e.title;
233
235
  const left = d && ph === 'during' && Number.isFinite(f) ? el('span', { class: 'mono dim', 'data-fc': 'left', title: 'Time left' }, clockText((f - nowMs()) / 1000)) : null;
234
236
  const lbl = F.ui.pill ? 'Open' : 'Fold to a pill';
235
- return [el('b', { class: 'mono' }, 'PMOS'), el('span', { class: 'fc-ph-topic', 'data-fc': 'pop-topic', title: typeof label === 'string' ? label : '' }, label), left,
237
+ return [F.back ? ib('Back to your day', 'prev', { 'data-fc': 'leave' }) : null, el('b', { class: 'mono' }, 'PMOS'), el('span', { class: 'fc-ph-topic', 'data-fc': 'pop-topic', title: typeof label === 'string' ? label : '' }, label), left,
236
238
  ib(lbl, F.ui.pill ? 'next' : 'fold', { 'data-fc': 'pill', 'aria-pressed': String(F.ui.pill) })];
237
239
  });
238
240
  }
@@ -4,10 +4,14 @@
4
4
  * Everything is scoped under .c3 and uses tokens only. */
5
5
  .c3.c3-phone-on .c3grid{display:none !important}
6
6
  .c3.c3-phone-on{min-height:100dvh}
7
- .c3 .c3ph{display:flex;flex-direction:column;min-height:100dvh;background:var(--background)}
7
+ .c3 .c3ph{display:flex;flex-direction:column;min-height:100dvh;width:100%;max-width:100vw;min-width:0;overflow-x:hidden;background:var(--background)}
8
+ /* a long title or a long list of people wraps or is cut with an ellipsis; it never widens the companion past the screen */
9
+ .c3 .c3ph *{min-width:0}
10
+ .c3 .c3ph-body,.c3 .c3ph-card,.c3 .c3ph-later,.c3 .c3ph-fit,.c3 .c3ph-sheetbody{grid-template-columns:minmax(0,1fr)}
11
+ .c3 .c3ph-big,.c3 .c3ph-tt,.c3 .c3ph-line,.c3 .c3ph-hd b{overflow-wrap:anywhere}
8
12
  .c3 .c3ph-hd{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:calc(10px + env(safe-area-inset-top)) 16px 10px;position:sticky;top:0;background:var(--background);z-index:2}
9
- .c3 .c3ph-hd b{font-size:var(--fs-4)}
10
- .c3 .c3ph-hdr{display:flex;align-items:center;gap:8px}
13
+ .c3 .c3ph-hd b{font-size:var(--fs-4);flex:1;min-width:0}
14
+ .c3 .c3ph-hdr{display:flex;align-items:center;gap:8px;flex:none}
11
15
  .c3 .c3ph-dotn{display:inline-flex;align-items:center;gap:4px;font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
12
16
  .c3 .c3ph-body{flex:1;display:grid;gap:12px;align-content:start;padding:0 12px 16px}
13
17
  .c3 .c3ph-card{display:grid;gap:8px;padding:14px}
@@ -20,13 +24,13 @@
20
24
  .c3 .c3ph-rec-why{font-size:var(--fs-2);line-height:1.45;background:var(--muted);border-radius:8px;padding:8px 10px}
21
25
  .c3 .c3ph-prep{display:flex;gap:6px;align-items:flex-start;font-size:var(--fs-2);line-height:1.45}
22
26
  .c3 .c3ph-before,.c3 .c3ph-fit{display:grid;gap:6px;padding-top:8px;border-top:1px solid var(--border);font-size:var(--fs-2)}
23
- .c3 .c3ph-before{grid-template-columns:1fr auto;align-items:center}
27
+ .c3 .c3ph-before{grid-template-columns:minmax(0,1fr) auto;align-items:center}
24
28
  .c3 .c3ph-acts{display:grid;gap:8px}
25
29
  .c3 .c3ph-acts.two{grid-template-columns:1fr 1fr}
26
30
  .c3 .c3ph-b{min-height:44px;justify-content:center}
27
31
  .c3 .c3ph-cnt{display:flex;justify-content:space-between;font-weight:600;font-size:var(--fs-2)}
28
32
  .c3 .c3ph-cnt .dim{font-weight:400;font-family:var(--mono)}
29
- .c3 .c3ph-later{display:grid;gap:4px;font-size:var(--fs-2);color:var(--muted-foreground);padding:0 4px}
33
+ .c3 .c3ph-later{display:grid;overflow:hidden;gap:4px;font-size:var(--fs-2);color:var(--muted-foreground);padding:0 4px}
30
34
  .c3 .c3ph-ft{position:sticky;bottom:0;display:flex;align-items:center;gap:10px;padding:8px 12px calc(12px + env(safe-area-inset-bottom));border-top:1px solid var(--border);background:var(--card);z-index:2}
31
35
  .c3 .c3ph-ask{flex:1;display:flex;align-items:center;gap:8px;border:1px solid var(--border);border-radius:8px;padding:0 4px 0 12px;min-height:44px;color:var(--muted-foreground);background:var(--background)}
32
36
  .c3 .c3ph-ask input.c3ph-in{flex:1;min-width:0;border:0;background:transparent;font-family:inherit;font-size:var(--fs-3);color:var(--foreground);padding:10px 0;outline:none;border-radius:0}
@@ -18,7 +18,7 @@
18
18
  * points to the keyboard's own dictation; this page does not send audio anywhere.
19
19
  * Record the red button records a conversation or a meeting, the same recorder as the desktop Record control (note-dock.js).
20
20
  * While it records, the bar shows the clock and Stop. In demo mode it drives the simulated recorder and saves nothing.
21
- * "Full console" switches this device to the desktop layout (kept on the device); the desktop More menu has "Phone companion" to come back.
21
+ * There is no way to the desktop pages on a phone. The meeting companion opens as the compact focus page with "Back to your day".
22
22
  * The moment is worked out again every minute; when it changes, a short banner says so. ?at=HH:MM sets the clock in demo mode only. */
23
23
  import { el } from './dom.js';
24
24
  import { button, iconButton } from './primitives.js';
@@ -46,10 +46,11 @@ function ensureCss() {
46
46
  document.head.appendChild(el('link', { id: CSS_ID, rel: 'stylesheet', href: '/pm/c3-phone.css' }));
47
47
  }
48
48
 
49
- /** Is this device in phone mode? Phone width, and the PM has not chosen the full console on it. */
49
+ /** Is this device in phone mode? Phone width: there is no choice to show the desktop pages on a phone (Wawan, 2026-10-13).
50
+ * pmos.c3.testDesk is for the browser suites that test the desktop layout at phone width (lib/c3-full-console.js); nothing offers it. */
50
51
  export function phoneWanted(store) {
51
52
  if (typeof matchMedia !== 'function' || !matchMedia('(max-width:640px)').matches) return false;
52
- return !(store && store.get('pmos.c3.phone') === 'desk');
53
+ return !(store && store.get('pmos.c3.testDesk') === 'desk');
53
54
  }
54
55
 
55
56
  export function mountPhone(root, ctx = {}) {
@@ -194,7 +195,7 @@ export function mountPhone(root, ctx = {}) {
194
195
  }
195
196
  const decisions = () => waitRows().filter((r) => !r.pm).length;
196
197
  const needOpts = { onDone: (r) => itemDone(r), onLater: (r) => itemLater(r, 'later-today') };
197
- const focus = (m) => import('./c3-focus.js').then((f) => f.openFocus({ event: { id: m.id, start: m.start }, pop: true })).catch(() => toast('The meeting companion did not open.'));
198
+ const focus = (m) => import('./c3-focus.js').then((f) => f.openFocus({ event: { id: m.id, start: m.start }, pop: true, back: true })).catch(() => toast('The meeting companion did not open.'));
198
199
  /* a meeting's sheet (c3-phone-detail.js): Record it while the meeting has not ended, and the meeting companion */
199
200
  function meetingSheet(m) {
200
201
  const t = now().getTime(); const ends = Date.parse(m.end || m.start) || 0;
@@ -311,8 +312,7 @@ export function mountPhone(root, ctx = {}) {
311
312
  const d = now();
312
313
  const n = waitRows().length;
313
314
  head.replaceChildren(el('b', null, WD[d.getDay()] + ' ' + d.getDate() + ' ' + MO[d.getMonth()]),
314
- el('span', { class: 'c3ph-hdr' }, n ? el('span', { class: 'c3ph-dotn', 'data-c3ph-count': String(n) }, c3ic('inbox', 'sm'), n + ' waiting') : null,
315
- button({ label: 'Full console', variant: 'ghost', size: 'sm', attrs: { 'data-c3ph-desk': '' }, onClick: () => ctx.onDesk && ctx.onDesk() })));
315
+ el('span', { class: 'c3ph-hdr' }, n ? el('span', { class: 'c3ph-dotn', 'data-c3ph-count': String(n) }, c3ic('inbox', 'sm'), n + ' waiting') : null));
316
316
  }
317
317
 
318
318
  const BANNER = { meeting: (m) => m.title + ' has started', before: (m, mo) => m.title + ' starts ' + inText(mo.minutes), after: (m) => m.title + ' just ended', gap: (m, mo) => (mo.minutes || 0) + ' free minutes', wrap: () => 'Time to wrap up', morning: () => 'Good morning' };
@@ -138,7 +138,6 @@ function toggleMore() {
138
138
  item('alert', 'Incident day', () => toggleIncident(), S.incident, S.demo ? { 'data-a': 'inc' } : { 'data-a': 'inc', disabled: true, title: 'No incident feed is connected, so this page shows no incident.' }),
139
139
  item('spark', 'Agent playbooks', () => openPlaybooks(), false, { 'data-a': 'pbopen' }),
140
140
  item('phone', 'Phone preview', () => togglePreview(), !!S.preview, { 'data-a': 'phone' }),
141
- S.root.classList.contains('ph') && store.get('pmos.c3.phone') === 'desk' ? item('phone', 'Phone companion', () => { store.set('pmos.c3.phone', ''); syncPhone(); }, false, { 'data-a': 'phcomp' }) : null,
142
141
  item(S.theme === 'dark' ? 'sun' : 'moon', S.theme === 'dark' ? 'Light theme' : 'Dark theme', () => setTheme(S.theme === 'dark' ? 'light' : 'dark')),
143
142
  ((b) => { b.firstChild.replaceWith(gearIcon('sm')); return b; })(item('info', 'Settings', () => showTab('settings', true), false, { 'data-a': 'settings' })),
144
143
  item('term', S.dockOpen ? 'Fold the dock' : 'Open the dock', () => { S.dockOpen = !S.dockOpen; keepDock(); syncDock(); }, false, { 'data-a': 'dockt' }, 'Ctrl J'),
@@ -151,13 +150,14 @@ function toggleMore() {
151
150
  document.addEventListener('keydown', S.menuOff); document.addEventListener('pointerdown', S.menuOff);
152
151
  const first = S.menu.querySelector('.c3pg-mi:not([disabled])'); if (first) first.focus();
153
152
  }
154
- /* The phone companion (c3-phone.js): at 640px or less, unless the PM chose the full console on this device, it fills the page and the
155
- * desktop grid is hidden. "Full console" stores that choice; the More menu's "Phone companion" clears it. */
153
+ /* The phone companion (c3-phone.js): at 640px or less it fills the page and the desktop grid is hidden. There is no Full console:
154
+ * the old choice (pmos.c3.phone = 'desk', stored before 2026-10-13) is cleared so no phone stays on the desktop pages. */
156
155
  function syncPhone() {
156
+ if (store.get('pmos.c3.phone')) store.set('pmos.c3.phone', '');
157
157
  const want = phoneWanted(store);
158
158
  if (want && !S.phone) {
159
159
  S.root.classList.add('c3-phone-on');
160
- S.phone = mountPhone(S.root, { ws: S.ws, demo: S.demo, onDesk: () => { store.set('pmos.c3.phone', 'desk'); syncPhone(); } });
160
+ S.phone = mountPhone(S.root, { ws: S.ws, demo: S.demo });
161
161
  } else if (!want && S.phone) {
162
162
  S.phone.dispose(); S.phone = null;
163
163
  S.root.classList.remove('c3-phone-on');