create-pmos 0.192.0 → 0.193.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.192.0",
3
+ "version": "0.193.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@6849bbf
1
+ kernel-source: pmos@1e4f936
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-13T07:20:02Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T08:02:12Z"}
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-13T07:20:02Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T08:02:12Z"}
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-13T07:20:02Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T08:02:12Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -137,7 +137,7 @@ When PMOS holds nothing for a past meeting, the detail says so. For a coming mee
137
137
 
138
138
  ### Meeting focus
139
139
 
140
- **Focus** opens one calm page for a meeting that is today. It shows on the detail of a meeting that has not ended, and next to the recording on the header while a meeting is recorded. The page covers the console. **Leave focus** gives the console back. Leaving does not stop the recording. The page stays open when you reload it.
140
+ **Focus** opens one calm page for a meeting that is today. It shows on the detail of a meeting that has not ended, and next to the recording on the header while a meeting is recorded. The page covers the console. **Leave focus** gives the console back. Leaving does not stop the recording. The page stays open when you reload it. From the recording, the page finds the meeting by its start time and title, so it also finds a meeting that has no calendar id, or that moved on the same day after you started to record.
141
141
 
142
142
  The page has these parts:
143
143
 
@@ -55,14 +55,32 @@ function linesOf(runtimeDir, keys) {
55
55
 
56
56
  /* ── the meeting, its note and its brief ──────────────────────────────────────────────────────────────────────────── */
57
57
 
58
+ const minuteOf = (t) => { const ms = Date.parse(t); return Number.isFinite(ms) ? Math.floor(ms / 60000) : null; };
59
+ const normTitle = (t) => require('./calendar-memory.js').normTitle(t);
60
+ /** A meeting named by its start and title (a recording whose meeting has no stored id). The start is compared as an instant, not as
61
+ * text: a note keeps its meeting's start in UTC ("…T07:00:00.000Z") while the calendar writes it with the local offset ("…T15:00:00+08:00").
62
+ * Same minute and same title first, then the only meeting of that title on that local day (a meeting moved after the note linked it). */
63
+ function findByStart(events, by) {
64
+ const m = minuteOf(by.at); if (m === null) return null;
65
+ const rows = (events || []).filter((e) => e && e.id && minuteOf(e.at) !== null);
66
+ const title = by.title ? normTitle(by.title) : '';
67
+ const atMin = rows.filter((e) => minuteOf(e.at) === m);
68
+ const both = title ? atMin.filter((e) => normTitle(e.title) === title) : atMin;
69
+ if (both.length) return both[0];
70
+ if (!title) return null;
71
+ const day = localDay(m * 60000);
72
+ const named = rows.filter((e) => normTitle(e.title) === title && localDay(Date.parse(e.at)) === day);
73
+ return named.length === 1 ? named[0] : null;
74
+ }
58
75
  function findEvent(events, id, date, by) {
59
- if (!id && by && by.at) { // a meeting without a stored id: its start and title name it
60
- const hit = (events || []).filter((e) => e && e.id && e.at === String(by.at) && (!by.title || e.title === String(by.title)));
61
- return hit[0] || null;
62
- }
76
+ if (!id && by && by.at) return findByStart(events, by);
63
77
  const rows = (events || []).filter((e) => e && e.id && (String(e.id) === id || baseId(e.id) === baseId(id)));
64
78
  const onDay = rows.filter((e) => !date || String(e.at || '').slice(0, 10) === date);
65
- return onDay.find((e) => String(e.id) === id) || onDay[0] || null;
79
+ const hit = onDay.find((e) => String(e.id) === id) || onDay[0] || null;
80
+ if (hit) return hit;
81
+ // a date that is the UTC day of the start, not its local day (an early-morning meeting east of UTC): the start names it
82
+ if (by && by.at) return findByStart(events, by);
83
+ return rows.find((e) => String(e.id) === id) || null; // an occurrence id names one meeting whatever the day says
66
84
  }
67
85
  /** A recording that is not on the calendar (a talk in the corridor): the note stands in for the meeting. It has no end, no people
68
86
  * and no brief, so focus shows what is said, PMOS's notes and Captured, and no topics from an invite. */
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.192.0",
3
+ "version": "0.193.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.192.0",
9
+ "version": "0.193.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.192.0",
3
+ "version": "0.193.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",
@@ -32,6 +32,8 @@ import { toast } from './c3-toast.js';
32
32
  const POLL_MS = 3000;
33
33
  const LAST_MINUTES = 5; // the last minutes of the meeting: the captured items show under the stage
34
34
  const pad = (n) => String(n).padStart(2, '0');
35
+ /** The local day of a start: a note keeps its meeting's start in UTC, and its first ten characters can be the day before. */
36
+ const localDayOf = (t) => { const ms = Date.parse(t); if (!Number.isFinite(ms)) return ''; const d = new Date(ms); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; };
35
37
  const hhmm = (ms) => { const d = new Date(ms); return pad(d.getHours()) + ':' + pad(d.getMinutes()); };
36
38
  export const baseId = (id) => String(id || '').split('@')[0];
37
39
  /** 3:07, or 1:03:07 from an hour on; a minus sign before a negative time. Pure. */
@@ -84,7 +86,7 @@ export function closeFocus() {
84
86
  const grid = host && host.querySelector('.c3grid'); if (grid) grid.inert = false;
85
87
  const comp = host && host.querySelector('[data-c3ph]'); if (comp) comp.inert = false;
86
88
  if (f.phone) { const st = f.phone && document.querySelector('[data-c3ph-focus-bar]'); if (st) st.focus({ preventScroll: true }); return; }
87
- try { const u = new URL(location.href); if (u.searchParams.has('focus')) { u.searchParams.delete('focus'); u.searchParams.delete('fdate'); history.replaceState(history.state, '', u); } } catch { /* no history */ }
89
+ try { const u = new URL(location.href); if (u.searchParams.has('focus')) { u.searchParams.delete('focus'); u.searchParams.delete('fdate'); u.searchParams.delete('fat'); u.searchParams.delete('ftitle'); history.replaceState(history.state, '', u); } } catch { /* no history */ }
88
90
  if (f.opener && document.contains(f.opener) && typeof f.opener.focus === 'function') { try { f.opener.focus({ preventScroll: true }); } catch { /* not focusable any more */ } }
89
91
  }
90
92
 
@@ -95,7 +97,7 @@ export function openFocus({ event, date, pop, back, phone, note, meet, mtitle }
95
97
  if (phone) pop = false; // the phone has the whole page, in one column
96
98
  const resolve = !!pop && want === 'now'; // the extension names no meeting: which one this call is comes first
97
99
  const id = resolve ? '' : want;
98
- const day = String(date || String(ev.at || ev.start || '').slice(0, 10) || '');
100
+ const day = String(date || localDayOf(ev.at || ev.start) || '');
99
101
  ensureCss();
100
102
  const host = document.getElementById('c3-root') || document.body;
101
103
  const root = el('div', { class: 'c3focus' + (pop ? ' fc-pop' : '') + (phone ? ' fc-phone' : ''), role: 'region', 'aria-label': pop ? 'PMOS meeting helper' : 'Meeting focus', 'data-c3focus': '', ...(pop ? { 'data-fc-pop': '' } : {}), ...(phone ? { 'data-fc-phone': '' } : {}) });
@@ -142,7 +144,7 @@ export function openFocus({ event, date, pop, back, phone, note, meet, mtitle }
142
144
  host.append(root);
143
145
  const grid = host.querySelector && host.querySelector('.c3grid'); if (grid) grid.inert = true;
144
146
  const comp = host.querySelector && host.querySelector('[data-c3ph]'); if (comp) comp.inert = true; // the phone companion under the page
145
- if (!pop && !phone) { try { const u = new URL(location.href); u.searchParams.set('focus', id); if (day) u.searchParams.set('fdate', day); history.replaceState(history.state, '', u); } catch { /* no history */ } } // the compact page keeps its address: a reload asks again which meeting it is
147
+ if (!pop && !phone) { try { const u = new URL(location.href); u.searchParams.set('focus', id); if (day) u.searchParams.set('fdate', day); if (!id && ev.at) { u.searchParams.set('fat', ev.at); if (ev.title) u.searchParams.set('ftitle', ev.title); } history.replaceState(history.state, '', u); } catch { /* no history */ } } // the compact page keeps its address: a reload asks again which meeting it is
146
148
  draw();
147
149
  F.timers.clock = setInterval(tick, 1000);
148
150
  if (resolve) resolveNow(); else load();
@@ -185,7 +187,7 @@ async function load() {
185
187
  if (f.id) p.set('event', f.id);
186
188
  else if (f.noteId) p.set('note', f.noteId); // a recording with no meeting
187
189
  if (f.date && !f.noteId) p.set('date', f.date);
188
- if (!f.id && !f.noteId && (e.at || e.start)) { p.set('at', e.at || e.start); if (e.title) p.set('title', e.title); }
190
+ if (!f.noteId && (e.at || e.start)) { p.set('at', e.at || e.start); if (e.title) p.set('title', e.title); } // the start and title still find the meeting when the id or the day does not
189
191
  const j = await pmGet('/api/meeting-focus?' + p.toString());
190
192
  if (F !== f) return;
191
193
  if (j && j.ok) {
@@ -623,7 +623,7 @@ async function boot() {
623
623
  let start = q.get('tab');
624
624
  const saved = DEMO ? null : await pmGet('/api/settings' + (S.ws ? '?ws=' + encodeURIComponent(S.ws) : '')).catch(() => null); // demo mode's workspace is not registered
625
625
  if (saved && saved.ok) { applyLook(root, saved.settings); if (!start) { const s = (saved.settings || []).find((x) => x.key === 'look.start'); if (s && BUILD[s.value]) start = s.value; } }
626
- if (q.get('focus') || q.get('fdate')) import('./c3-focus.js').then((m) => m.openFocus({ event: { id: q.get('focus') || '' }, date: q.get('fdate') || '', pop: q.get('pop') === '1', meet: q.get('meet') || '', mtitle: q.get('mtitle') || '' })).catch(() => { /* the focus page is not available */ }); // a reload keeps the Meeting focus page open; ?pop=1 is its compact form for the Chrome extension (focus=now asks which meeting the Meet call is)
626
+ if (q.get('focus') || q.get('fdate')) import('./c3-focus.js').then((m) => m.openFocus({ event: { id: q.get('focus') || '', ...(q.get('fat') ? { at: q.get('fat'), title: q.get('ftitle') || '' } : {}) }, date: q.get('fdate') || '', pop: q.get('pop') === '1', meet: q.get('meet') || '', mtitle: q.get('mtitle') || '' })).catch(() => { /* the focus page is not available */ }); // a reload keeps the Meeting focus page open; ?pop=1 is its compact form for the Chrome extension (focus=now asks which meeting the Meet call is)
627
627
  if (!S.userNav) showTab(start || 'brief', false); // a press during the read above (the gear, a tab) wins over the start page
628
628
  new ResizeObserver(moveInd).observe(S.tabNav);
629
629
  workCount(); briefCount(); askCount(); S.dock.start();
@@ -173,9 +173,8 @@ function meetingOfNote(id) {
173
173
  return m && m.kind === 'event' && m.start ? m : null;
174
174
  }
175
175
  /** Meeting focus (initiative c3-meeting-focus): the page for the meeting being recorded. The event id is empty for a meeting without a stored id; the page then finds it by start and title. */
176
- function openFocusFor(m) {
177
- const day = String(m.start || '').slice(0, 10);
178
- import('./c3-focus.js').then((f) => f.openFocus({ event: { id: m.event_id || '', at: m.start, end: m.end || null, title: m.title }, date: day }))
176
+ function openFocusFor(m) { // no date: the page takes the local day of the start (the note keeps it in UTC)
177
+ import('./c3-focus.js').then((f) => f.openFocus({ event: { id: m.event_id || '', at: m.start, end: m.end || null, title: m.title } }))
179
178
  .catch(() => toast({ text: 'The focus page could not load.', tone: 'error' }));
180
179
  }
181
180