@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,25 @@
1
+ /**
2
+ * Entry-uuid keyed review surface — `/dev/editorial-review/entry/:entryId`.
3
+ *
4
+ * Pipeline-redesign Task 35. The legacy `/dev/editorial-review/:slug`
5
+ * (and its `:id` UUID variant) routes are workflow-uuid + calendar-entry
6
+ * keyed. This sibling route is keyed by the *entry uuid* (the sidecar
7
+ * id), uses the eight-stage entry model (Task 33/34), and renders the
8
+ * minimal affordance set returned by `getAffordances(entry)`.
9
+ *
10
+ * The two surfaces coexist during the migration window. Once the
11
+ * workflow-keyed routes are retired, this becomes the canonical review
12
+ * surface; until then, the dashboard's per-row "review" links continue
13
+ * to point at the legacy route.
14
+ *
15
+ * Rendering is intentionally minimal — the goal of Task 35 is the route
16
+ * shape + affordance plumbing, not a fully-styled UI. Styling will land
17
+ * once the affordance set stabilizes against real entries.
18
+ */
19
+ interface EntryReviewResult {
20
+ status: 200 | 404;
21
+ html: string;
22
+ }
23
+ export declare function renderEntryReviewPage(projectRoot: string, entryId: string): Promise<EntryReviewResult>;
24
+ export {};
25
+ //# sourceMappingURL=entry-review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-review.d.ts","sourceRoot":"","sources":["../../src/pages/entry-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+IH,UAAU,iBAAiB;IACzB,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAgB5B"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Entry-uuid keyed review surface — `/dev/editorial-review/entry/:entryId`.
3
+ *
4
+ * Pipeline-redesign Task 35. The legacy `/dev/editorial-review/:slug`
5
+ * (and its `:id` UUID variant) routes are workflow-uuid + calendar-entry
6
+ * keyed. This sibling route is keyed by the *entry uuid* (the sidecar
7
+ * id), uses the eight-stage entry model (Task 33/34), and renders the
8
+ * minimal affordance set returned by `getAffordances(entry)`.
9
+ *
10
+ * The two surfaces coexist during the migration window. Once the
11
+ * workflow-keyed routes are retired, this becomes the canonical review
12
+ * surface; until then, the dashboard's per-row "review" links continue
13
+ * to point at the legacy route.
14
+ *
15
+ * Rendering is intentionally minimal — the goal of Task 35 is the route
16
+ * shape + affordance plumbing, not a fully-styled UI. Styling will land
17
+ * once the affordance set stabilizes against real entries.
18
+ */
19
+ import { resolveEntry } from "../lib/entry-resolver.js";
20
+ import { getAffordances } from "../lib/stage-affordances.js";
21
+ import { html, unsafe } from "./html.js";
22
+ import { layout } from "./layout.js";
23
+ const STAGE_PICKER_OPTIONS = [
24
+ 'Ideas',
25
+ 'Planned',
26
+ 'Outlining',
27
+ 'Drafting',
28
+ 'Final',
29
+ ];
30
+ const CONTROL_LABELS = {
31
+ save: 'Save',
32
+ iterate: 'Iterate',
33
+ approve: 'Approve',
34
+ reject: 'Reject',
35
+ 'historical-stage-dropdown': 'Historical stage',
36
+ 'view-only': 'Read-only',
37
+ 'fork-placeholder': 'Fork (coming)',
38
+ 'induct-to': 'Induct to',
39
+ };
40
+ function renderControl(control, entry) {
41
+ const label = CONTROL_LABELS[control] ?? control;
42
+ if (control === 'induct-to') {
43
+ const options = STAGE_PICKER_OPTIONS.map((s) => unsafe(html `<option value="${s}">${s}</option>`));
44
+ return unsafe(html `
45
+ <label class="er-entry-control er-entry-control--induct">
46
+ <span class="er-entry-control-label">${label}</span>
47
+ <select name="induct-to" data-entry-uuid="${entry.uuid}">
48
+ ${options}
49
+ </select>
50
+ </label>`);
51
+ }
52
+ if (control === 'historical-stage-dropdown') {
53
+ const stages = Object.keys(entry.iterationByStage);
54
+ if (stages.length === 0) {
55
+ return unsafe('');
56
+ }
57
+ const options = stages.map((s) => unsafe(html `<option value="${s}">${s}</option>`));
58
+ return unsafe(html `
59
+ <label class="er-entry-control er-entry-control--history">
60
+ <span class="er-entry-control-label">${label}</span>
61
+ <select name="history-stage" data-entry-uuid="${entry.uuid}">
62
+ ${options}
63
+ </select>
64
+ </label>`);
65
+ }
66
+ if (control === 'view-only') {
67
+ return unsafe(html `<span class="er-entry-control er-entry-control--readonly">${label}</span>`);
68
+ }
69
+ if (control === 'fork-placeholder') {
70
+ return unsafe(html `<button class="er-entry-control er-entry-control--button" type="button" disabled data-control="fork">${label}</button>`);
71
+ }
72
+ return unsafe(html `<button class="er-entry-control er-entry-control--button" type="button" data-control="${control}" data-entry-uuid="${entry.uuid}">${label}</button>`);
73
+ }
74
+ function renderControls(entry, affordances) {
75
+ const buttons = affordances.controls.map((c) => renderControl(c, entry));
76
+ const className = affordances.mutable
77
+ ? 'er-entry-controls er-entry-controls--mutable'
78
+ : 'er-entry-controls er-entry-controls--readonly';
79
+ return unsafe(html `<nav class="${className}" aria-label="Entry controls">${buttons}</nav>`);
80
+ }
81
+ function renderArtifact(body, mutable) {
82
+ if (mutable) {
83
+ return unsafe(html `
84
+ <textarea class="er-entry-body" name="body" rows="24" data-mutable="true">${body}</textarea>`);
85
+ }
86
+ return unsafe(html `
87
+ <pre class="er-entry-body er-entry-body--readonly" data-mutable="false">${body}</pre>`);
88
+ }
89
+ function renderEntryReview(entry, artifactBody, artifactPath, affordances) {
90
+ const stageBadge = unsafe(html `<span class="er-entry-stage" data-stage="${entry.currentStage}">${entry.currentStage}</span>`);
91
+ const priorBadge = entry.priorStage
92
+ ? unsafe(html `<span class="er-entry-prior-stage">paused from ${entry.priorStage}</span>`)
93
+ : '';
94
+ const body = html `
95
+ <main class="er-entry-shell" data-entry-uuid="${entry.uuid}">
96
+ <header class="er-entry-head">
97
+ <p class="er-entry-kicker">Editorial Review · entry</p>
98
+ <h1 class="er-entry-title">${entry.title}</h1>
99
+ <p class="er-entry-meta">
100
+ ${stageBadge}
101
+ ${priorBadge}
102
+ <code class="er-entry-uuid">${entry.uuid}</code>
103
+ </p>
104
+ <p class="er-entry-artifact-path"><code>${artifactPath}</code></p>
105
+ </header>
106
+ ${renderControls(entry, affordances)}
107
+ <section class="er-entry-artifact">
108
+ ${renderArtifact(artifactBody, affordances.mutable)}
109
+ </section>
110
+ </main>`;
111
+ return layout({
112
+ title: `${entry.title} — entry review — dev`,
113
+ cssHrefs: ['/static/css/editorial-review.css'],
114
+ bodyAttrs: 'data-review-ui="entry-review"',
115
+ bodyHtml: body,
116
+ scriptModules: [],
117
+ });
118
+ }
119
+ function renderNotFound(entryId, reason) {
120
+ const body = html `
121
+ <main class="er-entry-shell er-entry-shell--missing">
122
+ <h1>Entry not found</h1>
123
+ <p>No sidecar matched <code>${entryId}</code>.</p>
124
+ <p class="er-entry-detail">${reason}</p>
125
+ <p><a href="/dev/editorial-studio">Back to the studio</a></p>
126
+ </main>`;
127
+ return layout({
128
+ title: 'Entry not found — dev',
129
+ cssHrefs: ['/static/css/editorial-review.css'],
130
+ bodyAttrs: 'data-review-ui="entry-review-missing"',
131
+ bodyHtml: body,
132
+ scriptModules: [],
133
+ });
134
+ }
135
+ export async function renderEntryReviewPage(projectRoot, entryId) {
136
+ let resolved;
137
+ try {
138
+ resolved = await resolveEntry(projectRoot, entryId);
139
+ }
140
+ catch (err) {
141
+ const reason = err instanceof Error ? err.message : String(err);
142
+ return { status: 404, html: renderNotFound(entryId, reason) };
143
+ }
144
+ const affordances = getAffordances(resolved.entry);
145
+ const html = renderEntryReview(resolved.entry, resolved.artifactBody, resolved.artifactPath, affordances);
146
+ return { status: 200, html };
147
+ }
148
+ //# sourceMappingURL=entry-review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-review.js","sourceRoot":"","sources":["../../src/pages/entry-review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Entry-uuid keyed review surface — `/dev/editorial-review/entry/:entryId`.
3
+ *
4
+ * Pipeline-redesign Task 35. The legacy `/dev/editorial-review/:slug`
5
+ * (and its `:id` UUID variant) routes are workflow-uuid + calendar-entry
6
+ * keyed. This sibling route is keyed by the *entry uuid* (the sidecar
7
+ * id), uses the eight-stage entry model (Task 33/34), and renders the
8
+ * minimal affordance set returned by `getAffordances(entry)`.
9
+ *
10
+ * The two surfaces coexist during the migration window. Once the
11
+ * workflow-keyed routes are retired, this becomes the canonical review
12
+ * surface; until then, the dashboard's per-row "review" links continue
13
+ * to point at the legacy route.
14
+ *
15
+ * Rendering is intentionally minimal — the goal of Task 35 is the route
16
+ * shape + affordance plumbing, not a fully-styled UI. Styling will land
17
+ * once the affordance set stabilizes against real entries.
18
+ */
19
+
20
+ import { resolveEntry } from '../lib/entry-resolver.ts';
21
+ import { getAffordances } from '../lib/stage-affordances.ts';
22
+ import type { Affordances } from '../lib/stage-affordances.ts';
23
+ import type { Entry } from '@deskwork/core/schema/entry';
24
+ import { html, unsafe, type RawHtml } from './html.ts';
25
+ import { layout } from './layout.ts';
26
+
27
+ const STAGE_PICKER_OPTIONS = [
28
+ 'Ideas',
29
+ 'Planned',
30
+ 'Outlining',
31
+ 'Drafting',
32
+ 'Final',
33
+ ] as const;
34
+
35
+ const CONTROL_LABELS: Readonly<Record<string, string>> = {
36
+ save: 'Save',
37
+ iterate: 'Iterate',
38
+ approve: 'Approve',
39
+ reject: 'Reject',
40
+ 'historical-stage-dropdown': 'Historical stage',
41
+ 'view-only': 'Read-only',
42
+ 'fork-placeholder': 'Fork (coming)',
43
+ 'induct-to': 'Induct to',
44
+ };
45
+
46
+ function renderControl(control: string, entry: Entry): RawHtml {
47
+ const label = CONTROL_LABELS[control] ?? control;
48
+ if (control === 'induct-to') {
49
+ const options = STAGE_PICKER_OPTIONS.map(
50
+ (s) => unsafe(html`<option value="${s}">${s}</option>`),
51
+ );
52
+ return unsafe(html`
53
+ <label class="er-entry-control er-entry-control--induct">
54
+ <span class="er-entry-control-label">${label}</span>
55
+ <select name="induct-to" data-entry-uuid="${entry.uuid}">
56
+ ${options}
57
+ </select>
58
+ </label>`);
59
+ }
60
+ if (control === 'historical-stage-dropdown') {
61
+ const stages = Object.keys(entry.iterationByStage);
62
+ if (stages.length === 0) {
63
+ return unsafe('');
64
+ }
65
+ const options = stages.map(
66
+ (s) => unsafe(html`<option value="${s}">${s}</option>`),
67
+ );
68
+ return unsafe(html`
69
+ <label class="er-entry-control er-entry-control--history">
70
+ <span class="er-entry-control-label">${label}</span>
71
+ <select name="history-stage" data-entry-uuid="${entry.uuid}">
72
+ ${options}
73
+ </select>
74
+ </label>`);
75
+ }
76
+ if (control === 'view-only') {
77
+ return unsafe(html`<span class="er-entry-control er-entry-control--readonly">${label}</span>`);
78
+ }
79
+ if (control === 'fork-placeholder') {
80
+ return unsafe(
81
+ html`<button class="er-entry-control er-entry-control--button" type="button" disabled data-control="fork">${label}</button>`,
82
+ );
83
+ }
84
+ return unsafe(
85
+ html`<button class="er-entry-control er-entry-control--button" type="button" data-control="${control}" data-entry-uuid="${entry.uuid}">${label}</button>`,
86
+ );
87
+ }
88
+
89
+ function renderControls(entry: Entry, affordances: Affordances): RawHtml {
90
+ const buttons = affordances.controls.map((c) => renderControl(c, entry));
91
+ const className = affordances.mutable
92
+ ? 'er-entry-controls er-entry-controls--mutable'
93
+ : 'er-entry-controls er-entry-controls--readonly';
94
+ return unsafe(html`<nav class="${className}" aria-label="Entry controls">${buttons}</nav>`);
95
+ }
96
+
97
+ function renderArtifact(body: string, mutable: boolean): RawHtml {
98
+ if (mutable) {
99
+ return unsafe(html`
100
+ <textarea class="er-entry-body" name="body" rows="24" data-mutable="true">${body}</textarea>`);
101
+ }
102
+ return unsafe(html`
103
+ <pre class="er-entry-body er-entry-body--readonly" data-mutable="false">${body}</pre>`);
104
+ }
105
+
106
+ function renderEntryReview(
107
+ entry: Entry,
108
+ artifactBody: string,
109
+ artifactPath: string,
110
+ affordances: Affordances,
111
+ ): string {
112
+ const stageBadge = unsafe(
113
+ html`<span class="er-entry-stage" data-stage="${entry.currentStage}">${entry.currentStage}</span>`,
114
+ );
115
+ const priorBadge = entry.priorStage
116
+ ? unsafe(html`<span class="er-entry-prior-stage">paused from ${entry.priorStage}</span>`)
117
+ : '';
118
+ const body = html`
119
+ <main class="er-entry-shell" data-entry-uuid="${entry.uuid}">
120
+ <header class="er-entry-head">
121
+ <p class="er-entry-kicker">Editorial Review · entry</p>
122
+ <h1 class="er-entry-title">${entry.title}</h1>
123
+ <p class="er-entry-meta">
124
+ ${stageBadge}
125
+ ${priorBadge}
126
+ <code class="er-entry-uuid">${entry.uuid}</code>
127
+ </p>
128
+ <p class="er-entry-artifact-path"><code>${artifactPath}</code></p>
129
+ </header>
130
+ ${renderControls(entry, affordances)}
131
+ <section class="er-entry-artifact">
132
+ ${renderArtifact(artifactBody, affordances.mutable)}
133
+ </section>
134
+ </main>`;
135
+ return layout({
136
+ title: `${entry.title} — entry review — dev`,
137
+ cssHrefs: ['/static/css/editorial-review.css'],
138
+ bodyAttrs: 'data-review-ui="entry-review"',
139
+ bodyHtml: body,
140
+ scriptModules: [],
141
+ });
142
+ }
143
+
144
+ function renderNotFound(entryId: string, reason: string): string {
145
+ const body = html`
146
+ <main class="er-entry-shell er-entry-shell--missing">
147
+ <h1>Entry not found</h1>
148
+ <p>No sidecar matched <code>${entryId}</code>.</p>
149
+ <p class="er-entry-detail">${reason}</p>
150
+ <p><a href="/dev/editorial-studio">Back to the studio</a></p>
151
+ </main>`;
152
+ return layout({
153
+ title: 'Entry not found — dev',
154
+ cssHrefs: ['/static/css/editorial-review.css'],
155
+ bodyAttrs: 'data-review-ui="entry-review-missing"',
156
+ bodyHtml: body,
157
+ scriptModules: [],
158
+ });
159
+ }
160
+
161
+ interface EntryReviewResult {
162
+ status: 200 | 404;
163
+ html: string;
164
+ }
165
+
166
+ export async function renderEntryReviewPage(
167
+ projectRoot: string,
168
+ entryId: string,
169
+ ): Promise<EntryReviewResult> {
170
+ let resolved;
171
+ try {
172
+ resolved = await resolveEntry(projectRoot, entryId);
173
+ } catch (err) {
174
+ const reason = err instanceof Error ? err.message : String(err);
175
+ return { status: 404, html: renderNotFound(entryId, reason) };
176
+ }
177
+ const affordances = getAffordances(resolved.entry);
178
+ const html = renderEntryReview(
179
+ resolved.entry,
180
+ resolved.artifactBody,
181
+ resolved.artifactPath,
182
+ affordances,
183
+ );
184
+ return { status: 200, html };
185
+ }
@@ -2,7 +2,7 @@
2
2
  * The Compositor's Manual — `/dev/editorial-help`.
3
3
  *
4
4
  * Static (read-only) operator manual. Renders six sections:
5
- * I — the working model (calendar stages + review states diagrams)
5
+ * I — the working model (eight-stage pipeline + universal verbs)
6
6
  * II — three tracks (longform / shortform / distribution)
7
7
  * III — the skill catalogue (specimen grid)
8
8
  * IV — the studio surfaces, described
@@ -14,10 +14,15 @@
14
14
  * `editorial-skills-catalogue.ts` so a single edit shows up here, in
15
15
  * any future docs generator, and in any CLI inventory.
16
16
  *
17
- * Ported from `editorial-help.astro`. The audiocontrol original
18
- * referenced the two hardcoded sites (`audiocontrol.org ·
19
- * editorialcontrol.org`) in the cover imprint; here we render the
20
- * configured site hosts instead.
17
+ * Pipeline-redesign vocabulary (Phase 6, Task 37): the longform model
18
+ * is the entry-centric eight-stage pipeline (Ideas → Planned →
19
+ * Outlining Drafting Final → Published; Blocked / Cancelled
20
+ * off-pipeline) operated by universal verbs (`/deskwork:add`,
21
+ * `/deskwork:iterate`, `/deskwork:approve`, `/deskwork:publish`,
22
+ * `/deskwork:block`, `/deskwork:cancel`, `/deskwork:induct`). The
23
+ * stage-named skills of the prior model (`plan`, `outline`, `draft`,
24
+ * `pause`, `resume`, `review-start`, `review-cancel`) are retired.
25
+ * Shortform + distribution still use the workflow-object model.
21
26
  */
22
27
  import type { StudioContext } from '../routes/api.ts';
23
28
  export declare function renderHelpPage(ctx: StudioContext): string;
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/pages/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAiZtD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4BzD"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/pages/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AA0ZtD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4BzD"}