@deskwork/studio 0.9.5

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 (93) hide show
  1. package/dist/build-client-assets.d.ts +51 -0
  2. package/dist/build-client-assets.d.ts.map +1 -0
  3. package/dist/build-client-assets.js +341 -0
  4. package/dist/build-client-assets.js.map +1 -0
  5. package/dist/components/scrapbook-item.d.ts +108 -0
  6. package/dist/components/scrapbook-item.d.ts.map +1 -0
  7. package/dist/components/scrapbook-item.js +205 -0
  8. package/dist/components/scrapbook-item.js.map +1 -0
  9. package/dist/lib/editorial-skills-catalogue.d.ts +33 -0
  10. package/dist/lib/editorial-skills-catalogue.d.ts.map +1 -0
  11. package/dist/lib/editorial-skills-catalogue.js +211 -0
  12. package/dist/lib/editorial-skills-catalogue.js.map +1 -0
  13. package/dist/lib/override-render.d.ts +41 -0
  14. package/dist/lib/override-render.d.ts.map +1 -0
  15. package/dist/lib/override-render.js +80 -0
  16. package/dist/lib/override-render.js.map +1 -0
  17. package/dist/listen.d.ts +78 -0
  18. package/dist/listen.d.ts.map +1 -0
  19. package/dist/listen.js +155 -0
  20. package/dist/listen.js.map +1 -0
  21. package/dist/pages/chrome.d.ts +26 -0
  22. package/dist/pages/chrome.d.ts.map +1 -0
  23. package/dist/pages/chrome.js +50 -0
  24. package/dist/pages/chrome.js.map +1 -0
  25. package/dist/pages/content-detail.d.ts +14 -0
  26. package/dist/pages/content-detail.d.ts.map +1 -0
  27. package/dist/pages/content-detail.js +279 -0
  28. package/dist/pages/content-detail.js.map +1 -0
  29. package/dist/pages/content.d.ts +39 -0
  30. package/dist/pages/content.d.ts.map +1 -0
  31. package/dist/pages/content.js +414 -0
  32. package/dist/pages/content.js.map +1 -0
  33. package/dist/pages/dashboard.d.ts +32 -0
  34. package/dist/pages/dashboard.d.ts.map +1 -0
  35. package/dist/pages/dashboard.js +803 -0
  36. package/dist/pages/dashboard.js.map +1 -0
  37. package/dist/pages/help.d.ts +24 -0
  38. package/dist/pages/help.d.ts.map +1 -0
  39. package/dist/pages/help.js +433 -0
  40. package/dist/pages/help.js.map +1 -0
  41. package/dist/pages/html.d.ts +35 -0
  42. package/dist/pages/html.d.ts.map +1 -0
  43. package/dist/pages/html.js +73 -0
  44. package/dist/pages/html.js.map +1 -0
  45. package/dist/pages/index.d.ts +21 -0
  46. package/dist/pages/index.d.ts.map +1 -0
  47. package/dist/pages/index.js +174 -0
  48. package/dist/pages/index.js.map +1 -0
  49. package/dist/pages/layout.d.ts +33 -0
  50. package/dist/pages/layout.d.ts.map +1 -0
  51. package/dist/pages/layout.js +50 -0
  52. package/dist/pages/layout.js.map +1 -0
  53. package/dist/pages/review-scrapbook-drawer.d.ts +20 -0
  54. package/dist/pages/review-scrapbook-drawer.d.ts.map +1 -0
  55. package/dist/pages/review-scrapbook-drawer.js +98 -0
  56. package/dist/pages/review-scrapbook-drawer.js.map +1 -0
  57. package/dist/pages/review.d.ts +68 -0
  58. package/dist/pages/review.d.ts.map +1 -0
  59. package/dist/pages/review.js +434 -0
  60. package/dist/pages/review.js.map +1 -0
  61. package/dist/pages/scrapbook.d.ts +21 -0
  62. package/dist/pages/scrapbook.d.ts.map +1 -0
  63. package/dist/pages/scrapbook.js +250 -0
  64. package/dist/pages/scrapbook.js.map +1 -0
  65. package/dist/pages/shortform.d.ts +17 -0
  66. package/dist/pages/shortform.d.ts.map +1 -0
  67. package/dist/pages/shortform.js +142 -0
  68. package/dist/pages/shortform.js.map +1 -0
  69. package/dist/request-context.d.ts +52 -0
  70. package/dist/request-context.d.ts.map +1 -0
  71. package/dist/request-context.js +84 -0
  72. package/dist/request-context.js.map +1 -0
  73. package/dist/routes/api.d.ts +36 -0
  74. package/dist/routes/api.d.ts.map +1 -0
  75. package/dist/routes/api.js +175 -0
  76. package/dist/routes/api.js.map +1 -0
  77. package/dist/routes/scrapbook-file.d.ts +19 -0
  78. package/dist/routes/scrapbook-file.d.ts.map +1 -0
  79. package/dist/routes/scrapbook-file.js +77 -0
  80. package/dist/routes/scrapbook-file.js.map +1 -0
  81. package/dist/routes/scrapbook-mutations.d.ts +33 -0
  82. package/dist/routes/scrapbook-mutations.d.ts.map +1 -0
  83. package/dist/routes/scrapbook-mutations.js +310 -0
  84. package/dist/routes/scrapbook-mutations.js.map +1 -0
  85. package/dist/server.d.ts +52 -0
  86. package/dist/server.d.ts.map +1 -0
  87. package/dist/server.js +581 -0
  88. package/dist/server.js.map +1 -0
  89. package/dist/tailscale.d.ts +63 -0
  90. package/dist/tailscale.d.ts.map +1 -0
  91. package/dist/tailscale.js +118 -0
  92. package/dist/tailscale.js.map +1 -0
  93. package/package.json +60 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * HTML tagged-template helper.
3
+ *
4
+ * `html` escapes interpolated values by default. `unsafe(s)` wraps a
5
+ * pre-built HTML string in an opaque marker so the tag inlines it raw.
6
+ *
7
+ * Behaviour summary for interpolated values:
8
+ * - `null` / `undefined` / `false` → ''
9
+ * - `unsafe(...)` (object with `__raw`) → inserted verbatim
10
+ * - Array<string> → joined with no separator
11
+ * (so `${items.map(i => html`...`)}` Just Works)
12
+ * - everything else → escaped via `escapeHtml`
13
+ *
14
+ * No JSX, no virtual DOM — just string concatenation. The studio is a
15
+ * tiny dev-only surface, so the simplest thing that gets escaping
16
+ * right is the right thing.
17
+ */
18
+ /** Wrap a pre-built HTML string so the `html` tag inlines it without escaping. */
19
+ export function unsafe(s) {
20
+ return { __raw: s };
21
+ }
22
+ /** Type guard for `unsafe(...)` values. */
23
+ function isRaw(value) {
24
+ return (typeof value === 'object' &&
25
+ value !== null &&
26
+ '__raw' in value &&
27
+ typeof value.__raw === 'string');
28
+ }
29
+ /**
30
+ * Escape a string for safe insertion into HTML text or attribute context.
31
+ * `&` first, then `<`, `>`, `"`, `'`. The single-quote escape lets the
32
+ * same function serve both contexts without a separate `escapeAttr`.
33
+ */
34
+ export function escapeHtml(s) {
35
+ return s
36
+ .replaceAll('&', '&amp;')
37
+ .replaceAll('<', '&lt;')
38
+ .replaceAll('>', '&gt;')
39
+ .replaceAll('"', '&quot;')
40
+ .replaceAll("'", '&#39;');
41
+ }
42
+ function renderValue(value) {
43
+ if (value === null || value === undefined || value === false)
44
+ return '';
45
+ if (isRaw(value))
46
+ return value.__raw;
47
+ if (Array.isArray(value))
48
+ return value.map(renderValue).join('');
49
+ if (typeof value === 'string')
50
+ return escapeHtml(value);
51
+ if (typeof value === 'number' || typeof value === 'bigint')
52
+ return String(value);
53
+ if (typeof value === 'boolean')
54
+ return value ? 'true' : '';
55
+ // Stringify objects defensively — the only legitimate object case is the
56
+ // `unsafe(...)` marker, which we handled above. Anything else is a
57
+ // template author bug; show it loudly so it gets caught in tests.
58
+ return escapeHtml(String(value));
59
+ }
60
+ /**
61
+ * Tagged-template helper. Static parts go in verbatim; interpolated
62
+ * values run through `renderValue`.
63
+ */
64
+ export function html(strings, ...values) {
65
+ let out = '';
66
+ for (let i = 0; i < strings.length; i++) {
67
+ out += strings[i];
68
+ if (i < values.length)
69
+ out += renderValue(values[i]);
70
+ }
71
+ return out;
72
+ }
73
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/pages/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,kFAAkF;AAClF,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AACtB,CAAC;AAED,2CAA2C;AAC3C,SAAS,KAAK,CAAC,KAAc;IAC3B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAO,IAAI,KAAK;QAChB,OAAQ,KAA4B,CAAC,KAAK,KAAK,QAAQ,CACxD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IACxE,IAAI,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC;IACrC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACjF,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,yEAAyE;IACzE,mEAAmE;IACnE,kEAAkE;IAClE,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAClB,OAA6B,EAC7B,GAAG,MAAiB;IAEpB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM;YAAE,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Studio index page — `/dev/`.
3
+ *
4
+ * The title page of the studio. Reads like the table-of-contents spread
5
+ * of a pressed volume: roman numerals, leader dots, route paths in mono
6
+ * on the right (the "page numbers"). Templated routes (longform reviews,
7
+ * scrapbook) render their slug placeholder in red-pencil italic — they
8
+ * can't be linked because they require a slug, so they appear as
9
+ * non-link entries with the path shown.
10
+ *
11
+ * Four sections × six entries:
12
+ * - Pipeline (i.) — Dashboard
13
+ * - Review desk (ii.–iii.) — Shortform, Longform (templated)
14
+ * - Browse (iv.–v.) — Content view, Scrapbook (templated)
15
+ * - Reference (vi.) — The Compositor's Manual
16
+ *
17
+ * Read-only — links to existing routes only. No editing capability here.
18
+ */
19
+ import type { StudioContext } from '../routes/api.ts';
20
+ export declare function renderStudioIndex(_ctx: StudioContext): string;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pages/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAoKtD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CA6B7D"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Studio index page — `/dev/`.
3
+ *
4
+ * The title page of the studio. Reads like the table-of-contents spread
5
+ * of a pressed volume: roman numerals, leader dots, route paths in mono
6
+ * on the right (the "page numbers"). Templated routes (longform reviews,
7
+ * scrapbook) render their slug placeholder in red-pencil italic — they
8
+ * can't be linked because they require a slug, so they appear as
9
+ * non-link entries with the path shown.
10
+ *
11
+ * Four sections × six entries:
12
+ * - Pipeline (i.) — Dashboard
13
+ * - Review desk (ii.–iii.) — Shortform, Longform (templated)
14
+ * - Browse (iv.–v.) — Content view, Scrapbook (templated)
15
+ * - Reference (vi.) — The Compositor's Manual
16
+ *
17
+ * Read-only — links to existing routes only. No editing capability here.
18
+ */
19
+ import { html, unsafe } from "./html.js";
20
+ import { layout } from "./layout.js";
21
+ import { renderEditorialFolio } from "./chrome.js";
22
+ const SECTIONS = [
23
+ {
24
+ ornament: '¶',
25
+ name: 'Pipeline',
26
+ count: 'i. — 1 surface',
27
+ entries: [
28
+ {
29
+ numeral: 'I',
30
+ titleHtml: 'Dashboard',
31
+ titleText: 'Dashboard',
32
+ route: '/dev/editorial-studio',
33
+ desc: 'Press-check. The calendar across all sites; awaiting press; recent proofs; voice-drift signal.',
34
+ },
35
+ ],
36
+ },
37
+ {
38
+ ornament: '¶',
39
+ name: 'Review desk',
40
+ count: 'ii.–iii. — 2 surfaces',
41
+ entries: [
42
+ {
43
+ numeral: 'II',
44
+ titleHtml: 'Shortform reviews',
45
+ titleText: 'Shortform reviews',
46
+ route: '/dev/editorial-review-shortform',
47
+ desc: 'Cross-platform copy desk. Reddit, LinkedIn, YouTube, Instagram — galley slips, one per platform.',
48
+ },
49
+ {
50
+ numeral: 'III',
51
+ titleHtml: 'Longform reviews',
52
+ titleText: 'Longform reviews',
53
+ route: '/dev/editorial-review/<slug>',
54
+ template: { prefix: '/dev/editorial-review/', placeholder: '<slug>' },
55
+ desc: 'Per-entry margin notes, decisions, iterate flow.',
56
+ hint: 'entry-by-entry',
57
+ postHint: 'Reach via the Dashboard or Content view; each review is opened against a specific slug.',
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ ornament: '¶',
63
+ name: 'Browse',
64
+ count: 'iv.–v. — 2 surfaces',
65
+ entries: [
66
+ {
67
+ numeral: 'IV',
68
+ titleHtml: 'Content view',
69
+ titleText: 'Content view',
70
+ route: '/dev/content',
71
+ desc: 'The shape of the work. A drillable tree of nodes; click any to read its head matter and browse its scrapbook.',
72
+ },
73
+ {
74
+ numeral: 'V',
75
+ titleHtml: 'Scrapbook',
76
+ titleText: 'Scrapbook',
77
+ route: '/dev/scrapbook/<site>/<path>',
78
+ template: { prefix: '/dev/scrapbook/', placeholder: '<site>/<path>' },
79
+ desc: 'Research, receipts, working notes. Addressed by hierarchical path; secret items appear in their own section.',
80
+ hint: 'path-addressed',
81
+ postHint: "Reach via the Content view's per-node drawer, or address directly.",
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ ornament: '¶',
87
+ name: 'Reference',
88
+ count: 'vi. — 1 surface',
89
+ entries: [
90
+ {
91
+ numeral: 'VI',
92
+ titleHtml: "The Compositor's <em>Manual</em>",
93
+ titleText: "The Compositor's Manual",
94
+ route: '/dev/editorial-help',
95
+ desc: 'The workflow, the skill catalogue, the names of the things — read once, return when the work asks.',
96
+ },
97
+ ],
98
+ },
99
+ ];
100
+ function renderEntryTitle(entry) {
101
+ if (entry.template) {
102
+ return html `<span class="er-toc-entry__title">${unsafe(entry.titleHtml)}</span>`;
103
+ }
104
+ return html `<a class="er-toc-entry__title" href="${entry.route}">${unsafe(entry.titleHtml)}</a>`;
105
+ }
106
+ function renderEntryRoute(entry) {
107
+ if (entry.template) {
108
+ return html `<span class="er-toc-entry__route is-template">${entry.template.prefix}<em>${entry.template.placeholder}</em></span>`;
109
+ }
110
+ return html `<span class="er-toc-entry__route">${entry.route}</span>`;
111
+ }
112
+ function renderEntryDesc(entry) {
113
+ const hint = entry.hint
114
+ ? html ` <span class="er-toc-entry__hint">${entry.hint}</span>`
115
+ : '';
116
+ const post = entry.postHint
117
+ ? html ` <em>${entry.postHint}</em>`
118
+ : '';
119
+ return html `<p class="er-toc-entry__desc">${entry.desc}${unsafe(hint)}${unsafe(post)}</p>`;
120
+ }
121
+ function renderEntry(entry) {
122
+ return unsafe(html `
123
+ <li class="er-toc-entry">
124
+ <div class="er-toc-entry__row">
125
+ <span class="er-toc-entry__num">${entry.numeral}</span>
126
+ ${unsafe(renderEntryTitle(entry))}
127
+ ${unsafe(renderEntryRoute(entry))}
128
+ </div>
129
+ ${unsafe(renderEntryDesc(entry))}
130
+ </li>`);
131
+ }
132
+ function renderSection(section) {
133
+ return unsafe(html `
134
+ <section class="er-toc-section">
135
+ <div class="er-toc-section-head">
136
+ <span class="er-toc-section-head__ornament">${section.ornament}</span>
137
+ <span class="er-toc-section-head__name">${section.name}</span>
138
+ <span class="er-toc-section-head__count">${section.count}</span>
139
+ </div>
140
+ <ol class="er-toc-list">
141
+ ${section.entries.map(renderEntry)}
142
+ </ol>
143
+ </section>`);
144
+ }
145
+ export function renderStudioIndex(_ctx) {
146
+ const body = html `
147
+ ${renderEditorialFolio('index', 'index of the press')}
148
+ <main class="er-toc-page">
149
+ <header class="er-pagehead er-pagehead--centered er-pagehead--toc">
150
+ <p class="er-pagehead__kicker">Index of the <em>Press</em></p>
151
+ <h1 class="er-pagehead__title">Editorial <em>Studio</em></h1>
152
+ <p class="er-pagehead__deck">
153
+ A reference of the dev surfaces — pipeline, review desk, browse, manual.
154
+ Begin where the work is.
155
+ </p>
156
+ </header>
157
+ ${SECTIONS.map(renderSection)}
158
+ <footer class="er-toc-colophon">
159
+ Pressed in the deskwork studio. Loopback only.<br>
160
+ <span class="er-toc-colophon__rule"></span>
161
+ </footer>
162
+ </main>`;
163
+ return layout({
164
+ title: 'Editorial Studio — Index',
165
+ cssHrefs: [
166
+ '/static/css/editorial-review.css',
167
+ '/static/css/editorial-nav.css',
168
+ ],
169
+ bodyAttrs: 'data-review-ui="studio"',
170
+ bodyHtml: body,
171
+ scriptModules: [],
172
+ });
173
+ }
174
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pages/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared HTML shell for studio pages. Replaces audiocontrol's `<BlogLayout>`
3
+ * Astro component with a plain string-builder.
4
+ */
5
+ export interface EmbeddedJson {
6
+ /** `id` attribute of the `<script type="application/json">` tag. */
7
+ id: string;
8
+ /** Value to JSON-stringify into the tag body. */
9
+ data: unknown;
10
+ /** Optional extra attribute (e.g. `data-rename-slugs`). */
11
+ attr?: string;
12
+ }
13
+ export interface LayoutOptions {
14
+ title: string;
15
+ cssHrefs: string[];
16
+ bodyHtml: string;
17
+ /**
18
+ * Optional attributes for the `<body>` tag itself, e.g.
19
+ * `data-review-ui="studio"`. Caller is responsible for any escaping
20
+ * inside the string — typically these are static.
21
+ */
22
+ bodyAttrs?: string;
23
+ /**
24
+ * Embed JSON payloads as `<script type="application/json" id="...">`.
25
+ * The client reads `document.getElementById(id).textContent` and
26
+ * `JSON.parse`s it for hydration.
27
+ */
28
+ embeddedJson?: ReadonlyArray<EmbeddedJson>;
29
+ /** Module scripts loaded after the body. */
30
+ scriptModules: string[];
31
+ }
32
+ export declare function layout(options: LayoutOptions): string;
33
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/pages/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,iDAAiD;IACjD,IAAI,EAAE,OAAO,CAAC;IACd,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAC3C,4CAA4C;IAC5C,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA4CrD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared HTML shell for studio pages. Replaces audiocontrol's `<BlogLayout>`
3
+ * Astro component with a plain string-builder.
4
+ */
5
+ import { escapeHtml } from "./html.js";
6
+ export function layout(options) {
7
+ const { title, cssHrefs, bodyHtml, bodyAttrs, embeddedJson, scriptModules, } = options;
8
+ const cssTags = cssHrefs
9
+ .map((href) => ` <link rel="stylesheet" href="${escapeAttr(href)}">`)
10
+ .join('\n');
11
+ const jsonTags = (embeddedJson ?? [])
12
+ .map((j) => {
13
+ const attrPart = j.attr ? ` ${j.attr}` : '';
14
+ const idPart = j.id ? ` id="${escapeAttr(j.id)}"` : '';
15
+ return ` <script type="application/json"${idPart}${attrPart}>${escapeForScriptTag(JSON.stringify(j.data))}</script>`;
16
+ })
17
+ .join('\n');
18
+ const scriptTags = scriptModules
19
+ .map((src) => ` <script type="module" src="${escapeAttr(src)}"></script>`)
20
+ .join('\n');
21
+ const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : '<body>';
22
+ return `<!DOCTYPE html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="utf-8">
26
+ <meta name="viewport" content="width=device-width, initial-scale=1">
27
+ <meta name="robots" content="noindex">
28
+ <title>${escapeHtml(title)}</title>
29
+ ${cssTags}
30
+ </head>
31
+ ${bodyOpen}
32
+ ${bodyHtml}
33
+ ${jsonTags}
34
+ ${scriptTags}
35
+ </body>
36
+ </html>
37
+ `;
38
+ }
39
+ function escapeAttr(s) {
40
+ return escapeHtml(s);
41
+ }
42
+ /**
43
+ * Escape a JSON payload so it's safe inside a `<script>` tag. The only
44
+ * sequence we need to neutralize is `</script>` (and a few defense-in-
45
+ * depth cousins) so the browser doesn't terminate the script element.
46
+ */
47
+ function escapeForScriptTag(json) {
48
+ return json.replace(/<\/(script|!--)/gi, '<\\/$1');
49
+ }
50
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/pages/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AA+BvC,MAAM,UAAU,MAAM,CAAC,OAAsB;IAC3C,MAAM,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,aAAa,GACd,GAAG,OAAO,CAAC;IAEZ,MAAM,OAAO,GAAG,QAAQ;SACrB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,oCAAoC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;SACvE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,QAAQ,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,OAAO,sCAAsC,MAAM,GAAG,QAAQ,IAAI,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC;IAC1H,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,UAAU,GAAG,aAAa;SAC7B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,kCAAkC,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC;SAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE9D,OAAO;;;;;;aAMI,UAAU,CAAC,KAAK,CAAC;EAC5B,OAAO;;IAEL,QAAQ;EACV,QAAQ;EACR,QAAQ;EACR,UAAU;;;CAGX,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Scrapbook drawer for the review surface — extracted from `review.ts`
3
+ * to keep that file under the project's 500-line guideline.
4
+ *
5
+ * Renders the IMMEDIATE node's scrapbook (no ancestors) per Phase 16c.
6
+ * Always visible: an empty scrapbook still renders the section so the
7
+ * operator sees the affordance for this node.
8
+ *
9
+ * Phase 19c+: when the calendar entry has a stable id binding AND a
10
+ * per-request content index is wired, resolve the on-disk scrapbook
11
+ * directory via the index. This makes writingcontrol-shape entries
12
+ * (slug != fs path) list their items at the actual file location.
13
+ * Falls back to slug-template addressing for unbound / legacy entries.
14
+ */
15
+ import type { ContentIndex } from '@deskwork/core/content-index';
16
+ import type { CalendarEntry } from '@deskwork/core/types';
17
+ import type { StudioContext } from '../routes/api.ts';
18
+ import { type RawHtml } from './html.ts';
19
+ export declare function renderScrapbookDrawer(ctx: StudioContext, site: string, entry: CalendarEntry | null, slug: string, index?: ContentIndex): RawHtml;
20
+ //# sourceMappingURL=review-scrapbook-drawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-scrapbook-drawer.d.ts","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAOtD,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AA2DvD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,GAAG,IAAI,EAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,YAAY,GACnB,OAAO,CAkDT"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Scrapbook drawer for the review surface — extracted from `review.ts`
3
+ * to keep that file under the project's 500-line guideline.
4
+ *
5
+ * Renders the IMMEDIATE node's scrapbook (no ancestors) per Phase 16c.
6
+ * Always visible: an empty scrapbook still renders the section so the
7
+ * operator sees the affordance for this node.
8
+ *
9
+ * Phase 19c+: when the calendar entry has a stable id binding AND a
10
+ * per-request content index is wired, resolve the on-disk scrapbook
11
+ * directory via the index. This makes writingcontrol-shape entries
12
+ * (slug != fs path) list their items at the actual file location.
13
+ * Falls back to slug-template addressing for unbound / legacy entries.
14
+ */
15
+ import { readFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { listScrapbook, listScrapbookAtDir, scrapbookDirForEntry, } from '@deskwork/core/scrapbook';
18
+ import { resolveContentDir } from '@deskwork/core/paths';
19
+ import { renderEmptyScrapbookRow, renderReadOnlyScrapbookRow, scrapbookViewerUrl, } from "../components/scrapbook-item.js";
20
+ import { html, unsafe } from "./html.js";
21
+ /**
22
+ * Build an inline-text loader for the shared scrapbook-item renderer.
23
+ * Reads at most `maxBytes` from a file inside the scrapbook directory
24
+ * and returns the bytes decoded as UTF-8. Returns null when the file
25
+ * isn't readable as text — the renderer falls back to a download link.
26
+ *
27
+ * Path resolution prefers the index-driven binding (the entry's id is
28
+ * looked up against the per-request content index) so writingcontrol-
29
+ * shape entries — slug `the-outbound`, file at
30
+ * `<contentDir>/projects/the-outbound/index.md` — read scrapbook items
31
+ * from the right on-disk directory. Falls back to the slug-template
32
+ * directory for entries that have no id binding yet (pre-doctor).
33
+ */
34
+ function makeInlineTextLoader(ctx, site, entry, slug, index) {
35
+ const scrapbookDir = entry
36
+ ? scrapbookDirForEntry(ctx.projectRoot, ctx.config, site, entry, index)
37
+ : join(resolveContentDir(ctx.projectRoot, ctx.config, site), slug, 'scrapbook');
38
+ return (filename, maxBytes) => {
39
+ try {
40
+ const buf = readFileSync(join(scrapbookDir, filename));
41
+ const slice = buf.subarray(0, Math.min(buf.byteLength, maxBytes));
42
+ return slice.toString('utf-8');
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ };
48
+ }
49
+ function renderScrapbookDrawerItems(site, slug, items, loader) {
50
+ if (items.length === 0) {
51
+ return renderEmptyScrapbookRow();
52
+ }
53
+ const rows = items.map((item) => renderReadOnlyScrapbookRow({ site, path: slug }, item, { inlinePreviewLoader: loader }));
54
+ return unsafe(rows.map((r) => r.__raw).join(''));
55
+ }
56
+ export function renderScrapbookDrawer(ctx, site, entry, slug, index) {
57
+ const summary = (() => {
58
+ try {
59
+ if (entry !== null && entry.id !== undefined && entry.id !== '') {
60
+ const scrapbookDir = scrapbookDirForEntry(ctx.projectRoot, ctx.config, site, entry, index);
61
+ return listScrapbookAtDir(site, entry.slug, scrapbookDir);
62
+ }
63
+ return listScrapbook(ctx.projectRoot, ctx.config, site, slug);
64
+ }
65
+ catch {
66
+ // listScrapbook validates the slug; an invalid slug shouldn't
67
+ // tank the whole review page. Treat as empty drawer + log via
68
+ // the unobtrusive empty state.
69
+ return null;
70
+ }
71
+ })();
72
+ const items = summary?.items ?? [];
73
+ const secretItems = summary?.secretItems ?? [];
74
+ const total = items.length + secretItems.length;
75
+ const loader = makeInlineTextLoader(ctx, site, entry, slug, index);
76
+ return unsafe(html `
77
+ <aside class="er-scrapbook-drawer" data-scrapbook-drawer aria-label="Scrapbook for this entry">
78
+ <header class="er-scrapbook-drawer-head">
79
+ <span class="er-scrapbook-drawer-kicker">§ Scrapbook</span>
80
+ <span class="er-scrapbook-drawer-count">${total} ${total === 1 ? 'item' : 'items'}</span>
81
+ <a class="er-scrapbook-drawer-open" href="${scrapbookViewerUrl({ site, path: slug })}"
82
+ title="Open the standalone scrapbook viewer">open ↗</a>
83
+ </header>
84
+ <div class="er-scrapbook-drawer-body">
85
+ ${renderScrapbookDrawerItems(site, slug, items, loader)}
86
+ ${secretItems.length > 0
87
+ ? unsafe(html `
88
+ <div class="er-scrapbook-drawer-secret">
89
+ <p class="er-scrapbook-drawer-secret-head">
90
+ <span aria-hidden="true">⚿</span> secret · ${secretItems.length}
91
+ </p>
92
+ ${renderScrapbookDrawerItems(site, slug, secretItems, loader)}
93
+ </div>`)
94
+ : ''}
95
+ </div>
96
+ </aside>`);
97
+ }
98
+ //# sourceMappingURL=review-scrapbook-drawer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-scrapbook-drawer.js","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Per-post review page — `/dev/editorial-review/:slug`.
3
+ *
4
+ * Renders one workflow's current draft inside a margin-note review
5
+ * shell. The body markdown is rendered server-side (so the operator
6
+ * sees content immediately, no flash of unrendered text), with the
7
+ * frontmatter description injected as a dek paragraph after the
8
+ * body's repeated H1.
9
+ *
10
+ * Ported from `pages/dev/editorial-review/[slug].astro`. Differences:
11
+ * - The full <BlogLayout> Astro component went away; we render the
12
+ * review shell as the page body. Site-specific blog chrome was
13
+ * never useful in the review surface (the review is about the
14
+ * prose, not the page chrome).
15
+ * - Site defaults to `config.defaultSite` rather than the hardcoded
16
+ * `'editorialcontrol'` upstream used.
17
+ * - The outline-split helper lives under the plugin tree's `public/src/`
18
+ * (it's a browser module) but it's pure TS, so it can run server-side
19
+ * for the initial render too. After the marketplace-install fix
20
+ * (issue #4), `public/` was relocated from packages/studio/ into
21
+ * plugins/deskwork-studio/, hence the long relative import below.
22
+ */
23
+ import type { ContentIndex } from '@deskwork/core/content-index';
24
+ import type { StudioContext } from '../routes/api.ts';
25
+ /**
26
+ * Per-request content-index getter. The route layer wires this to the
27
+ * Hono context's memoized cache so a single review render only builds
28
+ * the index once per site even though both the inline-text loader and
29
+ * the scrapbook drawer ask for it. When omitted, callers fall back to
30
+ * slug-template path resolution.
31
+ */
32
+ export type ReviewIndexGetter = (site: string) => ContentIndex;
33
+ interface ReviewQuery {
34
+ /** ?site=<slug> override; null falls back to config.defaultSite. */
35
+ site: string | null;
36
+ /** ?v=<n>; null shows the workflow's currentVersion. */
37
+ version: string | null;
38
+ /** ?kind=outline | longform; null defaults to longform. */
39
+ kind?: string | null;
40
+ }
41
+ /**
42
+ * How the route resolved the request. Phase 19d added the canonical
43
+ * id-based URL; the legacy slug URL still resolves to a render via the
44
+ * 302-redirect path (the redirect target lands here as `kind: 'id'`).
45
+ *
46
+ * `kind: 'slug'` is reserved for the legacy fallback when the calendar
47
+ * entry has no id stamped on it yet — pre-doctor state, not a "fallback"
48
+ * the project rules forbid but the migration path the plan calls out.
49
+ *
50
+ * Phase 21c added `kind: 'workflow'` so the dashboard can deep-link
51
+ * straight to a specific workflow id without first knowing the entry
52
+ * id — shortform cells use this to land on the unified review surface
53
+ * after `start-shortform` returns the new workflow.
54
+ */
55
+ export type ReviewLookup = {
56
+ kind: 'id';
57
+ entryId: string;
58
+ slug: string;
59
+ } | {
60
+ kind: 'slug';
61
+ slug: string;
62
+ } | {
63
+ kind: 'workflow';
64
+ workflowId: string;
65
+ };
66
+ export declare function renderReviewPage(ctx: StudioContext, lookup: ReviewLookup, query: ReviewQuery, getIndex?: ReviewIndexGetter): Promise<string>;
67
+ export {};
68
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/pages/review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAStD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;AAE/D,UAAU,WAAW;IACnB,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAiU7C,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAuJjB"}