create-pmos 0.133.0 → 0.135.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/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 +35 -2
- 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-audio.js +28 -9
- package/workspace/lib/notes-capture.js +136 -0
- package/workspace/lib/notes-final.js +38 -13
- package/workspace/lib/notes-live-guard.js +117 -0
- package/workspace/lib/notes-replay.js +125 -0
- package/workspace/lib/notes-screen.js +180 -0
- package/workspace/lib/notes-whisper.js +139 -16
- package/workspace/lib/notes.js +4 -2
- 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/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 +4 -3
- package/workspace/lib/routes-c3-life.js +2 -1
- package/workspace/lib/routes-c3-reports.js +96 -13
- package/workspace/lib/routes-c3-strategy.js +4 -3
- 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 +113 -15
- package/workspace/lib/routes-outbox.js +23 -3
- package/workspace/lib/routes-pm-items.js +19 -6
- package/workspace/lib/routes-records.js +3 -2
- 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 +5 -4
- 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 +26 -13
- package/workspace/public/pm/components.css +319 -213
- package/workspace/public/pm/console3.css +118 -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 +148 -17
- package/workspace/public/pm/tokens.css +2 -2
- package/workspace/public/pm/view-notes.js +53 -11
- 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 +14 -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 +12 -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,14 +1,15 @@
|
|
|
1
1
|
/* pm/pm-work-view.js — the Work view of My work, built on the console3 mockup's own markup and CSS (initiative
|
|
2
2
|
* planning-flows-and-agent-sessions). The mockup (prototype/pmos-console-handoff/mockups/console3.html) is the UI: the rows
|
|
3
3
|
* (.nrow with .rv, .ki, .nb, .tt, .chips, .xp), the lens bar (.calbar, .seg), the add line (.c3add, .chips2), the case file
|
|
4
|
-
* (.case, .c3jf, .c3steps), the
|
|
4
|
+
* (.case, .c3jf, .c3steps), the Focus card (.card.sect) and the swipe gesture are the mockup's; the styles are pm/console3.css
|
|
5
5
|
* (see pm/console3-lift.md). This file keeps the real data calls and draws what they return, nothing else.
|
|
6
6
|
*
|
|
7
7
|
* mountPmWork(host, ctx) draws once per host; disposePmWork(host) removes it.
|
|
8
8
|
* Views (initiative console3-work-page, planning/prd/console3-work-page-prd.md): Inbox, Today, Upcoming, Waiting, Someday and Review.
|
|
9
9
|
* The SERVER places every item and answers `work`, `review`, `focus` and `counts`; this file draws them and never copies an item from
|
|
10
|
-
* one view to another.
|
|
11
|
-
*
|
|
10
|
+
* one view to another. The right column of every view is Focus (up to three jobs, each with the reason: Accept, Edit, Not today; a row opens the
|
|
11
|
+
* job's sheet) (initiative console3-work-parity). Today is Overdue, Due today and Flagged. Upcoming is Next 7 days by day, then a Later card. Review
|
|
12
|
+
* also carries the agent's drafts section that Briefing used to show. Review is a weekly pass, one item at a time (Keep, Change, Drop). An item done outside PMOS
|
|
12
13
|
* (`kind: offline`) shows the person and offers Done with an optional outcome, Not yet and Drop, and "Looks done?" with Confirm when
|
|
13
14
|
* it carries `evidence`; PMOS never closes it by itself.
|
|
14
15
|
* Routes (lib/routes-pm-items.js). Every write is a page press through pmPost:
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
* drawn disabled ("no channel connected"); nothing is sent.
|
|
23
24
|
* In demo mode (/c3.html?demo=1, window.__c3demo) the answers of the writes are laid over the fixtures in memory (`applyOv`) and placed
|
|
24
25
|
* with work-place.js, the server's rule; the agent run of a row with a suggested move is a mock that only walks the steps and shows a
|
|
25
|
-
* labelled draft.
|
|
26
|
+
* labelled draft. ctx.go(tab) opens another tab. The microphone is drawn disabled.
|
|
26
27
|
*
|
|
27
28
|
* The helpers c3ic, c3ring, ensureC3Css and c3Phone are shared with briefing-deck.js and calendar-views.js. */
|
|
28
29
|
import { el } from './dom.js';
|
|
@@ -32,13 +33,16 @@ import { renderCase } from './c3-case.js';
|
|
|
32
33
|
import { makeXp, attachExpand } from './c3-expand.js';
|
|
33
34
|
import { attachSwipe } from './c3-swipe.js';
|
|
34
35
|
import { toast } from './c3-toast.js';
|
|
36
|
+
import { openSheet } from './c3-sheet.js';
|
|
37
|
+
import { onBriefing, loadBriefing } from './shell-ticker.js';
|
|
38
|
+
import { reviewSection } from './c3-brief.js';
|
|
35
39
|
import { VIEWS, REVIEW_DAYS, workPlace, arrives, buildWork, addIso, daysBetween, untouchedDays } from './work-place.js';
|
|
36
40
|
|
|
37
41
|
/* ── shared with the Briefing and Calendar views: the stylesheet, the icon sprite, the ring, the phone class ───────── */
|
|
38
42
|
|
|
39
43
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
40
44
|
/* The mockup's icon sprite (console3.html lines 970 to 1026, ids renamed c3i-*, plus the c3 layer's edit and trash). */
|
|
41
|
-
const SPRITE = '<symbol id="c3i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol><symbol id="c3i-moon" viewBox="0 0 24 24"><path d="M20 14a8 8 0 11-10-10 6 6 0 0010 10z"/></symbol><symbol id="c3i-cal" viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M8 3v4M16 3v4"/></symbol><symbol id="c3i-inbox" viewBox="0 0 24 24"><path d="M4 13l2-8h12l2 8M4 13v6h16v-6M4 13h5l1 2h4l1-2h5"/></symbol><symbol id="c3i-todo" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 12l3 3 5-6"/></symbol><symbol id="c3i-init" viewBox="0 0 24 24"><path d="M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5"/></symbol><symbol id="c3i-report" viewBox="0 0 24 24"><path d="M5 20V10M12 20V4M19 20v-7"/></symbol><symbol id="c3i-notes" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 13h7M9 17h5"/></symbol><symbol id="c3i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></symbol><symbol id="c3i-doc" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5"/></symbol><symbol id="c3i-sheet" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></symbol><symbol id="c3i-img" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 16l-5-5-9 9"/></symbol><symbol id="c3i-pdf" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 15h6M9 18h4"/></symbol><symbol id="c3i-term" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/></symbol><symbol id="c3i-branch" viewBox="0 0 24 24"><circle cx="6" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 7v10M18 11c0 4-6 3-12 6"/></symbol><symbol id="c3i-repo" viewBox="0 0 24 24"><path d="M5 4h11a3 3 0 013 3v13H8a3 3 0 01-3-3zM5 17a3 3 0 013-3h11"/></symbol><symbol id="c3i-flag" viewBox="0 0 24 24"><path d="M6 21V4M6 5h11l-2 4 2 4H6"/></symbol><symbol id="c3i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol><symbol id="c3i-alert" viewBox="0 0 24 24"><path d="M12 3l10 18H2zM12 10v5M12 18v.5"/></symbol><symbol id="c3i-check" viewBox="0 0 24 24"><path d="M5 12l5 5 9-10"/></symbol><symbol id="c3i-x" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></symbol><symbol id="c3i-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol><symbol id="c3i-undo" viewBox="0 0 24 24"><path d="M4 12a8 8 0 108-8M4 4v6h6"/></symbol><symbol id="c3i-out" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></symbol><symbol id="c3i-shield" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/></symbol><symbol id="c3i-mic" viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0014 0M12 18v3"/></symbol><symbol id="c3i-clip" viewBox="0 0 24 24"><rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4h6v3H9zM9 12h6M9 16h4"/></symbol><symbol id="c3i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></symbol><symbol id="c3i-phone" viewBox="0 0 24 24"><rect x="7" y="2" width="10" height="20" rx="2"/><path d="M11 18h2"/></symbol><symbol id="c3i-cloud" viewBox="0 0 24 24"><path d="M7 18a4 4 0 010-8 5 5 0 019.6-1A4.5 4.5 0 0117 18z"/></symbol><symbol id="c3i-bolt" viewBox="0 0 24 24"><path d="M13 2L4 14h7l-1 8 9-12h-7z"/></symbol><symbol id="c3i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol><symbol id="c3i-scale" viewBox="0 0 24 24"><path d="M12 3v18M5 7h14M5 7l-3 7a3 3 0 006 0zM19 7l-3 7a3 3 0 006 0z"/></symbol><symbol id="c3i-reply" viewBox="0 0 24 24"><path d="M9 7L4 12l5 5M4 12h10a6 6 0 016 6"/></symbol><symbol id="c3i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.5v.5"/></symbol><symbol id="c3i-wait" viewBox="0 0 24 24"><path d="M7 3h10M7 21h10M8 3c0 5 8 5 8 9s-8 4-8 9"/></symbol><symbol id="c3i-up" viewBox="0 0 24 24"><path d="M7 11v9H4v-9zM7 11l4-8c2 0 3 1 3 3v3h5a2 2 0 012 2l-1 6a2 2 0 01-2 2H7"/></symbol><symbol id="c3i-cols" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol><symbol id="c3i-fold" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></symbol><symbol id="c3i-spark" viewBox="0 0 24 24"><path d="M12 3l2 5 5 2-5 2-2 5-2-5-5-2 5-2z"/></symbol><symbol id="c3i-target" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/></symbol><symbol id="c3i-filter" viewBox="0 0 24 24"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></symbol><symbol id="c3i-mail" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></symbol><symbol id="c3i-lock" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 018 0v3"/></symbol><symbol id="c3i-play" viewBox="0 0 24 24"><path d="M7 4l13 8-13 8z"/></symbol><symbol id="c3i-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="1"/></symbol><symbol id="i-pause" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></symbol><symbol id="c3i-prev" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol><symbol id="c3i-next" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol><symbol id="c3i-tree" viewBox="0 0 24 24"><path d="M5 4h7v4H5zM13 10h7v4h-7zM13 17h7v4h-7zM8 8v11h5M8 12h5"/></symbol><symbol id="c3i-org" viewBox="0 0 24 24"><path d="M9 3h6v5H9zM3 16h6v5H3zM15 16h6v5h-6zM12 8v4M6 16v-4h12v4"/></symbol><symbol id="c3i-drop" viewBox="0 0 24 24"><path d="M12 3c4 5 6 8 6 11a6 6 0 01-12 0c0-3 2-6 6-11z"/></symbol><symbol id="c3i-more" viewBox="0 0 24 24"><path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3"/></symbol><symbol id="c3i-zin" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6M11 8v6"/></symbol><symbol id="c3i-zout" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6"/></symbol><symbol id="c3i-bell" viewBox="0 0 24 24"><path d="M6 17v-6a6 6 0 0112 0v6l2 2H4zM10 21h4"/></symbol><symbol id="c3i-vol" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9zM16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"/></symbol><symbol id="c3i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol><symbol id="c3i-edit" viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16v4zM13.5 6.5l4 4"/></symbol><symbol id="c3i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></symbol>';
|
|
45
|
+
const SPRITE = '<symbol id="c3i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol><symbol id="c3i-star" viewBox="0 0 24 24"><path d="M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.5 6.6 19.5l1.2-6L3.3 9.3l6.1-.7z"/></symbol><symbol id="c3i-moon" viewBox="0 0 24 24"><path d="M20 14a8 8 0 11-10-10 6 6 0 0010 10z"/></symbol><symbol id="c3i-cal" viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M8 3v4M16 3v4"/></symbol><symbol id="c3i-inbox" viewBox="0 0 24 24"><path d="M4 13l2-8h12l2 8M4 13v6h16v-6M4 13h5l1 2h4l1-2h5"/></symbol><symbol id="c3i-todo" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 12l3 3 5-6"/></symbol><symbol id="c3i-init" viewBox="0 0 24 24"><path d="M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5"/></symbol><symbol id="c3i-report" viewBox="0 0 24 24"><path d="M5 20V10M12 20V4M19 20v-7"/></symbol><symbol id="c3i-notes" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 13h7M9 17h5"/></symbol><symbol id="c3i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></symbol><symbol id="c3i-doc" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5"/></symbol><symbol id="c3i-sheet" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></symbol><symbol id="c3i-img" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 16l-5-5-9 9"/></symbol><symbol id="c3i-pdf" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 15h6M9 18h4"/></symbol><symbol id="c3i-term" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/></symbol><symbol id="c3i-branch" viewBox="0 0 24 24"><circle cx="6" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 7v10M18 11c0 4-6 3-12 6"/></symbol><symbol id="c3i-repo" viewBox="0 0 24 24"><path d="M5 4h11a3 3 0 013 3v13H8a3 3 0 01-3-3zM5 17a3 3 0 013-3h11"/></symbol><symbol id="c3i-flag" viewBox="0 0 24 24"><path d="M6 21V4M6 5h11l-2 4 2 4H6"/></symbol><symbol id="c3i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol><symbol id="c3i-alert" viewBox="0 0 24 24"><path d="M12 3l10 18H2zM12 10v5M12 18v.5"/></symbol><symbol id="c3i-check" viewBox="0 0 24 24"><path d="M5 12l5 5 9-10"/></symbol><symbol id="c3i-x" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></symbol><symbol id="c3i-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol><symbol id="c3i-undo" viewBox="0 0 24 24"><path d="M4 12a8 8 0 108-8M4 4v6h6"/></symbol><symbol id="c3i-out" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></symbol><symbol id="c3i-shield" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/></symbol><symbol id="c3i-mic" viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0014 0M12 18v3"/></symbol><symbol id="c3i-clip" viewBox="0 0 24 24"><rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4h6v3H9zM9 12h6M9 16h4"/></symbol><symbol id="c3i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></symbol><symbol id="c3i-phone" viewBox="0 0 24 24"><rect x="7" y="2" width="10" height="20" rx="2"/><path d="M11 18h2"/></symbol><symbol id="c3i-cloud" viewBox="0 0 24 24"><path d="M7 18a4 4 0 010-8 5 5 0 019.6-1A4.5 4.5 0 0117 18z"/></symbol><symbol id="c3i-bolt" viewBox="0 0 24 24"><path d="M13 2L4 14h7l-1 8 9-12h-7z"/></symbol><symbol id="c3i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol><symbol id="c3i-scale" viewBox="0 0 24 24"><path d="M12 3v18M5 7h14M5 7l-3 7a3 3 0 006 0zM19 7l-3 7a3 3 0 006 0z"/></symbol><symbol id="c3i-reply" viewBox="0 0 24 24"><path d="M9 7L4 12l5 5M4 12h10a6 6 0 016 6"/></symbol><symbol id="c3i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.5v.5"/></symbol><symbol id="c3i-wait" viewBox="0 0 24 24"><path d="M7 3h10M7 21h10M8 3c0 5 8 5 8 9s-8 4-8 9"/></symbol><symbol id="c3i-up" viewBox="0 0 24 24"><path d="M7 11v9H4v-9zM7 11l4-8c2 0 3 1 3 3v3h5a2 2 0 012 2l-1 6a2 2 0 01-2 2H7"/></symbol><symbol id="c3i-cols" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol><symbol id="c3i-fold" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></symbol><symbol id="c3i-spark" viewBox="0 0 24 24"><path d="M12 3l2 5 5 2-5 2-2 5-2-5-5-2 5-2z"/></symbol><symbol id="c3i-target" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/></symbol><symbol id="c3i-filter" viewBox="0 0 24 24"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></symbol><symbol id="c3i-mail" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></symbol><symbol id="c3i-lock" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 018 0v3"/></symbol><symbol id="c3i-play" viewBox="0 0 24 24"><path d="M7 4l13 8-13 8z"/></symbol><symbol id="c3i-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="1"/></symbol><symbol id="i-pause" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></symbol><symbol id="c3i-prev" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol><symbol id="c3i-next" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol><symbol id="c3i-tree" viewBox="0 0 24 24"><path d="M5 4h7v4H5zM13 10h7v4h-7zM13 17h7v4h-7zM8 8v11h5M8 12h5"/></symbol><symbol id="c3i-org" viewBox="0 0 24 24"><path d="M9 3h6v5H9zM3 16h6v5H3zM15 16h6v5h-6zM12 8v4M6 16v-4h12v4"/></symbol><symbol id="c3i-drop" viewBox="0 0 24 24"><path d="M12 3c4 5 6 8 6 11a6 6 0 01-12 0c0-3 2-6 6-11z"/></symbol><symbol id="c3i-more" viewBox="0 0 24 24"><path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3"/></symbol><symbol id="c3i-zin" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6M11 8v6"/></symbol><symbol id="c3i-zout" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6"/></symbol><symbol id="c3i-bell" viewBox="0 0 24 24"><path d="M6 17v-6a6 6 0 0112 0v6l2 2H4zM10 21h4"/></symbol><symbol id="c3i-vol" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9zM16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"/></symbol><symbol id="c3i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol><symbol id="c3i-edit" viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16v4zM13.5 6.5l4 4"/></symbol><symbol id="c3i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></symbol>';
|
|
42
46
|
|
|
43
47
|
/** Link console3.css once, from this module's own folder (the same way primitives.js links components.css). */
|
|
44
48
|
export function ensureC3Css() {
|
|
@@ -106,37 +110,44 @@ const API = '/api/pm-items';
|
|
|
106
110
|
const JOBS = '/api/pm-jobs';
|
|
107
111
|
/* The six views (initiative console3-work-page, PRD R1): five places an open item can be, and the weekly Review pass. */
|
|
108
112
|
const LENSES = [['inbox', 'Inbox'], ['today', 'Today'], ['upcoming', 'Upcoming'], ['waiting', 'Waiting'], ['someday', 'Someday'], ['review', 'Review']];
|
|
109
|
-
const WLI = { inbox: 'inbox', today: 'sun', upcoming: 'cal', waiting: 'wait', someday: 'moon', review: '
|
|
113
|
+
const WLI = { inbox: 'inbox', today: 'sun', upcoming: 'cal', waiting: 'wait', someday: 'moon', review: 'check' };
|
|
110
114
|
const WHO = ['you', 'agent-drafts', 'agent-does'];
|
|
111
115
|
const OUTWARD = /\b(send|sends|sending|email|emails|message|messages|tell|post|reply to)\b/i; // mirrors lib/item-steps.js
|
|
112
116
|
const RESOLVES_TIP = 'Your work. The team task is separate: close it in its initiative.';
|
|
113
117
|
|
|
114
118
|
export const COPY = Object.freeze({
|
|
115
119
|
addPlaceholder: 'Add work: type or speak',
|
|
116
|
-
addLabel: 'Add work', addBtn: 'Add', hint: 'Try: Send
|
|
120
|
+
addLabel: 'Add work', addBtn: 'Add', hint: 'Try: Send Dewi the new date by Fri',
|
|
117
121
|
addsTo: 'adds to', noJob: 'no job yet', waitingOn: (w) => 'waiting on ' + w,
|
|
118
122
|
empty: 'Nothing here.', noJobGroup: 'No job yet', jobs: 'Jobs to be done', noJobs: 'No jobs yet.',
|
|
119
123
|
addJob: 'Add a job', addJobPlaceholder: 'when I plan a release, I want honest dates, so Dana can trust them',
|
|
120
124
|
groupJob: 'Group by job to be done', groupTime: 'Group by time', loading: 'Reading…',
|
|
121
125
|
micOff: 'Speak is off on this line: on-device dictation runs in Notes, in the main app. Type the item here.',
|
|
122
126
|
jobsNote: 'The count is every item in the job, across all views. Tap a job to edit or delete it.',
|
|
123
|
-
legend: ['open', 'done'
|
|
127
|
+
legend: ['open', 'done'], readError: 'Your items could not be read.', saveError: 'Nothing was saved.',
|
|
124
128
|
removed: 'Removed.', undo: 'Undo', restored: 'Restored.', tooLate: 'Too late to undo.', drop: 'Drop',
|
|
125
129
|
reassigned: (n) => (n === 1 ? '1 item now has no job.' : `${n} items now have no job.`),
|
|
126
130
|
progress: (d, t) => `${d} of ${t} done`,
|
|
127
131
|
jobLine: (j) => `When ${j.when}, I want ${j.want}, so ${j.so}.`,
|
|
132
|
+
/* The header of each view (the mockup's INTRO): a bold line and a dim line. */
|
|
128
133
|
lens: {
|
|
129
|
-
inbox:
|
|
130
|
-
waiting:
|
|
134
|
+
inbox: 'Decide when or whether', today: 'Overdue, due today and flagged', upcoming: 'The next 7 days, then anything later',
|
|
135
|
+
waiting: 'Things you handed to someone', someday: 'Parked on purpose', review: 'A weekly pass over items without a date',
|
|
131
136
|
},
|
|
132
137
|
lensSub: {
|
|
133
|
-
inbox: '
|
|
134
|
-
today: '
|
|
135
|
-
upcoming: '
|
|
136
|
-
waiting: '
|
|
137
|
-
someday: '
|
|
138
|
-
review: 'Items in Inbox, Waiting or Someday that nobody touched for 7 days, most stale first.
|
|
138
|
+
inbox: 'Nothing here has a date. Tap Decide to give each item a day, park it or drop it.',
|
|
139
|
+
today: 'Focus says which jobs matter most today.',
|
|
140
|
+
upcoming: 'A "not before" date keeps an item out of Today until it arrives.',
|
|
141
|
+
waiting: 'You expect these back. Nudge when one goes quiet.',
|
|
142
|
+
someday: 'Nothing here asks for attention until the weekly Review.',
|
|
143
|
+
review: 'Items in Inbox, Waiting or Someday that nobody touched for 7 days, most stale first. One at a time: keep it, change it, or drop it.',
|
|
139
144
|
},
|
|
145
|
+
sect: { inbox: 'Inbox', waiting: 'Waiting', someday: 'Someday', overdue: 'Overdue', dueToday: 'Due today', flagged: 'Flagged', next7: 'Next 7 days', later: 'Later' },
|
|
146
|
+
decisions: (n) => ` ${n} decision${n === 1 ? '' : 's'} wait${n === 1 ? 's' : ''} in Briefing, Needs you.`, openBriefing: 'Open Briefing',
|
|
147
|
+
inboxNote: 'New asks for initiatives are triaged in', initiatives: 'Initiatives', inboxNoteEnd: '. This inbox is only for your own work.',
|
|
148
|
+
clear: 'Clear', beyond: 'beyond 7 days', nothingLater: 'Nothing beyond 7 days.', tomorrowHead: 'Tomorrow',
|
|
149
|
+
decide: 'Decide', nudge: 'Nudge', doneBtn: 'Done',
|
|
150
|
+
nudged: (w) => `Nudge to ${w} drafted. No channel is connected, so nothing is sent.`,
|
|
140
151
|
emptyView: {
|
|
141
152
|
inbox: 'Inbox is empty. Capture anything that crosses your mind.', today: 'Nothing due, overdue or flagged. Pick from Upcoming when you are ready.',
|
|
142
153
|
upcoming: 'Nothing dated in the days ahead.', waiting: 'Nobody owes you anything right now.', someday: 'Nothing parked.',
|
|
@@ -144,17 +155,19 @@ export const COPY = Object.freeze({
|
|
|
144
155
|
done: 'Done', doneToast: 'Marked done.', steps: 'Steps', noSteps: 'No steps yet.', addStep: 'Add a step',
|
|
145
156
|
who: { you: 'You', 'agent-drafts': 'Agent drafts', 'agent-does': 'Agent does' },
|
|
146
157
|
whoOutward: 'Sends to a person: the agent drafts, you slide to send.', options: 'Options', plan: 'Plan the steps',
|
|
147
|
-
/* Focus (PRD R4) */
|
|
148
|
-
focus: 'Focus
|
|
149
|
-
focusPinned: 'Focus today',
|
|
150
|
-
why: (w) => 'Why: ' + w, focusAccepted: '
|
|
158
|
+
/* Focus (PRD R4; console3-work-parity R2) */
|
|
159
|
+
focus: 'Focus: jobs to be done', focusNone: 'No focus for today. Everything below still counts.',
|
|
160
|
+
focusPinned: 'Focus today', accept: 'Accept', editTitle: 'Edit title', edit: 'Edit', notToday: 'Not today', unpin: 'Unpin', save: 'Save', cancel: 'Cancel',
|
|
161
|
+
why: (w) => 'Why: ' + w, focusAccepted: 'Added to Focus today', focusDismissed: 'Not today. PMOS will suggest it again tomorrow.', focusCleared: 'Unpinned.',
|
|
162
|
+
markedNot: (n) => `${n} marked not today.`, bringBack2: 'Bring back', broughtBack: 'Brought back.', newJob: 'New job', jobSheet: 'Job to be done',
|
|
151
163
|
/* offline work and the shared actions (PRD R6) */
|
|
152
164
|
offline: 'outside PMOS', offlineNote: (p) => 'Happens outside PMOS' + (p ? ', with ' + p : '') + '. Tell PMOS how it went.',
|
|
153
165
|
outcome: 'What came out of it (optional)', notYet: 'Not yet:', laterToday: 'Later today', tomorrow: 'Tomorrow', nextWeek: 'Next week',
|
|
154
166
|
looksDone: 'Looks done?', confirm: 'Confirm done', flag: 'Flag', unflag: 'Unflag', notBefore: 'Not before', someday: 'Someday', bringBack: 'Bring back',
|
|
155
167
|
waitingField: 'Waiting on', waitingWho: 'Who?', stopWaiting: 'Stop waiting', flagged: 'Flagged',
|
|
156
|
-
notBeforeChip: (d) => 'Not before ' + d, advances: (t) => 'Advances
|
|
157
|
-
overdue: (n) => (n === 1 ? '
|
|
168
|
+
notBeforeChip: (d) => 'Not before ' + d, advances: (t) => 'Advances team task ' + t, moved: (n) => `Moved ${n}×`,
|
|
169
|
+
overdue: (n) => (n === 1 ? 'Overdue since yesterday' : `${n} days overdue`), dueToday: 'Due today', dueOn: (d) => 'Due ' + d,
|
|
170
|
+
waitingSince: (w, since, ago) => `Waiting for ${w}` + (since ? ` since ${since}` : '') + (ago ? ` (${ago})` : ''), repeats: (r) => 'Repeats ' + String(r).toLowerCase(),
|
|
158
171
|
/* Review (PRD R7) */
|
|
159
172
|
reviewHead: 'Weekly review', keep: 'Keep', change: 'Change', next: 'Done changing, next', reviewDone: 'Review done',
|
|
160
173
|
reviewDoneText: (n) => `You looked at ${n} item${n === 1 ? '' : 's'}. Anything you changed or dropped has moved to its new view.`,
|
|
@@ -195,6 +208,7 @@ function applyOv(base, ov) {
|
|
|
195
208
|
d.focus = {
|
|
196
209
|
accepted: [...(F.accepted || []).filter((a) => !st(a.key)), ...[...ov.focus.values()].filter((x) => x.state === 'acc').map((x) => x.f)],
|
|
197
210
|
suggestions: (F.suggestions || []).filter((s) => (!st(s.key) || st(s.key).state === 'clear') && live(s)).slice(0, 3),
|
|
211
|
+
dismissed: [...new Set([...(F.dismissed || []), ...[...ov.focus.entries()].filter(([, x]) => x.state === 'no').map(([k]) => k)])].filter((k) => !st(k) || st(k).state === 'no'),
|
|
198
212
|
};
|
|
199
213
|
const jobs = [...(d.jobs || []), ...ov.jobsAdded].filter((j) => !ov.jobsGone.has(j.id)).map((j) => ({ ...j, ...(ov.jobEdits.get(j.id) || {}) }));
|
|
200
214
|
jobs.forEach((j) => {
|
|
@@ -269,9 +283,9 @@ export function mountPmWork(host, ctx) {
|
|
|
269
283
|
if (inst) return;
|
|
270
284
|
if (ctx && ctx.FIXTURE) return;
|
|
271
285
|
ensureC3Css();
|
|
272
|
-
inst = { host, ctx: ctx || {}, root: el('div', { class: 'c3 calwrap', 'data-pm-work': '' }), data: null, base: null, ov: newOv(), lens: 'today',
|
|
273
|
-
opens: new Set(), picks: new Map(), editing: new Set(), planning: new Set(), runs: new Map(), trays: new Set(), cases: new Set(), timers: new Set(), hidden: new Set(),
|
|
274
|
-
focEdit: new Set(), rv: { ids: null, handled: new Set(), changing: false } };
|
|
286
|
+
inst = { host, ctx: ctx || {}, root: el('div', { class: 'c3 calwrap wk', 'data-pm-work': '' }), data: null, base: null, ov: newOv(), lens: 'today',
|
|
287
|
+
opens: new Set(), picks: new Map(), editing: new Set(), planning: new Set(), runs: new Map(), trays: new Set(), cases: new Set(), timers: new Set(), hidden: new Set(),
|
|
288
|
+
focEdit: new Set(), rv: { ids: null, handled: new Set(), changing: false }, decisions: 0, drafts: null, draftsSig: '', draftsHost: el('div', { 'data-pm-review-drafts': '' }) };
|
|
275
289
|
inst.body = el('div', { class: 'calbody pmw-body' });
|
|
276
290
|
inst.bar = el('div', { class: 'calbar' });
|
|
277
291
|
inst.addBox = addLine(inst);
|
|
@@ -279,7 +293,9 @@ export function mountPmWork(host, ctx) {
|
|
|
279
293
|
inst.stopPhone = c3Phone(inst.root);
|
|
280
294
|
insts.set(host, inst);
|
|
281
295
|
host.appendChild(inst.root);
|
|
282
|
-
if (inst.ctx.onLens) inst.ctx.onLens(inst.lens);
|
|
296
|
+
if (inst.ctx.onLens) inst.ctx.onLens(inst.lens);
|
|
297
|
+
inst.stopBrief = onBriefing((b) => briefRead(inst, b)); // the decisions that wait in Briefing, and the drafts the Review view carries
|
|
298
|
+
loadBriefing();
|
|
283
299
|
load(inst);
|
|
284
300
|
}
|
|
285
301
|
|
|
@@ -287,6 +303,7 @@ export function disposePmWork(host) {
|
|
|
287
303
|
const inst = insts.get(host);
|
|
288
304
|
if (!inst) return;
|
|
289
305
|
if (inst.stopPhone) inst.stopPhone();
|
|
306
|
+
if (inst.stopBrief) inst.stopBrief();
|
|
290
307
|
disposeCases(inst);
|
|
291
308
|
inst.timers.forEach((t) => clearTimeout(t));
|
|
292
309
|
inst.root.remove();
|
|
@@ -435,7 +452,7 @@ function addLine(inst) {
|
|
|
435
452
|
const r = await press(inst, API + '/add', { line }, { quiet: true });
|
|
436
453
|
if (!r) return;
|
|
437
454
|
input.value = ''; chips.replaceChildren(...previewChips(inst, ''));
|
|
438
|
-
inst.lens = workPlace(o, before); if (inst.ctx.onLens) inst.ctx.onLens(inst.lens);
|
|
455
|
+
inst.lens = workPlace(o, before); if (inst.ctx.onLens) inst.ctx.onLens(inst.lens);
|
|
439
456
|
if (r.item) inst.opens.add(r.item.id);
|
|
440
457
|
say({ text: 'Added to ' + labelOf(inst.lens), timeout: r.undo_ms || 6000, actionLabel: COPY.undo, onAction: async () => { if (r.item) await press(inst, API + '/remove', { id: r.item.id }, { quiet: true }); await load(inst); } });
|
|
441
458
|
await load(inst);
|
|
@@ -741,7 +758,7 @@ function swipeSpec(inst, item, lens) {
|
|
|
741
758
|
function expandRow(inst, row, key, build, onClose) {
|
|
742
759
|
const xp = makeXp();
|
|
743
760
|
const xi = xp.firstChild;
|
|
744
|
-
attachExpand(row, xp, {
|
|
761
|
+
row.__exp = attachExpand(row, xp, {
|
|
745
762
|
key, initial: inst.opens.has(key),
|
|
746
763
|
onToggle: (on) => {
|
|
747
764
|
if (on) {
|
|
@@ -761,49 +778,73 @@ const KIND_ICON = { reply: 'reply', decide: 'scale', review: 'eye', do: 'check'
|
|
|
761
778
|
/* The mockup's time-left bar (.fuse) with its text, for an item that carries `left` ({ text, pct, tone }). */
|
|
762
779
|
const fuseBar = (l) => { const c = l.tone === 'warn' ? 'var(--warning)' : 'var(--acc)'; return el('div', { class: 'chips2', style: 'margin-top:6px;gap:8px' }, el('div', { class: 'fuse' }, el('i', { style: 'width:' + Math.round(l.pct * 100) + '%;background:' + c })), el('span', { class: 'mono', style: 'font-size:var(--fs-1);color:' + c }, l.text)); };
|
|
763
780
|
|
|
781
|
+
/* "yesterday", "7 days ago": how long ago a day was (the mockup's ago()). */
|
|
782
|
+
const ago = (from, today) => { const n = daysBetween(from, today); return n <= 0 ? 'today' : n === 1 ? 'yesterday' : n + ' days ago'; };
|
|
783
|
+
|
|
784
|
+
/* The due chip of a row (the mockup's dueChip): a tag for overdue and for today, a calendar chip for a later day. */
|
|
785
|
+
function dueChip(inst, item) {
|
|
786
|
+
if (!item.due) return null;
|
|
787
|
+
const today = inst.data.today; const due = String(item.due).slice(0, 10);
|
|
788
|
+
if (due < today) return el('span', { class: 'tag cal3-tone-bad', 'data-pm-due': 'overdue' }, COPY.overdue(daysBetween(due, today)));
|
|
789
|
+
if (due === today) return el('span', { class: 'tag cal3-tone-warn', 'data-pm-due': 'today' }, COPY.dueToday + (item.time ? ' ' + item.time : ''));
|
|
790
|
+
return el('span', { class: 'chip2', 'data-pm-due': 'later' }, c3ic('cal', 'sm'), COPY.dueOn(dayLabel(due)));
|
|
791
|
+
}
|
|
792
|
+
|
|
764
793
|
/* The chips the Work page adds to a row: outside PMOS and the person, Not before, Flagged, how often it moved, Looks done, the team task it advances. */
|
|
765
|
-
function workChips(inst, item) {
|
|
794
|
+
function workChips(inst, item, o = {}) {
|
|
766
795
|
const today = inst.data.today; const c = [];
|
|
767
796
|
if (item.kind === 'offline') c.push(el('span', { class: 'tag wk-off', 'data-pm-offline-tag': item.id }, COPY.offline), el('span', { class: 'chip2' }, c3ic('user', 'sm'), item.person || 'someone'));
|
|
768
|
-
if (item.defer && item.defer > today) c.push(el('span', { class: 'tag wk-nb', 'data-pm-notbefore': item.id }, COPY.notBeforeChip(dayLabel(item.defer))));
|
|
769
|
-
if (item.flagged) c.push(el('span', { class: '
|
|
770
|
-
if (item.
|
|
797
|
+
if (item.defer && item.defer > today) c.push(el('span', { class: 'tag wk-nb cal3-up', 'data-pm-notbefore': item.id }, COPY.notBeforeChip(dayLabel(item.defer))));
|
|
798
|
+
if (item.flagged && !o.noFlag) c.push(el('span', { class: 'tag cal3-up', 'data-pm-flagged': item.id }, COPY.flagged));
|
|
799
|
+
if (item.recurring) c.push(el('span', { class: 'chip2' }, c3ic('undo', 'sm'), COPY.repeats(item.recurring)));
|
|
771
800
|
if (item.not_yet_count) c.push(el('span', { class: 'tag' }, COPY.moved(item.not_yet_count)));
|
|
772
801
|
if (item.kind === 'offline' && item.evidence) c.push(el('span', { class: 'st warn', 'data-pm-looks-done': item.id }, c3ic('eye', 'sm'), COPY.looksDone));
|
|
773
802
|
if (item.resolves) c.push(el('span', { class: 'chip2', title: RESOLVES_TIP, 'data-pm-resolves': item.resolves }, c3ic('init', 'sm'), COPY.advances(resolvesTitle(inst, item))));
|
|
774
803
|
return c;
|
|
775
804
|
}
|
|
776
805
|
|
|
777
|
-
/* The mockup's workRow(): li.nrow.wrow > .rv.l, .rv.r (made by the swipe), .ki, .nb (.tt, .chips, .xp > .xi), and the .sg glyphs (line 3258). */
|
|
778
|
-
function itemRow(inst, item, lens) {
|
|
806
|
+
/* The mockup's workRow(): li.nrow.wrow > .rv.l, .rv.r (made by the swipe), .ki, .nb (.tt, .chips, .xp > .xi), the .acts button and the .sg glyphs (line 3258). */
|
|
807
|
+
function itemRow(inst, item, lens, o = {}) {
|
|
779
808
|
const j = item.job && jobById(inst, item.job);
|
|
809
|
+
const today = inst.data.today;
|
|
780
810
|
const chips = [];
|
|
781
811
|
/* An item with `mock_row` (the /c3.html demo mode's mockup rows) is drawn as the mockup draws a work row: agent move, effort, time, job, source, initiative. */
|
|
782
812
|
const mock = item.mock_row === true;
|
|
783
813
|
const rs = inst.runs.get(item.id);
|
|
784
814
|
const agent = mock && item.agent_move && (item.ix || {}).move;
|
|
815
|
+
const overdue = !!item.due && String(item.due).slice(0, 10) < today;
|
|
785
816
|
if (rs && rs.phase === 'work') chips.push(el('span', { class: 'st c3strip', 'data-sxs': item.id }, el('span', { class: 'c3spin' }, c3ic('spark', 'sm')), rs.steps[Math.min(rs.i, rs.steps.length - 1)] + ' · ' + (Math.min(rs.i, rs.steps.length - 1) + 1) + ' of ' + rs.steps.length));
|
|
786
817
|
else if (rs && rs.phase === 'done') chips.push(el('span', { class: 'st ok' }, c3ic('check', 'sm'), 'agent: result ready'));
|
|
787
818
|
else if (inst.planning.has(item.id)) chips.push(el('span', { class: 'st' }, el('span', { class: 'c3spin' }, c3ic('spark', 'sm')), 'planning the steps'));
|
|
819
|
+
if (item.waiting_on) {
|
|
820
|
+
const since = item.waiting_since ? String(item.waiting_since).slice(0, 10) : '';
|
|
821
|
+
chips.push(el('span', { class: 'chip2', 'data-pm-waiting-chip': item.id }, c3ic('wait', 'sm'), COPY.waitingSince(item.waiting_on, since ? dayLabel(since) : '', since ? ago(since, today) : '')));
|
|
822
|
+
}
|
|
823
|
+
chips.push(dueChip(inst, item));
|
|
824
|
+
chips.push(...workChips(inst, item, { noFlag: o.noFlag }));
|
|
788
825
|
if (mock && item.agent_move && !(rs && rs.phase === 'done')) chips.push(el('span', { class: 'chip2', style: 'gap:4px', title: 'Swipe right to let the agent act' }, c3ic('spark', 'sm'), 'agent move'));
|
|
789
826
|
if (mock && item.mins) chips.push(el('span', { class: 'chip2', title: 'Effort' }, c3ic('clock', 'sm'), item.mins <= 2 ? 'under 2 min' : item.mins + ' min'));
|
|
790
|
-
if (mock && item.time) chips.push(el('span', { class: 'chip2' }, c3ic('cal', 'sm'), item.time));
|
|
791
|
-
if (item.due) chips.push(el('span', { class: 'chip2' }, c3ic('cal', 'sm'), item.due));
|
|
792
827
|
if (item.priority) chips.push(el('span', { class: 'fl ' + item.priority.toLowerCase() }, item.priority));
|
|
793
|
-
if (item.waiting_on) chips.push(el('span', { class: 'chip2' }, c3ic('wait', 'sm'), COPY.waitingOn(item.waiting_on)));
|
|
794
|
-
chips.push(...workChips(inst, item));
|
|
795
828
|
if ((item.steps || []).length) { const dn = item.steps.filter((s) => s.done).length; const ag = item.steps.filter((s) => s.who !== 'you' && !s.done).length; chips.push(el('span', { class: 'chip2', title: 'Steps' }, c3ic('tree', 'sm'), dn + ' of ' + item.steps.length + ' steps' + (ag ? ' · agent can do ' + ag : ''))); }
|
|
796
829
|
if (j) chips.push(el('span', { class: 'chip2 jc', title: j.want }, c3ic('target', 'sm'), mock && j.mock_name ? j.mock_name : j.want));
|
|
797
830
|
if (mock && item.src) chips.push(el('span', { class: 'tag' }, item.src));
|
|
798
831
|
if (mock && item.init) chips.push(el('span', { class: 'chip2' }, c3ic('init', 'sm'), item.init.length > 16 ? item.init.slice(0, 15) + '…' : item.init));
|
|
799
832
|
const title = el('span', { class: 'tt', 'data-pm-title': item.id }, item.title);
|
|
800
833
|
const spec = swipeSpec(inst, item, lens);
|
|
801
|
-
const recL = itemCase(inst, item, lens).model.opts.find((
|
|
834
|
+
const recL = itemCase(inst, item, lens).model.opts.find((q) => q.rec);
|
|
802
835
|
const recSide = agent ? 'r' : (recL && /^(drop|keep|wait|defer|someday)/.test(recL.id) ? 'l' : 'r');
|
|
803
|
-
|
|
836
|
+
/* the row icon (the mockup): user for work outside PMOS, flag when flagged, wait in Waiting, check otherwise */
|
|
837
|
+
const icon = mock ? (KIND_ICON[item.kind] || 'check') : item.kind === 'offline' ? 'user' : item.flagged ? 'flag' : lens === 'waiting' ? 'wait' : 'check';
|
|
838
|
+
const hot = overdue || item.flagged || item.priority === 'P1';
|
|
839
|
+
/* the right side (mockup W8): Decide in Inbox, Nudge in Waiting, nothing in Someday, a Done check everywhere else */
|
|
840
|
+
let right = null;
|
|
841
|
+
if (lens === 'inbox') right = button({ label: COPY.decide, variant: 'ghost', size: 'sm', icon: 'next', attrs: { 'data-pm-decide': item.id }, onClick: (e) => { e.stopPropagation(); if (row.__exp) row.__exp.toggle(); } });
|
|
842
|
+
else if (lens === 'waiting') right = c3ib(COPY.nudge, 'out', { 'data-pm-nudge': item.id, onClick: (e) => { e.stopPropagation(); say({ text: COPY.nudged(item.waiting_on || 'them'), timeout: 4000 }); } }, 'sm');
|
|
843
|
+
else if (lens !== 'someday') right = c3ib(COPY.doneBtn, 'check', { 'data-pm-done': item.id, onClick: (e) => { e.stopPropagation(); doneAct(inst, item, row, 'Done.'); } }, 'sm');
|
|
804
844
|
const row = el('li', { class: 'nrow wrow', 'data-row': '', 'data-id': item.id, 'data-w': '1', 'data-pm-item': item.id, 'data-lens': lens },
|
|
805
|
-
el('span', { class: 'ki' + (
|
|
806
|
-
el('div', { class: 'nb' }, title, mock && item.left ? fuseBar(item.left) : null, chips.length ? el('div', { class: 'chips' }, chips) : null),
|
|
845
|
+
el('span', { class: 'ki' + (hot ? ' hot' : ''), title: item.kind === 'offline' ? 'Happens outside PMOS' : item.priority || '' }, c3ic(icon)),
|
|
846
|
+
el('div', { class: 'nb' }, title, mock && item.left ? fuseBar(item.left) : null, chips.filter(Boolean).length ? el('div', { class: 'chips' }, chips.filter(Boolean)) : null),
|
|
847
|
+
right ? el('div', { class: 'acts' }, right) : el('span'),
|
|
807
848
|
el('span', { class: 'sg', title: agent ? 'Drag right: the agent acts. Drag left: reply.' : 'Drag right: ' + spec.right.label + '. Drag left: ' + spec.left.label + '.' },
|
|
808
849
|
el('span', { class: 'g l' + (recSide === 'l' ? ' y' : '') }, c3ic(spec.left.icon)), el('span', { class: 'g r' + (recSide === 'r' ? ' y' : '') }, c3ic(spec.right.icon))));
|
|
809
850
|
const xp = expandRow(inst, row, item.id, () => caseFile(inst, item, lens));
|
|
@@ -825,52 +866,105 @@ function itemRow(inst, item, lens) {
|
|
|
825
866
|
return row;
|
|
826
867
|
}
|
|
827
868
|
|
|
828
|
-
/* ── Focus:
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
el('
|
|
869
|
+
/* ── Focus: the right-hand column of every view (console3-work-parity R2, R3; Flow today removed by console3-deck-flow-fixes) ── */
|
|
870
|
+
|
|
871
|
+
/* One Focus row (the mockup's focusHTML row): target icon, title, progress, the reason, the open items, and Accept / Edit / Not today. A row that
|
|
872
|
+
* has a job opens the job's sheet (R3): the whole row, or its job chip for the keyboard. */
|
|
873
|
+
function focusRow(inst, f, pinned) {
|
|
874
|
+
const editing = inst.focEdit.has(f.key);
|
|
875
|
+
const input = editing ? el('input', { type: 'text', 'aria-label': COPY.editTitle, 'data-pm-focus-input': f.key, autocomplete: 'off' }) : null;
|
|
876
|
+
if (input) input.value = f.title;
|
|
877
|
+
const save = () => focusAccept(inst, { ...f, title: (input && input.value.trim()) || f.title });
|
|
878
|
+
const cancel = () => { inst.focEdit.delete(f.key); draw(inst); };
|
|
879
|
+
const edit = () => { inst.focEdit.add(f.key); draw(inst); later(inst, () => { const i = inst.root.querySelector('[data-pm-focus-input="' + f.key + '"]'); if (i) i.focus(); }, 30); };
|
|
880
|
+
if (input) input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.isComposing) save(); else if (e.key === 'Escape') cancel(); });
|
|
881
|
+
const job = f.job && jobById(inst, f.job);
|
|
882
|
+
const refs = (f.item_ids || []).map((id) => itemById(inst, id)).filter((i) => i && !i.done && i.title !== f.title); // a one-item job does not list itself
|
|
883
|
+
const total = job ? job.total || 0 : (f.item_ids || []).length; const done = job ? job.done || 0 : 0;
|
|
884
|
+
const btns = editing ? [pill(COPY.save, null, { class: 'pill', 'data-pm-focus-save': f.key, onclick: save }), pill(COPY.cancel, null, { 'data-pm-focus-cancel': f.key, onclick: cancel })]
|
|
885
|
+
: pinned ? [pill(COPY.edit, null, { 'data-pm-focus-edit': f.key, onclick: edit }), pill(COPY.unpin, null, { 'data-pm-focus-clear': f.key, onclick: () => focusClear(inst, f) }),
|
|
886
|
+
pill(COPY.notToday, null, { 'data-pm-focus-dismiss': f.key, onclick: () => focusDismiss(inst, f) })]
|
|
887
|
+
: [pill(COPY.accept, 'check', { class: 'pill', 'data-pm-focus-accept': f.key, onclick: () => focusAccept(inst, f) }), pill(COPY.edit, null, { 'data-pm-focus-edit': f.key, onclick: edit }),
|
|
888
|
+
pill(COPY.notToday, null, { 'data-pm-focus-dismiss': f.key, onclick: () => focusDismiss(inst, f) })];
|
|
889
|
+
const pfill = el('i'); pfill.style.setProperty('--wl-w', (total ? Math.round((done / total) * 100) : 0) + '%');
|
|
890
|
+
const openJob = () => openJobSheet(inst, job);
|
|
891
|
+
const li = el('li', { class: 'nrow wkfr', 'data-pm-focus-card': f.key, 'data-pm-focus-state': pinned ? 'accepted' : 'suggested', 'data-row': '' },
|
|
892
|
+
el('span', { class: 'ki' + (pinned ? ' hot' : '') }, c3ic('target')),
|
|
893
|
+
el('div', { class: 'nb' },
|
|
894
|
+
editing ? el('span', { class: 'form' }, input) : el('span', { class: 'tt', title: f.why || '', 'data-pm-focus-title': f.key }, f.title),
|
|
895
|
+
el('div', { class: 'chips' }, el('div', { class: 'pbar wkpb' }, pfill),
|
|
896
|
+
el('span', { class: 'mono dim wk-s' }, COPY.progress(done, total)),
|
|
897
|
+
pinned ? el('span', { class: 'tag cal3-up wk-on' }, COPY.focusPinned) : null,
|
|
898
|
+
job ? pill(job.mock_name || job.want, 'target', { class: 'pill wkjob', title: COPY.jobSheet, 'data-pm-focus-job': job.id, onclick: (e) => { e.stopPropagation(); openJob(); } }) : null),
|
|
899
|
+
f.why ? el('div', { class: 'dim wk-s wkfc-why' }, COPY.why(f.why)) : null,
|
|
900
|
+
refs.length ? el('div', { class: 'chips' }, refs.map((i) => el('span', { class: 'chip2' }, i.title))) : null,
|
|
901
|
+
el('div', { class: 'chips2 wkq wkfr-b' }, btns)),
|
|
902
|
+
el('span'));
|
|
903
|
+
return li;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function focusSection(inst) {
|
|
907
|
+
const F = inst.data.focus || { suggestions: [], accepted: [], dismissed: [] };
|
|
908
|
+
const acc = F.accepted || []; const sug = (F.suggestions || []).slice(0, 3); const hidden = F.dismissed || [];
|
|
909
|
+
const n = acc.length + sug.length;
|
|
910
|
+
const restore = async () => {
|
|
911
|
+
for (const k of hidden) await press(inst, API + '/focus/clear', { key: k }, { quiet: true });
|
|
912
|
+
say({ text: COPY.broughtBack, timeout: 2500 });
|
|
913
|
+
await load(inst);
|
|
854
914
|
};
|
|
915
|
+
const foot = hidden.length ? el('div', { class: 'fold', 'data-pm-focus-hidden': '' }, COPY.markedNot(hidden.length), ' ', pill(COPY.bringBack2, null, { 'data-pm-focus-restore': '', onclick: restore })) : null;
|
|
916
|
+
const add = c3ib(COPY.newJob, 'plus', { 'data-pm-job-add-toggle': '', onClick: () => openNewJob(inst) }, 'sm');
|
|
855
917
|
return el('section', { class: 'card sect wkfoc', 'data-pm-focus': '' },
|
|
856
|
-
el('div', { class: 'sh2' }, el('h2', null, c3ic('target', 'sm'), COPY.focus
|
|
857
|
-
|
|
918
|
+
el('div', { class: 'sh2' }, el('h2', null, c3ic('target', 'sm'), COPY.focus, ' ', el('span', { class: 'bdg g' }, String(n))), el('span', { class: 'r' }, add)),
|
|
919
|
+
n ? el('ul', { class: 'list', role: 'list' }, ...acc.map((f) => focusRow(inst, f, true)), ...sug.map((f) => focusRow(inst, f, false)))
|
|
920
|
+
: el('div', { class: 'fold', 'data-pm-focus-none': '' }, c3ic('check', 'sm'), COPY.focusNone),
|
|
921
|
+
foot);
|
|
858
922
|
}
|
|
859
923
|
|
|
860
|
-
/* ── Upcoming:
|
|
924
|
+
/* ── Upcoming: Next 7 days by day, then a Later card (console3-work-parity R6) ──────────────────────────────────────── */
|
|
925
|
+
|
|
926
|
+
const legendEl = () => el('span', { class: 'fl2', 'data-pm-legend': '' }, COPY.legend.map((w, k) => el('span', null, ['• ', '× '][k] + w)));
|
|
861
927
|
|
|
862
928
|
function upcomingMain(inst, list) {
|
|
863
929
|
const today = inst.data.today;
|
|
864
|
-
const block = (key, label, sub, its) => el('div', { class: 'wkday', 'data-pm-day': key },
|
|
865
|
-
el('h3', null, label, sub ? el('span', { class: 'dim' }, sub) : null, its.length ? el('span', { class: 'bdg g' }, String(its.length)) : null),
|
|
866
|
-
its.length ? el('ul', { class: 'list', role: 'list' }, its.map((i) => itemRow(inst, i, 'upcoming'))) : el('div', { class: 'wkday-none' }, 'Nothing'));
|
|
867
930
|
const at = (i) => { const a = arrives(i, today); return a ? Math.max(1, daysBetween(today, a)) : 99; };
|
|
931
|
+
const near = list.filter((i) => at(i) <= 7); const far = list.filter((i) => at(i) > 7);
|
|
868
932
|
const days = [];
|
|
869
|
-
for (let n = 1; n <= 7; n++) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
933
|
+
for (let n = 1; n <= 7; n++) {
|
|
934
|
+
const d = addIso(today, n); const its = near.filter((i) => at(i) === n);
|
|
935
|
+
days.push(el('div', { class: 'wkday', 'data-pm-day': d },
|
|
936
|
+
el('h3', { class: 'wkdayh' }, n === 1 ? COPY.tomorrowHead + ' ' : null, el('span', { class: 'dim' }, dayLabel(d))),
|
|
937
|
+
its.length ? el('ul', { class: 'list', role: 'list' }, its.map((i) => itemRow(inst, i, 'upcoming'))) : el('div', { class: 'dim wkday-none' }, COPY.clear)));
|
|
938
|
+
}
|
|
939
|
+
const nextSec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic('cal', 'sm'), COPY.sect.next7, ' ', el('span', { class: 'bdg g' }, String(near.length))), legendEl()), el('div', { class: 'wkdays' }, days));
|
|
940
|
+
nextSec.setAttribute('data-pm-list', 'upcoming');
|
|
941
|
+
const laterSec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic('moon', 'sm'), COPY.sect.later, ' ', el('span', { class: 'bdg g' }, String(far.length))), el('span', { class: 'dim wk-s' }, COPY.beyond)),
|
|
942
|
+
far.length ? el('ul', { class: 'list', role: 'list' }, far.map((i) => itemRow(inst, i, 'upcoming'))) : el('ul', { class: 'list' }, el('li', { class: 'fold' }, c3ic('check', 'sm'), COPY.nothingLater)));
|
|
943
|
+
laterSec.setAttribute('data-pm-day', 'later');
|
|
944
|
+
return [nextSec, laterSec];
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/* ── Today: Overdue, Due today and Flagged, each its own card (console3-work-parity R5) ─────────────────────────────── */
|
|
948
|
+
|
|
949
|
+
function todayMain(inst, list) {
|
|
950
|
+
const today = inst.data.today; const dayOf = (i) => (i.due ? String(i.due).slice(0, 10) : '');
|
|
951
|
+
const byDue = (a, b) => (dayOf(a) < dayOf(b) ? -1 : dayOf(a) > dayOf(b) ? 1 : String(a.time || '') < String(b.time || '') ? -1 : String(a.time || '') > String(b.time || '') ? 1 : 0);
|
|
952
|
+
const over = list.filter((i) => dayOf(i) && dayOf(i) < today).sort(byDue);
|
|
953
|
+
const due = list.filter((i) => dayOf(i) === today).sort(byDue);
|
|
954
|
+
const flag = list.filter((i) => !over.includes(i) && !due.includes(i));
|
|
955
|
+
if (!list.length) {
|
|
956
|
+
const sec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic('sun', 'sm'), labelOf('today'), ' ', el('span', { class: 'bdg g' }, '0'))), el('ul', { class: 'list' }, el('li', { class: 'fold' }, c3ic('check', 'sm'), COPY.emptyView.today)));
|
|
957
|
+
sec.setAttribute('data-pm-list', 'today');
|
|
958
|
+
return [sec];
|
|
959
|
+
}
|
|
960
|
+
let first = true;
|
|
961
|
+
return [['overdue', 'alert', COPY.sect.overdue, over], ['due', 'sun', COPY.sect.dueToday, due], ['flagged', 'flag', COPY.sect.flagged, flag]].filter((g) => g[3].length).map(([key, ic, title, its]) => {
|
|
962
|
+
const sec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic(ic, 'sm'), title, ' ', el('span', { class: 'bdg g' }, String(its.length))), first ? legendEl() : null),
|
|
963
|
+
el('ul', { class: 'list', role: 'list' }, its.map((i) => itemRow(inst, i, 'today', { noFlag: key === 'flagged' }))));
|
|
964
|
+
first = false;
|
|
965
|
+
sec.setAttribute('data-pm-list', 'today'); sec.setAttribute('data-pm-sect', key);
|
|
966
|
+
return sec;
|
|
967
|
+
});
|
|
874
968
|
}
|
|
875
969
|
|
|
876
970
|
/* ── Review: a weekly pass, one item at a time (PRD R7) ────────────────────────────────────────────────────────────── */
|
|
@@ -932,18 +1026,18 @@ function jobAll(inst, job) {
|
|
|
932
1026
|
}
|
|
933
1027
|
|
|
934
1028
|
/* The job's case file (mockup C3.cases.job, line 4660): the words, the count, Keep / Delete and move / Delete and leave, slide to confirm. */
|
|
935
|
-
function jobCase(inst, j) {
|
|
1029
|
+
function jobCase(inst, j, close) {
|
|
936
1030
|
const its = (inst.data.items || []).filter((i) => i.job === j.id);
|
|
937
1031
|
const done = its.filter((i) => i.done).length;
|
|
938
1032
|
const others = (inst.data.jobs || []).filter((q) => q.id !== j.id); const near = others[0];
|
|
939
1033
|
const nm = (q) => q.mock_name || q.want;
|
|
940
|
-
const del = (reassign) => async () => { const r = await press(inst, JOBS + '/delete', { id: j.id, ...(reassign ? { reassign_to: reassign } : {}) }); if (r) say({ text: COPY.reassigned(r.reassigned || 0), timeout: 4000 }); };
|
|
1034
|
+
const del = (reassign) => async () => { const r = await press(inst, JOBS + '/delete', { id: j.id, ...(reassign ? { reassign_to: reassign } : {}) }); if (r) { say({ text: COPY.reassigned(r.reassigned || 0), timeout: 4000 }); close(); } };
|
|
941
1035
|
const model = { id: 'job:' + j.id, sit: COPY.jobLine(j), conf: 90, cta: 'Slide to keep it',
|
|
942
1036
|
chg: [['target', done + ' of ' + its.length + ' done', 'count across every view', 'ok'], ['info', 'Change the words below. They save at once and you can undo for 7 seconds.', '']],
|
|
943
1037
|
opts: [{ id: 'keep', l: 'Keep it', rec: 1, fx: [fx('check', 'Nothing changes', 'ok')] },
|
|
944
1038
|
{ id: 'mv', l: near ? 'Delete it and move its ' + its.length + ' item' + (its.length === 1 ? '' : 's') + ' to "' + nm(near) + '"' : 'Delete it', rec: 0, fx: [fx('trash', 'The job is gone', 'warn'), fx('target', its.length + ' items change job', 'warn')] },
|
|
945
1039
|
{ id: 'nj', l: 'Delete it and leave its items without a job', rec: 0, fx: [fx('trash', 'The job is gone', 'warn'), fx('alert', 'The items have no job', '')] }] };
|
|
946
|
-
const run = { keep: () => say({ text: 'Kept.', timeout: 2500 }), mv: del(near && near.id), nj: del(null) };
|
|
1040
|
+
const run = { keep: () => { say({ text: 'Kept.', timeout: 2500 }); close(); }, mv: del(near && near.id), nj: del(null) };
|
|
947
1041
|
const root = renderCase(model, { onConfirm: ({ option }) => { if (run[option.id]) run[option.id](); } });
|
|
948
1042
|
root.setAttribute('data-pm-case', j.id);
|
|
949
1043
|
root.querySelectorAll('.opt .rk').forEach((n) => { n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); n.removeAttribute('title'); }); // a job carries no confidence figure: never show a made-up percentage
|
|
@@ -954,9 +1048,9 @@ function jobCase(inst, j) {
|
|
|
954
1048
|
}
|
|
955
1049
|
|
|
956
1050
|
/* The "+ New job" draft (mockup c3jdraft, line 4678): one line in, the agent-style draft of the four fields out, then "Slide to create". */
|
|
957
|
-
function jobNewCase(inst) {
|
|
958
|
-
const x =
|
|
959
|
-
const d =
|
|
1051
|
+
function jobNewCase(inst, st, close) {
|
|
1052
|
+
const x = st.line || 'keep the week calm';
|
|
1053
|
+
const d = st.draft;
|
|
960
1054
|
const inputs = ['when', 'want', 'so'].map((k) => el('input', { type: 'text', 'aria-label': k === 'when' ? 'When' : k === 'want' ? 'I want' : 'So', 'data-pm-job-new': k, value: d[k], autocomplete: 'off' }));
|
|
961
1055
|
const val = (k) => inputs[['when', 'want', 'so'].indexOf(k)].value.trim();
|
|
962
1056
|
const demo = DEMO(); // demo: the mockup's draft also names the job (its "Job" field); the server has no name field
|
|
@@ -965,53 +1059,48 @@ function jobNewCase(inst) {
|
|
|
965
1059
|
chg: [['spark', 'Drafted by the agent', 'assumption', 'warn']],
|
|
966
1060
|
opts: [{ id: 'mk', l: 'Create the job', rec: 1, fx: [fx('target', 'Items can now be filed under it', 'ok')] }, { id: 'no', l: 'Not now', rec: 0, fx: [] }] };
|
|
967
1061
|
const root = renderCase(model, { onConfirm: async ({ option }) => {
|
|
968
|
-
if (option.id === 'no') {
|
|
1062
|
+
if (option.id === 'no') { say({ text: 'Dropped the draft.', timeout: 2500 }); close(); return; }
|
|
969
1063
|
const line = 'when ' + val('when') + ', I want ' + val('want') + ', so ' + val('so');
|
|
970
1064
|
const r = await press(inst, JOBS + '/add', nameIn ? { line, name: nameIn.value.trim() } : { line });
|
|
971
|
-
if (r) {
|
|
1065
|
+
if (r) { say({ text: 'Job created. Add work to it by saying which job it serves.', timeout: 3000 }); close(); await load(inst); }
|
|
972
1066
|
} });
|
|
973
1067
|
if (!demo) {
|
|
974
1068
|
root.querySelectorAll('.opt .rk').forEach((n) => { n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); n.removeAttribute('title'); });
|
|
975
1069
|
root.querySelectorAll('.opt .conf').forEach((n) => n.remove());
|
|
976
1070
|
}
|
|
977
1071
|
root.querySelector('.sit').after(el('div', { class: 'c3jf' }, nameIn ? field('Job', nameIn) : null, ...['when', 'want', 'so'].map((k, i) => field(k === 'when' ? 'When' : k === 'want' ? 'I want' : 'So', inputs[i]))));
|
|
978
|
-
inst.cases.add(root);
|
|
979
1072
|
return root;
|
|
980
1073
|
}
|
|
981
1074
|
|
|
982
|
-
/* The
|
|
983
|
-
function
|
|
984
|
-
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
});
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return el('section', { class: 'card sect', 'data-pm-jobs': '' },
|
|
1007
|
-
el('div', { class: 'sh2' }, el('h2', null, c3ic('target', 'sm'), COPY.jobs), el('span', { class: 'r' }, plus)), // no count: the mockup's title has none
|
|
1008
|
-
js.length || lines.length ? el('ul', { class: 'list', role: 'list' }, ...lines, ...rows) : el('div', { class: 'dim mono', style: small }, COPY.noJobs),
|
|
1009
|
-
el('div', { class: 'dim', style: small }, COPY.jobsNote));
|
|
1075
|
+
/* The job's sheet (console3-work-parity R3): a Focus row opens it. The case file lives in the sheet, so a redraw of the page does not dispose it. */
|
|
1076
|
+
function openJobSheet(inst, job) {
|
|
1077
|
+
let h = null;
|
|
1078
|
+
const body = jobCase(inst, job, () => h && h.close());
|
|
1079
|
+
h = openSheet({ title: COPY.jobSheet, icon: 'target', body, onClose: () => { try { body.c3case.dispose(); } catch { /* already gone */ } } });
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/* The New job button of the Focus section: one line in, the draft of the three fields out, then "Slide to create", all in a sheet. */
|
|
1083
|
+
function openNewJob(inst) {
|
|
1084
|
+
let h = null; let case2 = null;
|
|
1085
|
+
const holder = el('div', { 'data-pm-job-new-sheet': '' });
|
|
1086
|
+
const close = () => h && h.close();
|
|
1087
|
+
const input = el('input', { type: 'text', placeholder: 'What are you trying to get done? One line', 'aria-label': 'New job', autocomplete: 'off', 'data-pm-add': JOBS + '/add' });
|
|
1088
|
+
const go = () => {
|
|
1089
|
+
const x = (input.value || '').trim() || 'keep the week calm';
|
|
1090
|
+
const st = { line: x, draft: { when: 'it comes up', want: 'a clear way to ' + x.toLowerCase().replace(/^to /, '').replace(/\.$/, ''), so: 'the outcome is not left to chance' } };
|
|
1091
|
+
case2 = jobNewCase(inst, st, close);
|
|
1092
|
+
holder.replaceChildren(case2);
|
|
1093
|
+
later(inst, () => { const i = holder.querySelector('[data-pm-job-new="name"], [data-pm-job-new="when"]'); if (i) i.focus(); }, 30);
|
|
1094
|
+
};
|
|
1095
|
+
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.isComposing) go(); });
|
|
1096
|
+
holder.append(el('div', { class: 'form' }, input, c3ib('Draft it', 'spark', { 'data-pm-job-draft': '', onClick: go }, 'pri')));
|
|
1097
|
+
h = openSheet({ title: COPY.newJob, icon: 'target', body: holder, onClose: () => { try { if (case2) case2.c3case.dispose(); } catch { /* already gone */ } } });
|
|
1098
|
+
later(inst, () => input.focus(), 30);
|
|
1010
1099
|
}
|
|
1011
1100
|
|
|
1012
1101
|
/* ── the page ────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
1013
1102
|
|
|
1014
|
-
/* The mockup's workHead() calbar: the .seg view bar with a count on every view
|
|
1103
|
+
/* The mockup's workHead() calbar: the .seg view bar with a count on every view (line 3281). */
|
|
1015
1104
|
function lensBar(inst) {
|
|
1016
1105
|
const seg = el('div', { class: 'seg', role: 'group', 'aria-label': 'Work view', 'data-pm-lenses': '' },
|
|
1017
1106
|
LENSES.map(([id, label]) => {
|
|
@@ -1023,16 +1112,7 @@ function lensBar(inst) {
|
|
|
1023
1112
|
badge.setAttribute('data-pm-count', id);
|
|
1024
1113
|
return b;
|
|
1025
1114
|
}));
|
|
1026
|
-
|
|
1027
|
-
grp.addEventListener('click', () => { inst.group = !inst.group; draw(inst); });
|
|
1028
|
-
return [seg, el('div', { class: 'chips2' }, grp)];
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
/* The ring at the right of Today's header: done today of done today plus what is still in Today. */
|
|
1032
|
-
function todayRing(inst) {
|
|
1033
|
-
const d = inst.data;
|
|
1034
|
-
const doneToday = (d.items || []).filter((i) => i.done && String(i.done_at || '').slice(0, 10) === d.today).length;
|
|
1035
|
-
return c3ring(doneToday, doneToday + shown(inst, 'today').length, 44);
|
|
1115
|
+
return [seg];
|
|
1036
1116
|
}
|
|
1037
1117
|
|
|
1038
1118
|
/* A .card.sect with an .sh2 title row and a ul.list of rows (line 3262). */
|
|
@@ -1040,6 +1120,18 @@ function sect(head, rows) { return el('section', { class: 'card sect' }, head, r
|
|
|
1040
1120
|
|
|
1041
1121
|
const FOCUS_ATTRS = ['data-pm-edit', 'data-pm-step-add', 'data-pm-job-edit', 'data-pm-agent-reply', 'data-pm-job-new', 'data-pm-outcome', 'data-pm-wait-on', 'data-pm-focus-input'];
|
|
1042
1122
|
|
|
1123
|
+
/* What the briefing tells this page: how many decisions wait in Briefing (the header says so), and the drafts section that Review carries (decision 1). */
|
|
1124
|
+
function briefRead(inst, b) {
|
|
1125
|
+
const decide = ((b && b.cards) || []).filter((c) => c && (c.move || c.tag === 'Critical'));
|
|
1126
|
+
const n = b && Array.isArray(b.needs) && b.needs.length ? b.needs.length : decide.length;
|
|
1127
|
+
const rv = b && b.review ? b.review : null;
|
|
1128
|
+
const sig = JSON.stringify([n, rv]);
|
|
1129
|
+
if (sig === inst.briefSig) return;
|
|
1130
|
+
inst.briefSig = sig; inst.decisions = n;
|
|
1131
|
+
inst.draftsHost.replaceChildren(...(rv ? [reviewSection(rv)] : []));
|
|
1132
|
+
if (inst.data && insts.get(inst.host) === inst) draw(inst);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1043
1135
|
function draw(inst) {
|
|
1044
1136
|
if (inst.lens !== inst.prevLens) { if (inst.lens === 'review') inst.rv.ids = null; inst.prevLens = inst.lens; } // a fresh Review pass each time the view is opened
|
|
1045
1137
|
/* keep the focus: a field the PM is typing in, or the row they were on, is found again after the redraw */
|
|
@@ -1055,33 +1147,28 @@ function draw(inst) {
|
|
|
1055
1147
|
if (!d) { inst.body.replaceChildren(el('p', { class: 'dim mono', style: small }, COPY.loading)); return; }
|
|
1056
1148
|
const lens = inst.lens;
|
|
1057
1149
|
const list = lens === 'review' ? [] : shown(inst, lens);
|
|
1150
|
+
const go = inst.ctx.go;
|
|
1058
1151
|
const lh = el('div', { class: 'lh', 'data-pm-lens-head': lens },
|
|
1059
1152
|
el('span', { class: 'ki' }, c3ic(WLI[lens])),
|
|
1060
|
-
el('div', { class: 'wk-min' }, el('b', null, COPY.lens[lens]
|
|
1061
|
-
|
|
1153
|
+
el('div', { class: 'wk-min' }, el('b', null, COPY.lens[lens]),
|
|
1154
|
+
el('div', { class: 'dim' }, COPY.lensSub[lens] + (inst.decisions ? COPY.decisions(inst.decisions) : ''),
|
|
1155
|
+
inst.decisions && go ? [' ', pill(COPY.openBriefing, 'next', { 'data-pm-open-briefing': '', onclick: () => go('brief') })] : null)));
|
|
1062
1156
|
const empty = el('li', { class: 'fold' }, c3ic('check', 'sm'), COPY.emptyView[lens] || COPY.empty);
|
|
1063
1157
|
let main;
|
|
1064
|
-
if (lens === 'review') main = [reviewMain(inst)];
|
|
1065
|
-
else if (inst
|
|
1066
|
-
|
|
1067
|
-
.concat([[null, list.filter((i) => !i.job || !jobById(inst, i.job))]]).filter((g) => g[1].length);
|
|
1068
|
-
main = groups.length ? groups.map(([j, its]) => {
|
|
1069
|
-
const sec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic('target', 'sm'), j ? (j.mock_name || j.want) : COPY.noJobGroup),
|
|
1070
|
-
j ? el('span', { class: 'chips2 dim mono', style: small }, COPY.progress(j.done, j.total)) : null),
|
|
1071
|
-
el('ul', { class: 'list', role: 'list' }, its.map((i) => itemRow(inst, i, lens))));
|
|
1072
|
-
sec.setAttribute('data-pm-group-card', j ? j.id : '');
|
|
1073
|
-
return sec;
|
|
1074
|
-
}) : [sect(el('div', { class: 'sh2' }, el('h2', null, c3ic(WLI[lens], 'sm'), labelOf(lens))), el('ul', { class: 'list' }, empty))];
|
|
1075
|
-
} else if (lens === 'upcoming') main = [upcomingMain(inst, list)];
|
|
1158
|
+
if (lens === 'review') main = [reviewMain(inst), inst.draftsHost];
|
|
1159
|
+
else if (lens === 'upcoming') main = upcomingMain(inst, list);
|
|
1160
|
+
else if (lens === 'today') main = todayMain(inst, list);
|
|
1076
1161
|
else {
|
|
1077
|
-
const sec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic(WLI[lens], 'sm'),
|
|
1078
|
-
el('span', { class: 'fl2', 'data-pm-legend': '' }, COPY.legend.map((w, k) => el('span', null, ['• ', '× ', '> ', '< '][k] + w)))),
|
|
1162
|
+
const sec = sect(el('div', { class: 'sh2' }, el('h2', null, c3ic(WLI[lens], 'sm'), COPY.sect[lens], ' ', el('span', { class: 'bdg g' }, String(list.length))), legendEl()),
|
|
1079
1163
|
el('ul', { class: 'list', role: 'list' }, list.length ? list.map((i) => itemRow(inst, i, lens)) : empty));
|
|
1080
1164
|
sec.setAttribute('data-pm-list', lens);
|
|
1081
1165
|
main = [sec];
|
|
1166
|
+
if (lens === 'inbox') {
|
|
1167
|
+
const to = go ? pill(COPY.initiatives, 'next', { 'data-pm-go-init': '', onclick: () => go('init') }) : COPY.initiatives;
|
|
1168
|
+
main.push(el('div', { class: 'fold', 'data-pm-inbox-note': '' }, COPY.inboxNote + ' ', to, COPY.inboxNoteEnd));
|
|
1169
|
+
}
|
|
1082
1170
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
inst.body.replaceChildren(lh, el('div', { class: 'brief sbb' }, el('div', { class: 'bcols' }, el('div', { class: 'bcol l' }, inst.ctx.plan || null, ...main), el('div', { class: 'bcol r' }, jobsCard(inst)))));
|
|
1171
|
+
/* The mockup's vWork() body: .calbody > .lh + .brief.sbb > .bcols > .bcol.l (the view) and .bcol.r (Focus). The PM removed the mockup's Flow today card (console3-deck-flow-fixes). */
|
|
1172
|
+
inst.body.replaceChildren(lh, el('div', { class: 'brief sbb' }, el('div', { class: 'bcols' }, el('div', { class: 'bcol l' }, ...main), el('div', { class: 'bcol r' }, focusSection(inst)))));
|
|
1086
1173
|
if (keep) { const n = inst.root.querySelector(keep.sel); if (n && n.focus) { try { n.focus({ preventScroll: true }); } catch { /* not focusable */ } } }
|
|
1087
1174
|
}
|