@deskwork/studio 0.20.0 → 0.22.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.
Files changed (62) hide show
  1. package/dist/pages/dashboard/adjacent-section.d.ts +38 -0
  2. package/dist/pages/dashboard/adjacent-section.d.ts.map +1 -0
  3. package/dist/pages/dashboard/adjacent-section.js +61 -0
  4. package/dist/pages/dashboard/adjacent-section.js.map +1 -0
  5. package/dist/pages/dashboard/adjacent-section.ts +71 -0
  6. package/dist/pages/dashboard/data.d.ts +35 -4
  7. package/dist/pages/dashboard/data.d.ts.map +1 -1
  8. package/dist/pages/dashboard/data.js +69 -5
  9. package/dist/pages/dashboard/data.js.map +1 -1
  10. package/dist/pages/dashboard/data.ts +84 -5
  11. package/dist/pages/dashboard/section.d.ts.map +1 -1
  12. package/dist/pages/dashboard/section.js +7 -0
  13. package/dist/pages/dashboard/section.js.map +1 -1
  14. package/dist/pages/dashboard/section.ts +7 -0
  15. package/dist/pages/dashboard/shortform-section.d.ts +86 -0
  16. package/dist/pages/dashboard/shortform-section.d.ts.map +1 -0
  17. package/dist/pages/dashboard/shortform-section.js +189 -0
  18. package/dist/pages/dashboard/shortform-section.js.map +1 -0
  19. package/dist/pages/dashboard/shortform-section.ts +228 -0
  20. package/dist/pages/dashboard.d.ts.map +1 -1
  21. package/dist/pages/dashboard.js +34 -1
  22. package/dist/pages/dashboard.js.map +1 -1
  23. package/dist/pages/dashboard.ts +40 -1
  24. package/dist/pages/entry-review/index.d.ts.map +1 -1
  25. package/dist/pages/entry-review/index.js +24 -2
  26. package/dist/pages/entry-review/index.js.map +1 -1
  27. package/dist/pages/entry-review/mobile-sheet.d.ts +65 -0
  28. package/dist/pages/entry-review/mobile-sheet.d.ts.map +1 -0
  29. package/dist/pages/entry-review/mobile-sheet.js +170 -0
  30. package/dist/pages/entry-review/mobile-sheet.js.map +1 -0
  31. package/dist/pages/masthead-menu.d.ts +38 -0
  32. package/dist/pages/masthead-menu.d.ts.map +1 -0
  33. package/dist/pages/masthead-menu.js +126 -0
  34. package/dist/pages/masthead-menu.js.map +1 -0
  35. package/dist/pages/masthead-menu.ts +128 -0
  36. package/dist/pages/masthead.d.ts +85 -0
  37. package/dist/pages/masthead.d.ts.map +1 -0
  38. package/dist/pages/masthead.js +99 -0
  39. package/dist/pages/masthead.js.map +1 -0
  40. package/dist/pages/masthead.ts +155 -0
  41. package/dist/pages/mobile-bar.d.ts +72 -0
  42. package/dist/pages/mobile-bar.d.ts.map +1 -0
  43. package/dist/pages/mobile-bar.js +88 -0
  44. package/dist/pages/mobile-bar.js.map +1 -0
  45. package/dist/pages/mobile-bar.ts +129 -0
  46. package/dist/pages/shortform-review-mobile-sheet.d.ts +76 -0
  47. package/dist/pages/shortform-review-mobile-sheet.d.ts.map +1 -0
  48. package/dist/pages/shortform-review-mobile-sheet.js +159 -0
  49. package/dist/pages/shortform-review-mobile-sheet.js.map +1 -0
  50. package/dist/pages/shortform-review-mobile-sheet.ts +185 -0
  51. package/dist/pages/shortform-review.d.ts +18 -0
  52. package/dist/pages/shortform-review.d.ts.map +1 -1
  53. package/dist/pages/shortform-review.js +62 -111
  54. package/dist/pages/shortform-review.js.map +1 -1
  55. package/dist/pages/shortform-review.ts +68 -140
  56. package/dist/pages/shortform.d.ts.map +1 -1
  57. package/dist/pages/shortform.js +0 -1
  58. package/dist/pages/shortform.js.map +1 -1
  59. package/dist/pages/shortform.ts +0 -1
  60. package/dist/server.js +41 -1
  61. package/dist/server.js.map +1 -1
  62. package/package.json +2 -2
@@ -15,13 +15,35 @@
15
15
  *
16
16
  * Workflow-keyed wording in this file is documenting that deliberate
17
17
  * deferral; do not flag in audits.
18
+ *
19
+ * Step 2.2.10 (v7 universal chrome + state-machine compliance):
20
+ * - Server-side: er-strip / er-stamp / er-pending-state / shortcut
21
+ * overlay / state-branched control rendering all REMOVED. The
22
+ * universal `renderMobileBar` primitive + a shortform-specific
23
+ * sheet host (`./shortform-review-mobile-sheet.ts`) replace them.
24
+ * - Per DESIGN-STANDARDS.md § Universal bar contract: this surface
25
+ * composes its `Cell[]` mechanically (TOC / Versions / Actions)
26
+ * and passes it to `renderMobileBar`. No bespoke chrome shape.
27
+ * - Per DESKWORK-STATE-MACHINE.md Commandment III: no review-state
28
+ * labels on the page (the stamp + pending pills were the
29
+ * violations).
30
+ * - The desktop edit-mode panes (`renderEditPanes`) stay — they're
31
+ * gated by the existing `data-edit-mode` body attribute and the
32
+ * Edit toolbar is still part of the desktop chrome. Mobile edit
33
+ * entry-point is deferred to a future task (the workplan keeps
34
+ * the broader shortform state-machine migration explicitly out
35
+ * of scope for this step).
18
36
  */
19
37
  import { handleGetWorkflow } from '@deskwork/core/review/handlers';
38
+ import { extractToc } from '@deskwork/core/review/toc';
20
39
  import { parseDraftFrontmatter, renderMarkdownToHtml, } from '@deskwork/core/review/render';
21
- import { html, unsafe } from "./html.js";
40
+ import { escapeHtml, html, unsafe } from "./html.js";
22
41
  import { layout } from "./layout.js";
23
42
  import { renderEditorialFolio } from "./chrome.js";
24
- import { escapeHtml, gloss } from "./html.js";
43
+ import { renderMasthead } from "./masthead.js";
44
+ import { renderMastheadMenu } from "./masthead-menu.js";
45
+ import { renderMobileBar } from "./mobile-bar.js";
46
+ import { getShortformBarCells, renderShortformMobileSheet, } from "./shortform-review-mobile-sheet.js";
25
47
  function isSuccessBody(body) {
26
48
  if (typeof body !== 'object' || body === null)
27
49
  return false;
@@ -38,9 +60,6 @@ function errorFromBody(body) {
38
60
  function stringField(v) {
39
61
  return typeof v === 'string' ? v : undefined;
40
62
  }
41
- function stateLabel(state) {
42
- return (state ?? '').replace('-', ' ');
43
- }
44
63
  async function prepareShortformRender(markdown) {
45
64
  const parsed = parseDraftFrontmatter(markdown);
46
65
  const fm = parsed.frontmatter;
@@ -56,94 +75,12 @@ async function prepareShortformRender(markdown) {
56
75
  : dekHtml + bodyHtml;
57
76
  return { fm, bodyHtml: renderedHtml };
58
77
  }
59
- function renderVersionsStrip(versions, current) {
60
- if (versions.length <= 1)
61
- return unsafe('');
62
- const links = versions
63
- .map((v) => {
64
- const isActive = v.version === current.version;
65
- const href = `?v=${v.version}`;
66
- return html `<a href="${href}" class="${isActive ? 'active' : ''}">v${v.version}</a>`;
67
- })
68
- .join('');
69
- return unsafe(html `<span class="er-strip-versions">${unsafe(links)}</span>`);
70
- }
71
- function pendingSkillCmd(workflow) {
72
- const { site, slug, state } = workflow;
73
- if (state === 'iterating') {
74
- return `/deskwork:iterate --site ${site} ${slug}`;
75
- }
76
- if (state === 'approved') {
77
- return `/deskwork:approve --site ${site} ${slug}`;
78
- }
79
- return '';
80
- }
81
- function shortcutChipWrap(buttonHtml, letter) {
82
- return html `<span class="er-shortcut-chip-wrap">${unsafe(buttonHtml)}<small class="er-shortcut-chip"><kbd>${letter}</kbd><kbd>${letter}</kbd></small></span>`;
83
- }
84
- function renderControlsRight(workflow) {
85
- const isActive = workflow.state === 'open' || workflow.state === 'in-review';
86
- const isApproved = workflow.state === 'approved';
87
- const isIterating = workflow.state === 'iterating';
88
- const isTerminal = workflow.state === 'applied' || workflow.state === 'cancelled';
89
- const buttons = [];
90
- buttons.push(html `<button class="er-btn er-btn-small" data-action="toggle-edit" type="button">Edit</button><span class="er-edit-mode-label" data-mode="preview">preview</span>`);
91
- if (isActive) {
92
- buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-approve" data-action="approve" type="button">Approve</button>`, 'a'));
93
- buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small" data-action="iterate" type="button">Iterate</button>`, 'i'));
94
- buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`, 'r'));
95
- }
96
- if (isApproved) {
97
- const applyCmd = pendingSkillCmd(workflow);
98
- buttons.push(html `<span class="er-pending-state">awaiting apply…</span>`);
99
- buttons.push(html `<button class="er-btn er-btn-small" data-action="copy-cmd" data-cmd="${applyCmd}" title="Copy ${applyCmd} to clipboard" type="button">copy <code>/deskwork:approve</code></button>`);
100
- buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`, 'r'));
101
- }
102
- if (isIterating) {
103
- const iterateCmd = pendingSkillCmd(workflow);
104
- buttons.push(html `<span class="er-pending-state">agent iterating…</span>`);
105
- buttons.push(html `<button class="er-btn er-btn-small" data-action="copy-cmd" data-cmd="${iterateCmd}" title="Copy ${iterateCmd} to clipboard" type="button">copy <code>/deskwork:iterate</code></button>`);
106
- }
107
- if (isTerminal) {
108
- buttons.push(html `<span class="er-pending-state er-pending-state--filed">filed (${workflow.state})</span>`);
109
- }
110
- buttons.push(html `<button class="er-btn er-btn-small" data-action="shortcuts" type="button" aria-label="Show keyboard shortcuts" title="Keyboard shortcuts">?</button>`);
111
- return unsafe(`<span class="er-strip-right">${buttons.join('')}</span>`);
112
- }
113
- function renderShortcutsOverlay() {
114
- return unsafe(html `
115
- <div class="er-shortcuts" data-shortcuts-overlay hidden role="dialog" aria-modal="true" aria-label="Keyboard shortcuts">
116
- <div class="er-shortcuts-backdrop" data-shortcuts-backdrop></div>
117
- <div class="er-shortcuts-panel">
118
- <h2>Keyboard</h2>
119
- <dl>
120
- <dt><kbd>e</kbd> / dbl-click</dt><dd>toggle edit mode</dd>
121
- <dt><kbd>a</kbd> <kbd>a</kbd></dt><dd>approve <em>— press twice within 500ms</em></dd>
122
- <dt><kbd>i</kbd> <kbd>i</kbd></dt><dd>iterate <em>— press twice within 500ms</em></dd>
123
- <dt><kbd>r</kbd> <kbd>r</kbd></dt><dd>reject <em>— press twice within 500ms</em></dd>
124
- <dt><kbd>?</kbd></dt><dd>this panel</dd>
125
- <dt><kbd>esc</kbd></dt><dd>close</dd>
126
- </dl>
127
- <p class="er-shortcuts-footer">Press <kbd>?</kbd> anytime.</p>
128
- </div>
129
- </div>`);
130
- }
131
- function renderEditToolbar() {
132
- return unsafe(html `
133
- <div class="er-edit-toolbar" data-edit-toolbar hidden>
134
- <div class="er-edit-modes" role="tablist" aria-label="Editor mode">
135
- <button class="er-edit-mode-btn" data-edit-view="source" type="button" aria-pressed="true">Source</button>
136
- <button class="er-edit-mode-btn" data-edit-view="split" type="button" aria-pressed="false">Split</button>
137
- <button class="er-edit-mode-btn" data-edit-view="preview" type="button" aria-pressed="false">Preview</button>
138
- </div>
139
- <div class="er-edit-actions">
140
- <button class="er-btn er-btn-small" data-action="focus-mode" type="button" title="Distraction-free mode (Shift+F)" aria-pressed="false">Focus ⛶</button>
141
- <button class="er-btn er-btn-primary" data-action="save-version" type="button">Save as new version</button>
142
- <button class="er-btn" data-action="cancel-edit" type="button">Cancel</button>
143
- <span class="er-edit-hint" data-edit-hint></span>
144
- </div>
145
- </div>`);
146
- }
78
+ /**
79
+ * Desktop-only edit panes. The `data-edit-mode` body attribute is the
80
+ * gate (set by the existing client toggle-edit handler). On mobile,
81
+ * edit mode is not currently surfaced from this surface — the broader
82
+ * shortform state-machine migration is the right place to design that.
83
+ */
147
84
  function renderEditPanes() {
148
85
  return unsafe(html `
149
86
  <div class="er-edit-mode" data-edit-panes-host hidden>
@@ -211,6 +148,9 @@ export async function renderShortformReviewPage(ctx, workflowId, query) {
211
148
  const { fm, bodyHtml } = await prepareShortformRender(currentVersion.markdown);
212
149
  const draftState = { workflow, currentVersion, versions };
213
150
  const titleField = stringField(fm.title) ?? `Draft: ${slug}`;
151
+ // #244 — extract TOC from the rendered body. `rehype-slug` already
152
+ // gave every h2/h3/h4 an `id`; `extractToc` reads the slugs + text.
153
+ const tocEntries = extractToc(bodyHtml);
214
154
  const shortformMeta = unsafe(html `
215
155
  <div class="er-shortform-meta">
216
156
  <span class="er-platform">${workflow.platform ?? 'other'}</span>
@@ -219,6 +159,30 @@ export async function renderShortformReviewPage(ctx, workflowId, query) {
219
159
  : ''}
220
160
  </div>`);
221
161
  const folioSpine = `shortform · ${workflow.platform ?? '?'}${workflow.channel ? ` · ${workflow.channel}` : ''} · ${slug}`;
162
+ // v7 universal masthead (mobile-only at this commit). Kicker carries
163
+ // the platform badge + channel as inline markup; the meta tag holds
164
+ // the galley number. Slug occupies the bottom row.
165
+ const platformLabel = workflow.platform ?? 'other';
166
+ const channelLabel = workflow.channel ? ` ${workflow.channel}` : '';
167
+ const mastheadKicker = unsafe(html `<span class="platform">${platformLabel}</span>${channelLabel}`);
168
+ const masthead = renderMasthead({
169
+ kickerHtml: mastheadKicker,
170
+ slug,
171
+ metaInline: `№ ${currentVersion.version}`,
172
+ isHub: false,
173
+ });
174
+ // v7 universal mobile bar. The cell list is composed mechanically
175
+ // from the surface's TOC + Versions + (always) Actions per
176
+ // DESIGN-STANDARDS.md § Universal bar contract. The Actions cell
177
+ // guarantees the bar is never empty.
178
+ const barCells = getShortformBarCells({ tocEntries, versions });
179
+ const mobileBar = renderMobileBar({ contextual: barCells });
180
+ const mobileSheet = renderShortformMobileSheet({
181
+ tocEntries,
182
+ versions,
183
+ workflow,
184
+ currentVersion,
185
+ });
222
186
  const pageGrid = html `
223
187
  <div class="er-page-grid">
224
188
  <div class="er-draft-frame">
@@ -229,30 +193,16 @@ export async function renderShortformReviewPage(ctx, workflowId, query) {
229
193
  </div>`;
230
194
  const body = html `
231
195
  <div data-review-ui="shortform" class="er-review-shell">
196
+ ${masthead}
197
+ ${renderMastheadMenu()}
232
198
  ${renderEditorialFolio('shortform', folioSpine)}
233
199
  ${shortformMeta}
234
- <div class="er-strip">
235
- <div class="er-strip-inner">
236
- <a class="er-strip-back" href="/dev/editorial-studio" title="Back to the editorial studio">← studio</a>
237
- <span class="er-strip-galley">${gloss('galley')} <em>№ ${currentVersion.version}</em></span>
238
- <span class="er-strip-slug">${workflow.site} / ${workflow.slug}</span>
239
- ${renderVersionsStrip(versions, currentVersion)}
240
- <span class="er-strip-center">
241
- <span class="er-stamp er-stamp-big er-stamp-${workflow.state}" data-state-label>
242
- ${stateLabel(workflow.state)}
243
- </span>
244
- <span class="er-strip-hint">double-click to edit · <kbd>?</kbd> for shortcuts</span>
245
- </span>
246
- ${renderControlsRight(workflow)}
247
- </div>
248
- </div>
249
- ${renderEditToolbar()}
250
200
  <article class="er-page">
251
201
  ${unsafe(pageGrid)}
252
202
  </article>
203
+ ${mobileBar}
204
+ ${mobileSheet}
253
205
  <div class="er-toast" data-toast hidden></div>
254
- ${renderShortcutsOverlay()}
255
- <div class="er-poll-indicator" data-poll>auto-refresh · 8s</div>
256
206
  </div>`;
257
207
  return layout({
258
208
  title: `${titleField} — Review`,
@@ -261,6 +211,7 @@ export async function renderShortformReviewPage(ctx, workflowId, query) {
261
211
  '/static/css/editorial-nav.css',
262
212
  '/static/css/blog-figure.css',
263
213
  '/static/css/review-viewport.css',
214
+ '/static/css/mobile-shell.css',
264
215
  ],
265
216
  bodyHtml: body,
266
217
  embeddedJson: [{ id: 'draft-state', data: draftState }],
@@ -1 +1 @@
1
- {"version":3,"file":"shortform-review.js","sourceRoot":"","sources":["../../src/pages/shortform-review.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shortform-review.js","sourceRoot":"","sources":["../../src/pages/shortform-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAKvD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAgB,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,oCAAoC,CAAC;AAO5C,SAAS,aAAa,CACpB,IAAa;IAEb,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5D,OAAO,UAAU,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,IAAa;IAClC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,SAAS,WAAW,CAAC,CAAU;IAC7B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/C,CAAC;AAOD,KAAK,UAAU,sBAAsB,CAAC,QAAgB;IACpD,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;IAC9B,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEzD,+DAA+D;IAC/D,MAAM,WAAW,GAAG,WAAW,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,WAAW;QACzB,CAAC,CAAC,8BAA8B,UAAU,CAAC,WAAW,CAAC,MAAM;QAC7D,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,YAAY,GAChB,OAAO,IAAI,OAAO,IAAI,CAAC;QACrB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;QACxE,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC;IAEzB,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;AACxC,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe;IACtB,OAAO,MAAM,CAAC,IAAI,CAAA;;;;;;;;;;;;;;WAcT,CAAC,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,UAAkB,EAAE,OAAe;IACtD,MAAM,IAAI,GAAG,IAAI,CAAA;;QAEX,oBAAoB,CAAC,WAAW,EAAE,eAAe,UAAU,EAAE,CAAC;;;8CAGxB,UAAU;aAC3C,OAAO;;;WAGT,CAAC;IACV,OAAO,MAAM,CAAC;QACZ,KAAK,EAAE,YAAY,UAAU,UAAU;QACvC,QAAQ,EAAE;YACR,kCAAkC;YAClC,+BAA+B;SAChC;QACD,QAAQ,EAAE,IAAI;QACd,aAAa,EAAE,EAAE;KAClB,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,GAAkB,EAClB,UAAkB,EAClB,KAA2B;IAE3B,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE;QAC7D,EAAE,EAAE,UAAU;QACd,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,OAAO,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE3B,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;IACxF,MAAM,cAAc,GAClB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEjF,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,WAAW,CAAC,UAAU,EAAE,qCAAqC,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,MAAM,sBAAsB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,UAAU,IAAI,EAAE,CAAC;IAE7D,mEAAmE;IACnE,oEAAoE;IACpE,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAExC,MAAM,aAAa,GAAY,MAAM,CAAC,IAAI,CAAA;;kCAEV,QAAQ,CAAC,QAAQ,IAAI,OAAO;QACtD,QAAQ,CAAC,OAAO;QAChB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAA,4BAA4B,QAAQ,CAAC,OAAO,SAAS,CAAC;QACnE,CAAC,CAAC,EAAE;WACD,CAAC,CAAC;IAEX,MAAM,UAAU,GAAG,eAAe,QAAQ,CAAC,QAAQ,IAAI,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC;IAE1H,qEAAqE;IACrE,oEAAoE;IACpE,mDAAmD;IACnD,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC;IACnD,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAA,0BAA0B,aAAa,UAAU,YAAY,EAAE,CAAC,CAAC;IACnG,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,UAAU,EAAE,cAAc;QAC1B,IAAI;QACJ,UAAU,EAAE,KAAK,cAAc,CAAC,OAAO,EAAE;QACzC,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,kEAAkE;IAClE,2DAA2D;IAC3D,iEAAiE;IACjE,qCAAqC;IACrC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChE,MAAM,SAAS,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAG,0BAA0B,CAAC;QAC7C,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,cAAc;KACf,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,IAAI,CAAA;;;;yCAIkB,MAAM,CAAC,QAAQ,CAAC;UAC/C,eAAe,EAAE;;WAEhB,CAAC;IAEV,MAAM,IAAI,GAAG,IAAI,CAAA;;QAEX,QAAQ;QACR,kBAAkB,EAAE;QACpB,oBAAoB,CAAC,WAAW,EAAE,UAAU,CAAC;QAC7C,aAAa;;UAEX,MAAM,CAAC,QAAQ,CAAC;;QAElB,SAAS;QACT,WAAW;;WAER,CAAC;IAEV,OAAO,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,UAAU,WAAW;QAC/B,QAAQ,EAAE;YACR,kCAAkC;YAClC,+BAA+B;YAC/B,6BAA6B;YAC7B,iCAAiC;YACjC,8BAA8B;SAC/B;QACD,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QACvD,aAAa,EAAE,CAAC,yBAAyB,CAAC;KAC3C,CAAC,CAAC;AACL,CAAC"}
@@ -15,9 +15,28 @@
15
15
  *
16
16
  * Workflow-keyed wording in this file is documenting that deliberate
17
17
  * deferral; do not flag in audits.
18
+ *
19
+ * Step 2.2.10 (v7 universal chrome + state-machine compliance):
20
+ * - Server-side: er-strip / er-stamp / er-pending-state / shortcut
21
+ * overlay / state-branched control rendering all REMOVED. The
22
+ * universal `renderMobileBar` primitive + a shortform-specific
23
+ * sheet host (`./shortform-review-mobile-sheet.ts`) replace them.
24
+ * - Per DESIGN-STANDARDS.md § Universal bar contract: this surface
25
+ * composes its `Cell[]` mechanically (TOC / Versions / Actions)
26
+ * and passes it to `renderMobileBar`. No bespoke chrome shape.
27
+ * - Per DESKWORK-STATE-MACHINE.md Commandment III: no review-state
28
+ * labels on the page (the stamp + pending pills were the
29
+ * violations).
30
+ * - The desktop edit-mode panes (`renderEditPanes`) stay — they're
31
+ * gated by the existing `data-edit-mode` body attribute and the
32
+ * Edit toolbar is still part of the desktop chrome. Mobile edit
33
+ * entry-point is deferred to a future task (the workplan keeps
34
+ * the broader shortform state-machine migration explicitly out
35
+ * of scope for this step).
18
36
  */
19
37
 
20
38
  import { handleGetWorkflow } from '@deskwork/core/review/handlers';
39
+ import { extractToc } from '@deskwork/core/review/toc';
21
40
  import type {
22
41
  DraftVersion,
23
42
  DraftWorkflowItem,
@@ -27,10 +46,16 @@ import {
27
46
  renderMarkdownToHtml,
28
47
  } from '@deskwork/core/review/render';
29
48
  import type { StudioContext } from '../routes/api.ts';
30
- import { html, unsafe, type RawHtml } from './html.ts';
49
+ import { escapeHtml, html, unsafe, type RawHtml } from './html.ts';
31
50
  import { layout } from './layout.ts';
32
51
  import { renderEditorialFolio } from './chrome.ts';
33
- import { escapeHtml, gloss } from './html.ts';
52
+ import { renderMasthead } from './masthead.ts';
53
+ import { renderMastheadMenu } from './masthead-menu.ts';
54
+ import { renderMobileBar } from './mobile-bar.ts';
55
+ import {
56
+ getShortformBarCells,
57
+ renderShortformMobileSheet,
58
+ } from './shortform-review-mobile-sheet.ts';
34
59
 
35
60
  interface ShortformReviewQuery {
36
61
  /** ?v=<n>; null shows the workflow's currentVersion. */
@@ -56,10 +81,6 @@ function stringField(v: unknown): string | undefined {
56
81
  return typeof v === 'string' ? v : undefined;
57
82
  }
58
83
 
59
- function stateLabel(state?: string): string {
60
- return (state ?? '').replace('-', ' ');
61
- }
62
-
63
84
  interface PreparedRender {
64
85
  fm: Record<string, unknown>;
65
86
  bodyHtml: string;
@@ -84,122 +105,12 @@ async function prepareShortformRender(markdown: string): Promise<PreparedRender>
84
105
  return { fm, bodyHtml: renderedHtml };
85
106
  }
86
107
 
87
- function renderVersionsStrip(
88
- versions: readonly DraftVersion[],
89
- current: DraftVersion,
90
- ): RawHtml {
91
- if (versions.length <= 1) return unsafe('');
92
- const links = versions
93
- .map((v) => {
94
- const isActive = v.version === current.version;
95
- const href = `?v=${v.version}`;
96
- return html`<a href="${href}" class="${isActive ? 'active' : ''}">v${v.version}</a>`;
97
- })
98
- .join('');
99
- return unsafe(html`<span class="er-strip-versions">${unsafe(links)}</span>`);
100
- }
101
-
102
- function pendingSkillCmd(workflow: DraftWorkflowItem): string {
103
- const { site, slug, state } = workflow;
104
- if (state === 'iterating') {
105
- return `/deskwork:iterate --site ${site} ${slug}`;
106
- }
107
- if (state === 'approved') {
108
- return `/deskwork:approve --site ${site} ${slug}`;
109
- }
110
- return '';
111
- }
112
-
113
- function shortcutChipWrap(buttonHtml: string, letter: 'a' | 'i' | 'r'): string {
114
- return html`<span class="er-shortcut-chip-wrap">${unsafe(buttonHtml)}<small class="er-shortcut-chip"><kbd>${letter}</kbd><kbd>${letter}</kbd></small></span>`;
115
- }
116
-
117
- function renderControlsRight(workflow: DraftWorkflowItem): RawHtml {
118
- const isActive = workflow.state === 'open' || workflow.state === 'in-review';
119
- const isApproved = workflow.state === 'approved';
120
- const isIterating = workflow.state === 'iterating';
121
- const isTerminal = workflow.state === 'applied' || workflow.state === 'cancelled';
122
- const buttons: string[] = [];
123
- buttons.push(html`<button class="er-btn er-btn-small" data-action="toggle-edit" type="button">Edit</button><span class="er-edit-mode-label" data-mode="preview">preview</span>`);
124
- if (isActive) {
125
- buttons.push(
126
- shortcutChipWrap(
127
- html`<button class="er-btn er-btn-small er-btn-approve" data-action="approve" type="button">Approve</button>`,
128
- 'a',
129
- ),
130
- );
131
- buttons.push(
132
- shortcutChipWrap(
133
- html`<button class="er-btn er-btn-small" data-action="iterate" type="button">Iterate</button>`,
134
- 'i',
135
- ),
136
- );
137
- buttons.push(
138
- shortcutChipWrap(
139
- html`<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`,
140
- 'r',
141
- ),
142
- );
143
- }
144
- if (isApproved) {
145
- const applyCmd = pendingSkillCmd(workflow);
146
- buttons.push(html`<span class="er-pending-state">awaiting apply…</span>`);
147
- buttons.push(html`<button class="er-btn er-btn-small" data-action="copy-cmd" data-cmd="${applyCmd}" title="Copy ${applyCmd} to clipboard" type="button">copy <code>/deskwork:approve</code></button>`);
148
- buttons.push(
149
- shortcutChipWrap(
150
- html`<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`,
151
- 'r',
152
- ),
153
- );
154
- }
155
- if (isIterating) {
156
- const iterateCmd = pendingSkillCmd(workflow);
157
- buttons.push(html`<span class="er-pending-state">agent iterating…</span>`);
158
- buttons.push(html`<button class="er-btn er-btn-small" data-action="copy-cmd" data-cmd="${iterateCmd}" title="Copy ${iterateCmd} to clipboard" type="button">copy <code>/deskwork:iterate</code></button>`);
159
- }
160
- if (isTerminal) {
161
- buttons.push(html`<span class="er-pending-state er-pending-state--filed">filed (${workflow.state})</span>`);
162
- }
163
- buttons.push(html`<button class="er-btn er-btn-small" data-action="shortcuts" type="button" aria-label="Show keyboard shortcuts" title="Keyboard shortcuts">?</button>`);
164
- return unsafe(`<span class="er-strip-right">${buttons.join('')}</span>`);
165
- }
166
-
167
- function renderShortcutsOverlay(): RawHtml {
168
- return unsafe(html`
169
- <div class="er-shortcuts" data-shortcuts-overlay hidden role="dialog" aria-modal="true" aria-label="Keyboard shortcuts">
170
- <div class="er-shortcuts-backdrop" data-shortcuts-backdrop></div>
171
- <div class="er-shortcuts-panel">
172
- <h2>Keyboard</h2>
173
- <dl>
174
- <dt><kbd>e</kbd> / dbl-click</dt><dd>toggle edit mode</dd>
175
- <dt><kbd>a</kbd> <kbd>a</kbd></dt><dd>approve <em>— press twice within 500ms</em></dd>
176
- <dt><kbd>i</kbd> <kbd>i</kbd></dt><dd>iterate <em>— press twice within 500ms</em></dd>
177
- <dt><kbd>r</kbd> <kbd>r</kbd></dt><dd>reject <em>— press twice within 500ms</em></dd>
178
- <dt><kbd>?</kbd></dt><dd>this panel</dd>
179
- <dt><kbd>esc</kbd></dt><dd>close</dd>
180
- </dl>
181
- <p class="er-shortcuts-footer">Press <kbd>?</kbd> anytime.</p>
182
- </div>
183
- </div>`);
184
- }
185
-
186
- function renderEditToolbar(): RawHtml {
187
- return unsafe(html`
188
- <div class="er-edit-toolbar" data-edit-toolbar hidden>
189
- <div class="er-edit-modes" role="tablist" aria-label="Editor mode">
190
- <button class="er-edit-mode-btn" data-edit-view="source" type="button" aria-pressed="true">Source</button>
191
- <button class="er-edit-mode-btn" data-edit-view="split" type="button" aria-pressed="false">Split</button>
192
- <button class="er-edit-mode-btn" data-edit-view="preview" type="button" aria-pressed="false">Preview</button>
193
- </div>
194
- <div class="er-edit-actions">
195
- <button class="er-btn er-btn-small" data-action="focus-mode" type="button" title="Distraction-free mode (Shift+F)" aria-pressed="false">Focus ⛶</button>
196
- <button class="er-btn er-btn-primary" data-action="save-version" type="button">Save as new version</button>
197
- <button class="er-btn" data-action="cancel-edit" type="button">Cancel</button>
198
- <span class="er-edit-hint" data-edit-hint></span>
199
- </div>
200
- </div>`);
201
- }
202
-
108
+ /**
109
+ * Desktop-only edit panes. The `data-edit-mode` body attribute is the
110
+ * gate (set by the existing client toggle-edit handler). On mobile,
111
+ * edit mode is not currently surfaced from this surface — the broader
112
+ * shortform state-machine migration is the right place to design that.
113
+ */
203
114
  function renderEditPanes(): RawHtml {
204
115
  return unsafe(html`
205
116
  <div class="er-edit-mode" data-edit-panes-host hidden>
@@ -280,6 +191,10 @@ export async function renderShortformReviewPage(
280
191
  const draftState = { workflow, currentVersion, versions };
281
192
  const titleField = stringField(fm.title) ?? `Draft: ${slug}`;
282
193
 
194
+ // #244 — extract TOC from the rendered body. `rehype-slug` already
195
+ // gave every h2/h3/h4 an `id`; `extractToc` reads the slugs + text.
196
+ const tocEntries = extractToc(bodyHtml);
197
+
283
198
  const shortformMeta: RawHtml = unsafe(html`
284
199
  <div class="er-shortform-meta">
285
200
  <span class="er-platform">${workflow.platform ?? 'other'}</span>
@@ -290,6 +205,32 @@ export async function renderShortformReviewPage(
290
205
 
291
206
  const folioSpine = `shortform · ${workflow.platform ?? '?'}${workflow.channel ? ` · ${workflow.channel}` : ''} · ${slug}`;
292
207
 
208
+ // v7 universal masthead (mobile-only at this commit). Kicker carries
209
+ // the platform badge + channel as inline markup; the meta tag holds
210
+ // the galley number. Slug occupies the bottom row.
211
+ const platformLabel = workflow.platform ?? 'other';
212
+ const channelLabel = workflow.channel ? ` ${workflow.channel}` : '';
213
+ const mastheadKicker = unsafe(html`<span class="platform">${platformLabel}</span>${channelLabel}`);
214
+ const masthead = renderMasthead({
215
+ kickerHtml: mastheadKicker,
216
+ slug,
217
+ metaInline: `№ ${currentVersion.version}`,
218
+ isHub: false,
219
+ });
220
+
221
+ // v7 universal mobile bar. The cell list is composed mechanically
222
+ // from the surface's TOC + Versions + (always) Actions per
223
+ // DESIGN-STANDARDS.md § Universal bar contract. The Actions cell
224
+ // guarantees the bar is never empty.
225
+ const barCells = getShortformBarCells({ tocEntries, versions });
226
+ const mobileBar = renderMobileBar({ contextual: barCells });
227
+ const mobileSheet = renderShortformMobileSheet({
228
+ tocEntries,
229
+ versions,
230
+ workflow,
231
+ currentVersion,
232
+ });
233
+
293
234
  const pageGrid = html`
294
235
  <div class="er-page-grid">
295
236
  <div class="er-draft-frame">
@@ -301,30 +242,16 @@ export async function renderShortformReviewPage(
301
242
 
302
243
  const body = html`
303
244
  <div data-review-ui="shortform" class="er-review-shell">
245
+ ${masthead}
246
+ ${renderMastheadMenu()}
304
247
  ${renderEditorialFolio('shortform', folioSpine)}
305
248
  ${shortformMeta}
306
- <div class="er-strip">
307
- <div class="er-strip-inner">
308
- <a class="er-strip-back" href="/dev/editorial-studio" title="Back to the editorial studio">← studio</a>
309
- <span class="er-strip-galley">${gloss('galley')} <em>№ ${currentVersion.version}</em></span>
310
- <span class="er-strip-slug">${workflow.site} / ${workflow.slug}</span>
311
- ${renderVersionsStrip(versions, currentVersion)}
312
- <span class="er-strip-center">
313
- <span class="er-stamp er-stamp-big er-stamp-${workflow.state}" data-state-label>
314
- ${stateLabel(workflow.state)}
315
- </span>
316
- <span class="er-strip-hint">double-click to edit · <kbd>?</kbd> for shortcuts</span>
317
- </span>
318
- ${renderControlsRight(workflow)}
319
- </div>
320
- </div>
321
- ${renderEditToolbar()}
322
249
  <article class="er-page">
323
250
  ${unsafe(pageGrid)}
324
251
  </article>
252
+ ${mobileBar}
253
+ ${mobileSheet}
325
254
  <div class="er-toast" data-toast hidden></div>
326
- ${renderShortcutsOverlay()}
327
- <div class="er-poll-indicator" data-poll>auto-refresh · 8s</div>
328
255
  </div>`;
329
256
 
330
257
  return layout({
@@ -334,6 +261,7 @@ export async function renderShortformReviewPage(
334
261
  '/static/css/editorial-nav.css',
335
262
  '/static/css/blog-figure.css',
336
263
  '/static/css/review-viewport.css',
264
+ '/static/css/mobile-shell.css',
337
265
  ],
338
266
  bodyHtml: body,
339
267
  embeddedJson: [{ id: 'draft-state', data: draftState }],
@@ -1 +1 @@
1
- {"version":3,"file":"shortform.d.ts","sourceRoot":"","sources":["../../src/pages/shortform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAoGtD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4C9D"}
1
+ {"version":3,"file":"shortform.d.ts","sourceRoot":"","sources":["../../src/pages/shortform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAmGtD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4C9D"}
@@ -72,7 +72,6 @@ function renderRow(w, now) {
72
72
  <span class="er-row-site er-row-site--${w.site}" title="${w.site}">${siteLabel(w.site)}</span>
73
73
  <span class="er-row-slug">${w.slug}</span>
74
74
  ${channelMarkup}
75
- <span class="er-stamp er-stamp-${w.state}">${w.state.replace('-', ' ')}</span>
76
75
  <span class="er-row-ts">v${w.currentVersion} · ${fmtRelTime(w.updatedAt, now)}</span>
77
76
  <span class="er-row-hint">Open in review →</span>
78
77
  </a>`);
@@ -1 +1 @@
1
- {"version":3,"file":"shortform.js","sourceRoot":"","sources":["../../src/pages/shortform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shortform.js","sourceRoot":"","sources":["../../src/pages/shortform.ts"],"names":[],"mappings":"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"}
@@ -82,7 +82,6 @@ function renderRow(w: DraftWorkflowItem, now: Date): RawHtml {
82
82
  <span class="er-row-site er-row-site--${w.site}" title="${w.site}">${siteLabel(w.site)}</span>
83
83
  <span class="er-row-slug">${w.slug}</span>
84
84
  ${channelMarkup}
85
- <span class="er-stamp er-stamp-${w.state}">${w.state.replace('-', ' ')}</span>
86
85
  <span class="er-row-ts">v${w.currentVersion} · ${fmtRelTime(w.updatedAt, now)}</span>
87
86
  <span class="er-row-hint">Open in review →</span>
88
87
  </a>`);
package/dist/server.js CHANGED
@@ -474,10 +474,50 @@ async function main() {
474
474
  const { createServer: createViteServer } = await import('vite');
475
475
  const { getRequestListener } = await import('@hono/node-server');
476
476
  const http = await import('node:http');
477
+ const net = await import('node:net');
477
478
  const { join } = await import('node:path');
479
+ // Vite's HMR WebSocket needs its own port. Default is 24678. When
480
+ // multiple worktrees run dev studios simultaneously, the second
481
+ // worktree's Vite cannot bind 24678 — it silently fails to spin
482
+ // up an HMR WS server, and the browser ends up trying to connect
483
+ // to whatever process IS on 24678 (the OTHER worktree's Vite).
484
+ // The handshake fails with HTTP 426; the Vite client falls back
485
+ // to "polling for restart" mode which page-reloads several times
486
+ // per second. That presents to the operator as "pathologically
487
+ // refreshing as fast as possible." Walk forward to find a free
488
+ // port so each concurrent worktree gets its own HMR slot.
489
+ const findFreePort = async (base, max) => {
490
+ for (let p = base; p <= max; p += 1) {
491
+ const free = await new Promise((resolve) => {
492
+ const probe = net.createServer();
493
+ probe.once('error', () => resolve(false));
494
+ probe.once('listening', () => {
495
+ probe.close(() => resolve(true));
496
+ });
497
+ // Bind to all interfaces (dual-stack IPv4+IPv6) so the probe
498
+ // matches what Vite actually does. A previous version of this
499
+ // helper bound 127.0.0.1 only and got false-positives when
500
+ // another worktree's Vite was on IPv6 wildcard (*:24678) —
501
+ // the IPv4 probe succeeded, then Vite's actual bind failed.
502
+ probe.listen(p);
503
+ });
504
+ if (free)
505
+ return p;
506
+ }
507
+ throw new Error(`deskwork-studio: no free HMR port in [${base}, ${max}]. ` +
508
+ `Another worktree's dev studio is likely holding the range.`);
509
+ };
510
+ const hmrPort = await findFreePort(24678, 24777);
478
511
  const viteRoot = join(pluginRoot(), 'public');
479
512
  const vite = await createViteServer({
480
- server: { middlewareMode: true, allowedHosts: true },
513
+ server: {
514
+ middlewareMode: true,
515
+ allowedHosts: true,
516
+ // Pin the HMR WS to the port we just verified free. Without
517
+ // this, Vite uses its default 24678 and silently loses to
518
+ // any other worktree that grabbed it first.
519
+ hmr: { port: hmrPort },
520
+ },
481
521
  appType: 'custom',
482
522
  root: viteRoot,
483
523
  });