create-pmos 0.530.0 → 0.532.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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.530.0",
3
+ "version": "0.532.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.530.0",
9
+ "version": "0.532.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^1.3.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.530.0",
3
+ "version": "0.532.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",
@@ -261,6 +261,7 @@
261
261
  .vz-words .vz-eye { align-self: flex-start; padding: 3px 8px; border-radius: 4px; background: color-mix(in oklab, var(--cz-c-ffffff) 18%, transparent); font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
262
262
  .vz-fr { position: absolute; inset: 0; overflow: hidden; }
263
263
  .vz-fr iframe { width: 1280px; height: 720px; border: 0; transform-origin: 0 0; transform: scale(var(--rp-scale, .4)); pointer-events: none; background: var(--cz-c-ffffff); }
264
+ .vz-fr.vz-live iframe { pointer-events: auto; } /* a slide with CSS-only controls (report library timeline, actions): premium-report-read.js slideCard */
264
265
  .vz-snav { display: flex; align-items: center; gap: 8px; }
265
266
  .vz-snav > b { font-size: var(--fs-label); font-weight: var(--fw-strong); line-height: var(--lh-label); }
266
267
  .vz-snav > span:not(.vz-sp) { font-size: var(--fs-meta); color: var(--cz-ink3); line-height: var(--lh-meta); }
@@ -163,11 +163,32 @@ export function splitRender(html) {
163
163
  return { style, slides, body: d.body.innerHTML };
164
164
  }
165
165
  const href = (f) => new URL('./' + f, import.meta.url).href;
166
+ /* The workspace face inside the frame (batch 27 item 14, report library B2). The frame's origin is opaque, so a font file it asks for is refused
167
+ * by CORS; the page reads the face once (same origin) and hands it to the frame as a data: font. Until it is read the slide shows the fallback face,
168
+ * then it is drawn again. Only a render that names the face takes it. */
169
+ const FACE = { css: '', state: '' };
170
+ function faceCss(style) {
171
+ if (!/Plus Jakarta Sans/.test(style)) return '';
172
+ if (FACE.state === '' && typeof fetch === 'function' && typeof FileReader === 'function') {
173
+ FACE.state = 'reading';
174
+ fetch('/vendor/plus-jakarta-sans-var.woff2').then((r) => (r.ok ? r.blob() : null)).then((b) => {
175
+ if (!b) { FACE.state = 'none'; return; }
176
+ const fr = new FileReader();
177
+ fr.onload = () => { FACE.css = '@font-face{font-family:"Plus Jakarta Sans";font-style:normal;font-weight:200 800;font-display:block;src:url(' + fr.result + ') format("woff2")}'; FACE.state = 'ready'; if (R) draw(); };
178
+ fr.onerror = () => { FACE.state = 'none'; };
179
+ fr.readAsDataURL(b);
180
+ }).catch(() => { FACE.state = 'none'; });
181
+ }
182
+ return FACE.css;
183
+ }
166
184
  function frameDoc(style, inner, cls) {
167
185
  return '<!doctype html><html lang="en"><head><meta charset="utf-8"><link rel="stylesheet" href="' + esc(href('tokens.css')) + '"><link rel="stylesheet" href="' + esc(href('premium-report-frame.css')) + '">'
168
- + '<style>' + String(style || '').replace(/<\/style/gi, '<\\/style') + '</style></head><body class="pmos-frame ' + cls + '">' + String(inner || '') + '</body></html>';
186
+ + '<style>' + faceCss(String(style || '')) + String(style || '').replace(/<\/style/gi, '<\\/style') + '</style></head><body class="pmos-frame ' + cls + '">' + String(inner || '') + '</body></html>';
169
187
  }
170
- const frame = (style, inner, cls, title) => `<iframe sandbox="" tabindex="-1" title="${esc(title)}" srcdoc="${esc(frameDoc(style, inner, cls))}"></iframe>`;
188
+ /* A slide with CSS-only controls (data-pmos-live: the library's timeline span and filter, the ticked actions) takes the pointer and the keyboard in
189
+ * the big view and in Present; a thumbnail never does. Nothing in the frame runs: its sandbox stays empty. */
190
+ const frame = (style, inner, cls, title, live) => `<iframe sandbox=""${live ? '' : ' tabindex="-1"'} title="${esc(title)}" srcdoc="${esc(frameDoc(style, inner, cls))}"></iframe>`;
191
+ const LIVE_RE = /<section\b[^>]*\bdata-pmos-live=/;
171
192
 
172
193
  /* ── what is shown ── */
173
194
  function slides() {
@@ -210,7 +231,7 @@ export function headFigure(w) {
210
231
  }
211
232
  /** One slide: a designed frame, the title slide or a section's words. */
212
233
  export function slideCard(s, mini) {
213
- if (s.k === 'frame') return `<span class="vz-fr">${frame(s.css, s.html, 'pmos-one', s.h)}</span>`;
234
+ if (s.k === 'frame') { const live = !mini && LIVE_RE.test(s.html); return `<span class="vz-fr${live ? ' vz-live' : ''}">${frame(s.css, s.html, 'pmos-one', s.h, live)}</span>`; }
214
235
  if (s.k === 'title') return `<span class="vz-words vz-title"><span class="vz-eye">${esc(s.eye || (R && R.audience) || 'Report')}</span><h4 data-ed="title">${esc(s.title || s.h)}${s.acc ? ` <em class="vz-acc">${esc(s.acc)}</em>` : ''}</h4>${mini ? '' : `${s.sub ? `<b class="vz-tsub">${esc(s.sub)}</b>` : ''}<p>${esc(s.t || '')}</p>`}</span>${!mini && s.fig ? `<span class="vz-tfig"><i class="vz-tdot" aria-hidden="true"></i><span><b>${esc(s.fig.v)}</b>${s.fig.cap ? `<small>${esc(s.fig.cap)}</small>` : ''}</span></span>` : ''}`;
215
236
  return `<span class="vz-words"><h4>${esc(s.h)}</h4>${mini ? '' : s.html}</span>`;
216
237
  }
@@ -26,19 +26,22 @@ Procedure:
26
26
  1. Run `pmos report make <id>`. For an ad hoc report add `--ask "<the question>"`, and `--readers "<who>"` when you know them. Name sources the brief must read with `--source`, comma-separated: `connector:<name>=<what it is>` (Jira, BigQuery for GA4, Confluence, …), `document:<path>`, `pack:<state file of readings>`, `pmos` (the PMOS ledgers). It writes `<slot>.brief.json`, `<slot>.readings.json` and `<slot>.findings.json` next to the draft, and prints the gaps.
27
27
  - If it refuses because the slot is issued, stop: an issued report is frozen.
28
28
  - If it says there is no report with that id, it needs a question: ask the PM once for the question, or take it from the chat line.
29
+ - When the PM names a kind of report that a template fits (a weekly review, a kick-off, a meeting recap, a portfolio snapshot, a quarterly review, a decision brief, an incident review), add `--template <id>`.
29
30
  2. Read `<slot>.brief.json`: the readers, the questions, the window, the sources, the last issue, the agreed outline, the carried asks, the voice and the rules.
30
31
  3. Read each source that the gaps name, through its connector or the file, read-only. Write what you read as readings in one JSON file: `{"readings":[{"id":"web-login-rate","value":81,"unit":"%","n":597,"of":733,"at":"2026-10-19","source":"ga4","how":"events_20261019, sessions with a password start","label":"Web password login, recorded success"}],"gaps":[{"source":"native-app","reason":"read only on the device"}]}`. Give `target` and `better` ("higher" or "lower") when the reading has a target. Give `flag` when the reading is off what the readers expect. Run `pmos report read <id> --from <file>`. A parameter of a method (a 5-second match window) is a reading too.
31
32
  4. Find what answers the readers. Write the findings that answer each question, each citing the readings it rests on, and run `pmos report find <id> --from <file>` with `{"findings":[{"text":"…","readings":["…"],"kind":"answers-question","question":"…"}]}`. The engine adds its own findings: changes since the last issue, misses against a target, initiatives off plan, items due within 14 days, the asks carried over. If `<slot>.findings.json` says `"none": true`, there is nothing to report: stop and say so in one line. Do not write a report about nothing.
32
33
  5. Choose the outline for these readers and this question. Any sections, any titles, any order.
33
34
  - A recurring report keeps `agreed_outline`, the sections of its last issued issue. Add a section only when a finding needs one, drop one only when it would be empty, and say so in `outline_note` (one line).
34
35
  - `earlier_outline` is the shape of an issue written before the engine (the old fixed frame). It is an example, not an agreement: keep what served the readers.
36
+ - `template` is set when the PM started the report from a report template (`pmos report make <id> --template <name>`; `pmos report library --json` lists them). Its `outline` is a default order of parts, each with its slide layout: use it as the starting outline, retitle each part as a finding, and add or drop a part when the findings say so. An agreed outline wins over a template.
35
37
  - Each section title is a finding, not a label: "Sign-up stalls before submit", not "Sign-up". Reading only the titles tells the story.
36
38
  - Shapes other reports took, as examples and never a menu: a decision (the lean · the options and what each costs · what could reverse it · your call); a watch (today against yesterday and the week · the drop that moved most · why); a stakeholder update (are the dates on track · what each team needs and by when · open decisions · what could stop it); a review (the summary for decision · the outcomes by segment · the challenges and answers · method); did-it-work (what was promised · what we got · the verdict).
37
39
  6. Write `<slot>.report.json` (in a scratch file, then `pmos report write <id> --from <file>`):
38
40
  `{"title":"…{{r:…}}…","answer":{"text":"…","supports":["Label: …{{r:…}}…"]},"sections":[{"id":"…","title":"…","blocks":[{"kind":"p","text":"…"},{"kind":"list","items":["…"]},{"kind":"table","columns":["…"],"rows":[["…"]]},{"kind":"bars","title":"…","rows":[["label","{{r:id}}"]]},{"kind":"options","options":[{"name":"…","text":"…"}]},{"kind":"quote","text":"…"}]}],"ask":{"text":"…","who":"…","by":"YYYY-MM-DD","options":["…"]},"beyond":{"why":"…","so_what":"…","now_what":"…","since":"…","would_change":"…","unknown":["…"],"else":["…"],"next_question":"…"},"questions":[{"question":"<a question of the brief>","answered_in":"<section id or answer>","gap":"<why it cannot be answered>"}],"outline_note":"…"}`.
41
+ - A section can take a slide layout from the report library instead of blocks: `{"title":"…","layout":"<name>","data":{…},"insight":{"head":"…","text":"…","points":[{"tone":"ok|watch|late|bad|info|none","text":"…"}],"note":"…"},"eyebrow":"…","sub":"…"}`. The names: overview, tiles, ledger, timeline, compare, lists, risks, ranked, decisions, quotes, actions, agenda, heatmap, network, schedule, donut, bubbles, people, statement, sources, minutes, focus, next. Run `pmos report library` to see each one with its data; `pmos report write` names a field that does not fit. Pick the layout that fits the finding, never to fill a slide. Every text in `data` and `insight` is checked like any other: a figure is a reading token. A bar's `share`, a `target` mark and a bubble's `x`, `y` and `size` are numbers from 0 to 1: take them from the readings. The insight is optional; leave it out when the slide says it already.
39
42
  - `ask` is null when the readers have nothing to decide. When it is a call with no better option on the evidence, give the options and say "your call".
40
43
  - BEYOND THE ASK. Answer the readers' next three questions before they ask: why (the cause, from the evidence), so what (the effect on the target and its date), now what (the recommendation, who, by), since the last issue (what moved, and each carried ask), what would change it (the one or two readings that would flip the recommendation), what we don't know (the gaps, counted, never padded), what else is here (at most two findings from a named source the readers did not ask about, when their question depends on them) and the next question (the split they will ask for next). Each part cites a reading. Leave a part out when no reading backs it, and leave out a part the brief's rules turn off.
41
44
  - `questions`: map each question of the brief after the first to the section that answers it, or give the gap.
42
- 7. Read what `pmos report write` prints. A refusal names the check: fix the report and run it again. When it passes, it writes `<slot>.md` and `<slot>.checks.json`; the deck, the PDF and the PowerPoint are drawn from the same model, in the report's saved look.
45
+ 7. Read what `pmos report write` prints. A refusal names the check: fix the report and run it again. When it passes, it writes `<slot>.md` and `<slot>.checks.json`; the deck, the PDF and the PowerPoint are drawn from the same model, in the report library's frame.
43
46
  8. Learn. When the PM's change in the chat is about the words (shorter, plainer, another tone) and it would apply to every issue, keep it: `pmos report rule <id> add wording "<the rule, one line>"`. When the PM removes a part beyond the ask, `pmos report rule <id> add off <part>`. Then change report.json and run `pmos report write` again. A change to a number is a change to a reading: read the source again and file it.
44
47
  9. Finish by saying in one line where the draft is and how many readings, findings and gaps it has. Do not commit; the PM's Sync carries the files.
@@ -0,0 +1,9 @@
1
+ {
2
+ "_comment": "Batch 27 item 14, the report library: the B2 frame and its 23 layouts, the templates, the engine deck in that frame, the document and PowerPoint lines, and pmos report library (lib/report-slides.js, lib/report-library.js). Pure.",
3
+ "modules": [
4
+ "report-slides.test.js"
5
+ ],
6
+ "seconds": {
7
+ "report-slides.test.js": 2
8
+ }
9
+ }