create-pmos 0.134.0 → 0.136.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 +2 -2
- 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/payload/state/log.md +1 -1
- package/workspace/bin/notes-replay.js +235 -0
- package/workspace/c3-briefing-test.js +130 -0
- package/workspace/c3-foundation-test.js +159 -0
- package/workspace/c3-ib-items-test.js +105 -0
- package/workspace/c3-ib-more-test.js +120 -0
- package/workspace/c3-ib-triage-backlog-test.js +119 -0
- package/workspace/c3-ini-harness.js +66 -0
- package/workspace/c3-initiatives-test.js +140 -0
- package/workspace/c3-one-home-test.js +14 -1
- package/workspace/c3-reports-test.js +57 -18
- package/workspace/c3-settings-test.js +139 -0
- package/workspace/c3-work-test.js +42 -10
- package/workspace/fixtures/one-home-baseline.json +1 -9
- package/workspace/guide/briefing-cards.md +15 -3
- package/workspace/guide/briefing.md +12 -8
- package/workspace/guide/calendar-popup.md +2 -2
- package/workspace/guide/console-calendar.md +39 -10
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/initiative-boards.md +65 -0
- package/workspace/guide/jira.md +3 -1
- package/workspace/guide/layout.md +2 -0
- package/workspace/guide/my-work.md +5 -3
- package/workspace/guide/notes.md +16 -1
- package/workspace/guide/reports-dashboards.md +29 -5
- package/workspace/guide/settings.md +68 -0
- package/workspace/guide/welcome.md +10 -10
- package/workspace/lib/calendar-cli.js +3 -2
- package/workspace/lib/calendar-layers.js +21 -10
- package/workspace/lib/calendar-tidy.js +230 -0
- package/workspace/lib/engine/boards.js +542 -0
- package/workspace/lib/engine/breakdown-ops.js +180 -0
- package/workspace/lib/engine/calendar-import.js +84 -0
- package/workspace/lib/engine/calendar-model.js +185 -0
- package/workspace/lib/engine/card-state.js +92 -0
- package/workspace/lib/engine/derived/derive.js +92 -0
- package/workspace/lib/engine/derived/plan.js +3 -0
- package/workspace/lib/engine/derived/trust.js +3 -0
- package/workspace/lib/engine/doc-health.js +142 -0
- package/workspace/lib/engine/doc-sheet.js +75 -0
- package/workspace/lib/engine/intake.js +78 -0
- package/workspace/lib/engine/outbox.js +86 -0
- package/workspace/lib/engine/plan-forecast.js +342 -0
- package/workspace/lib/engine/promises.js +29 -0
- package/workspace/lib/engine/records/index.js +114 -0
- package/workspace/lib/engine/releases.js +96 -0
- package/workspace/lib/engine/replies.js +99 -0
- package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
- package/workspace/lib/engine/report-families.js +175 -0
- package/workspace/lib/engine/roadmap-ops.js +150 -0
- package/workspace/lib/engine/sessions-list.js +27 -0
- package/workspace/lib/engine/signal-rules.js +95 -0
- package/workspace/lib/engine/signals/cards.js +57 -0
- package/workspace/lib/engine/signals/detectors.js +370 -0
- package/workspace/lib/engine/signals/facts-cli.js +8 -0
- package/workspace/lib/engine/signals/facts.js +197 -0
- package/workspace/lib/engine/signals/index.js +33 -0
- package/workspace/lib/engine/signals/kinds.js +110 -0
- package/workspace/lib/engine/signals/layout-paths.js +44 -0
- package/workspace/lib/engine/signals/options.js +24 -0
- package/workspace/lib/engine/signals/score.js +72 -0
- package/workspace/lib/engine/signals/select.js +47 -0
- package/workspace/lib/engine/step-planner.js +65 -0
- package/workspace/lib/engine/trust/claim-rules.json +93 -0
- package/workspace/lib/engine/trust/import-calendar.js +21 -0
- package/workspace/lib/engine/trust/import-guard.js +36 -0
- package/workspace/lib/engine/trust/ledger.js +53 -0
- package/workspace/lib/engine/trust/outbox.js +45 -0
- package/workspace/lib/engine/trust/plan.js +5 -0
- package/workspace/lib/engine/trust/trust.js +21 -0
- package/workspace/lib/engine/truth-table.js +57 -0
- package/workspace/lib/engine/workspace-lib.js +24 -0
- package/workspace/lib/factory-join.js +1 -1
- package/workspace/lib/facts-async.js +4 -4
- package/workspace/lib/headless.js +95 -0
- package/workspace/lib/import-overlay.js +2 -1
- package/workspace/lib/jira-status.js +30 -3
- package/workspace/lib/notes-capture.js +9 -1
- package/workspace/lib/notes-final.js +38 -13
- package/workspace/lib/notes-replay.js +125 -0
- package/workspace/lib/notes-screen.js +180 -0
- package/workspace/lib/notes-whisper.js +10 -3
- package/workspace/lib/outbox-store.js +10 -6
- package/workspace/lib/pmos-verbs.js +3 -1
- package/workspace/lib/proactive-cli.js +7 -0
- package/workspace/lib/proactive.js +15 -2
- package/workspace/lib/records-dir.js +29 -0
- package/workspace/lib/repo-script.js +36 -0
- package/workspace/lib/report-dashboards.js +5 -2
- package/workspace/lib/report-frame.js +122 -0
- package/workspace/lib/report-schedules.js +113 -0
- package/workspace/lib/route-modules.js +9 -2
- package/workspace/lib/routes-boards.js +734 -0
- package/workspace/lib/routes-brief-page.js +13 -4
- package/workspace/lib/routes-briefing.js +22 -11
- package/workspace/lib/routes-c3-init.js +9 -8
- package/workspace/lib/routes-c3-life.js +2 -1
- package/workspace/lib/routes-c3-reports.js +99 -15
- package/workspace/lib/routes-c3-strategy.js +7 -5
- package/workspace/lib/routes-c3-timeline.js +3 -2
- package/workspace/lib/routes-calendar-import.js +3 -2
- package/workspace/lib/routes-calendar-views.js +9 -4
- package/workspace/lib/routes-doc-health.js +2 -1
- package/workspace/lib/routes-doc-sheet.js +2 -1
- package/workspace/lib/routes-forecast.js +2 -1
- package/workspace/lib/routes-hooks.js +3 -2
- package/workspace/lib/routes-initiatives.js +2 -1
- package/workspace/lib/routes-intake.js +2 -1
- package/workspace/lib/routes-notes.js +55 -9
- package/workspace/lib/routes-outbox.js +23 -3
- package/workspace/lib/routes-pm-items.js +19 -6
- package/workspace/lib/routes-records.js +10 -7
- package/workspace/lib/routes-releases.js +2 -1
- package/workspace/lib/routes-report-facts.js +4 -3
- package/workspace/lib/routes-report-families.js +2 -1
- package/workspace/lib/routes-roadmap-ops.js +17 -14
- package/workspace/lib/routes-runs.js +6 -5
- package/workspace/lib/routes-settings.js +51 -0
- package/workspace/lib/routes-signals.js +3 -2
- package/workspace/lib/routes-stage-tools.js +220 -0
- package/workspace/lib/run-answers.js +2 -1
- package/workspace/lib/settings-store.js +226 -0
- package/workspace/lib/stage-tools.js +208 -0
- package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
- package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
- package/workspace/lib/work-evidence.js +73 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/briefing-deck.js +63 -17
- package/workspace/public/pm/briefing-model.js +36 -7
- package/workspace/public/pm/c3-ambient.js +100 -0
- package/workspace/public/pm/c3-brief-needs.js +26 -6
- package/workspace/public/pm/c3-brief-pulse.js +12 -4
- package/workspace/public/pm/c3-brief-today.js +56 -21
- package/workspace/public/pm/c3-brief.js +2 -1
- package/workspace/public/pm/c3-calendar.js +161 -42
- package/workspace/public/pm/c3-calpop.js +5 -5
- package/workspace/public/pm/c3-demo-boards.js +649 -0
- package/workspace/public/pm/c3-demo-brief-page.js +1 -1
- package/workspace/public/pm/c3-demo-cal4.js +3 -2
- package/workspace/public/pm/c3-demo-cal5.js +52 -0
- package/workspace/public/pm/c3-demo-data.js +17 -27
- package/workspace/public/pm/c3-demo-rep3.js +46 -5
- package/workspace/public/pm/c3-demo.js +2 -1
- package/workspace/public/pm/c3-dock.js +1 -1
- package/workspace/public/pm/c3-ib-docs.js +80 -0
- package/workspace/public/pm/c3-ib-initiatives.js +134 -0
- package/workspace/public/pm/c3-ib-items.js +201 -0
- package/workspace/public/pm/c3-ib-releases.js +51 -0
- package/workspace/public/pm/c3-ib-sprints.js +55 -0
- package/workspace/public/pm/c3-ib-triage.js +178 -0
- package/workspace/public/pm/c3-ii-tabs.js +240 -0
- package/workspace/public/pm/c3-ini-records.js +197 -0
- package/workspace/public/pm/c3-ini-timeline.js +278 -0
- package/workspace/public/pm/c3-ini-ui.js +229 -0
- package/workspace/public/pm/c3-ini-work.js +139 -0
- package/workspace/public/pm/c3-initiatives.css +218 -0
- package/workspace/public/pm/c3-initiatives.js +741 -0
- package/workspace/public/pm/c3-plan.js +2 -2
- package/workspace/public/pm/c3-reports.css +47 -47
- package/workspace/public/pm/c3-reports.js +223 -185
- package/workspace/public/pm/c3-settings.css +74 -0
- package/workspace/public/pm/c3-settings.js +703 -0
- package/workspace/public/pm/c3-shell.js +102 -36
- package/workspace/public/pm/c3-terminal-icons.js +2 -2
- package/workspace/public/pm/c3-terminal.js +2 -2
- package/workspace/public/pm/calendar-views.js +1 -1
- package/workspace/public/pm/components.css +319 -213
- package/workspace/public/pm/console3.css +99 -23
- package/workspace/public/pm/dest-notes.js +26 -4
- package/workspace/public/pm/notes-screen-capture.js +165 -0
- package/workspace/public/pm/notes-screen-view.js +98 -0
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/pm-work-view.js +233 -146
- package/workspace/public/pm/shell-bell.js +3 -2
- package/workspace/public/pm/shell-recording.js +29 -8
- package/workspace/public/pm/tokens.css +2 -2
- package/workspace/public/pm/view-notes.js +46 -9
- package/workspace/public/pm/welcome.js +197 -89
- package/workspace/public/welcome.html +6 -4
- package/workspace/server.js +105 -6
- package/workspace/skills/day-check.skill +1 -1
- package/workspace/skills/jira-sync.skill +6 -4
- package/workspace/skills/morning-brief.skill +1 -1
- package/workspace/test-files.json +12 -3
- package/workspace/test-lists/c3-ib-items.json +9 -0
- package/workspace/test-lists/c3-ib-more.json +9 -0
- package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
- package/workspace/test-lists/c3-initiative-boards.json +6 -0
- package/workspace/test-lists/calendar-tidy-up.json +6 -0
- package/workspace/test-lists/console3-briefing.json +9 -0
- package/workspace/test-lists/console3-foundation.json +9 -0
- package/workspace/test-lists/console3-reports.json +3 -3
- package/workspace/test-lists/meeting-notes.json +10 -4
- package/workspace/test-lists/workspace-headless-jobs.json +5 -0
- package/workspace/test-lists/workspace-package-script-closure.json +6 -0
- package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
- package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
- package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
- package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
- package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
- package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
- package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
- package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
- package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
- package/workspace/lib/vendor/workspace/lib/state-root.js +0 -587
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
/* pm/welcome.js — the first-run page (/welcome). One primary button per screen.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
3
|
+
* About you → Your sources → First look → Your portfolio → Questions (only when the scan left any) → First Briefing
|
|
4
4
|
*
|
|
5
5
|
* Routes (lib/routes-onboarding.js): GET /api/onboarding/status|structure|briefing, POST /api/onboarding/scan|apply|undo.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* About you saves the name, role, work and job description to the proactive settings (POST /api/proactive/settings
|
|
7
|
+
* {name, about}); the brief and the day check read them from `pmos proactive settings`.
|
|
8
|
+
* Your sources lists the folders and the tools. A ticked tool starts when the PM presses Scan: Calendar and Jira through
|
|
9
|
+
* POST /api/sources/refresh (lib/routes-sources.js), which starts the source's read-only skill as a session with the PM's
|
|
10
|
+
* own connection; Inbox and Slack are sources of the daily brief, so they are turned on (briefSources) and the day is
|
|
11
|
+
* prepared once (POST /api/proactive/goals/plan). Nothing is read before the press; the server calls nothing outside.
|
|
8
12
|
* Presses carry the launch nonce through pm/http.js. The page writes nothing itself; text taken from the scanned folder is
|
|
9
13
|
* untrusted, so every value goes through esc() and no quoted text from a file is shown.
|
|
10
14
|
* data-testid attributes are the browser suite's handles.
|
|
11
15
|
*/
|
|
12
16
|
import { pmGet, pmPost } from './http.js';
|
|
17
|
+
import { el } from './dom.js';
|
|
18
|
+
import { button, field, SEL } from './primitives.js';
|
|
19
|
+
import { ensureC3Css } from './pm-work-view.js';
|
|
20
|
+
import { applyDayPart } from './c3-daypart.js';
|
|
13
21
|
|
|
14
22
|
const root = document.getElementById('wl-root');
|
|
15
23
|
const params = new URLSearchParams(location.search);
|
|
16
24
|
const S = {
|
|
17
25
|
ws: params.get('ws') || '', folder: null, added: [], step: 'welcome', busy: '', error: '', note: '',
|
|
18
|
-
|
|
26
|
+
about: { name: '', role: '', work: '', duties: '' }, pick: {},
|
|
27
|
+
look: null, structure: null, checked: {}, titles: {}, boardOf: {}, open: {}, edit: {}, answers: {}, skipped: {}, importId: '', briefing: [], sources: {},
|
|
19
28
|
};
|
|
20
|
-
const STEPS = [['welcome', '
|
|
21
|
-
/* The
|
|
22
|
-
*
|
|
23
|
-
const
|
|
29
|
+
const STEPS = [['welcome', 'About you'], ['sources', 'Your sources'], ['look', 'First look'], ['portfolio', 'Your portfolio'], ['questions', 'Questions'], ['briefing', 'First Briefing']];
|
|
30
|
+
/* The tools a first run can read besides the folders. `source` is a lib/freshness.js SOURCES id with a read-only skill; `brief` is a
|
|
31
|
+
* lib/proactive.js BRIEF_SOURCES key that the morning brief reads when it is on. */
|
|
32
|
+
const TOOLS = [
|
|
24
33
|
{ source: 'calendar', name: 'Calendar', adds: 'Your meetings today and for the next 14 days, so the Briefing can get you ready for them.' },
|
|
25
34
|
{ source: 'my-work', name: 'Jira', adds: 'The Jira issues assigned to you, so My work starts with them.' },
|
|
35
|
+
{ source: 'inbox', brief: 'mail', name: 'Inbox', adds: 'The threads waiting on you, by subject and sender only, in your daily Briefing. No message text is kept.' },
|
|
36
|
+
{ source: 'slack', brief: 'slack', name: 'Slack', adds: 'Messages that mention you and your direct messages, as short summaries in your daily Briefing.' },
|
|
26
37
|
];
|
|
27
38
|
|
|
28
39
|
const esc = (v) => String(v == null ? '' : v).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
@@ -37,7 +48,7 @@ function counts() {
|
|
|
37
48
|
return { n: on.length, m: new Set(on.map(boardOfRow)).size };
|
|
38
49
|
}
|
|
39
50
|
function adopt(structure) {
|
|
40
|
-
S.structure = structure; S.checked = {}; S.titles = {}; S.boardOf = {}; S.open = {}; S.answers = {}; S.skipped = {};
|
|
51
|
+
S.structure = structure; S.checked = {}; S.titles = {}; S.boardOf = {}; S.open = {}; S.edit = {}; S.answers = {}; S.skipped = {};
|
|
41
52
|
for (const r of rowsOf()) { S.checked[r.proposal_id] = !!r.checked; S.titles[r.proposal_id] = r.title; }
|
|
42
53
|
}
|
|
43
54
|
function revisions() {
|
|
@@ -75,7 +86,29 @@ const addFolder = () => run('add', async () => {
|
|
|
75
86
|
if (!w || !w.workspace) { fail(w, 'Adding the folder'); return; }
|
|
76
87
|
if (!S.added.some((a) => a.id === w.workspace.id) && w.workspace.id !== S.ws) S.added.push({ id: w.workspace.id, name: w.workspace.name, root: w.workspace.root });
|
|
77
88
|
});
|
|
89
|
+
const saveAbout = () => run('about', async () => {
|
|
90
|
+
const a = S.about;
|
|
91
|
+
const r = await pmPost('/api/proactive/settings', { name: a.name.trim().slice(0, 40), about: { role: a.role.trim(), work: a.work.trim(), duties: a.duties.trim() } });
|
|
92
|
+
if (!r || !r.ok) { fail(r, 'Saving what you told PMOS'); return; }
|
|
93
|
+
S.step = 'sources';
|
|
94
|
+
});
|
|
95
|
+
/* Start the ticked tools: each one is its own read-only agent session. A tool that cannot start says why on its row and
|
|
96
|
+
* does not stop the folder scan. */
|
|
97
|
+
async function startTools() {
|
|
98
|
+
const picked = TOOLS.filter((x) => S.pick[x.source]);
|
|
99
|
+
const brief = picked.filter((x) => x.brief);
|
|
100
|
+
if (brief.length) {
|
|
101
|
+
const on = await pmPost('/api/proactive/settings', { briefSources: Object.fromEntries(brief.map((x) => [x.brief, true])) });
|
|
102
|
+
const r = on && on.ok ? await pmPost('/api/proactive/goals/plan', { ws: S.ws }) : on;
|
|
103
|
+
for (const x of brief) S.sources[x.source] = r && r.ok ? { started: true } : { error: (r && r.error) || 'Starting the read did not work.' };
|
|
104
|
+
}
|
|
105
|
+
for (const x of picked.filter((t) => !t.brief)) {
|
|
106
|
+
const r = await pmPost('/api/sources/refresh', { ws: S.ws, source: x.source });
|
|
107
|
+
S.sources[x.source] = r && r.ok ? { started: true } : { error: (r && r.error) || 'Starting the read did not work.' };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
78
110
|
const scan = () => run('scan', async () => {
|
|
111
|
+
await startTools();
|
|
79
112
|
const r = await pmPost('/api/onboarding/scan', { ws: S.ws, extra_roots: S.added.map((a) => a.root) });
|
|
80
113
|
if (!r || !r.ok) { fail(r, 'The scan'); return; }
|
|
81
114
|
S.look = r.first_look; S.step = 'look';
|
|
@@ -101,7 +134,7 @@ const importNow = () => {
|
|
|
101
134
|
if ((S.structure.questions || []).length && S.step !== 'questions') { S.step = 'questions'; render(); return; }
|
|
102
135
|
run('apply', applyNow);
|
|
103
136
|
};
|
|
104
|
-
// "Not now": Welcome stops opening on its own for this folder (`pmos welcome` still opens it), then the console opens
|
|
137
|
+
// "Not now" and "Skip setup": Welcome stops opening on its own for this folder (`pmos welcome` still opens it), then the console opens
|
|
105
138
|
const notNow = () => run('dismiss', async () => {
|
|
106
139
|
const r = await pmPost('/api/onboarding/dismiss', { ws: S.ws });
|
|
107
140
|
if (!r || !r.ok) { fail(r, 'Not now'); return; }
|
|
@@ -115,111 +148,172 @@ const undo = () => run('undo', async () => {
|
|
|
115
148
|
});
|
|
116
149
|
|
|
117
150
|
/* ── views ───────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
118
|
-
|
|
151
|
+
/* Drawn with console3 parts (console3.css): .card.sect > .sh2, ul.list > li.nrow (.ki, .nb, .tt, .chips, .acts), .chip2, .tag,
|
|
152
|
+
* .bdg, .fold, .pbar, .xp. Buttons and checkboxes are the primitives (primitives.js button() and field()): a view writes a
|
|
153
|
+
* placeholder through ctl() and render() puts the primitive in its place. The .wl- rules in components.css add only the
|
|
154
|
+
* frame, the progress labels, the footer bar and the field blocks (the approved console3 mockup's Welcome page). Each step
|
|
155
|
+
* view returns [body, footer]. */
|
|
156
|
+
const ICONS = { folder: 'repo', board: 'cols', tools: 'org' };
|
|
157
|
+
const ic = (n, cls) => `<svg class="ic${cls ? ' ' + cls : ''}" aria-hidden="true"><use href="#c3i-${ICONS[n] || n}"></use></svg>`;
|
|
158
|
+
const spin = (txt) => `<span class="wl-spindot" data-testid="loading" aria-hidden="true"></span>${esc(txt)}`;
|
|
159
|
+
const dis = (on) => (on ? 'disabled' : '');
|
|
160
|
+
/* Controls drawn this render: ctl(spec) returns a placeholder, mountControls() swaps each for its primitive.
|
|
161
|
+
* spec: { variant, size, html (the label, already escaped), attrs, href, lead (a console3 icon) } or { check, text, value, attrs }. */
|
|
162
|
+
let CTL = [];
|
|
163
|
+
const ctl = (spec) => { CTL.push(spec); return `<span data-wl-ctl="${CTL.length - 1}"></span>`; };
|
|
164
|
+
function mountControls() {
|
|
165
|
+
for (const ph of root.querySelectorAll('[data-wl-ctl]')) {
|
|
166
|
+
const c = CTL[Number(ph.getAttribute('data-wl-ctl'))];
|
|
167
|
+
let node;
|
|
168
|
+
if (c.check) node = field({ label: c.text, kind: 'check', value: !!c.value, attrs: c.attrs });
|
|
169
|
+
else {
|
|
170
|
+
const { disabled, ...attrs } = c.attrs; // button() takes disabled on its own; a key in attrs would override it
|
|
171
|
+
const b = button({ label: '', variant: c.variant, size: c.size, disabled: !!disabled, attrs });
|
|
172
|
+
b.querySelector(SEL.btnLabel).innerHTML = c.html;
|
|
173
|
+
if (c.lead) b.insertAdjacentHTML('afterbegin', ic(c.lead, 'sm'));
|
|
174
|
+
if (c.href) { node = el('a', { ...attrs, class: b.className, href: c.href }); node.append(...b.childNodes); } else node = b;
|
|
175
|
+
}
|
|
176
|
+
ph.replaceWith(node);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const busyAttrs = (key) => (S.busy === key ? { 'aria-busy': 'true' } : {});
|
|
180
|
+
const primary = (id, text, busyKey, busyText, off) => ctl({ variant: 'primary', html: S.busy === busyKey ? spin(busyText) : esc(text), attrs: { class: 'wl-primary', 'data-testid': id, disabled: !!(S.busy || off), ...busyAttrs(busyKey) } });
|
|
181
|
+
const ghost = (id, text, more) => ctl({ variant: 'ghost', html: esc(text), attrs: { 'data-testid': id, disabled: !!S.busy, ...(more || {}) } });
|
|
182
|
+
const head = (h, lead, leadId) => `<header class="wl-head"><h1>${esc(h)}</h1>${lead ? `<p class="wl-lead"${leadId ? ` data-testid="${leadId}"` : ''}>${esc(lead)}</p>` : ''}</header>`;
|
|
183
|
+
const sub = (title, icon, right, count) => `<div class="sh2"><h2>${icon ? ic(icon, 'sm') : ''}${title}${count != null ? ` <span class="bdg g">${count}</span>` : ''}</h2>${right || ''}</div>`;
|
|
184
|
+
const pathChip = (t, attrs) => `<span class="chip2"${attrs ? ' ' + attrs : ''}>${ic('doc', 'sm')}${esc(t)}</span>`;
|
|
185
|
+
const foot = (left, right) => `<footer class="wl-foot"><div>${left || ''}</div><div>${right}</div></footer>`;
|
|
186
|
+
const backBtn = () => ghost('back-button', 'Back');
|
|
119
187
|
|
|
120
188
|
function viewWelcome() {
|
|
189
|
+
const a = S.about;
|
|
190
|
+
const field = (k, text, hint, max) => `<div class="wl-fld"><label class="wl-flabel" for="about-${k}">${text}</label><input type="text" id="about-${k}" data-testid="about-${k}" maxlength="${max}" value="${esc(a[k])}" placeholder="${esc(hint)}" autocomplete="off"></div>`;
|
|
191
|
+
return [head('Welcome to PMOS', 'Tell PMOS who you are, so your Briefing knows what matters to you. Every field is optional, and you can change them later in Settings.')
|
|
192
|
+
+ field('name', 'Your name', 'For example: Dana', 40)
|
|
193
|
+
+ field('role', 'Your role', 'For example: Senior PM, Payments', 80)
|
|
194
|
+
+ field('work', 'What you do', 'In one line, for example: I run checkout and the payment methods', 200)
|
|
195
|
+
+ `<div class="wl-fld"><label class="wl-flabel" for="about-duties">Your job description</label><textarea id="about-duties" data-testid="about-duties" maxlength="1000" rows="5" placeholder="What you own, decide and deliver. Paste it from your job description if you like.">${esc(a.duties)}</textarea></div>`,
|
|
196
|
+
foot('', primary('about-continue', 'Continue', 'about', 'Saving'))];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function toolRow(x) {
|
|
200
|
+
const st = S.sources[x.source] || {};
|
|
201
|
+
return `<div class="wl-tool" data-testid="source-row" data-id="${esc(x.source)}">${ctl({ check: true, text: x.name, value: S.pick[x.source], attrs: { 'data-testid': 'source-check', 'data-id': x.source, disabled: !!S.busy } })}
|
|
202
|
+
<span class="dim">${esc(x.adds)}</span>${st.error ? `<span class="wl-err" role="alert">${esc(st.error)}</span>` : ''}</div>`;
|
|
203
|
+
}
|
|
204
|
+
function viewSources() {
|
|
121
205
|
const f = S.folder || { name: '', root: '' };
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
206
|
+
const n = TOOLS.filter((x) => S.pick[x.source]).length;
|
|
207
|
+
const added = S.added.map((a) => `<li class="nrow" data-testid="added-folder"><span class="ki">${ic('folder')}</span><div class="nb"><span class="tt">${esc(a.name)}</span><div class="chips"><span class="chip2">${esc(a.root)}</span></div></div><span></span></li>`).join('');
|
|
208
|
+
const folders = `<ul class="list" data-testid="added-folders"><li class="nrow"><span class="ki">${ic('folder')}</span><div class="nb"><span class="tt" data-testid="folder-name">${esc(f.name || 'This folder')}</span><div class="chips"><span class="chip2" data-testid="folder-path">${esc(f.root)}</span></div></div><div class="acts"><span class="tag">this folder</span></div></li>${added}</ul>`;
|
|
209
|
+
const addBtn = `<div>${ctl({ variant: 'outline', size: 'sm', lead: S.busy === 'add' ? '' : 'plus', html: S.busy === 'add' ? spin('Waiting for the chooser') : 'Add another folder', attrs: { 'data-testid': 'add-folder', disabled: !!S.busy, ...busyAttrs('add') } })}</div>`;
|
|
210
|
+
return [head('Where does your work live?', 'PMOS reads your folders and the tools you tick, then shows you your initiatives, your documents and what needs you first.')
|
|
211
|
+
+ sub('Folders', 'folder', '', 1 + S.added.length) + folders + addBtn
|
|
212
|
+
+ (S.note ? `<div class="fold" data-testid="note">${esc(S.note)}</div>` : '')
|
|
213
|
+
+ sub('Tools', 'tools') + TOOLS.map(toolRow).join('')
|
|
214
|
+
+ `<p class="dim wl-small" data-testid="promise">${ic('lock', 'sm')} Reads only. Writes nothing in your folders or your tools. PMOS sends nothing out: your agent reads the tools you tick with your own connection.</p>`
|
|
215
|
+
+ (S.busy === 'scan' ? '<p class="dim wl-small" data-testid="scan-wait">A large folder can take a minute or two. The rest of PMOS keeps working.</p>' : ''),
|
|
216
|
+
foot(backBtn(), primary('scan-button', n ? `Scan the folder and read ${plural(n, 'tool', 'tools')}` : 'Scan', 'scan', 'Scanning your folder'))];
|
|
132
217
|
}
|
|
133
218
|
|
|
219
|
+
/* A numbered list (the number is drawn in the .ki by a CSS counter), one row per finding or Briefing item. */
|
|
220
|
+
const numbered = (rows) => `<ol class="list wl-num" role="list">${rows}</ol>`;
|
|
134
221
|
function viewLook() {
|
|
135
222
|
const L = S.look || {};
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div class="
|
|
223
|
+
const found = (L.findings || []).slice(0, 10);
|
|
224
|
+
const rows = found.map((f) => `<li class="nrow" data-testid="finding"><span class="ki"></span><div class="nb"><span class="tt"><b>${esc(f.title)}</b></span>${f.why ? `<div class="dim wl-small">${esc(f.why)}</div>` : ''}<div class="chips">${pathChip(ref((f.evidence || [])[0]), 'data-testid="finding-ref"')}</div></div><span></span></li>`).join('');
|
|
225
|
+
return [head('First look', L.sentence, 'first-look-sentence')
|
|
226
|
+
+ sub('What matters most', 'spark', '', found.length)
|
|
227
|
+
+ (rows ? numbered(rows) : '<div class="fold" data-testid="no-findings">Nothing in this folder needs you urgently.</div>'),
|
|
228
|
+
foot(backBtn(), primary('continue-button', 'Continue', 'structure', 'Building your portfolio'))];
|
|
141
229
|
}
|
|
142
230
|
|
|
143
231
|
function rowView(r) {
|
|
144
|
-
const id = r.proposal_id; const docs = r.docs || []; const open = !!S.open[id];
|
|
232
|
+
const id = r.proposal_id; const docs = r.docs || []; const open = !!S.open[id]; const ed = !!S.edit[id]; const on = !!S.checked[id];
|
|
233
|
+
const t = S.titles[id];
|
|
145
234
|
const opts = S.structure.boards.map((b) => `<option value="${esc(b.key)}" ${b.key === boardOfRow(r) ? 'selected' : ''}>${esc(b.name)}</option>`).join('');
|
|
146
|
-
return `<
|
|
147
|
-
<
|
|
148
|
-
<div
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
|
|
235
|
+
return `<li class="nrow${on ? '' : ' wl-off'}" data-testid="row" data-id="${esc(id)}"><span class="wl-cb">${ctl({ check: true, text: '', value: on, attrs: { 'data-testid': 'row-check', 'data-id': id, 'aria-label': 'Import ' + t } })}</span>
|
|
236
|
+
<div class="nb"><span class="tt">${esc(t)}</span>
|
|
237
|
+
<div class="chips"><span class="chip2" data-testid="row-lane">${ic('init', 'sm')}${esc(r.lane)}</span><span class="chip2" data-testid="row-anchor">${ic('target', 'sm')}${esc(r.anchor || 'no anchor')}</span><span class="tag" data-testid="row-stage">${esc(r.stage)}</span><button type="button" class="chip2 lk" data-testid="row-docs-toggle" data-id="${esc(id)}" aria-expanded="${open}">${ic('doc', 'sm')}${plural(docs.length, 'document', 'documents')}</button></div>
|
|
238
|
+
${open ? `<ul class="wl-docs" data-testid="row-docs">${docs.map((d) => `<li>${pathChip((d.title && d.title !== d.file ? d.title + ' · ' : '') + d.file)}</li>`).join('')}</ul>` : ''}
|
|
239
|
+
<div class="xp${ed ? ' open' : ''}"><div class="xi"><div class="wl-edit"><div class="wl-fld"><label class="wl-flabel" for="wt-${esc(id)}">Title</label><input type="text" id="wt-${esc(id)}" data-testid="row-title" data-id="${esc(id)}" value="${esc(t)}"></div>
|
|
240
|
+
<div class="wl-fld"><label class="wl-flabel" for="wb-${esc(id)}">Board</label><select id="wb-${esc(id)}" data-testid="row-board" data-id="${esc(id)}">${opts}</select></div></div></div></div></div>
|
|
241
|
+
<div class="acts">${ctl({ variant: 'ghost', size: 'sm', html: ed ? 'Done' : 'Edit', attrs: { 'data-testid': 'row-edit', 'data-id': id, 'aria-expanded': String(ed), disabled: !on } })}</div></li>`;
|
|
153
242
|
}
|
|
154
|
-
|
|
243
|
+
const importText = () => { const c = counts(); return `Import ${plural(c.n, 'initiative', 'initiatives')} into ${plural(c.m, 'board', 'boards')}`; };
|
|
244
|
+
const hasQuestions = () => !!(S.structure && (S.structure.questions || []).length);
|
|
155
245
|
function viewPortfolio() {
|
|
156
246
|
const all = rowsOf(); const c = counts();
|
|
157
247
|
const boards = S.structure.boards.map((b) => {
|
|
158
248
|
const mine = all.filter((r) => boardOfRow(r) === b.key);
|
|
159
|
-
return `<
|
|
160
|
-
|
|
161
|
-
${mine.length ? mine.map(rowView).join('') : '<p class="wl-p wl-muted">No initiatives here.</p>'}</section>`;
|
|
249
|
+
return `<div class="wl-board" data-testid="board" data-board="${esc(b.key)}">${sub(`<span data-testid="board-name">${esc(b.name)}</span>`, 'board', `<span class="chip2" data-testid="unsorted-count">${plural((b.unsorted || []).length, 'document', 'documents')} unsorted</span>`)}
|
|
250
|
+
${mine.length ? `<ul class="list">${mine.map(rowView).join('')}</ul>` : '<div class="fold">No initiatives here.</div>'}</div>`;
|
|
162
251
|
}).join('');
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
252
|
+
const q = hasQuestions();
|
|
253
|
+
return [head('Your portfolio', 'This is how PMOS would set up your work. Untick what you do not want, rename it, or move it to another board.') + boards
|
|
254
|
+
+ (q && c.n ? `<p class="dim wl-small" data-testid="import-summary">${importText()} after a few questions.</p>` : ''),
|
|
255
|
+
foot(backBtn(), primary('import-button', q ? 'Continue' : importText(), 'apply', 'Importing', !c.n))];
|
|
167
256
|
}
|
|
168
257
|
|
|
169
258
|
function viewQuestions() {
|
|
170
|
-
const qs = (S.structure.questions || []).map((q) => {
|
|
259
|
+
const qs = (S.structure.questions || []).map((q, n) => {
|
|
171
260
|
const skipped = !!S.skipped[q.id];
|
|
172
261
|
const cur = S.answers[q.id] != null ? S.answers[q.id] : q.default;
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
${q.
|
|
262
|
+
const off = skipped ? 'disabled' : '';
|
|
263
|
+
const val = (o) => (typeof o === 'object' ? o.value : o); const txt = (o) => (typeof o === 'object' ? (o.label || o.value) : o);
|
|
264
|
+
let input;
|
|
265
|
+
if (q.options && q.options.length > 4 && !q.free) input = `<select aria-label="${esc(q.text)}" data-testid="question-select" data-id="${esc(q.id)}" ${off}>${q.options.map((o) => `<option value="${esc(val(o))}" ${String(cur) === String(val(o)) ? 'selected' : ''}>${esc(txt(o))}</option>`).join('')}</select>`;
|
|
266
|
+
else if (q.options && q.options.length) {
|
|
267
|
+
const other = q.options.some((o) => String(val(o)) === String(cur)) ? '' : cur;
|
|
268
|
+
input = `<div role="radiogroup" aria-label="${esc(q.text)}">${q.options.map((o) => `<label class="wl-opt"><input type="radio" name="q-${esc(q.id)}" data-testid="question-option" data-id="${esc(q.id)}" value="${esc(val(o))}" ${String(cur) === String(val(o)) ? 'checked' : ''} ${off}>${esc(txt(o))}</label>`).join('')}
|
|
269
|
+
${q.free ? `<label class="wl-opt">${esc(q.free)} <input type="text" data-testid="question-input" data-id="${esc(q.id)}" placeholder="KR-A1 or health:upkeep" value="${esc(other)}" ${off}></label>` : ''}</div>`;
|
|
270
|
+
} else input = `<input type="text" aria-label="${esc(q.text)}" data-testid="question-input" data-id="${esc(q.id)}" value="${esc(cur)}" ${off}><p class="dim wl-small">Default: ${esc(q.default)}</p>`;
|
|
271
|
+
return `<div class="wl-q" data-testid="question" data-id="${esc(q.id)}">${sub(`Question ${n + 1}`, 'info', ctl({ variant: 'ghost', size: 'sm', html: skipped ? 'Skipped. Use the default' : 'Skip', attrs: { 'data-testid': 'question-skip', 'data-id': q.id, 'aria-pressed': String(skipped), disabled: !!S.busy } }))}
|
|
272
|
+
<b class="wl-qt">${esc(q.text)}</b>${input}</div>`;
|
|
181
273
|
}).join('');
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<div class="wl-actions"><button type="button" class="wl-btn wl-primary" data-testid="questions-continue" ${S.busy ? 'disabled' : ''}>${S.busy === 'apply' ? spin('Importing') : `Import ${plural(c.n, 'initiative', 'initiatives')} into ${plural(c.m, 'board', 'boards')}`}</button></div>`;
|
|
274
|
+
return [head('A few questions', 'The scan could not decide these. Each has a default, and you can skip any of them.') + qs,
|
|
275
|
+
foot(backBtn(), primary('questions-continue', importText(), 'apply', 'Importing'))];
|
|
185
276
|
}
|
|
186
277
|
|
|
187
278
|
function viewBriefing() {
|
|
188
|
-
const items = S.briefing.map((b) => `<li data-testid="briefing-item"><
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const st = S.sources[x.source] || {};
|
|
198
|
-
const busy = S.busy === 'source:' + x.source;
|
|
199
|
-
const state = st.started ? '<span class="wl-muted" data-testid="source-started">Started in an agent session. Follow it in PMOS.</span>'
|
|
200
|
-
: st.skipped ? '<span class="wl-muted" data-testid="source-skipped">Skipped. You can bring it in later from Settings.</span>'
|
|
201
|
-
: `<button type="button" class="wl-btn" data-testid="source-bring" data-id="${esc(x.source)}" ${S.busy ? 'disabled' : ''}>${busy ? spin('Starting') : 'Bring in'}</button>
|
|
202
|
-
<button type="button" class="wl-btn wl-quiet" data-testid="source-skip" data-id="${esc(x.source)}" ${S.busy ? 'disabled' : ''}>Skip</button>`;
|
|
203
|
-
return `<li class="wl-source" data-testid="source-row" data-id="${esc(x.source)}"><div class="wl-source-text"><strong>${esc(x.name)}</strong><div class="wl-muted">${esc(x.adds)}</div>${st.error ? `<div class="wl-error" role="alert">${esc(st.error)}</div>` : ''}</div><div class="wl-source-actions">${state}</div></li>`;
|
|
204
|
-
}
|
|
205
|
-
function viewSources() {
|
|
206
|
-
return `<h1 class="wl-h1">Bring in more sources</h1>
|
|
207
|
-
<p class="wl-p wl-muted">Your folder is in. These sources are optional. Each one runs in an agent session with your own connection, reads only, and writes nothing to the tool. Nothing is read until you press Bring in.</p>
|
|
208
|
-
<section class="wl-card"><ul class="wl-sources">${MORE_SOURCES.map(sourceRow).join('')}</ul>
|
|
209
|
-
<p class="wl-p wl-muted" data-testid="sources-not-yet">Inbox and Slack have no import yet.</p></section>
|
|
210
|
-
<div class="wl-actions"><a class="wl-btn wl-primary" data-testid="open-pmos" href="/c3.html?ws=${encodeURIComponent(S.ws)}">Open PMOS</a></div>`;
|
|
279
|
+
const items = S.briefing.map((b) => `<li class="nrow" data-testid="briefing-item"><span class="ki"></span><div class="nb"><span class="tt"><b>${esc(b.title)}</b></span>${b.why ? `<div class="dim wl-small">${esc(b.why)}</div>` : ''}<div class="chips">${b.initiative ? `<span class="chip2" data-testid="briefing-initiative">${esc(b.initiative.title)} · board ${esc(b.initiative.board)}</span>` : ''}${pathChip(ref((b.evidence || [])[0]))}</div></div><span></span></li>`).join('');
|
|
280
|
+
const tools = TOOLS.filter((x) => S.sources[x.source]).map((x) => { const st = S.sources[x.source]; const bad = !st.started;
|
|
281
|
+
return `<li class="nrow" data-testid="source-status" data-id="${esc(x.source)}"><span class="ki${bad ? ' hot' : ''}">${ic(bad ? 'alert' : 'clock')}</span><div class="nb"><span class="tt">${esc(x.name)}</span><div class="dim wl-small${bad ? ' wl-bad' : ''}">${bad ? esc(st.error || '') : `${esc(x.name)} is being read in an agent session. Its items join your Briefing in PMOS when it finishes.`}</div></div><span></span></li>`; });
|
|
282
|
+
return [head('Your first Briefing', S.briefing.length ? 'These need you first.' : 'Nothing needs you right now.')
|
|
283
|
+
+ sub('Briefing', 'sun', '', S.briefing.length)
|
|
284
|
+
+ (items ? numbered(items) : '<div class="fold" data-testid="no-briefing">Your portfolio is in. Open PMOS to see it.</div>')
|
|
285
|
+
+ (tools.length ? sub('Your tools', 'tools', '', tools.length) + `<ul class="list">${tools.join('')}</ul>` : ''),
|
|
286
|
+
foot(ctl({ variant: 'ghost', html: S.busy === 'undo' ? spin('Undoing') : 'Undo this import', attrs: { 'data-testid': 'undo-link', disabled: !!S.busy, ...busyAttrs('undo') } }),
|
|
287
|
+
ctl({ variant: 'primary', html: 'Open PMOS', href: '/c3.html?ws=' + encodeURIComponent(S.ws), attrs: { class: 'wl-primary', 'data-testid': 'open-pmos' } }))];
|
|
211
288
|
}
|
|
212
289
|
|
|
213
290
|
const VIEWS = { welcome: viewWelcome, look: viewLook, portfolio: viewPortfolio, questions: viewQuestions, briefing: viewBriefing, sources: viewSources };
|
|
291
|
+
const shownSteps = () => STEPS.filter(([k]) => k !== 'questions' || S.step === 'questions' || hasQuestions());
|
|
292
|
+
|
|
293
|
+
/* The frame: PMOS, the step count and Not now (About you) or Skip setup (until the import); a progress bar and the step names
|
|
294
|
+
* (a done step goes back to that step, except once the import is made: Undo is the way back then); one step card. */
|
|
295
|
+
function frame(body, footer) {
|
|
296
|
+
const l = shownSteps(); const at = Math.max(0, l.findIndex(([k]) => k === S.step));
|
|
297
|
+
const back = S.step !== 'briefing';
|
|
298
|
+
const steps = l.map(([k, t], n) => `<li><button type="button" class="wl-stepbtn${n < at ? ' wl-done' : ''}" data-testid="step" data-step="${k}" ${n === at ? 'aria-current="step"' : ''} ${n < at && back && !S.busy ? '' : 'disabled'}>${n < at ? ic('check', 'sm') : ''}${esc(t)}</button></li>`).join('');
|
|
299
|
+
const skip = S.step === 'briefing' ? '' : S.step === 'welcome' ? ghost('not-now', 'Not now') : ghost('skip-setup', 'Skip setup');
|
|
300
|
+
return `<div class="wl-top"><div><h1>PMOS</h1><div class="wl-sub" data-testid="step-count">Set up · step ${at + 1} of ${l.length}</div></div><div class="wl-tools">${skip}</div></div>
|
|
301
|
+
<div class="wl-col"><nav class="wl-prog" aria-label="Setup steps"><div class="pbar" aria-hidden="true"><i class="wl-fill"></i></div><ol class="wl-steps" data-testid="steps">${steps}</ol></nav>
|
|
302
|
+
${S.error ? `<div class="fold wl-e" role="alert" data-testid="error">${ic('alert', 'sm')}${esc(S.error)}</div>` : ''}
|
|
303
|
+
<section class="card sect wl-card">${body}${footer}</section></div>`;
|
|
304
|
+
}
|
|
214
305
|
|
|
215
306
|
function render() {
|
|
216
307
|
const keepScroll = window.scrollY;
|
|
217
|
-
const stepsBar = `<ol class="wl-steps" data-testid="steps">${STEPS.filter(([k]) => k !== 'questions' || S.step === 'questions' || (S.structure && (S.structure.questions || []).length))
|
|
218
|
-
.map(([k, t]) => `<li ${k === S.step ? 'aria-current="step"' : ''} data-step="${k}">${t}</li>`).join('')}</ol>`;
|
|
219
308
|
const a = document.activeElement;
|
|
220
309
|
const was = a && root.contains(a) ? [a.getAttribute('data-testid'), a.getAttribute('data-id')] : null;
|
|
221
310
|
root.setAttribute('data-step', S.step);
|
|
222
|
-
|
|
311
|
+
CTL = [];
|
|
312
|
+
const [body, footer] = VIEWS[S.step]();
|
|
313
|
+
root.innerHTML = frame(body, footer);
|
|
314
|
+
mountControls();
|
|
315
|
+
const l = shownSteps(); const at = Math.max(0, l.findIndex(([k]) => k === S.step));
|
|
316
|
+
const fill = root.querySelector('.wl-fill'); if (fill) fill.style.setProperty('--wl-prog', Math.round(((at + 1) / l.length) * 100) + '%');
|
|
223
317
|
window.scrollTo(0, keepScroll);
|
|
224
318
|
if (was && was[0]) { // keep the keyboard where it was after a re-render
|
|
225
319
|
const again = [...root.querySelectorAll('[data-testid="' + was[0] + '"]')].find((el) => (el.getAttribute('data-id') || null) === (was[1] || null));
|
|
@@ -229,10 +323,6 @@ function render() {
|
|
|
229
323
|
|
|
230
324
|
/* ── events (one delegate; the markup is rebuilt on each render) ─────────────────────────────────────────────────── */
|
|
231
325
|
const idOf = (el) => el.getAttribute('data-id');
|
|
232
|
-
const bringIn = (source) => run('source:' + source, async () => {
|
|
233
|
-
const r = await pmPost('/api/sources/refresh', { ws: S.ws, source });
|
|
234
|
-
S.sources[source] = r && r.ok ? { started: true } : { error: (r && r.error) || 'Starting the import did not work.' };
|
|
235
|
-
});
|
|
236
326
|
document.addEventListener('click', (e) => {
|
|
237
327
|
if (!root.contains(e.target)) return;
|
|
238
328
|
const t = e.target.closest('[data-testid]'); if (!t) return;
|
|
@@ -242,22 +332,26 @@ document.addEventListener('click', (e) => {
|
|
|
242
332
|
else if (k === 'continue-button') toPortfolio();
|
|
243
333
|
else if (k === 'import-button' || k === 'questions-continue') { if (k === 'questions-continue') run('apply', applyNow); else importNow(); }
|
|
244
334
|
else if (k === 'undo-link') undo();
|
|
245
|
-
else if (k === '
|
|
246
|
-
else if (k === '
|
|
247
|
-
else if (k === '
|
|
248
|
-
else if (k === 'not-now') notNow();
|
|
335
|
+
else if (k === 'about-continue') saveAbout();
|
|
336
|
+
else if (k === 'back-button') { const l = shownSteps().map(([s]) => s); S.step = l[l.indexOf(S.step) - 1] || 'welcome'; S.error = ''; render(); }
|
|
337
|
+
else if (k === 'step') { if (!t.disabled) { S.step = t.getAttribute('data-step'); S.error = ''; render(); } }
|
|
338
|
+
else if (k === 'not-now' || k === 'skip-setup') notNow();
|
|
339
|
+
else if (k === 'row-edit') { S.edit[idOf(t)] = !S.edit[idOf(t)]; render(); }
|
|
249
340
|
else if (k === 'row-docs-toggle') { S.open[idOf(t)] = !S.open[idOf(t)]; render(); }
|
|
250
341
|
else if (k === 'question-skip') { const id = idOf(t); S.skipped[id] = !S.skipped[id]; render(); }
|
|
251
342
|
});
|
|
252
343
|
root.addEventListener('change', (e) => {
|
|
253
344
|
const t = e.target; const k = t.getAttribute && t.getAttribute('data-testid');
|
|
254
|
-
if (k === 'row-check') { S.checked[idOf(t)] = t.checked; render(); }
|
|
345
|
+
if (k === 'row-check') { S.checked[idOf(t)] = t.checked; if (!t.checked) S.edit[idOf(t)] = false; render(); }
|
|
346
|
+
else if (k === 'row-title') render();
|
|
347
|
+
else if (k === 'source-check') { S.pick[idOf(t)] = t.checked; render(); }
|
|
255
348
|
else if (k === 'row-board') { S.boardOf[idOf(t)] = t.value; render(); }
|
|
256
349
|
else if (k === 'question-option' || k === 'question-select') S.answers[idOf(t)] = t.value;
|
|
257
350
|
});
|
|
258
351
|
root.addEventListener('input', (e) => {
|
|
259
352
|
const t = e.target; const k = t.getAttribute && t.getAttribute('data-testid');
|
|
260
353
|
if (k === 'row-title') S.titles[idOf(t)] = t.value;
|
|
354
|
+
else if (k && k.startsWith('about-')) S.about[k.slice(6)] = t.value;
|
|
261
355
|
else if (k === 'question-input') {
|
|
262
356
|
const id = idOf(t); const q = ((S.structure && S.structure.questions) || []).find((x) => x.id === id);
|
|
263
357
|
S.answers[id] = t.value.trim() ? t.value : (q ? q.default : t.value); // an emptied free answer goes back to the default
|
|
@@ -265,13 +359,27 @@ root.addEventListener('input', (e) => {
|
|
|
265
359
|
});
|
|
266
360
|
|
|
267
361
|
/* ── start ───────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
362
|
+
/* The console3 look: its stylesheet and icon sprite, the PM's console theme, and the time-of-day tint on the page root. */
|
|
363
|
+
function look() {
|
|
364
|
+
ensureC3Css();
|
|
365
|
+
const page = document.getElementById('wl-c3');
|
|
366
|
+
let theme = params.get('theme');
|
|
367
|
+
try { theme = theme || localStorage.getItem('pmos.c3.theme'); } catch { /* no storage */ }
|
|
368
|
+
theme = theme === 'dark' ? 'dark' : 'light';
|
|
369
|
+
document.documentElement.setAttribute('data-pm-page-theme', theme);
|
|
370
|
+
if (page) { page.setAttribute('data-pm-theme', theme); applyDayPart(page); setInterval(() => applyDayPart(page), 5 * 60 * 1000); }
|
|
371
|
+
}
|
|
372
|
+
|
|
268
373
|
async function start() {
|
|
374
|
+
look();
|
|
269
375
|
render();
|
|
270
376
|
const list = await pmGet('/api/workspaces');
|
|
271
377
|
const all = (list && list.workspaces) || [];
|
|
272
378
|
const mine = all.find((w) => w.id === S.ws) || (!S.ws ? all[0] : null);
|
|
273
379
|
if (!mine) { S.error = 'This folder is not open in PMOS. Start pmos in the folder, then reload.'; render(); return; }
|
|
274
380
|
S.ws = mine.id; S.folder = { name: mine.name, root: mine.root };
|
|
381
|
+
const ps = await pmGet('/api/proactive/settings');
|
|
382
|
+
if (ps && ps.ok && ps.settings) { const ab = ps.settings.about || {}; S.about = { name: ps.settings.name || '', role: ab.role || '', work: ab.work || '', duties: ab.duties || '' }; }
|
|
275
383
|
const st = await pmGet(`/api/onboarding/status?ws=${encodeURIComponent(S.ws)}`);
|
|
276
384
|
if (st && st.ok && st.state === 'imported') {
|
|
277
385
|
const live = (st.imports || []).filter((i) => !i.retracted).pop();
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en" class="wl-
|
|
2
|
+
<html lang="en" class="wl-html" data-pm-page-theme="light">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
6
|
<meta name="pmos-nonce" content="" />
|
|
7
7
|
<title>Welcome to PMOS</title>
|
|
8
8
|
<!-- The first-run page. The server fills the nonce meta the way it does for /c3.html, so a press (Scan, Import, Undo)
|
|
9
|
-
carries it. Tokens and fonts are the console's, so light and dark both resolve. The page does not load the console
|
|
9
|
+
carries it. Tokens and fonts are the console's, so light and dark both resolve. The page does not load the console:
|
|
10
|
+
pm/welcome.js links console3.css and its icon sprite and draws inside the same .c3 .c3pg root as /c3.html. -->
|
|
10
11
|
<link rel="preload" href="/vendor/ibm-plex-mono-400.woff2" as="font" type="font/woff2" crossorigin />
|
|
12
|
+
<link rel="preload" href="/vendor/ibm-plex-mono-500.woff2" as="font" type="font/woff2" crossorigin />
|
|
11
13
|
<link rel="stylesheet" href="/pm/tokens.css" />
|
|
12
14
|
<link rel="stylesheet" href="/pm/components.css" />
|
|
13
15
|
|
|
14
16
|
</head>
|
|
15
17
|
<body class="wl-body">
|
|
16
|
-
<main id="wl-root" class="wl-
|
|
17
|
-
<noscript><p class="wl-
|
|
18
|
+
<div id="wl-c3" class="wl-page c3 c3pg" data-pm-theme="light"><main id="wl-root" class="wl-welcome" data-testid="welcome-root"></main></div>
|
|
19
|
+
<noscript><p class="wl-noscript">This page needs JavaScript.</p></noscript>
|
|
18
20
|
<script type="module" src="/pm/welcome.js"></script>
|
|
19
21
|
</body>
|
|
20
22
|
</html>
|