gutterpress 0.9.0-alpha.1 → 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 (30) hide show
  1. package/dist/api/index.js +1 -1
  2. package/dist/{audit-k16v3n98.js → audit-nhn2pjz3.js} +3 -3
  3. package/dist/{build-8tqr4gbb.js → build-san7fv2z.js} +3 -3
  4. package/dist/{cli-rkqxbnkq.js → cli-e5zhb0xs.js} +1 -1
  5. package/dist/{cli-yp0d2hp8.js → cli-k4bd06sd.js} +377 -78
  6. package/dist/{cli-v2dze8ja.js → cli-najycadg.js} +1 -1
  7. package/dist/cli.js +15 -15
  8. package/dist/{doctor-57czxy3q.js → doctor-hrk0kxxz.js} +2 -2
  9. package/dist/{index-m3pa4wyf.js → index-bynn850m.js} +379 -80
  10. package/dist/index.js +1 -1
  11. package/dist/lib/markdown/assemble.d.ts +4 -6
  12. package/dist/lib/markdown/chapter-id.d.ts +1 -1
  13. package/dist/lib/markdown/index.d.ts +1 -1
  14. package/dist/{lint-4sq0dvf6.js → lint-96j9hrj4.js} +3 -3
  15. package/dist/{new-f2nmyr5b.js → new-8p38wavc.js} +3 -3
  16. package/dist/{pagedjs-interface-80g3xa8p.js → pagedjs-interface-qxvzgwd7.js} +92 -33
  17. package/dist/{plugin-xskq3dtx.js → plugin-ees6nhkc.js} +3 -3
  18. package/dist/{preflight-epcffags.js → preflight-1q6c2edh.js} +3 -3
  19. package/dist/preview/file-watcher.d.ts +39 -17
  20. package/dist/preview/http-server.d.ts +5 -2
  21. package/dist/preview/server-context.d.ts +2 -0
  22. package/dist/preview-shell-6dqexx1m.js +581 -0
  23. package/dist/{preview-xf5dktz2.js → preview-y5a2zen1.js} +3 -3
  24. package/dist/{publish-97vdwq31.js → publish-rm9yb3wh.js} +3 -3
  25. package/dist/render.js +10 -19
  26. package/dist/{repair-cz4z4hkg.js → repair-zgq7q2g6.js} +3 -3
  27. package/dist/server.d.ts +2 -0
  28. package/dist/{validate-swzejnd8.js → validate-nr0xa6sa.js} +3 -3
  29. package/package.json +1 -1
  30. package/dist/preview-shell-rghtfmc8.js +0 -155
package/dist/index.js CHANGED
@@ -142,7 +142,7 @@ import {
142
142
  validateProjectPlugins,
143
143
  verifyRepoReadable,
144
144
  writeAppHeartbeat
145
- } from "./index-m3pa4wyf.js";
145
+ } from "./index-bynn850m.js";
146
146
  import {
147
147
  AUTO_SNAPSHOT_MESSAGE,
148
148
  HISTORY_PAGE_LIMIT,
@@ -37,12 +37,10 @@ export interface AssembleBookHtmlOptions {
37
37
  plugins?: LoadedPlugin[];
38
38
  pluginCss?: string;
39
39
  /**
40
- * Add `data-chapter-src="<file>"` to the same source-mapped block elements
41
- * that receive `data-source-line`. The historical option name is retained as
42
- * public API, but no wrapper is emitted: even `display: contents` changes CSS
43
- * selector relationships such as `body > *`, `:first-child`, and cross-file
44
- * siblings, which can change pagination. Off by default; build output is
45
- * unaffected.
40
+ * Wrap each source file in `<div class="gutterpress-chapter"
41
+ * data-chapter-src="<file>">`. Used only by incremental preview so one
42
+ * source can be paginated and replaced independently. Off by default; build
43
+ * output is unaffected.
46
44
  */
47
45
  wrapChapters?: boolean;
48
46
  /**
@@ -5,7 +5,7 @@
5
5
  * boundary: project-root-relative, forward slashes, no `./` prefix, no
6
6
  * duplicate slashes. This single form is used by:
7
7
  *
8
- * - preview source blocks' `data-chapter-src` tagging
8
+ * - preview source wrappers' `data-chapter-src` tagging
9
9
  * (lib/markdown/assemble.ts assembleBookHtml — index.ts's renderChapters
10
10
  * is now just the thin node:fs wrapper around it)
11
11
  * - preview source inspection and chapter-scoped scroll restoration
@@ -37,7 +37,7 @@ export declare function renderChapters(inputDir: string, opts?: {
37
37
  files?: string[] | null;
38
38
  plugins?: LoadedPlugin[];
39
39
  pluginCss?: string;
40
- /** Add chapter identity to source-mapped blocks for preview inspection. */
40
+ /** Wrap each source file for incremental preview pagination. */
41
41
  wrapChapters?: boolean;
42
42
  /**
43
43
  * ARCH finding #4: per-chapter author-mistake warnings computed by
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  MANIFEST_FILENAMES,
3
3
  runLint
4
- } from "./cli-yp0d2hp8.js";
4
+ } from "./cli-k4bd06sd.js";
5
5
  import {
6
6
  log
7
- } from "./cli-v2dze8ja.js";
7
+ } from "./cli-najycadg.js";
8
8
  import {
9
9
  EXIT_CODES,
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-rkqxbnkq.js";
13
+ } from "./cli-e5zhb0xs.js";
14
14
  import"./cli-yzrh708h.js";
15
15
  import"./cli-wchtvxvw.js";
16
16
  import"./cli-yja077f6.js";
@@ -5,17 +5,17 @@ import {
5
5
  TARGETS,
6
6
  TARGET_IDS,
7
7
  scaffoldProject
8
- } from "./cli-yp0d2hp8.js";
8
+ } from "./cli-k4bd06sd.js";
9
9
  import {
10
10
  isToolAvailable,
11
11
  resolveGhostscript
12
- } from "./cli-v2dze8ja.js";
12
+ } from "./cli-najycadg.js";
13
13
  import {
14
14
  EXIT_CODES,
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-rkqxbnkq.js";
18
+ } from "./cli-e5zhb0xs.js";
19
19
  import"./cli-yzrh708h.js";
20
20
  import"./cli-wchtvxvw.js";
21
21
  import"./cli-yja077f6.js";
@@ -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,14 +1,14 @@
1
1
  import {
2
2
  addNpmPlugin,
3
3
  parseNpmPluginSpec
4
- } from "./cli-yp0d2hp8.js";
5
- import"./cli-v2dze8ja.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-rkqxbnkq.js";
11
+ } from "./cli-e5zhb0xs.js";
12
12
  import"./cli-yzrh708h.js";
13
13
  import"./cli-wchtvxvw.js";
14
14
  import"./cli-yja077f6.js";
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-yp0d2hp8.js";
5
+ } from "./cli-k4bd06sd.js";
6
6
  import {
7
7
  log
8
- } from "./cli-v2dze8ja.js";
8
+ } from "./cli-najycadg.js";
9
9
  import {
10
10
  EXIT_CODES,
11
11
  UsageError,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-rkqxbnkq.js";
14
+ } from "./cli-e5zhb0xs.js";
15
15
  import"./cli-yzrh708h.js";
16
16
  import"./cli-wchtvxvw.js";
17
17
  import"./cli-yja077f6.js";
@@ -8,9 +8,9 @@ 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
16
  /**
@@ -28,21 +28,12 @@ export declare function incrementalPreviewEnabled(): boolean;
28
28
  * serving it from a stable disk path lets the OS file-cache
29
29
  * and Defender hash-cache stay warm across sessions.
30
30
  *
31
- * PAGINATION FIDELITY: this injects NO page-break rule. It used to add
32
- * `<style>.gutterpress-chapter{break-before:page}</style>` whenever the incremental
33
- * preview was on (i.e. by default), which started EVERY source file on a new
34
- * page in the live view. `gutterpress build` emits no such rule — assembleBookHtml
35
- * in lib/markdown/assemble.ts concatenates source files flat into <body> and
36
- * breaks only where project CSS or a markdown-it-paged marker says to — so any
37
- * project that splits one chapter across several source files previewed with
38
- * different page boundaries than the PDF it built. Preview and build now take
39
- * their breaks from exactly the same rules.
40
- *
41
- * Chapter identity is attached to existing source-mapped blocks. No wrapper or
42
- * preview-only CSS is emitted, so selectors see exactly the same document tree
43
- * 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.
44
35
  */
45
- export declare function injectPreviewScripts(html: string): string;
36
+ export declare function injectPreviewScripts(html: string, pageIsolateChapters: boolean): string;
46
37
  /**
47
38
  * Generate HTML from markdown and write book.html to the temp directory.
48
39
  * The desktop's iframe loads `book.html` via a relative URL — same name in
@@ -65,6 +56,37 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
65
56
  };
66
57
  plugins?: ResolvedPluginConfig[];
67
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;
68
90
  /**
69
91
  * Chokidar 5's `ignored` matcher (regex or function) is tested against the
70
92
  * fully-qualified, forward-slash-normalized ABSOLUTE path — never a path
@@ -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) */