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 +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/c3-phone-test.js +25 -13
- package/workspace/c3-quiet-test.js +1 -1
- package/workspace/guide/phone-companion.md +3 -1
- package/workspace/lib/c3-full-console.js +4 -4
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-focus.js +8 -6
- package/workspace/public/pm/c3-phone.css +9 -5
- package/workspace/public/pm/c3-phone.js +6 -6
- package/workspace/public/pm/c3-shell.js +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "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": {
|
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-13T03:
|
|
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:
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
/* ──
|
|
149
|
-
await page.
|
|
150
|
-
|
|
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('
|
|
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
|
|
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
|
-
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.183.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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
|
|
155
|
-
*
|
|
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
|
|
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');
|