create-pmos 0.199.0 → 0.200.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-report-design-real-test.js +24 -1
- package/workspace/guide/report-design.md +4 -2
- package/workspace/lib/report-design.js +17 -2
- package/workspace/lib/routes-report-design.js +7 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-design-studio.js +21 -4
- package/workspace/public/pm/c3-report-deck.css +21 -2
- package/workspace/skills/report-design.skill +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.200.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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T11:08:21Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T11:08:21Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-13T11:08:21Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -123,6 +123,27 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
123
123
|
await page.click('[data-rd-act="panel"]'); await page.waitForSelector('[data-rd-panel]');
|
|
124
124
|
await page.setInputFiles('[data-rd-file]', { name: 'Biweekly update, 26 Mar.pptx', mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', buffer: PPTX });
|
|
125
125
|
ok('the uploaded sample shows in the conversation with its thumbnail', !!(await until(async () => /26 Mar\.pptx/.test(await text('[data-rd-chat]')) && !!(await page.$('[data-rd-chat] img.rd-dks-thumb')))));
|
|
126
|
+
// a Google Slides link is kept by its address; PMOS says it cannot open it and which download to add, and the long link stays in its column
|
|
127
|
+
if (!(await page.$('[data-rd-link]'))) { await page.click('[data-rd-act="panel"]'); await page.waitForSelector('[data-rd-link]'); }
|
|
128
|
+
await page.fill('[data-rd-link]', 'https://docs.google.com/presentation/d/1FqiafPx1SjmntCVT8SOOIfFbxHrkemAwEc_MpAnGGh0/edit');
|
|
129
|
+
await page.click('[data-rd-smp="link"]');
|
|
130
|
+
ok('a Google Slides link says PMOS cannot open it and to add the PDF download', !!(await until(async () => /Google Slides deck/.test(await text('[data-rd-chat]')) && /File, Download, PDF document \(\.pdf\)/.test(await text('[data-rd-chat] [data-rd-hint]')))));
|
|
131
|
+
if (!(await page.$('[data-rd-link]'))) { await page.click('[data-rd-act="panel"]'); await page.waitForSelector('[data-rd-link]'); }
|
|
132
|
+
await page.fill('[data-rd-link]', 'https://example.atlassian.net/wiki/d/1FqiafPx1SjmntCVT8SOOIfFbxHrkemAwEc_MpAnGGh0xYzW1FqiafPx1SjmntCVT8SOOIfFbx/edit');
|
|
133
|
+
await page.click('[data-rd-smp="link"]');
|
|
134
|
+
ok('any other link says PMOS cannot open web pages and to add a PDF or a screenshot', !!(await until(async () => /PMOS cannot open web pages/.test(await text('[data-rd-chat]')))));
|
|
135
|
+
const box = async (sel) => page.evaluate((q) => { const r = document.querySelector(q).getBoundingClientRect(); return { l: r.left, r: r.right, t: r.top, b: r.bottom, w: r.width }; }, sel);
|
|
136
|
+
// nothing in the conversation (a long link, a bubble) may reach past the column into the preview
|
|
137
|
+
const chatRight = () => page.evaluate(() => Math.max(...[...document.querySelectorAll('.rd-dks-chat, .rd-dks-chat *')].map((n) => n.getBoundingClientRect().right)));
|
|
138
|
+
const sideBySide = async () => { const o = await box('.rd-dks-out'); return (await chatRight()) <= o.l + 1; };
|
|
139
|
+
ok('the conversation column does not run under the preview', await sideBySide(), JSON.stringify([await chatRight(), await box('.rd-dks-out')]));
|
|
140
|
+
ok('the studio has Half, 3/4 and Whole, and opens at Whole', (await page.$$('[data-rd-size]')).length === 3 && await page.getAttribute('[data-rd-size="whole"]', 'aria-pressed') === 'true');
|
|
141
|
+
await page.click('[data-rd-size="half"]');
|
|
142
|
+
ok('Half makes the studio half the window and stacks the preview under the conversation', !!(await until(async () => { const s = await box('.sheet.rd-wide'); const c = await box('.rd-dks-chat'); const o = await box('.rd-dks-out'); return Math.abs(s.w - 720) < 4 && o.t >= c.b - 1; })), JSON.stringify(await box('.sheet.rd-wide')));
|
|
143
|
+
await page.click('[data-rd-size="three-quarters"]');
|
|
144
|
+
ok('3/4 makes the studio three-quarters of the window, side by side', !!(await until(async () => Math.abs((await box('.sheet.rd-wide')).w - 1080) < 4 && await sideBySide())));
|
|
145
|
+
await page.click('[data-rd-size="whole"]');
|
|
146
|
+
ok('Whole fills the window again', !!(await until(async () => Math.abs((await box('.sheet.rd-wide')).w - 1416) < 4)));
|
|
126
147
|
await page.fill('[data-rd-in]', 'A biweekly deck for leadership: numbered blue header band, one slide per initiative.');
|
|
127
148
|
await page.click('[data-rd-act="send"]');
|
|
128
149
|
ok('while the job runs the conversation shows PMOS working', !!(await until(() => page.$('[data-rd-running]'), 8000)));
|
|
@@ -159,10 +180,12 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
159
180
|
ok('This is it saves D2', !!(await until(async () => /Saved design: D2/.test(await text('[data-rd-saved]')))));
|
|
160
181
|
const P = D.paths(team, id);
|
|
161
182
|
ok('brief.md is written', fs.existsSync(P.brief));
|
|
183
|
+
ok('This is it says the current cycle is being redrawn in the design', /redrawing the .* cycle in it now/.test(await text('body')));
|
|
184
|
+
ok('the current cycle is redrawn in the saved design', !!(await until(() => { const c = D.cyclePaths(team, firstPath); if (!fs.existsSync(c.json)) return false; const j = JSON.parse(fs.readFileSync(c.json, 'utf8')); return j.design_version === 2; }, 30000)), (() => { try { return fs.readFileSync(D.cyclePaths(team, firstPath).json, 'utf8'); } catch (e) { return String(e); } })());
|
|
162
185
|
await page.click('[data-rd-act="brief"]');
|
|
163
186
|
await page.waitForSelector('[data-rd-brief]');
|
|
164
187
|
ok('the Design brief shows the design, the description, the sample, the reference version and the rules',
|
|
165
|
-
!!(await until(async () => /Design D2/.test(await text('[data-rd-design]')))) && /numbered blue header band/.test(await text('[data-rd-desc]')) && (await page.$$('[data-rd-sample]')).length ===
|
|
188
|
+
!!(await until(async () => /Design D2/.test(await text('[data-rd-design]')))) && /numbered blue header band/.test(await text('[data-rd-desc]')) && (await page.$$('[data-rd-sample]')).length === 3
|
|
166
189
|
&& !!(await until(() => page.$('[data-rd-ref] iframe'))) && !!(await page.$('[data-rd-rule="learned"]')));
|
|
167
190
|
ok('the brief lists the cycles drawn with the design', !!(await until(() => page.$('[data-rd-briefcycle]'))));
|
|
168
191
|
await closeAll();
|
|
@@ -22,10 +22,12 @@ Use one of these two buttons:
|
|
|
22
22
|
|
|
23
23
|
1. Select **Deck** or **Document** at the top of the studio.
|
|
24
24
|
2. Write what the report is for and who reads it. For example: "This goes to the progress session every other Thursday. The decisions must stand out."
|
|
25
|
-
3. Press **Add a sample** to give PMOS a report that people liked. You can upload a file, paste a link, or pick a report document that PMOS has. Each sample shows what PMOS took from it.
|
|
25
|
+
3. Press **Add a sample** to give PMOS a report that people liked. You can upload a file, paste a link, or pick a report document that PMOS has. Each sample shows what PMOS took from it. PMOS cannot open a link, so a link sample tells you which file to add. For Google Slides, choose **File**, **Download**, **PDF document (.pdf)** in Slides, then add that file. PMOS sees the pages of a PDF or an image, so it can follow their look. From a .pptx or a .docx, PMOS gets only the words.
|
|
26
26
|
4. Press **Make the next version**. PMOS makes a version from your words and this cycle's data. This takes a short time. You can leave the studio. The studio shows when the version is ready.
|
|
27
27
|
5. Read the version in the preview. Write what to change and press **Make the next version** again. Each turn makes a new version: D1, D2, D3 and so on.
|
|
28
28
|
|
|
29
|
+
To set the width of the studio, use **Half**, **3/4** or **Whole** at the top right. PMOS keeps your choice on this computer.
|
|
30
|
+
|
|
29
31
|
If a turn fails, the studio shows why. Nothing changes. Try the turn again.
|
|
30
32
|
|
|
31
33
|
### Point at a part
|
|
@@ -40,7 +42,7 @@ Keep **Point and say** on. Click a part of the preview and write what to change
|
|
|
40
42
|
|
|
41
43
|
### Save the design
|
|
42
44
|
|
|
43
|
-
Press **This is it** to save the version as the design.
|
|
45
|
+
Press **This is it** to save the version as the design. PMOS redraws the current cycle in the design at once, and every next draft follows it. Older cycles, and a cycle that was already sent, keep their look. Press **Design brief** to read the saved design: your description, your samples, the reference version, and what PMOS learned. What PMOS learned has three parts: what stays the same each cycle, what changes each cycle, and what it learned from your turns.
|
|
44
46
|
|
|
45
47
|
## Read a cycle as a deck
|
|
46
48
|
|
|
@@ -204,7 +204,8 @@ function addSample(root, cadence, input, { actor, now } = {}) {
|
|
|
204
204
|
let u; try { u = new URL(link); } catch { return refuse(400, 'link must be a full address, such as https://…'); }
|
|
205
205
|
if (u.protocol !== 'https:' && u.protocol !== 'http:') return refuse(400, 'link must be an http or https address');
|
|
206
206
|
if (link.length > 2000) return refuse(400, 'link is too long');
|
|
207
|
-
|
|
207
|
+
// the job reads files only (Read, Glob, Grep), so a link is kept by its address and the PM is told how to add a copy PMOS can read
|
|
208
|
+
rec = { id, name: oneLine(input.name || linkName(u), 120), kind: 'link', source: link, ext: '', readable: false, thumb: null, text_file: null };
|
|
208
209
|
} else if (typeof input.doc === 'string' && input.doc.trim()) {
|
|
209
210
|
const rel = input.doc.trim().replace(/\\/g, '/');
|
|
210
211
|
if (!/(?:^|\/)reports\/(?!definitions\/)[a-z][a-z0-9-]{0,39}\/\d{4}-\d{2}-\d{2}\.md$/.test(rel)) return refuse(400, 'doc must be a report document: <state>/reports/<cadence>/<slot>.md');
|
|
@@ -245,7 +246,21 @@ function addSample(root, cadence, input, { actor, now } = {}) {
|
|
|
245
246
|
return { ok: true, sample: sampleOut(rec) };
|
|
246
247
|
}
|
|
247
248
|
function sampleOut(s) {
|
|
248
|
-
|
|
249
|
+
const out = { id: s.id, name: s.name, kind: s.kind, source: s.source, took: s.took || null, thumb: typeof s.thumb === 'string' && s.thumb.startsWith('data:image/') ? s.thumb : null, readable: s.readable !== false };
|
|
250
|
+
if (s.kind === 'link') out.hint = linkHint(s.source);
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
/* Google files: name them for what they are, and say which download PMOS can read */
|
|
254
|
+
// a PDF, because the job sees its pages; from a .pptx or .docx it gets the words only, not the look
|
|
255
|
+
const GOOGLE = [[/^\/presentation\//, 'Google Slides deck', 'Slides'], [/^\/document\//, 'Google Docs document', 'Docs'], [/^\/spreadsheets\//, 'Google Sheets sheet', 'Sheets']];
|
|
256
|
+
const googleOf = (u) => (u && /(^|\.)docs\.google\.com$/i.test(u.hostname) ? GOOGLE.find((g) => g[0].test(u.pathname)) || null : null);
|
|
257
|
+
const parseUrl = (v) => { try { return new URL(String(v || '')); } catch { return null; } };
|
|
258
|
+
function linkName(u) { const g = googleOf(u); return g ? g[1] : u.hostname + u.pathname; }
|
|
259
|
+
/** What the PM sees under a link sample: PMOS cannot open web pages, and how to add a copy it can read. */
|
|
260
|
+
function linkHint(source) {
|
|
261
|
+
const g = googleOf(parseUrl(source));
|
|
262
|
+
if (g) return `PMOS cannot open ${g[2]} from a link, because it needs your Google sign-in. In ${g[2]}, choose File, Download, PDF document (.pdf), then add that file here. PMOS sees the pages of a PDF, so it can follow their look.`;
|
|
263
|
+
return 'PMOS cannot open web pages. Save the page as a PDF or take a screenshot, then add that file here.';
|
|
249
264
|
}
|
|
250
265
|
/** Record what a job took from a sample (also kept in samples/<id>.json). */
|
|
251
266
|
function recordTook(root, cadence, id, took, readable, { now } = {}) {
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* POST /api/report-design/describe { ws, id, text, format? }
|
|
7
7
|
* POST /api/report-design/sample { ws, id, name, kind, data } | { ws, id, link, name? } | { ws, id, doc, name? } → { ok, sample }
|
|
8
8
|
* POST /api/report-design/turn { ws, id, text, points: [{ n, slide, x, y, text }] } → { ok, running } ONE job per report; 409 while one runs
|
|
9
|
-
* POST /api/report-design/accept { ws, id, n } → { ok, design, brief } "This is it": writes brief.md
|
|
9
|
+
* POST /api/report-design/accept { ws, id, n } → { ok, design, brief, redraw } "This is it": writes brief.md and redraws the current
|
|
10
|
+
* cycle in the saved design (redraw: { path, slot, started, reason? }; a sent cycle keeps its look)
|
|
10
11
|
* POST /api/report-design/revert { ws, id, n } → { ok, design }
|
|
11
12
|
* GET /api/report-design/cycle?ws&path → { ok, designed, design_version, format, outline, differences, decisions, matches, rendering, previous }
|
|
12
13
|
* GET /api/report-design/cycle-html?ws&path → text/html, the sanitized cycle render
|
|
@@ -473,7 +474,11 @@ async function handle(req, res, url, ctx) {
|
|
|
473
474
|
D.append(root, id, { ev: 'accept', n, rules: D.rulesFor(root, id, n) }, { actor: g.actor });
|
|
474
475
|
const b = D.writeBrief(root, id, setup);
|
|
475
476
|
if (!b.ok) return fail(b);
|
|
476
|
-
|
|
477
|
+
// the current cycle is redrawn in the saved design at once (Wawan, 2026-10-13); older cycles, and a cycle already sent, keep their look
|
|
478
|
+
const cur = D.currentCycle(root, id);
|
|
479
|
+
let redraw = null;
|
|
480
|
+
if (cur) { const r = startCycle(ctx, w, id, cur.path); redraw = { path: cur.path, slot: cur.slot, started: !!r.ok, ...(r.ok ? {} : { reason: r.error === 'issued' ? 'sent' : String(r.error || '') }) }; }
|
|
481
|
+
return send({ status: 200, body: { ok: true, setup, brief: b.path, redraw, design: D.designAnswer(root, id, { running: runningOf(root, id) }) } });
|
|
477
482
|
}
|
|
478
483
|
return send(bad(404, 'no such route'));
|
|
479
484
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.200.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.200.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -21,6 +21,11 @@ const MAX = 20 * 1024 * 1024;
|
|
|
21
21
|
const KIND_OF = (name) => { const e = String(name || '').toLowerCase().split('.').pop(); return e === 'pdf' ? 'pdf' : e === 'pptx' ? 'pptx' : e === 'docx' ? 'docx' : /^(png|jpe?g)$/.test(e) ? 'image' : ''; };
|
|
22
22
|
const SICON = { pdf: 'pdf', pptx: 'img', docx: 'doc', image: 'img', link: 'link', 'pmos-doc': 'doc' };
|
|
23
23
|
const SWORD = { pdf: 'Upload, PDF', pptx: 'Upload, .pptx', docx: 'Upload, .docx', image: 'Upload, image', link: 'Link', 'pmos-doc': 'Past PMOS document' };
|
|
24
|
+
/* the studio's width, as a session's terminal sheet has it: half, three-quarters or the whole window; kept on this browser */
|
|
25
|
+
const SIZE_KEY = 'pmos.workspace.c3.studio-size';
|
|
26
|
+
export const STUDIO_SIZES = [['half', 'Half', 'Half of the window'], ['three-quarters', '3/4', 'Three-quarters of the window'], ['whole', 'Whole', 'Whole window']];
|
|
27
|
+
const sizeOf = (v) => (STUDIO_SIZES.some((x) => x[0] === v) ? v : 'whole');
|
|
28
|
+
const sizeStore = { get() { try { return localStorage.getItem(SIZE_KEY); } catch { return null; } }, set(v) { try { localStorage.setItem(SIZE_KEY, v); } catch { /* no storage */ } } };
|
|
24
29
|
const hhmm = (iso) => { const m = /T(\d{2}):(\d{2})/.exec(String(iso || '')); return m ? m[1] + ':' + m[2] : ''; };
|
|
25
30
|
const dayAt = (iso) => { const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})/.exec(String(iso || '')); return m ? slotWord(m[1]) + ', ' + m[2] + ':' + m[3] : String(iso || ''); };
|
|
26
31
|
const short = (t, n = 60) => { const s = String(t || '').replace(/\s+/g, ' ').trim(); return s.length > n ? s.slice(0, n - 1) + '…' : s; };
|
|
@@ -33,7 +38,8 @@ const local = (id) => LOCAL[id] || (LOCAL[id] = { view: null, cmp: false, cmpA:
|
|
|
33
38
|
function sampleChip(s) {
|
|
34
39
|
const th = s.thumb && /^data:image\//.test(s.thumb) ? el('img', { class: 'rd-dks-thumb img', src: s.thumb, alt: '' }) : el('span', { class: 'rd-dks-thumb ' + (s.kind === 'pptx' || s.kind === 'image' ? 'deck' : 'page'), 'aria-hidden': 'true' }, el('i'), el('i'), el('i'));
|
|
35
40
|
return el('div', { class: 'rd-dks-smp', 'data-rd-sample': s.id }, th, el('div', null, el('b', null, icon(SICON[s.kind] || 'doc', 'sm'), s.name), el('span', { class: 'dim mono' }, SWORD[s.kind] || s.kind),
|
|
36
|
-
s.
|
|
41
|
+
s.hint ? el('p', { class: 'rd-warn', 'data-rd-hint': '' }, s.hint)
|
|
42
|
+
: s.readable === false ? el('p', { class: 'rd-warn' }, 'PMOS could not read it with your tools. It is kept, but it does not shape the design.')
|
|
37
43
|
: el('p', null, el('span', { class: 'dim' }, 'PMOS took: '), s.took || 'PMOS reads it on the next turn.')));
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -43,7 +49,15 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
43
49
|
const K = local(id); if (view != null) { K.view = view; K.cmp = false; }
|
|
44
50
|
const S = { D: null, setup: null, err: '', cache: {}, timer: null, closed: false, busy: false };
|
|
45
51
|
const body = el('div', { class: 'brief rs rd-dk rd-dks-wrap', 'data-rd-studio': id });
|
|
46
|
-
|
|
52
|
+
let size = sizeOf(sizeStore.get());
|
|
53
|
+
const sizeBtns = STUDIO_SIZES.map(([k, label, title]) => btn({ class: 'ds-btn', 'aria-pressed': String(k === size), 'data-rd-size': k, title, 'aria-label': title, onclick: () => setSize(k) }, label));
|
|
54
|
+
const seg = el('div', { class: 'seg rd-dks-size', role: 'group', 'aria-label': 'Studio width' }, ...sizeBtns);
|
|
55
|
+
const ui = openSheet({ title: 'Design studio', icon: 'spark', wide: true, dw: true, cls: 'rd-wide rdsz-' + size, body, actions: [seg], onClose: () => { S.closed = true; clearTimeout(S.timer); if (onClose) onClose(); } });
|
|
56
|
+
function setSize(k) {
|
|
57
|
+
ui.sheet.classList.remove('rdsz-' + size); size = sizeOf(k); ui.sheet.classList.add('rdsz-' + size);
|
|
58
|
+
sizeStore.set(size);
|
|
59
|
+
sizeBtns.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.rdSize === size)));
|
|
60
|
+
}
|
|
47
61
|
|
|
48
62
|
async function load(keepView) {
|
|
49
63
|
const r = await RD.design(ws, id);
|
|
@@ -85,7 +99,7 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
85
99
|
function panel() {
|
|
86
100
|
if (!K.panel) return null;
|
|
87
101
|
const file = el('input', { type: 'file', class: 'rd-hide', accept: '.pdf,.pptx,.docx,.png,.jpg,.jpeg', 'data-rd-file': '', onChange: (e) => upload(e.target.files && e.target.files[0]) });
|
|
88
|
-
const link = el('input', { type: 'text', 'data-rd-link': '', placeholder: 'Paste a
|
|
102
|
+
const link = el('input', { type: 'text', 'data-rd-link': '', placeholder: 'Paste a link to keep with the design', 'aria-label': 'Link to a sample', onKeyDown: (e) => { if (e.key === 'Enter') addLink(); } });
|
|
89
103
|
const docs = (RS.docs || []).filter((d) => d.setup !== id || d.state === 'issued').slice(0, 3);
|
|
90
104
|
return el('div', { class: 'rd-dks-panel', 'data-rd-panel': '' }, el('div', { class: 'ds-h' }, 'Add a sample'),
|
|
91
105
|
btn({ class: 'rd-dks-opt', 'data-rd-smp': 'file', onclick: () => file.click() }, icon('drop', 'sm'), el('span', null, 'Upload a past deck or document', el('br'), el('span', { class: 'dim rd-small' }, 'PDF, .pptx, .docx or a screenshot, up to 20 MB'))), file,
|
|
@@ -240,7 +254,10 @@ export function openStudio({ ws, id, name, view, onClose } = {}) {
|
|
|
240
254
|
async function accept() {
|
|
241
255
|
const n = K.view != null ? K.view : S.D.current; if (n == null) { toast('Make a version first.'); return; }
|
|
242
256
|
const r = await RD.accept({ ws, id, n }); if (!r || !r.ok) { failed(r, 'The design was not saved'); return; }
|
|
243
|
-
|
|
257
|
+
const rd = r.redraw; const day = rd && rd.slot ? slotWord(rd.slot) : '';
|
|
258
|
+
const what = rd && rd.started ? ' PMOS is redrawing the ' + day + ' cycle in it now; older cycles keep their look.'
|
|
259
|
+
: rd && rd.reason === 'sent' ? ' The ' + day + ' cycle was already sent, so it keeps its look; the next cycle uses the design.' : ' PMOS draws each new cycle in it.';
|
|
260
|
+
await load(true); toast('Saved D' + n + ' as the design of this report.' + what + ' Nothing is sent.');
|
|
244
261
|
}
|
|
245
262
|
function paint() {
|
|
246
263
|
if (S.closed) return;
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
sheet's .ds-btn/.ds-log/.ds-sync/.ds-stale. The slide is a render in a sandboxed frame (.rd-card > iframe), scaled with --rp-scale.
|
|
4
4
|
Tokens only. Corners: 6px on a control, 8px on a container, 50% only on a dot. c3-report-deck.js links it once. */
|
|
5
5
|
.c3 .sheet.rd-wide { width: min(1320px, calc(100% - 24px)); max-width: none; }
|
|
6
|
+
/* the Design studio's width control (Half, 3/4, Whole), as a session's terminal sheet has it; a phone sheet is already full width */
|
|
7
|
+
@media (min-width: 761px) {
|
|
8
|
+
.c3 .sheet.rd-wide.rdsz-half { width: 50vw; }
|
|
9
|
+
.c3 .sheet.rd-wide.rdsz-three-quarters { width: 75vw; }
|
|
10
|
+
.c3 .sheet.rd-wide.rdsz-whole { width: calc(100vw - 24px); }
|
|
11
|
+
}
|
|
12
|
+
.c3 .rd-dks-size { flex: none; }
|
|
13
|
+
.c3 .rd-dks-size button { padding: 4px 8px; }
|
|
14
|
+
.c3.ph .rd-dks-size { display: none; }
|
|
6
15
|
.c3 .rd-dk { display: grid; gap: var(--sp-3); min-width: 0; }
|
|
7
16
|
.c3 .rd-dk > * { min-width: 0; }
|
|
8
17
|
.c3 .rd-wrap { flex-wrap: wrap; min-width: 0; }
|
|
@@ -92,8 +101,18 @@
|
|
|
92
101
|
|
|
93
102
|
/* the Design studio: the conversation left, the live result right */
|
|
94
103
|
.c3 .rd-dks { display: grid; grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); gap: var(--sp-4); align-items: start; }
|
|
95
|
-
.c3 .rd-dks-chat { display: grid; gap: var(--sp-2); position: sticky; top: 0; max-height: calc(100vh - 220px); grid-template-rows: auto minmax(0, 1fr) auto auto; min-width: 0; }
|
|
96
|
-
.c3 .rd-dks-desc
|
|
104
|
+
.c3 .rd-dks-chat { display: grid; gap: var(--sp-2); position: sticky; top: 0; max-height: calc(100vh - 220px); grid-template-rows: auto minmax(0, 1fr) auto auto; grid-template-columns: minmax(0, 1fr); min-width: 0; }
|
|
105
|
+
.c3 .rd-dks-chat > *, .c3 .rd-dks-desc > * { min-width: 0; }
|
|
106
|
+
.c3 .rd-dks-desc { display: grid; gap: 6px; grid-template-columns: minmax(0, 1fr); }
|
|
107
|
+
/* a long sample name (a pasted link) stays in the conversation column */
|
|
108
|
+
.c3 .rd-dks-desc .chip2 { display: block; max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
109
|
+
/* the studio stacks when its sheet is narrow (Half, or a small window) */
|
|
110
|
+
.c3 .rd-dks-wrap { container-type: inline-size; }
|
|
111
|
+
@container (max-width: 820px) {
|
|
112
|
+
.c3 .rd-dks { grid-template-columns: minmax(0, 1fr); }
|
|
113
|
+
.c3 .rd-dks-chat { position: static; max-height: none; }
|
|
114
|
+
.c3 .rd-dks-log { max-height: 420px; }
|
|
115
|
+
}
|
|
97
116
|
.c3 .rd-dks-desc .dim { font-size: var(--fs-1); }
|
|
98
117
|
.c3 .rd-dks-log { list-style: none; margin: 0; padding: 0 4px 0 0; display: grid; gap: 12px; align-content: start; overflow: auto; min-height: 120px; }
|
|
99
118
|
.c3 .rd-dks-msg { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 8px; align-items: start; }
|
|
@@ -37,7 +37,7 @@ The modes:
|
|
|
37
37
|
- match: as cycle, but match the reference exactly: the same slides, ids, kinds and order. Put data that has no place in the reference into the nearest slide of the reference.
|
|
38
38
|
|
|
39
39
|
How to read a sample:
|
|
40
|
-
- A PDF or an image: read the file with Read.
|
|
40
|
+
- A PDF or an image: read the file with Read and look at every page. Follow its look closely: the grid and the place of each part, the colours, the header and footer bands, the type sizes, how dense each slide or page is, and the slide kinds and their order. Say in "took" what you followed.
|
|
41
41
|
- A .pptx or a .docx: read its words in the .txt file named for it. You cannot see its look, only its words and order.
|
|
42
42
|
- A report document: read the .md file named for it.
|
|
43
43
|
- A link: you have no web tool. Read it only through a connector the PM's settings allow. If you cannot read it, say so with "readable": false.
|