@deskwork/studio 0.10.2 → 0.11.1

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.
Files changed (56) hide show
  1. package/dist/lib/editorial-skills-catalogue.d.ts +15 -1
  2. package/dist/lib/editorial-skills-catalogue.d.ts.map +1 -1
  3. package/dist/lib/editorial-skills-catalogue.js +66 -59
  4. package/dist/lib/editorial-skills-catalogue.js.map +1 -1
  5. package/dist/lib/entry-resolver.d.ts +22 -0
  6. package/dist/lib/entry-resolver.d.ts.map +1 -0
  7. package/dist/lib/entry-resolver.js +42 -0
  8. package/dist/lib/entry-resolver.js.map +1 -0
  9. package/dist/lib/stage-affordances.d.ts +31 -0
  10. package/dist/lib/stage-affordances.d.ts.map +1 -0
  11. package/dist/lib/stage-affordances.js +37 -0
  12. package/dist/lib/stage-affordances.js.map +1 -0
  13. package/dist/pages/dashboard/affordances.d.ts +41 -0
  14. package/dist/pages/dashboard/affordances.d.ts.map +1 -0
  15. package/dist/pages/dashboard/affordances.js +87 -0
  16. package/dist/pages/dashboard/affordances.js.map +1 -0
  17. package/dist/pages/dashboard/affordances.ts +95 -0
  18. package/dist/pages/dashboard/data.d.ts +24 -0
  19. package/dist/pages/dashboard/data.d.ts.map +1 -0
  20. package/dist/pages/dashboard/data.js +49 -0
  21. package/dist/pages/dashboard/data.js.map +1 -0
  22. package/dist/pages/dashboard/data.ts +56 -0
  23. package/dist/pages/dashboard/header.d.ts +13 -0
  24. package/dist/pages/dashboard/header.d.ts.map +1 -0
  25. package/dist/pages/dashboard/header.js +70 -0
  26. package/dist/pages/dashboard/header.js.map +1 -0
  27. package/dist/pages/dashboard/header.ts +80 -0
  28. package/dist/pages/dashboard/section.d.ts +37 -0
  29. package/dist/pages/dashboard/section.d.ts.map +1 -0
  30. package/dist/pages/dashboard/section.js +117 -0
  31. package/dist/pages/dashboard/section.js.map +1 -0
  32. package/dist/pages/dashboard/section.ts +132 -0
  33. package/dist/pages/dashboard.d.ts +30 -21
  34. package/dist/pages/dashboard.d.ts.map +1 -1
  35. package/dist/pages/dashboard.js +34 -799
  36. package/dist/pages/dashboard.js.map +1 -1
  37. package/dist/pages/dashboard.ts +44 -980
  38. package/dist/pages/entry-review.d.ts +25 -0
  39. package/dist/pages/entry-review.d.ts.map +1 -0
  40. package/dist/pages/entry-review.js +148 -0
  41. package/dist/pages/entry-review.js.map +1 -0
  42. package/dist/pages/entry-review.ts +185 -0
  43. package/dist/pages/help.d.ts +10 -5
  44. package/dist/pages/help.d.ts.map +1 -1
  45. package/dist/pages/help.js +113 -99
  46. package/dist/pages/help.js.map +1 -1
  47. package/dist/pages/help.ts +113 -99
  48. package/dist/pages/index.d.ts +13 -1
  49. package/dist/pages/index.d.ts.map +1 -1
  50. package/dist/pages/index.js +39 -24
  51. package/dist/pages/index.js.map +1 -1
  52. package/dist/pages/index.ts +43 -27
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +23 -3
  55. package/dist/server.js.map +1 -1
  56. package/package.json +4 -4
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Per-row affordance helpers for the dashboard.
3
+ *
4
+ * Pipeline-redesign Task 34. Buttons are static HTML — they link to
5
+ * the entry's review surface (`/dev/editorial-review/<uuid>`) or
6
+ * carry a `data-copy` payload that the existing studio client copies
7
+ * to the clipboard. No new backend handlers are wired here; the
8
+ * universal-verb skills are the canonical action path.
9
+ */
10
+
11
+ import { html, unsafe, type RawHtml } from '../html.ts';
12
+ import type { Entry, Stage, ReviewState } from '@deskwork/core/schema/entry';
13
+
14
+ const REVIEW_STATE_LABEL: Record<ReviewState, string> = {
15
+ 'in-review': 'in review',
16
+ iterating: 'iterating',
17
+ approved: 'approved',
18
+ };
19
+
20
+ /**
21
+ * Render the reviewState badge. When the entry has no reviewState
22
+ * (most pre-review stages), render an em-dash placeholder so the row
23
+ * stays grid-aligned with sibling rows that DO carry a badge.
24
+ */
25
+ export function renderReviewStateBadge(state: ReviewState | undefined): RawHtml {
26
+ if (state === undefined) {
27
+ return unsafe('<span class="er-stamp er-stamp-none" data-review-state="none">—</span>');
28
+ }
29
+ return unsafe(html`<span class="er-stamp er-stamp-${state}" data-review-state="${state}">${REVIEW_STATE_LABEL[state]}</span>`);
30
+ }
31
+
32
+ /**
33
+ * Iteration count for the entry's current stage. The sidecar's
34
+ * `iterationByStage` records every stage the entry has touched; this
35
+ * surfaces the count for the stage the entry is currently in. Defaults
36
+ * to 0 when the bucket is missing (a brand-new entry on its first tick
37
+ * before any iterate has fired).
38
+ */
39
+ export function iterationForCurrentStage(entry: Entry): number {
40
+ return entry.iterationByStage[entry.currentStage] ?? 0;
41
+ }
42
+
43
+ /**
44
+ * Build the per-row action strip. Affordances vary by stage:
45
+ *
46
+ * - Linear pipeline stages (Ideas / Planned / Outlining / Drafting /
47
+ * Final): "open →" link to the review surface, plus an "iterate"
48
+ * copy-CLI button when reviewState is `iterating` and an
49
+ * "approve" copy-CLI button when reviewState is `approved`.
50
+ * - Published: "view →" (read-only review surface).
51
+ * - Blocked / Cancelled: "induct →" copy-CLI to bring the entry back.
52
+ *
53
+ * Each button's behavior is parked behind a `data-copy` attribute so
54
+ * the existing studio client (editorial-studio-client.ts) handles
55
+ * clipboard wiring without new server handlers.
56
+ */
57
+ export function renderRowActions(entry: Entry): RawHtml {
58
+ const buttons: string[] = [];
59
+ const stage = entry.currentStage;
60
+ const reviewLink = `/dev/editorial-review/${entry.uuid}`;
61
+
62
+ if (isLinearActiveStage(stage)) {
63
+ buttons.push(html`<a class="er-btn er-btn-small" href="${reviewLink}"
64
+ title="open the review surface for ${entry.slug}">open →</a>`);
65
+ if (entry.reviewState === 'iterating') {
66
+ buttons.push(html`<button class="er-btn er-btn-small er-btn-primary er-copy-btn" type="button"
67
+ data-copy="/deskwork:iterate ${entry.slug}"
68
+ title="operator clicked Iterate — run the iterate skill in Claude Code">iterate →</button>`);
69
+ }
70
+ if (entry.reviewState === 'approved') {
71
+ buttons.push(html`<button class="er-btn er-btn-small er-btn-approve er-copy-btn" type="button"
72
+ data-copy="/deskwork:approve ${entry.slug}"
73
+ title="approved — graduate to the next stage">approve →</button>`);
74
+ }
75
+ } else if (stage === 'Published') {
76
+ buttons.push(html`<a class="er-btn er-btn-small" href="${reviewLink}"
77
+ title="read-only review surface for the published entry">view →</a>`);
78
+ } else if (stage === 'Blocked' || stage === 'Cancelled') {
79
+ buttons.push(html`<button class="er-btn er-btn-small er-copy-btn" type="button"
80
+ data-copy="/deskwork:induct ${entry.slug}"
81
+ title="bring this entry back into the pipeline">induct →</button>`);
82
+ }
83
+
84
+ return unsafe(`<span class="er-calendar-action">${buttons.join('')}</span>`);
85
+ }
86
+
87
+ function isLinearActiveStage(stage: Stage): boolean {
88
+ return (
89
+ stage === 'Ideas' ||
90
+ stage === 'Planned' ||
91
+ stage === 'Outlining' ||
92
+ stage === 'Drafting' ||
93
+ stage === 'Final'
94
+ );
95
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Dashboard data loader. Reads every sidecar under
3
+ * `<projectRoot>/.deskwork/entries/*.json` and groups them by
4
+ * `currentStage` so the renderer can iterate the eight canonical
5
+ * stage sections without re-walking the disk per stage.
6
+ *
7
+ * Pipeline-redesign Task 34. Replaces the legacy
8
+ * `loadDashboardData` (calendar.md + workflow store) with a
9
+ * sidecar-only reader.
10
+ */
11
+ import type { Entry, Stage } from '@deskwork/core/schema/entry';
12
+ /**
13
+ * The eight canonical stages, in display order. Linear pipeline
14
+ * (Ideas → Published) first, then off-pipeline (Blocked, Cancelled)
15
+ * pinned at the bottom so the visual flow reads top-down through the
16
+ * normal lifecycle.
17
+ */
18
+ export declare const DASHBOARD_STAGE_ORDER: readonly Stage[];
19
+ export interface DashboardData {
20
+ readonly entries: readonly Entry[];
21
+ readonly byStage: ReadonlyMap<Stage, readonly Entry[]>;
22
+ }
23
+ export declare function loadDashboardData(projectRoot: string): Promise<DashboardData>;
24
+ //# sourceMappingURL=data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/pages/dashboard/data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,KAAK,EASxC,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC,CAAC;CACxD;AAiBD,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAInF"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Dashboard data loader. Reads every sidecar under
3
+ * `<projectRoot>/.deskwork/entries/*.json` and groups them by
4
+ * `currentStage` so the renderer can iterate the eight canonical
5
+ * stage sections without re-walking the disk per stage.
6
+ *
7
+ * Pipeline-redesign Task 34. Replaces the legacy
8
+ * `loadDashboardData` (calendar.md + workflow store) with a
9
+ * sidecar-only reader.
10
+ */
11
+ import { readAllSidecars } from '@deskwork/core/sidecar';
12
+ /**
13
+ * The eight canonical stages, in display order. Linear pipeline
14
+ * (Ideas → Published) first, then off-pipeline (Blocked, Cancelled)
15
+ * pinned at the bottom so the visual flow reads top-down through the
16
+ * normal lifecycle.
17
+ */
18
+ export const DASHBOARD_STAGE_ORDER = [
19
+ 'Ideas',
20
+ 'Planned',
21
+ 'Outlining',
22
+ 'Drafting',
23
+ 'Final',
24
+ 'Published',
25
+ 'Blocked',
26
+ 'Cancelled',
27
+ ];
28
+ function bucketize(entries) {
29
+ const out = new Map();
30
+ for (const stage of DASHBOARD_STAGE_ORDER)
31
+ out.set(stage, []);
32
+ for (const e of entries) {
33
+ const bucket = out.get(e.currentStage);
34
+ if (bucket !== undefined)
35
+ bucket.push(e);
36
+ }
37
+ // Sort each bucket by slug — hierarchical entries cluster under
38
+ // their ancestor (display-only ordering; storage stays flat).
39
+ for (const bucket of out.values()) {
40
+ bucket.sort((a, b) => a.slug.localeCompare(b.slug));
41
+ }
42
+ return out;
43
+ }
44
+ export async function loadDashboardData(projectRoot) {
45
+ const entries = await readAllSidecars(projectRoot);
46
+ const byStage = bucketize(entries);
47
+ return { entries, byStage };
48
+ }
49
+ //# sourceMappingURL=data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.js","sourceRoot":"","sources":["../../../src/pages/dashboard/data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAqB;IACrD,OAAO;IACP,SAAS;IACT,WAAW;IACX,UAAU;IACV,OAAO;IACP,WAAW;IACX,SAAS;IACT,WAAW;CACH,CAAC;AAOX,SAAS,SAAS,CAAC,OAAyB;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,qBAAqB;QAAE,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9D,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,gEAAgE;IAChE,8DAA8D;IAC9D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IACzD,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Dashboard data loader. Reads every sidecar under
3
+ * `<projectRoot>/.deskwork/entries/*.json` and groups them by
4
+ * `currentStage` so the renderer can iterate the eight canonical
5
+ * stage sections without re-walking the disk per stage.
6
+ *
7
+ * Pipeline-redesign Task 34. Replaces the legacy
8
+ * `loadDashboardData` (calendar.md + workflow store) with a
9
+ * sidecar-only reader.
10
+ */
11
+
12
+ import { readAllSidecars } from '@deskwork/core/sidecar';
13
+ import type { Entry, Stage } from '@deskwork/core/schema/entry';
14
+
15
+ /**
16
+ * The eight canonical stages, in display order. Linear pipeline
17
+ * (Ideas → Published) first, then off-pipeline (Blocked, Cancelled)
18
+ * pinned at the bottom so the visual flow reads top-down through the
19
+ * normal lifecycle.
20
+ */
21
+ export const DASHBOARD_STAGE_ORDER: readonly Stage[] = [
22
+ 'Ideas',
23
+ 'Planned',
24
+ 'Outlining',
25
+ 'Drafting',
26
+ 'Final',
27
+ 'Published',
28
+ 'Blocked',
29
+ 'Cancelled',
30
+ ] as const;
31
+
32
+ export interface DashboardData {
33
+ readonly entries: readonly Entry[];
34
+ readonly byStage: ReadonlyMap<Stage, readonly Entry[]>;
35
+ }
36
+
37
+ function bucketize(entries: readonly Entry[]): Map<Stage, Entry[]> {
38
+ const out = new Map<Stage, Entry[]>();
39
+ for (const stage of DASHBOARD_STAGE_ORDER) out.set(stage, []);
40
+ for (const e of entries) {
41
+ const bucket = out.get(e.currentStage);
42
+ if (bucket !== undefined) bucket.push(e);
43
+ }
44
+ // Sort each bucket by slug — hierarchical entries cluster under
45
+ // their ancestor (display-only ordering; storage stays flat).
46
+ for (const bucket of out.values()) {
47
+ bucket.sort((a, b) => a.slug.localeCompare(b.slug));
48
+ }
49
+ return out;
50
+ }
51
+
52
+ export async function loadDashboardData(projectRoot: string): Promise<DashboardData> {
53
+ const entries = await readAllSidecars(projectRoot);
54
+ const byStage = bucketize(entries);
55
+ return { entries, byStage };
56
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Dashboard masthead + filter strip.
3
+ *
4
+ * Pipeline-redesign Task 34. The masthead reads from sidecar-derived
5
+ * counts (total entries, in-review entries) instead of the legacy
6
+ * workflow store. The filter strip exposes one chip per stage so
7
+ * operators can collapse to a single section quickly.
8
+ */
9
+ import type { DashboardData } from './data.ts';
10
+ import { type RawHtml } from '../html.ts';
11
+ export declare function renderHeader(data: DashboardData, projectRoot: string, now: Date): RawHtml;
12
+ export declare function renderFilterStrip(): RawHtml;
13
+ //# sourceMappingURL=header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../src/pages/dashboard/header.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAuBxD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,IAAI,GACR,OAAO,CA0BT;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAa3C"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Dashboard masthead + filter strip.
3
+ *
4
+ * Pipeline-redesign Task 34. The masthead reads from sidecar-derived
5
+ * counts (total entries, in-review entries) instead of the legacy
6
+ * workflow store. The filter strip exposes one chip per stage so
7
+ * operators can collapse to a single section quickly.
8
+ */
9
+ import { DASHBOARD_STAGE_ORDER } from "./data.js";
10
+ import { html, unsafe } from "../html.js";
11
+ const MONTH_NAMES = [
12
+ 'January', 'February', 'March', 'April', 'May', 'June',
13
+ 'July', 'August', 'September', 'October', 'November', 'December',
14
+ ];
15
+ function reviewActiveCount(data) {
16
+ let n = 0;
17
+ for (const entry of data.entries) {
18
+ if (entry.reviewState === 'in-review' || entry.reviewState === 'iterating')
19
+ n++;
20
+ }
21
+ return n;
22
+ }
23
+ function approvedCount(data) {
24
+ let n = 0;
25
+ for (const entry of data.entries) {
26
+ if (entry.reviewState === 'approved')
27
+ n++;
28
+ }
29
+ return n;
30
+ }
31
+ export function renderHeader(data, projectRoot, now) {
32
+ const volume = '01';
33
+ const issueNum = String(data.entries.length).padStart(2, '0');
34
+ const issueDate = `${now.getDate()} ${MONTH_NAMES[now.getMonth()]} ${now.getFullYear()}`;
35
+ return unsafe(html `
36
+ <header class="er-pagehead er-pagehead--centered">
37
+ <p class="er-pagehead__kicker">
38
+ Vol. ${volume} &middot; № ${issueNum} &middot; Press-check
39
+ </p>
40
+ <h1 class="er-pagehead__title">
41
+ Editorial <em>Studio</em>
42
+ </h1>
43
+ <p class="er-pagehead__deck">
44
+ Project: <code>${projectRoot}</code>
45
+ &nbsp;·&nbsp; <a class="er-link-marginalia" href="/dev/editorial-help">the manual</a>
46
+ </p>
47
+ <p class="er-pagehead__meta">
48
+ <span>${issueDate}</span>
49
+ <span class="sep">·</span>
50
+ <span>${data.entries.length} on the calendar</span>
51
+ <span class="sep">·</span>
52
+ <span>${reviewActiveCount(data)} in review</span>
53
+ <span class="sep">·</span>
54
+ <span>${approvedCount(data)} approved</span>
55
+ </p>
56
+ </header>`);
57
+ }
58
+ export function renderFilterStrip() {
59
+ return unsafe(html `
60
+ <section class="er-filter" data-filter-strip>
61
+ <span class="er-filter-label">Find</span>
62
+ <input type="search" data-filter-input placeholder="slug, title…" autocomplete="off" />
63
+ <span class="er-filter-label er-filter-label--gap">Stage</span>
64
+ <div class="er-chips" role="tablist">
65
+ <button class="er-chip" aria-pressed="true" data-stage-chip="all">all</button>
66
+ ${DASHBOARD_STAGE_ORDER.map((s) => unsafe(html `<button class="er-chip" data-stage-chip="${s}">${s.toLowerCase()}</button>`))}
67
+ </div>
68
+ </section>`);
69
+ }
70
+ //# sourceMappingURL=header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.js","sourceRoot":"","sources":["../../../src/pages/dashboard/header.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAgB,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,GAAG;IAClB,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;IACtD,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU;CACjE,CAAC;AAEF,SAAS,iBAAiB,CAAC,IAAmB;IAC5C,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW;YAAE,CAAC,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,aAAa,CAAC,IAAmB;IACxC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,WAAW,KAAK,UAAU;YAAE,CAAC,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAmB,EACnB,WAAmB,EACnB,GAAS;IAET,MAAM,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;IACzF,OAAO,MAAM,CAAC,IAAI,CAAA;;;aAGP,MAAM,eAAe,QAAQ;;;;;;uBAMnB,WAAW;;;;cAIpB,SAAS;;cAET,IAAI,CAAC,OAAO,CAAC,MAAM;;cAEnB,iBAAiB,CAAC,IAAI,CAAC;;cAEvB,aAAa,CAAC,IAAI,CAAC;;YAErB,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,MAAM,CAAC,IAAI,CAAA;;;;;;;UAOV,qBAAqB,CAAC,GAAG,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAA,4CAA4C,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAChG;;eAEM,CAAC,CAAC;AACjB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Dashboard masthead + filter strip.
3
+ *
4
+ * Pipeline-redesign Task 34. The masthead reads from sidecar-derived
5
+ * counts (total entries, in-review entries) instead of the legacy
6
+ * workflow store. The filter strip exposes one chip per stage so
7
+ * operators can collapse to a single section quickly.
8
+ */
9
+
10
+ import type { DashboardData } from './data.ts';
11
+ import { DASHBOARD_STAGE_ORDER } from './data.ts';
12
+ import { html, unsafe, type RawHtml } from '../html.ts';
13
+
14
+ const MONTH_NAMES = [
15
+ 'January', 'February', 'March', 'April', 'May', 'June',
16
+ 'July', 'August', 'September', 'October', 'November', 'December',
17
+ ];
18
+
19
+ function reviewActiveCount(data: DashboardData): number {
20
+ let n = 0;
21
+ for (const entry of data.entries) {
22
+ if (entry.reviewState === 'in-review' || entry.reviewState === 'iterating') n++;
23
+ }
24
+ return n;
25
+ }
26
+
27
+ function approvedCount(data: DashboardData): number {
28
+ let n = 0;
29
+ for (const entry of data.entries) {
30
+ if (entry.reviewState === 'approved') n++;
31
+ }
32
+ return n;
33
+ }
34
+
35
+ export function renderHeader(
36
+ data: DashboardData,
37
+ projectRoot: string,
38
+ now: Date,
39
+ ): RawHtml {
40
+ const volume = '01';
41
+ const issueNum = String(data.entries.length).padStart(2, '0');
42
+ const issueDate = `${now.getDate()} ${MONTH_NAMES[now.getMonth()]} ${now.getFullYear()}`;
43
+ return unsafe(html`
44
+ <header class="er-pagehead er-pagehead--centered">
45
+ <p class="er-pagehead__kicker">
46
+ Vol. ${volume} &middot; № ${issueNum} &middot; Press-check
47
+ </p>
48
+ <h1 class="er-pagehead__title">
49
+ Editorial <em>Studio</em>
50
+ </h1>
51
+ <p class="er-pagehead__deck">
52
+ Project: <code>${projectRoot}</code>
53
+ &nbsp;·&nbsp; <a class="er-link-marginalia" href="/dev/editorial-help">the manual</a>
54
+ </p>
55
+ <p class="er-pagehead__meta">
56
+ <span>${issueDate}</span>
57
+ <span class="sep">·</span>
58
+ <span>${data.entries.length} on the calendar</span>
59
+ <span class="sep">·</span>
60
+ <span>${reviewActiveCount(data)} in review</span>
61
+ <span class="sep">·</span>
62
+ <span>${approvedCount(data)} approved</span>
63
+ </p>
64
+ </header>`);
65
+ }
66
+
67
+ export function renderFilterStrip(): RawHtml {
68
+ return unsafe(html`
69
+ <section class="er-filter" data-filter-strip>
70
+ <span class="er-filter-label">Find</span>
71
+ <input type="search" data-filter-input placeholder="slug, title…" autocomplete="off" />
72
+ <span class="er-filter-label er-filter-label--gap">Stage</span>
73
+ <div class="er-chips" role="tablist">
74
+ <button class="er-chip" aria-pressed="true" data-stage-chip="all">all</button>
75
+ ${DASHBOARD_STAGE_ORDER.map(
76
+ (s) => unsafe(html`<button class="er-chip" data-stage-chip="${s}">${s.toLowerCase()}</button>`),
77
+ )}
78
+ </div>
79
+ </section>`);
80
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Single-stage section renderer.
3
+ *
4
+ * Pipeline-redesign Task 34. Each of the eight stage sections renders
5
+ * with a section heading (stage name + entry count) and either a list
6
+ * of rows or an empty-state placeholder. Per-row HTML carries the
7
+ * sidecar-derived state inline (iteration count + reviewState badge)
8
+ * so an operator can see at a glance where each entry sits without
9
+ * opening it.
10
+ */
11
+ import { type RawHtml } from '../html.ts';
12
+ import type { Entry, Stage } from '@deskwork/core/schema/entry';
13
+ /**
14
+ * Render one entry as a single dashboard row. Carries inline:
15
+ * - slug (linked to the review surface)
16
+ * - title
17
+ * - iteration count for the entry's currentStage
18
+ * - reviewState badge (or an em-dash placeholder)
19
+ * - updatedAt timestamp
20
+ * - per-stage action buttons
21
+ */
22
+ export declare function renderRow(entry: Entry, index: number): RawHtml;
23
+ /**
24
+ * Render one full stage section: heading + ornaments + count + rows.
25
+ * Empty stages render the placeholder message — keeping the section
26
+ * visible (rather than collapsing it) so the operator sees that the
27
+ * stage exists and is currently empty.
28
+ */
29
+ export declare function renderStageSection(stage: Stage, entries: readonly Entry[]): RawHtml;
30
+ /**
31
+ * Render the reserved Distribution placeholder. Stays a separate
32
+ * sibling of the stage sections — distribution records (shortform
33
+ * cross-posts) are tracked under their own model and the dashboard
34
+ * surfaces only a placeholder here until that integration lands.
35
+ */
36
+ export declare function renderDistributionPlaceholder(): RawHtml;
37
+ //# sourceMappingURL=section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/pages/dashboard/section.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AA6BhE;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA8B9D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG,OAAO,CAkBnF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAWvD"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Single-stage section renderer.
3
+ *
4
+ * Pipeline-redesign Task 34. Each of the eight stage sections renders
5
+ * with a section heading (stage name + entry count) and either a list
6
+ * of rows or an empty-state placeholder. Per-row HTML carries the
7
+ * sidecar-derived state inline (iteration count + reviewState badge)
8
+ * so an operator can see at a glance where each entry sits without
9
+ * opening it.
10
+ */
11
+ import { html, unsafe } from "../html.js";
12
+ import { iterationForCurrentStage, renderReviewStateBadge, renderRowActions, } from "./affordances.js";
13
+ const STAGE_ORNAMENTS = {
14
+ Ideas: '◇',
15
+ Planned: '§',
16
+ Outlining: '⊹',
17
+ Drafting: '✎',
18
+ Final: '※',
19
+ Published: '✓',
20
+ Blocked: '⊘',
21
+ Cancelled: '✗',
22
+ };
23
+ const STAGE_EMPTY_MESSAGES = {
24
+ Ideas: 'No open ideas. Run /deskwork:add to capture one.',
25
+ Planned: 'Nothing planned. /deskwork:approve <slug> to graduate an idea.',
26
+ Outlining: 'Nothing in outlining.',
27
+ Drafting: 'No posts in drafting.',
28
+ Final: 'Nothing in final review.',
29
+ Published: 'No published posts yet.',
30
+ Blocked: 'Nothing blocked.',
31
+ Cancelled: 'No cancelled entries.',
32
+ };
33
+ /**
34
+ * Render one entry as a single dashboard row. Carries inline:
35
+ * - slug (linked to the review surface)
36
+ * - title
37
+ * - iteration count for the entry's currentStage
38
+ * - reviewState badge (or an em-dash placeholder)
39
+ * - updatedAt timestamp
40
+ * - per-stage action buttons
41
+ */
42
+ export function renderRow(entry, index) {
43
+ const iteration = iterationForCurrentStage(entry);
44
+ const reviewLink = `/dev/editorial-review/${entry.uuid}`;
45
+ const search = [entry.slug, entry.title, entry.keywords.join(' ')].join(' ').toLowerCase();
46
+ // Hierarchical entries (slugs containing `/`) get a visual indent
47
+ // marker the CSS layer reads. Storage stays flat; this is display-only.
48
+ const depth = entry.slug.split('/').length - 1;
49
+ const depthAttrs = depth > 0
50
+ ? unsafe(html ` data-depth="${depth}" style="--er-row-depth: ${depth}"`)
51
+ : '';
52
+ return unsafe(html `
53
+ <div class="er-calendar-row-wrap" data-row-wrap data-search="${search}"${depthAttrs}>
54
+ <div class="er-calendar-row" data-stage="${entry.currentStage}"
55
+ data-uuid="${entry.uuid}" data-slug="${entry.slug}" data-search="${search}">
56
+ <span class="er-row-num">№ ${String(index + 1).padStart(2, '0')}</span>
57
+ <div class="er-calendar-body">
58
+ <span class="er-row-slug"><a href="${reviewLink}"
59
+ title="open the review surface">${entry.slug}</a></span>
60
+ <span class="er-calendar-title">${entry.title}</span>
61
+ <span class="er-calendar-meta er-calendar-meta-iteration"
62
+ data-iteration="${iteration}">iteration: ${iteration}</span>
63
+ <span class="er-calendar-meta er-calendar-meta-updated"
64
+ title="${entry.updatedAt}">${formatDate(entry.updatedAt)}</span>
65
+ </div>
66
+ <span class="er-calendar-status">${renderReviewStateBadge(entry.reviewState)}</span>
67
+ ${renderRowActions(entry)}
68
+ </div>
69
+ </div>`);
70
+ }
71
+ /**
72
+ * Render one full stage section: heading + ornaments + count + rows.
73
+ * Empty stages render the placeholder message — keeping the section
74
+ * visible (rather than collapsing it) so the operator sees that the
75
+ * stage exists and is currently empty.
76
+ */
77
+ export function renderStageSection(stage, entries) {
78
+ const body = entries.length === 0
79
+ ? unsafe(html `<div class="er-empty" data-empty-stage="${stage}"
80
+ style="padding: 1rem 0.25rem; font-size: 0.95rem;">
81
+ ${STAGE_EMPTY_MESSAGES[stage]}
82
+ </div>`)
83
+ : unsafe(entries.map((e, i) => renderRow(e, i).__raw).join(''));
84
+ return unsafe(html `
85
+ <section class="er-section" id="stage-${stage.toLowerCase()}" data-stage-section="${stage}">
86
+ <h2 class="er-section-head">
87
+ <span>${stage}</span>
88
+ <span class="ornament">${STAGE_ORNAMENTS[stage]}</span>
89
+ <span class="count">№ ${entries.length}</span>
90
+ </h2>
91
+ ${body}
92
+ </section>`);
93
+ }
94
+ /**
95
+ * Render the reserved Distribution placeholder. Stays a separate
96
+ * sibling of the stage sections — distribution records (shortform
97
+ * cross-posts) are tracked under their own model and the dashboard
98
+ * surfaces only a placeholder here until that integration lands.
99
+ */
100
+ export function renderDistributionPlaceholder() {
101
+ return unsafe(html `
102
+ <section class="er-section" id="stage-distribution" data-stage-section="Distribution">
103
+ <h2 class="er-section-head">
104
+ <span>Distribution</span>
105
+ <span class="ornament">⌘</span>
106
+ </h2>
107
+ <div class="er-empty" style="padding: 1rem 0.25rem; font-size: 0.95rem;">
108
+ Reserved for shortform DistributionRecords — separate model.
109
+ </div>
110
+ </section>`);
111
+ }
112
+ function formatDate(iso) {
113
+ // Trim to YYYY-MM-DD for compact display. Full timestamp is on the
114
+ // <span title="...">.
115
+ return iso.slice(0, 10);
116
+ }
117
+ //# sourceMappingURL=section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.js","sourceRoot":"","sources":["../../../src/pages/dashboard/section.ts"],"names":[],"mappings":"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"}