gutterpress 0.0.0 → 0.9.0-alpha.2

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 (82) hide show
  1. package/README.md +21 -16
  2. package/dist/api/index.d.ts +6 -2
  3. package/dist/api/index.js +15 -3
  4. package/dist/{audit-1jrwwacz.js → audit-nhn2pjz3.js} +5 -5
  5. package/dist/{build-5s92kgpc.js → build-san7fv2z.js} +5 -5
  6. package/dist/checks/types.d.ts +6 -0
  7. package/dist/{cli-hvqjs342.js → cli-e5zhb0xs.js} +2 -2
  8. package/dist/{cli-7ykx7pp0.js → cli-k4bd06sd.js} +982 -331
  9. package/dist/{cli-h7phg3ka.js → cli-najycadg.js} +258 -258
  10. package/dist/{index-1tyj4bk3.js → cli-wchtvxvw.js} +4 -1
  11. package/dist/{cli-5bxx0c4y.js → cli-yzrh708h.js} +2 -2
  12. package/dist/cli.js +15 -15
  13. package/dist/{doctor-25zjt8dz.js → doctor-hrk0kxxz.js} +2 -2
  14. package/dist/{index-3h3fef7r.js → index-bynn850m.js} +1200 -410
  15. package/dist/{cli-1tyj4bk3.js → index-wchtvxvw.js} +4 -1
  16. package/dist/{index-5bxx0c4y.js → index-yzrh708h.js} +2 -2
  17. package/dist/index.js +15 -3
  18. package/dist/lib/asset-inline.d.ts +19 -0
  19. package/dist/lib/build-error.d.ts +0 -1
  20. package/dist/lib/build-fingerprint.d.ts +14 -0
  21. package/dist/lib/build-runner.d.ts +21 -1
  22. package/dist/lib/ghostscript.d.ts +0 -9
  23. package/dist/lib/manifest-config.d.ts +7 -0
  24. package/dist/lib/manifest.d.ts +8 -0
  25. package/dist/lib/markdown/assemble.d.ts +4 -6
  26. package/dist/lib/markdown/chapter-id.d.ts +1 -1
  27. package/dist/lib/markdown/index.d.ts +1 -2
  28. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  29. package/dist/lib/output-paths.d.ts +0 -1
  30. package/dist/lib/pagination.d.ts +0 -20
  31. package/dist/lib/pdf-inspect.d.ts +2 -2
  32. package/dist/lib/pdf-parse.d.ts +2 -1
  33. package/dist/lib/plugin-manager.d.ts +0 -2
  34. package/dist/lib/presets.d.ts +36 -7
  35. package/dist/lib/project-scaffold.d.ts +39 -2
  36. package/dist/lib/project-source.d.ts +11 -9
  37. package/dist/lib/project-templates.d.ts +45 -7
  38. package/dist/lib/publish/providers/azure-swa.d.ts +0 -1
  39. package/dist/lib/publish/providers/itch.d.ts +0 -1
  40. package/dist/lib/publish/providers/shopify.d.ts +0 -1
  41. package/dist/lib/remote-auth/github-repos.d.ts +0 -2
  42. package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -5
  43. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +3 -2
  44. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -8
  45. package/dist/lib/remote-auth/sync.d.ts +2 -10
  46. package/dist/lib/remote-auth/test-access.d.ts +0 -2
  47. package/dist/lib/remote-auth/test-support/git-http-server.d.ts +0 -37
  48. package/dist/lib/source-provider.d.ts +0 -14
  49. package/dist/lib/static-serve.d.ts +39 -0
  50. package/dist/lib/style-resolver.d.ts +10 -1
  51. package/dist/lib/targets.d.ts +84 -0
  52. package/dist/lib/theme-import.d.ts +0 -2
  53. package/dist/lib/theme-manager.d.ts +1 -1
  54. package/dist/lib/validation-exec.d.ts +4 -3
  55. package/dist/{lint-f63nxjfj.js → lint-96j9hrj4.js} +5 -5
  56. package/dist/{manifest.schema-rn1dpdpn.json → manifest.schema-z61rzw44.json} +12 -6
  57. package/dist/new-8p38wavc.js +185 -0
  58. package/dist/{pagedjs-interface-80g3xa8p.js → pagedjs-interface-qxvzgwd7.js} +92 -33
  59. package/dist/{plugin-wf2cbmwc.js → plugin-ees6nhkc.js} +5 -5
  60. package/dist/{preflight-tg4mpz1h.js → preflight-1q6c2edh.js} +24 -29
  61. package/dist/preview/file-watcher.d.ts +48 -39
  62. package/dist/preview/http-server.d.ts +5 -2
  63. package/dist/preview/server-context.d.ts +24 -0
  64. package/dist/preview-shell-6dqexx1m.js +581 -0
  65. package/dist/{preview-h2bwbb2x.js → preview-y5a2zen1.js} +5 -5
  66. package/dist/{project-source-0tg0r6vm.js → project-source-p0gn1wd5.js} +3 -3
  67. package/dist/{publish-ztaet53r.js → publish-rm9yb3wh.js} +5 -5
  68. package/dist/render.js +10 -19
  69. package/dist/{repair-jkp4n3j8.js → repair-zgq7q2g6.js} +5 -5
  70. package/dist/schema/manifest.types.d.ts +14 -3
  71. package/dist/server.d.ts +2 -0
  72. package/dist/{source-provider-hp96b5jf.js → source-provider-c1rjm2c0.js} +2 -8
  73. package/dist/types.d.ts +0 -1
  74. package/dist/{validate-k2drs19j.js → validate-nr0xa6sa.js} +8 -8
  75. package/package.json +2 -2
  76. package/dist/chapter-01-jj8ftn4c.md +0 -29
  77. package/dist/lib/validation-profile.d.ts +0 -18
  78. package/dist/manifest-ryyg71nf.yaml +0 -9
  79. package/dist/new-7b08a42h.js +0 -105
  80. package/dist/preview-shell-rghtfmc8.js +0 -155
  81. package/dist/theme-0yzvthe8.css +0 -65
  82. package/dist/theme-6a2fngc8.json +0 -6
@@ -10,6 +10,8 @@
10
10
  var debugMode = false;
11
11
  var currentViewMode = 'two-column';
12
12
  var ignoreScrollUntil = 0;
13
+ var lastSourceLine = -1;
14
+ var lastSourceChapter = null;
13
15
 
14
16
  function refreshPages() {
15
17
  pages = Array.from(document.querySelectorAll('.pagedjs_page'));
@@ -51,6 +53,7 @@
51
53
  currentPage = page;
52
54
  ignoreScrollUntil = Date.now() + 300;
53
55
  pages[page - 1].scrollIntoView({ behavior: 'instant', block: 'start', inline: 'nearest' });
56
+ recordVisibleSource();
54
57
  }
55
58
 
56
59
  function pageStep(mode) {
@@ -129,11 +132,13 @@
129
132
  var top = el.getBoundingClientRect().top;
130
133
  if (top < ref) {
131
134
  // Find the next annotated sibling-in-flow (document order, higher line —
132
- // a higher line also guarantees we haven't crossed into the next chapter,
133
- // where data-source-line restarts).
135
+ // explicitly constrained to this chapter because source lines restart per
136
+ // file and a following chapter may begin at any line after front matter).
134
137
  var blocks = sourcedBlocks();
135
138
  var idx = blocks.indexOf(el);
139
+ var chapter = chapterOf(el);
136
140
  for (var i = idx + 1; i < blocks.length; i++) {
141
+ if (chapterOf(blocks[i]) !== chapter) break;
137
142
  var nl = lineOf(blocks[i]);
138
143
  if (nl == null) continue;
139
144
  if (nl <= line) break;
@@ -148,6 +153,34 @@
148
153
  return { el: el, line: line };
149
154
  }
150
155
 
156
+ function recordVisibleSource() {
157
+ var pos = visibleSourcePosition();
158
+ lastSourceLine = pos && pos.line != null ? pos.line : -1;
159
+ lastSourceChapter = pos ? chapterOf(pos.el) : null;
160
+ }
161
+
162
+ function preserveViewport(change) {
163
+ refreshPages();
164
+ var position = visibleSourcePosition();
165
+ var anchor = position ? {
166
+ el: position.el,
167
+ top: position.el.getBoundingClientRect().top
168
+ } : null;
169
+ change();
170
+ refreshPages();
171
+ if (!anchor) {
172
+ scrollToCurrentPage();
173
+ return;
174
+ }
175
+ ignoreScrollUntil = Date.now() + 300;
176
+ var delta = anchor.el.getBoundingClientRect().top - anchor.top;
177
+ if (delta) {
178
+ window.scrollBy({ top: delta, behavior: 'instant' });
179
+ }
180
+ currentPage = detectVisiblePage();
181
+ recordVisibleSource();
182
+ }
183
+
151
184
  // Resolve a {line, chapter?} target to its enclosing annotated block PLUS the
152
185
  // following annotated block, so callers can interpolate within long blocks
153
186
  // (markdown-it-source-map only annotates top-level *_open tokens, so a target
@@ -205,16 +238,19 @@
205
238
  prevPage: function (mode) { return api.goToPage(currentPage - pageStep(mode)); },
206
239
  nextPage: function (mode) { return api.goToPage(currentPage + pageStep(mode)); },
207
240
  lastPage: function () { refreshPages(); return api.goToPage(pages.length); },
208
- setViewMode: function (mode) {
209
- refreshPages();
210
- currentViewMode = mode || 'two-column';
211
- document.body.classList.remove('view-single', 'view-spread', 'view-two-column');
212
- if (mode) document.body.classList.add('view-' + mode);
213
- scrollToCurrentPage();
214
- return api.notifyPageChange();
241
+ setViewMode: function (mode, silent) {
242
+ preserveViewport(function () {
243
+ currentViewMode = mode || 'two-column';
244
+ document.body.classList.remove('view-single', 'view-spread', 'view-two-column');
245
+ if (mode) document.body.classList.add('view-' + mode);
246
+ });
247
+ if (!silent) return api.notifyPageChange();
215
248
  },
216
- setZoom: function (z) {
217
- document.documentElement.style.setProperty('--gutterpress-zoom', z);
249
+ setZoom: function (z, silent) {
250
+ preserveViewport(function () {
251
+ document.documentElement.style.setProperty('--gutterpress-zoom', z);
252
+ });
253
+ if (!silent) api.notifyPageChange();
218
254
  },
219
255
  toggleDebugMode: function () {
220
256
  debugMode = !debugMode;
@@ -237,13 +273,23 @@
237
273
  refresh: function () {
238
274
  refreshPages();
239
275
  currentPage = detectVisiblePage();
276
+ recordVisibleSource();
240
277
  return api.notifyPageChange();
241
278
  },
242
279
 
243
280
  // ── ADR 0005 generic primitives ─────────────────────────────────────────
244
281
  // Bumped whenever a command/event is added so a hot-updated SPA can
245
282
  // feature-detect against an older bundled lib.
246
- getProtocolVersion: function () { return 2; },
283
+ getProtocolVersion: function () { return 3; },
284
+
285
+ // Publish any debounced reader movement before a host atomically replaces
286
+ // this frame. `silent` lets the shell relay the returned event itself so a
287
+ // queued postMessage from the outgoing frame cannot arrive after the swap.
288
+ flushScroll: function (silent) {
289
+ if (scrollTimer) clearTimeout(scrollTimer);
290
+ scrollTimer = null;
291
+ return publishScrollPosition(true, silent === true);
292
+ },
247
293
 
248
294
  // Heading tree with page + source line — powers chapter jump (UX-013), TOC,
249
295
  // minimap, scrollspy. Page math needs same-origin paged.js access, so it
@@ -313,6 +359,7 @@
313
359
  }
314
360
  var page = pageIndexOf(el);
315
361
  if (page) currentPage = page;
362
+ if (!opts.smooth) recordVisibleSource();
316
363
  return { page: page || currentPage, sourceLine: lineOf(el) };
317
364
  },
318
365
 
@@ -369,6 +416,7 @@
369
416
  if (spec.scroll && els[0]) {
370
417
  ignoreScrollUntil = Date.now() + 350;
371
418
  els[0].scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
419
+ recordVisibleSource();
372
420
  }
373
421
  if (spec.transient && els.length) {
374
422
  setTimeout(function () {
@@ -458,28 +506,38 @@
458
506
 
459
507
  // Scroll tracking
460
508
  var scrollTimer = null;
461
- var lastSourceLine = -1;
462
- window.addEventListener('scroll', function () {
509
+ function publishScrollPosition(force, silent) {
510
+ scrollTimer = null;
511
+ var remainingGuard = ignoreScrollUntil - Date.now();
512
+ if (!force && remainingGuard > 0) {
513
+ scrollTimer = setTimeout(publishScrollPosition, remainingGuard);
514
+ return null;
515
+ }
463
516
  if (pages.length === 0) refreshPages();
464
- if (pages.length === 0) return;
465
- if (scrollTimer) clearTimeout(scrollTimer);
466
- scrollTimer = setTimeout(function () {
467
- if (Date.now() < ignoreScrollUntil) return;
468
- var page = detectVisiblePage();
469
- if (page !== currentPage) {
470
- currentPage = page;
471
- api.notifyPageChange();
472
- }
473
- // Emit finer-grained source position for editor sync (ADR 0005).
474
- var pos = visibleSourcePosition();
475
- var sl = pos ? pos.line : null;
476
- if (sl != null && sl !== lastSourceLine) {
477
- lastSourceLine = sl;
478
- window.dispatchEvent(new CustomEvent('sourceLineChanged', {
479
- detail: { sourceLine: sl, chapter: chapterOf(pos.el), page: pageIndexOf(pos.el) }
480
- }));
517
+ if (pages.length === 0) return null;
518
+ var page = detectVisiblePage();
519
+ if (page !== currentPage) {
520
+ currentPage = page;
521
+ if (!silent) api.notifyPageChange();
522
+ }
523
+ // Emit finer-grained source position for editor sync (ADR 0005).
524
+ var pos = visibleSourcePosition();
525
+ var sl = pos ? pos.line : null;
526
+ var chapter = pos ? chapterOf(pos.el) : null;
527
+ if (sl != null && (sl !== lastSourceLine || chapter !== lastSourceChapter)) {
528
+ lastSourceLine = sl;
529
+ lastSourceChapter = chapter;
530
+ var detail = { sourceLine: sl, chapter: chapter, page: pageIndexOf(pos.el) };
531
+ if (!silent) {
532
+ window.dispatchEvent(new CustomEvent('sourceLineChanged', { detail: detail }));
481
533
  }
482
- }, 150);
534
+ return detail;
535
+ }
536
+ return null;
537
+ }
538
+ window.addEventListener('scroll', function () {
539
+ if (scrollTimer) clearTimeout(scrollTimer);
540
+ scrollTimer = setTimeout(publishScrollPosition, 150);
483
541
  });
484
542
 
485
543
  // Paged.js calls this when rendering is complete
@@ -490,7 +548,8 @@
490
548
  pageObserver.disconnect();
491
549
  currentPage = 1;
492
550
  ignoreScrollUntil = Date.now() + 300;
493
- window.scrollTo(0, 0);
551
+ window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
552
+ recordVisibleSource();
494
553
  console.log('Paged.js rendered ' + pages.length + ' pages');
495
554
  api.notifyRenderingComplete();
496
555
  setTimeout(api.notifyPageChange, 0);
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  addNpmPlugin,
3
3
  parseNpmPluginSpec
4
- } from "./cli-7ykx7pp0.js";
5
- import"./cli-h7phg3ka.js";
4
+ } from "./cli-k4bd06sd.js";
5
+ import"./cli-najycadg.js";
6
6
  import {
7
7
  EXIT_CODES,
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-hvqjs342.js";
12
- import"./cli-5bxx0c4y.js";
13
- import"./cli-1tyj4bk3.js";
11
+ } from "./cli-e5zhb0xs.js";
12
+ import"./cli-yzrh708h.js";
13
+ import"./cli-wchtvxvw.js";
14
14
  import"./cli-yja077f6.js";
15
15
  import"./cli-37x76zdn.js";
16
16
 
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  executeValidation,
3
+ publishTargetFor,
3
4
  reportMissingTools
4
- } from "./cli-7ykx7pp0.js";
5
+ } from "./cli-k4bd06sd.js";
5
6
  import {
6
7
  log
7
- } from "./cli-h7phg3ka.js";
8
+ } from "./cli-najycadg.js";
8
9
  import {
9
10
  EXIT_CODES,
10
11
  UsageError,
11
12
  rejectExtraPositionals,
12
13
  rejectUnknownFlags
13
- } from "./cli-hvqjs342.js";
14
- import"./cli-5bxx0c4y.js";
15
- import"./cli-1tyj4bk3.js";
14
+ } from "./cli-e5zhb0xs.js";
15
+ import"./cli-yzrh708h.js";
16
+ import"./cli-wchtvxvw.js";
16
17
  import"./cli-yja077f6.js";
17
18
  import"./cli-37x76zdn.js";
18
19
 
@@ -35,25 +36,19 @@ function severityRank(severity) {
35
36
  return 2;
36
37
  }
37
38
  function buildRequiredChecks(execution) {
38
- if (execution.profile !== "dtrpg")
39
- return [];
40
- const requiredIds = [
41
- "pdf.structure.qpdf",
42
- "pdf.print.pdfx-markers",
43
- "pdf.print.pdfx-metadata",
44
- "pdf.print.embedded-fonts"
45
- ];
46
39
  const skipped = new Set(execution.tools.skippedChecks);
47
- const failed = new Set(execution.report.errors.map((item) => item.checkId));
48
40
  const passed = new Set(execution.report.passed);
49
- return requiredIds.map((id) => {
50
- if (skipped.has(id))
51
- return { id, status: "skipped" };
52
- if (failed.has(id))
53
- return { id, status: "fail" };
54
- if (passed.has(id))
55
- return { id, status: "pass" };
56
- return { id, status: "fail" };
41
+ return execution.targets.flatMap((targetId) => {
42
+ const failed = new Set(execution.report.errors.filter((item) => item.target === undefined || item.target === targetId).map((item) => item.checkId));
43
+ return publishTargetFor(targetId).requiredChecks.map((id) => {
44
+ if (skipped.has(id))
45
+ return { target: targetId, id, status: "skipped" };
46
+ if (failed.has(id))
47
+ return { target: targetId, id, status: "fail" };
48
+ if (passed.has(id))
49
+ return { target: targetId, id, status: "pass" };
50
+ return { target: targetId, id, status: "fail" };
51
+ });
57
52
  });
58
53
  }
59
54
  function buildPreflightPayload(execution) {
@@ -85,8 +80,8 @@ function buildPreflightPayload(execution) {
85
80
  const requiredChecks = buildRequiredChecks(execution);
86
81
  const skippedRequired = requiredChecks.some((check) => check.status === "skipped");
87
82
  return {
88
- schemaVersion: 1,
89
- profile: execution.profile ?? null,
83
+ schemaVersion: 2,
84
+ targets: [...execution.targets],
90
85
  status: computeStatus(execution.report.summary.errors, execution.report.summary.warnings, execution.tools.missing.length, skippedRequired),
91
86
  pdf: execution.context.pdfPath ?? "",
92
87
  summary: { ...execution.report.summary },
@@ -103,7 +98,7 @@ function toPreflightMarkdown(payload) {
103
98
  lines.push("# gutterpress preflight");
104
99
  lines.push("");
105
100
  lines.push(`- Status: **${payload.status}**`);
106
- lines.push(`- Profile: ${payload.profile ?? "none"}`);
101
+ lines.push(`- Targets: ${payload.targets.length > 0 ? payload.targets.join(", ") : "none"}`);
107
102
  lines.push(`- PDF: ${payload.pdf}`);
108
103
  lines.push(`- Summary: ${payload.summary.errors} error(s), ${payload.summary.warnings} warning(s), ${payload.summary.passed}/${payload.summary.total} checks passed`);
109
104
  if (payload.tools.missing.length > 0) {
@@ -117,7 +112,7 @@ function toPreflightMarkdown(payload) {
117
112
  lines.push("");
118
113
  lines.push("## Required Checks");
119
114
  for (const check of payload.requiredChecks) {
120
- lines.push(`- ${check.id}: ${check.status}`);
115
+ lines.push(`- [${check.target}] ${check.id}: ${check.status}`);
121
116
  }
122
117
  }
123
118
  if (payload.issues.length > 0) {
@@ -155,9 +150,9 @@ var preflightArgs = {
155
150
  type: "string",
156
151
  description: "Path to manifest.yaml"
157
152
  },
158
- profile: {
153
+ target: {
159
154
  type: "string",
160
- description: "Validation profile lock (currently: dtrpg)"
155
+ description: "Publish targets to preflight against (comma-separated, e.g. dtrpg,itch), overriding the manifest's `targets:`"
161
156
  },
162
157
  "report-dir": {
163
158
  type: "string",
@@ -187,7 +182,7 @@ var preflight_default = defineCommand({
187
182
  pdf: typeof args.pdf === "string" ? args.pdf : undefined,
188
183
  input,
189
184
  phase: "post-build",
190
- profile: typeof args.profile === "string" ? args.profile : undefined
185
+ target: typeof args.target === "string" ? args.target : undefined
191
186
  });
192
187
  } catch (error) {
193
188
  log.error(error instanceof Error ? error.message : String(error));
@@ -8,31 +8,11 @@ import { type FSWatcher } from 'chokidar';
8
8
  import type { ServerState } from './server-context';
9
9
  import type { ResolvedPluginConfig } from '../schema/manifest.types';
10
10
  /**
11
- * Whether the double-buffered preview shell is active. The historical env name
12
- * is retained because users may already set it; chapter splicing is gone and
13
- * every source change now performs a full-document pagination.
11
+ * Whether the incremental preview shell is active. The historical env name is
12
+ * retained because users may already set it. A single Markdown edit paginates
13
+ * only that source file; geometry-wide changes still swap a full document.
14
14
  */
15
15
  export declare function incrementalPreviewEnabled(): boolean;
16
- /**
17
- * Shared preview render path. renderChapters() does all Markdown, CSS, and
18
- * Paged.js-slot work.
19
- *
20
- * Named `renderPreviewBook` (ARCH finding #53) to distinguish it from
21
- * build-runner.ts's `renderBook` — same name, different module, and a
22
- * genuinely different contract: this one is degrade-and-report (a plugin the
23
- * author enabled but hasn't installed yet is skipped with a loud warning so
24
- * the rest of the live preview still renders); build-runner.ts's is
25
- * fail-fast, because a final artifact must never silently drop a plugin.
26
- * Both preambles now share {@link loadPluginsWithCss}.
27
- */
28
- export declare function renderPreviewBook(inputPath: string, config: {
29
- title?: string;
30
- styles?: string[];
31
- plugins?: ResolvedPluginConfig[];
32
- }, opts: {
33
- files: string[] | null;
34
- wrapChapters: boolean;
35
- }): Promise<string>;
36
16
  /**
37
17
  * Rewrite rendered book HTML for the live preview. Injects, in order:
38
18
  * 1. pagedjs-interface.js — defines window.previewAPI for in-iframe controls
@@ -48,21 +28,12 @@ export declare function renderPreviewBook(inputPath: string, config: {
48
28
  * serving it from a stable disk path lets the OS file-cache
49
29
  * and Defender hash-cache stay warm across sessions.
50
30
  *
51
- * PAGINATION FIDELITY: this injects NO page-break rule. It used to add
52
- * `<style>.gutterpress-chapter{break-before:page}</style>` whenever the incremental
53
- * preview was on (i.e. by default), which started EVERY source file on a new
54
- * page in the live view. `gutterpress build` emits no such rule — assembleBookHtml
55
- * in lib/markdown/assemble.ts concatenates source files flat into <body> and
56
- * breaks only where project CSS or a markdown-it-paged marker says to — so any
57
- * project that splits one chapter across several source files previewed with
58
- * different page boundaries than the PDF it built. Preview and build now take
59
- * their breaks from exactly the same rules.
60
- *
61
- * Chapter identity is attached to existing source-mapped blocks. No wrapper or
62
- * preview-only CSS is emitted, so selectors see exactly the same document tree
63
- * as the build.
31
+ * With `pageIsolateChapters`, each source wrapper starts on a fresh page. This
32
+ * is the v0.8.3 incremental-preview invariant: a standalone source render owns
33
+ * the same page boundary as that source in the live preview and can be spliced
34
+ * without paginating the full document.
64
35
  */
65
- export declare function injectPreviewScripts(html: string): string;
36
+ export declare function injectPreviewScripts(html: string, pageIsolateChapters: boolean): string;
66
37
  /**
67
38
  * Generate HTML from markdown and write book.html to the temp directory.
68
39
  * The desktop's iframe loads `book.html` via a relative URL — same name in
@@ -70,6 +41,12 @@ export declare function injectPreviewScripts(html: string): string;
70
41
  *
71
42
  * Empty `inputPath` writes a static placeholder — the desktop app (packages/desktop)
72
43
  * supplies a real path via its own folder picker.
44
+ *
45
+ * `cssAssets` is REQUIRED, not optional: it is the map the HTTP server resolves
46
+ * the inliner's rewritten asset URLs against (see {@link ServerState.cssAssets}),
47
+ * so a caller that skipped it would render a book.html whose shared-art URLs
48
+ * 404 — the exact bug this parameter exists to fix, reintroduced silently.
49
+ * Every render replaces its contents.
73
50
  */
74
51
  export declare function generateAndWriteHtml(inputPath: string, tempDir: string, config: {
75
52
  title?: string;
@@ -78,7 +55,38 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
78
55
  files?: string[] | null;
79
56
  };
80
57
  plugins?: ResolvedPluginConfig[];
81
- }): Promise<void>;
58
+ }, cssAssets: Map<string, string>): Promise<void>;
59
+ /**
60
+ * Render one source file with the same CSS, plugins, source metadata, and
61
+ * preview scripts as the full book. The shell paginates this small document in
62
+ * a hidden iframe and replaces only the edited source file's pages.
63
+ */
64
+ export declare function renderChapterPreviewHtml(inputPath: string, file: string, config: {
65
+ title?: string;
66
+ styles?: string[];
67
+ plugins?: ResolvedPluginConfig[];
68
+ }): Promise<string>;
69
+ /** One changed project file, named for the preview broadcast decision. */
70
+ export interface ChangedFile {
71
+ relativePath: string;
72
+ ext: string;
73
+ event: string;
74
+ }
75
+ /**
76
+ * Describe in-project changes using the same canonical path form emitted in
77
+ * `data-chapter-src`. Declared external dependencies intentionally drop out;
78
+ * their presence in the original change count forces a full reload.
79
+ */
80
+ export declare function describeChanges(changes: [filePath: string, event: string][], inputResolved: string): ChangedFile[];
81
+ export type BroadcastDecision = {
82
+ kind: 'chapter-splice';
83
+ chapterId: string;
84
+ relativePath: string;
85
+ } | {
86
+ kind: 'full-reload';
87
+ };
88
+ /** A single surviving Markdown edit can be paginated independently. */
89
+ export declare function decideBroadcast(files: ChangedFile[], changeCount: number, incremental: boolean): BroadcastDecision;
82
90
  /**
83
91
  * Chokidar 5's `ignored` matcher (regex or function) is tested against the
84
92
  * fully-qualified, forward-slash-normalized ABSOLUTE path — never a path
@@ -102,7 +110,8 @@ export declare function isDotPathUnderRoot(candidatePath: string, root: string):
102
110
  * Whether the watcher should ignore a path.
103
111
  *
104
112
  * IN-PROJECT paths get the dotfile rule ({@link isDotPathUnderRoot}) — a
105
- * project's own `.git/`, `.DS_Store`, editor swap files, and so on are noise.
113
+ * project's own `.git/`, `.DS_Store`, editor swap files, and so on are noise
114
+ * plus the generated/vendored-subtree rule ({@link isGeneratedProjectPath}).
106
115
  *
107
116
  * A path OUTSIDE the project is only ever seen because it is a DECLARED
108
117
  * external dependency the manifest named explicitly (see
@@ -17,10 +17,13 @@ export interface PreviewServer {
17
17
  /** Stop the server and close all connections. */
18
18
  close(): Promise<void>;
19
19
  /**
20
- * Broadcast a `{ type: "full-reload" }` message to every connected HMR
21
- * client. Safe to call after `close()` (no-op).
20
+ * Advance the rendered revision and notify every connected HMR client.
21
+ * Disconnected clients reconcile to the latest revision when they reconnect.
22
+ * Safe to call after `close()` (no-op).
22
23
  */
23
24
  broadcastReload(): void;
25
+ /** Paginate and replace one edited Markdown source in connected shells. */
26
+ broadcastContentUpdate(file: string): void;
24
27
  }
25
28
  export declare function isPortAvailable(port: number, host?: string): Promise<boolean>;
26
29
  /**
@@ -19,6 +19,8 @@ export interface ServerState {
19
19
  isRebuilding: boolean;
20
20
  /** Active rebuild, awaited before a project restart closes its watcher. */
21
21
  rebuildPromise?: Promise<void> | null;
22
+ /** Bypass watcher settling for a host write that has already completed. */
23
+ notifySettledWrite?: ((filePath: string, writtenContent: string) => void) | null;
22
24
  /** node:http + `ws` preview HTTP/WebSocket server instance */
23
25
  previewServer: PreviewServer | null;
24
26
  /** Is server shutting down? (prevents multiple shutdown calls) */
@@ -29,6 +31,28 @@ export interface ServerState {
29
31
  config: ResolvedConfig;
30
32
  /** Server options */
31
33
  options: PreviewServerOptions;
34
+ /**
35
+ * Files the inlined CSS references but could not embed, as
36
+ * `book.html`-relative URL path → absolute source path.
37
+ *
38
+ * `asset-inline.ts` embeds fonts and images up to
39
+ * `IMAGE_INLINE_MAX_BYTES`; anything larger keeps its project-relative path
40
+ * (if it lives in the book) or becomes `assets/<contentHash><ext>` (if it
41
+ * does not — e.g. art referenced from a repo-root shared stylesheet, the
42
+ * normative multi-book layout). Either way the inliner returns a COPY PLAN,
43
+ * which the build executes into its output dir.
44
+ *
45
+ * The preview serves the project in place and has no output dir, so a
46
+ * rewritten `assets/<hash>` URL had nothing behind it and shared art
47
+ * rendered broken in the live preview while building correctly. Keeping the
48
+ * plan here lets the server resolve those URLs straight from their real
49
+ * location — same URL as the build, still nothing copied.
50
+ *
51
+ * Rebuilt from scratch on every render, so a stylesheet edit that drops an
52
+ * image drops its URL too. Exact-match lookups only: never a path-traversal
53
+ * surface.
54
+ */
55
+ cssAssets: Map<string, string>;
32
56
  }
33
57
  /**
34
58
  * Create initial server state