gutterpress 0.11.10 → 0.11.11-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 (92) hide show
  1. package/README.md +3 -3
  2. package/dist/api/index.js +2 -2
  3. package/dist/{build-83dnh2qe.js → build-pjd42x09.js} +4 -4
  4. package/dist/checks/asset/extensions.d.ts +2 -2
  5. package/dist/checks/policy.d.ts +5 -7
  6. package/dist/checks/register-builtins.d.ts +6 -6
  7. package/dist/checks/registry.d.ts +1 -1
  8. package/dist/{cli-79tsqp3p.js → cli-1kdtzgxm.js} +1 -1
  9. package/dist/{cli-mv1mhpkk.js → cli-d2he8zph.js} +6 -6
  10. package/dist/{cli-b36za51e.js → cli-ny50k8md.js} +3 -3
  11. package/dist/{cli-30j93gzt.js → cli-tjhn2mdn.js} +3 -3
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-sy4gc8r9.js → doctor-qkh9byxa.js} +2 -2
  14. package/dist/engine/shared/cdp.d.ts +5 -9
  15. package/dist/engine/shared/pdf-inspect.d.ts +0 -4
  16. package/dist/{engine-mwshptgf.js → engine-132czsq9.js} +1 -1
  17. package/dist/{engine-3gwctsty.js → engine-bgd4aa99.js} +2 -2
  18. package/dist/{ext-a7ffvyc0.js → ext-271xnh70.js} +4 -4
  19. package/dist/{index-dzp0hc2c.js → index-b6p1fh61.js} +1 -1
  20. package/dist/{index-r4wvy5v3.js → index-mw8b25yx.js} +3 -3
  21. package/dist/{index-0cvkvpkg.js → index-ssjh6tnd.js} +7 -7
  22. package/dist/index.js +3 -3
  23. package/dist/lib/asset-inline.d.ts +1 -2
  24. package/dist/lib/build-error.d.ts +4 -6
  25. package/dist/lib/build-fingerprint.d.ts +3 -4
  26. package/dist/lib/build-preflight.d.ts +4 -6
  27. package/dist/lib/build-runner.d.ts +24 -35
  28. package/dist/lib/build-staging.d.ts +3 -5
  29. package/dist/lib/cli-args.d.ts +1 -1
  30. package/dist/lib/desktop.d.ts +2 -8
  31. package/dist/lib/engine.d.ts +2 -2
  32. package/dist/lib/exec.d.ts +3 -6
  33. package/dist/lib/fetch-timeout.d.ts +3 -4
  34. package/dist/lib/ghostscript.d.ts +1 -1
  35. package/dist/lib/host-policy.d.ts +6 -8
  36. package/dist/lib/image-inspect.d.ts +2 -2
  37. package/dist/lib/install-hints.d.ts +2 -3
  38. package/dist/lib/manifest-config.d.ts +2 -3
  39. package/dist/lib/manifest-doc.d.ts +2 -3
  40. package/dist/lib/manifest.d.ts +4 -7
  41. package/dist/lib/markdown/assemble.d.ts +7 -13
  42. package/dist/lib/markdown/gutterpress-css.d.ts +6 -9
  43. package/dist/lib/markdown/images.d.ts +0 -7
  44. package/dist/lib/markdown/index.d.ts +7 -11
  45. package/dist/lib/markdown/markers.d.ts +3 -4
  46. package/dist/lib/markdown/plugins.d.ts +9 -12
  47. package/dist/lib/markdown/renderer.d.ts +10 -14
  48. package/dist/lib/missing-asset-placeholder.d.ts +2 -5
  49. package/dist/lib/output-paths.d.ts +4 -5
  50. package/dist/lib/pdf-inspect.d.ts +5 -5
  51. package/dist/lib/pdf-parse.d.ts +6 -7
  52. package/dist/lib/presets.d.ts +2 -3
  53. package/dist/lib/project-source.d.ts +3 -4
  54. package/dist/lib/publish/command-runner.d.ts +1 -1
  55. package/dist/lib/publish/connect-google.d.ts +2 -2
  56. package/dist/lib/publish/types.d.ts +3 -3
  57. package/dist/lib/remote-auth/clone.d.ts +2 -2
  58. package/dist/lib/remote-auth/converge-merge.d.ts +1 -1
  59. package/dist/lib/remote-auth/diagnose.d.ts +3 -3
  60. package/dist/lib/remote-auth/generic-auth.d.ts +1 -1
  61. package/dist/lib/remote-auth/git-http.d.ts +5 -5
  62. package/dist/lib/remote-auth/github-auth.d.ts +3 -3
  63. package/dist/lib/remote-auth/github-repos.d.ts +4 -4
  64. package/dist/lib/remote-auth/sync-types.d.ts +1 -1
  65. package/dist/lib/remote-auth/sync.d.ts +1 -1
  66. package/dist/lib/remote-auth/test-access.d.ts +1 -1
  67. package/dist/lib/remote-auth/token-store.d.ts +1 -1
  68. package/dist/lib/remote-auth/transport.d.ts +3 -3
  69. package/dist/lib/render-parity-raster.d.ts +28 -0
  70. package/dist/lib/render-parity.d.ts +4 -1
  71. package/dist/lib/source-provider.d.ts +4 -5
  72. package/dist/lib/static-serve.d.ts +8 -13
  73. package/dist/lib/style-resolver.d.ts +1 -1
  74. package/dist/lib/targets.d.ts +1 -5
  75. package/dist/lib/tool-probe.d.ts +2 -3
  76. package/dist/lib/validation-exec.d.ts +3 -4
  77. package/dist/{new-994trd8k.js → new-7w4ezfpy.js} +4 -4
  78. package/dist/platform.d.ts +8 -41
  79. package/dist/{preflight-2rtrakyf.js → preflight-f3h6arjp.js} +4 -4
  80. package/dist/preview/file-watcher.d.ts +3 -5
  81. package/dist/preview/lifecycle.d.ts +9 -17
  82. package/dist/{preview-bridge-b4jbtb79.js → preview-bridge-8jhd1wpb.js} +1 -1
  83. package/dist/{preview-interface-9rwz21yw.js → preview-interface-twd6nsf5.js} +74 -13
  84. package/dist/{preview-shell-tqndvhbx.js → preview-shell-y8yrm3f0.js} +8 -19
  85. package/dist/{preview-n9s46142.js → preview-y39jrpbs.js} +4 -4
  86. package/dist/{publish-wte9548r.js → publish-tvj3xh8m.js} +4 -4
  87. package/dist/render.d.ts +5 -5
  88. package/dist/render.js +2 -2
  89. package/dist/schema/manifest.types.d.ts +2 -2
  90. package/dist/utils/logger.d.ts +1 -2
  91. package/dist/{validate-jfsh3m1c.js → validate-9enr4ecc.js} +4 -4
  92. package/package.json +3 -3
@@ -7,16 +7,16 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-mv1mhpkk.js";
10
+ } from "./cli-d2he8zph.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-79tsqp3p.js";
13
+ } from "./cli-1kdtzgxm.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-b36za51e.js";
19
- import"./cli-30j93gzt.js";
18
+ } from "./cli-ny50k8md.js";
19
+ import"./cli-tjhn2mdn.js";
20
20
  import {
21
21
  EXIT_CODES
22
22
  } from "./cli-46ycxe6r.js";
@@ -2,22 +2,19 @@
2
2
  * Platform abstraction contract (GitHub #41).
3
3
  *
4
4
  * The desktop talks to its host through ONE seam so that the app code never
5
- * branches on `electron` vs `web` directly. Today the only host is Electron
6
- * (IPC + native dialogs). When the PWA lands (0.6.0, #...), a `WebAdapter`
7
- * backed by the File System Access API drops in behind the same interface and
8
- * the app code does not change.
5
+ * branches on the host directly. The only host is Electron (IPC + native
6
+ * dialogs).
9
7
  *
10
8
  * This module is **types only** — it is imported by the browser SPA via
11
9
  * `import type`, so it must never pull in a Node runtime dependency.
12
10
  *
13
11
  * Canonical home: `gutterpress`. The desktop re-exports these types
14
- * from `src/lib/platform/` and implements them in `electron-adapter.ts` /
15
- * `web-adapter.ts`. Keep the implementations in lockstep with this contract.
12
+ * from `src/lib/platform/` and implements them in `electron-adapter.ts`.
13
+ * Keep the implementation in lockstep with this contract.
16
14
  */
17
15
  /**
18
- * The narrow set of capabilities whose implementation genuinely differs between
19
- * a desktop (Electron) host and a browser (PWA) host: native dialogs, raw file
20
- * IO, filesystem watching, and OS-keychain-backed secrets.
16
+ * The narrow set of host file primitives: raw file IO and filesystem
17
+ * watching.
21
18
  *
22
19
  * Host RPC services that are *also* host-divergent (preview/build/doctor/prefs/
23
20
  * updater) are modelled separately as {@link HostServices} so this primitive
@@ -45,56 +42,26 @@ export interface FileWriteResult {
45
42
  mtimeMs: number;
46
43
  }
47
44
  export interface PlatformAdapter {
48
- /** Which host backs this adapter. Lets the rare unavoidable branch be explicit. */
49
- readonly platform: "electron" | "web";
50
- /**
51
- * Prompt the user to choose a project folder.
52
- * @returns the absolute path, or `null` if the user cancelled.
53
- * Electron: native directory dialog. Web: File System Access API.
54
- */
55
- openFolder(): Promise<string | null>;
56
45
  /**
57
46
  * Read a UTF-8 text file by absolute path.
58
- * Editor seam for #38/#39 — no current consumer in 0.4.0.
59
47
  */
60
48
  readFile(path: string): Promise<string>;
61
49
  /**
62
50
  * Write a UTF-8 text file by absolute path, creating/overwriting it.
63
51
  * Resolves with the post-write {@link FileWriteResult} (`{ mtimeMs }`) so the
64
- * editor (#44) can record the on-disk baseline mtime; callers that ignore the
65
- * value are unaffected (additive widening — was `Promise<void>`).
52
+ * editor (#44) can record the on-disk baseline mtime.
66
53
  */
67
54
  writeFile(path: string, content: string): Promise<FileWriteResult>;
68
- /**
69
- * List the immediate entries of a directory (single level, no recursion).
70
- * @returns each entry's `name`, absolute `path`, and whether it `isDir`.
71
- * Editor seam for #38 (file-tree sidebar). Electron: `node:fs/promises`
72
- * `readdir`. Web: File System Access API (0.6.0).
73
- */
74
- listDir(path: string): Promise<Array<{
75
- name: string;
76
- path: string;
77
- isDir: boolean;
78
- }>>;
79
55
  /**
80
56
  * Stat a file by absolute path (GitHub #44). Used by the editor to confirm a
81
57
  * `watchFolder` event reflects a real on-disk change (mtime moved) versus the
82
58
  * self-echo of our own `writeFile`. Resolves with `exists: false` rather than
83
- * rejecting when the path is absent. Web throws until 0.6.0 (FS Access API).
59
+ * rejecting when the path is absent.
84
60
  */
85
61
  statFile(path: string): Promise<FileStat>;
86
62
  /**
87
63
  * Watch a folder for changes, invoking `cb` on each change.
88
64
  * @returns an unsubscribe function.
89
- * Web has no general recursive-watch primitive — `WebAdapter` throws here
90
- * until 0.6.0. Electron wiring lands with the in-app editor (#38).
91
65
  */
92
66
  watchFolder(path: string, cb: () => void): () => void;
93
- /**
94
- * Read a secret by key (e.g. a GitHub token). Lands with #12.
95
- * Electron: OS keychain via `safeStorage`. Web: encrypted localStorage.
96
- */
97
- getSecret(key: string): Promise<string | null>;
98
- /** Store a secret by key. Lands with #12. */
99
- setSecret(key: string, value: string): Promise<void>;
100
67
  }
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-mv1mhpkk.js";
5
+ } from "./cli-d2he8zph.js";
6
6
  import {
7
7
  log
8
- } from "./cli-79tsqp3p.js";
8
+ } from "./cli-1kdtzgxm.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-b36za51e.js";
14
- import"./cli-30j93gzt.js";
13
+ } from "./cli-ny50k8md.js";
14
+ import"./cli-tjhn2mdn.js";
15
15
  import {
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
@@ -125,11 +125,9 @@ export declare function externalWatchTargets(projectDir: string, config: {
125
125
  * Create and configure a file watcher for the project's input directory plus
126
126
  * the book's declared external dependencies (see {@link externalWatchTargets}).
127
127
  *
128
- * Nothing is mirrored anywhere: the old external-asset-root watching (a sibling
129
- * `../_shared` directory copied under its own name into the temp dir) went away
130
- * with `copyAssets` and the manifest's `source.assets` field it depended on.
131
- * The project is served straight from disk and stylesheets are inlined at
132
- * render time, so an external dependency needs watching, not staging.
128
+ * Nothing is mirrored anywhere: the project is served straight from disk and
129
+ * stylesheets are inlined at render time, so an external dependency needs
130
+ * watching, not staging.
133
131
  */
134
132
  export declare function createFileWatcher(state: ServerState): FSWatcher;
135
133
  /**
@@ -9,9 +9,8 @@ import type { ServerState } from './server-context';
9
9
  /**
10
10
  * Initialize the preview's temp directory.
11
11
  *
12
- * SERVE-IN-PLACE (this replaces the old whole-tree `copyDirectory(inputPath,
13
- * tempDir)` + manifest-asset `copyAssets` call): the temp dir is no longer a
14
- * mirror of the project. It holds ONLY files gutterpress itself generates — right
12
+ * SERVE-IN-PLACE: the temp dir is not a mirror of the project. It holds ONLY
13
+ * files gutterpress itself generates — right
15
14
  * now that's `book.html` (written later by {@link generateAndWriteHtml}) and
16
15
  * the PID marker file below. Every other path the served HTML asks for
17
16
  * (images, anything else under the project) is read straight from the
@@ -23,12 +22,8 @@ import type { ServerState } from './server-context';
23
22
  * so there is no separate copy step that can drift from what a build actually
24
23
  * ships (the "works in preview, broken in the PDF" bug class). It also means
25
24
  * a project's `.env`, `.git`, or any other dotfile is never duplicated into a
26
- * world-readable temp dir the way the old whole-tree copy did — see
27
- * http-server.ts's dotfile guard, which is what stands between a request and
28
- * the project's real dotfiles now that the project is served directly.
29
- *
30
- * No longer takes `inputPath`/`config` — both were needed only for the copy
31
- * steps removed above, and there is nothing left to configure here.
25
+ * world-readable temp dir — see http-server.ts's dotfile guard, which is what
26
+ * stands between a request and the project's real dotfiles.
32
27
  */
33
28
  export declare function initializePreviewDirectories(): Promise<string>;
34
29
  /**
@@ -46,14 +41,11 @@ export declare function initializeConfiguration(inputPath: string): Promise<Reso
46
41
  /**
47
42
  * Restart the preview server with a new input directory.
48
43
  *
49
- * SERVE-IN-PLACE makes this trivially correct where the old copy-based
50
- * version was not: the HTTP server (http-server.ts) reads `state.
51
- * currentInputPath` fresh on every request instead of serving a stale mirror,
52
- * so repointing that one field IS the switch — there is no per-project copy
53
- * to redo, and therefore no old project's files left behind in the (now
54
- * shared-nothing, generated-files-only) temp dir the way the previous
55
- * `copyDirectory(newInputPath, state.tempDir)` could leave a stale mix of two
56
- * projects' content if it partially failed.
44
+ * SERVE-IN-PLACE makes this trivially correct: the HTTP server
45
+ * (http-server.ts) reads `state.currentInputPath` fresh on every request
46
+ * instead of serving a mirror, so repointing that one field IS the switch —
47
+ * there is no per-project copy to redo, and no old project's files left
48
+ * behind in the generated-files-only temp dir.
57
49
  */
58
50
  export declare function restartPreview(newInputPath: string, state: ServerState): Promise<void>;
59
51
  /**
@@ -67,7 +67,7 @@
67
67
  window.addEventListener('renderingComplete', function (e) {
68
68
  post({ type: 'gutterpress:event', name: 'renderingComplete', detail: e.detail });
69
69
  });
70
- // ADR 0005: source-position sync + click-to-source.
70
+ // source-position sync + click-to-source.
71
71
  window.addEventListener('sourceLineChanged', function (e) {
72
72
  post({ type: 'gutterpress:event', name: 'sourceLineChanged', detail: e.detail });
73
73
  });
@@ -82,16 +82,18 @@
82
82
  return (bestArea > 0 ? best : nearest) + 1;
83
83
  }
84
84
 
85
- function scrollToCurrentPage() {
85
+ function scrollToCurrentPage(smooth) {
86
86
  if (pages.length === 0) return;
87
87
  var page = clampPage(currentPage);
88
88
  currentPage = page;
89
- ignoreScrollUntil = Date.now() + 300;
89
+ // A smooth scroll is still moving when the plain 300ms guard expires, and
90
+ // detectVisiblePage would then report the page it is passing through.
91
+ ignoreScrollUntil = Date.now() + (smooth ? 700 : 300);
90
92
  // Native's rows can be wider than the viewport (a long chapter scrolls
91
93
  // horizontally within its own row) — align the target sheet's left edge
92
94
  // to the viewport's left edge (matching detectVisiblePage's `refX`).
93
95
  pages[page - 1].scrollIntoView({
94
- behavior: 'instant',
96
+ behavior: smooth ? 'smooth' : 'instant',
95
97
  block: 'start',
96
98
  inline: 'start'
97
99
  });
@@ -102,7 +104,7 @@
102
104
  return (mode || currentViewMode) === 'single' ? 1 : 2;
103
105
  }
104
106
 
105
- // ── Source-mapping helpers (ADR 0005) ──────────────────────────────────────
107
+ // ── Source-mapping helpers ──────────────────────────────────────
106
108
  // Every block element carries data-source-line (markdown-it-source-map). These
107
109
  // map rendered DOM <-> markdown source line and rendered DOM <-> page. The
108
110
  // native fragmenter MOVES elements into strips, it does not clone them, so
@@ -360,7 +362,7 @@
360
362
  // plate UNDER the page's own text (gutterpress-css.ts's depth ladder) —
361
363
  // hit-tests beneath the covering paragraph boxes and the annotated
362
364
  // `.page`/`.section` containers too, so document.elementFromPoint() can
363
- // NEVER return it. Every right-click used to resolve the covering element
365
+ // NEVER return it: a right-click would resolve the covering element
364
366
  // instead, leaving the image's context menu unreachable at EVERY point.
365
367
  // Probe the full hit stack for the first (= top-most in paint order, so
366
368
  // the upper of two overlapping plates wins) image layered at negative z.
@@ -632,13 +634,11 @@
632
634
  // swapped for that block's markdown source under
633
635
  // `contenteditable="plaintext-only"`, so the caret sits in the real page, in
634
636
  // the book's own typography, and Chromium's fragmenter re-flows the pages
635
- // around it as the author types. This replaced a floating CodeMirror panel
636
- // positioned in host-SPA coordinates; `getRectsFor()`/`setEditMask()` existed
637
- // ONLY to serve that panel and went with it (protocol v8).
637
+ // around it as the author types.
638
638
  //
639
639
  // Three properties of the native viewer are what make this work, each
640
- // spike-verified rather than assumed (plan §2) — the floating panel existed
641
- // because none of them can be assumed of a paginator that pre-cuts the DOM:
640
+ // verified rather than assumed (plan §2) — none of them can be assumed of
641
+ // a paginator that pre-cuts the DOM:
642
642
  // 1. A block spanning a page break is ONE element with several client
643
643
  // rects, so it takes ONE contenteditable and the caret crosses the
644
644
  // break natively (ArrowDown walks into the next page).
@@ -1064,7 +1064,7 @@
1064
1064
  return api.notifyPageChange();
1065
1065
  },
1066
1066
 
1067
- // ── ADR 0005 generic primitives ─────────────────────────────────────────
1067
+ // ── Generic primitives ─────────────────────────────────────────
1068
1068
  // Bumped whenever a command/event is added so a hot-updated SPA can
1069
1069
  // feature-detect against an older bundled lib.
1070
1070
  // v6 (WORK PACKAGE B item 2): getRectsFor()/setEditMask() dropped the
@@ -1360,7 +1360,7 @@
1360
1360
 
1361
1361
  // Click-to-source: emit elementActivated when the user clicks a source-mapped
1362
1362
  // block. Never preventDefault (links/selection keep working); the host decides
1363
- // whether to act. (ADR 0005)
1363
+ // whether to act.
1364
1364
  if (typeof document.addEventListener === 'function') {
1365
1365
  document.addEventListener('click', function (e) {
1366
1366
  var el = e.target && e.target.closest ? e.target.closest('[data-source-line]') : null;
@@ -1472,7 +1472,7 @@
1472
1472
  currentPage = page;
1473
1473
  if (!silent) api.notifyPageChange();
1474
1474
  }
1475
- // Emit finer-grained source position for editor sync (ADR 0005).
1475
+ // Emit finer-grained source position for editor sync.
1476
1476
  var pos = visibleSourcePosition();
1477
1477
  var sl = pos ? pos.line : null;
1478
1478
  var chapter = pos ? chapterOf(pos.el) : null;
@@ -1505,6 +1505,67 @@
1505
1505
  });
1506
1506
  window.addEventListener('resize', scheduleViewportChanged);
1507
1507
 
1508
+ // Wheel / trackpad page flip (#301). Viewer tooling, not pagination: it
1509
+ // only chooses which already-laid-out page to show. It lives here, in the
1510
+ // book document, because wheel events over a cross-origin iframe never
1511
+ // reach the host page.
1512
+ //
1513
+ // One flick turns one step — the same 1 page (single view) or 2 pages
1514
+ // (spread) the arrow keys move. A gesture is every wheel event until the
1515
+ // wheel has been quiet for WHEEL_GESTURE_IDLE_MS, so a fast mouse spin or a
1516
+ // trackpad's inertia tail turns exactly one step.
1517
+ //
1518
+ // A page taller than the viewport (fit-width on a short window, high zoom)
1519
+ // is READ first: while there is more of it in the wheel's direction, the
1520
+ // wheel scrolls normally, and only a gesture that starts at its edge turns
1521
+ // the page. The choice is made once per gesture, so the momentum of reading
1522
+ // down to a page's end never turns the page by itself. Pinch / Ctrl+wheel
1523
+ // (zoom), sideways swipes and in-place editing are left alone.
1524
+ var WHEEL_FLIP_PX = 50;
1525
+ var WHEEL_GESTURE_IDLE_MS = 220;
1526
+ var wheelGesture = null; // null | 'scroll' | 'flip'
1527
+ var wheelAccum = 0;
1528
+ var wheelTurned = false;
1529
+ var wheelIdleTimer = null;
1530
+ function wheelDeltaPx(e) {
1531
+ if (e.deltaMode === 1) return e.deltaY * 16; // lines
1532
+ if (e.deltaMode === 2) return e.deltaY * window.innerHeight; // pages
1533
+ return e.deltaY;
1534
+ }
1535
+ // More of the current page lies beyond the viewport edge in direction `dir`.
1536
+ function pageContinuesPastViewport(dir) {
1537
+ if (pages.length === 0) refreshPages();
1538
+ var sheet = pages[clampPage(currentPage) - 1];
1539
+ if (!sheet) return false;
1540
+ var r = sheet.getBoundingClientRect();
1541
+ return dir > 0 ? r.bottom > window.innerHeight + 1 : r.top < -1;
1542
+ }
1543
+ window.addEventListener('wheel', function (e) {
1544
+ if (e.ctrlKey || edit || e.deltaY === 0 || Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
1545
+ if (wheelIdleTimer) clearTimeout(wheelIdleTimer);
1546
+ wheelIdleTimer = setTimeout(function () {
1547
+ wheelIdleTimer = null;
1548
+ wheelGesture = null;
1549
+ wheelAccum = 0;
1550
+ wheelTurned = false;
1551
+ }, WHEEL_GESTURE_IDLE_MS);
1552
+ if (wheelGesture === null) {
1553
+ wheelGesture = pageContinuesPastViewport(e.deltaY > 0 ? 1 : -1) ? 'scroll' : 'flip';
1554
+ }
1555
+ if (wheelGesture === 'scroll') return;
1556
+ e.preventDefault();
1557
+ if (wheelTurned) return;
1558
+ wheelAccum += wheelDeltaPx(e);
1559
+ if (Math.abs(wheelAccum) < WHEEL_FLIP_PX) return;
1560
+ wheelTurned = true;
1561
+ refreshPages();
1562
+ var target = clampPage(currentPage + (wheelAccum > 0 ? 1 : -1) * pageStep());
1563
+ if (target === currentPage) return;
1564
+ currentPage = target;
1565
+ scrollToCurrentPage(true);
1566
+ api.notifyPageChange();
1567
+ }, { passive: false });
1568
+
1508
1569
  // Reset to page 1, scroll to top, and announce completion once the native
1509
1570
  // viewer's pagination finishes.
1510
1571
  function onRenderingComplete() {
@@ -288,27 +288,16 @@
288
288
  armPendingSwap();
289
289
  }
290
290
 
291
- // Incremental chapter splice used to live here (spliceChapter): it grafted
292
- // a freshly-paginated replacement into a chapter's live page range. It was
293
- // tried and removed (2026-08-08 review), and the standing reason is
294
- // PERFORMANCE, not soundness: measured end-to-end (file write -> change
295
- // visible, 5 samples, 34pp field guide) the plain full reload (`swap`,
296
- // below) is 509ms avg vs the incremental splice's 998ms avg. The server
297
- // now sends ONE update kind, `full-reload`, and every one goes straight to
298
- // `swap()`; the `/__chapter` route and `content-update` message that fed
299
- // the splice were deleted with it.
291
+ // The server sends ONE update kind, `full-reload`, and every one goes
292
+ // straight to `swap()` (below). An incremental chapter splice was measured
293
+ // SLOWER, not unsound: end-to-end (file write -> change visible, 5 samples,
294
+ // 34pp field guide) the full reload is 509ms avg vs the splice's 998ms avg.
300
295
  //
301
- // CORRECTED 2026-08-24: the 2026-08-08 review ALSO recorded a soundness
302
- // objection — that `refresh()` -> `relayout()` "only re-measures the
303
- // EXISTING strips", silently dropping any page context the edit
304
- // introduces. That is false against the current `fragment.ts`, whose
305
- // `relayout()` unwraps the strips and re-runs `buildStrips()` from
306
- // scratch before re-measuring, precisely so a mutation that adds or
307
- // removes a page-context run is seen. Do not cite the old objection as a
308
- // reason a DOM mutation cannot be re-paginated — it can, and the inline
296
+ // A DOM mutation CAN be re-paginated: `refresh()` -> `relayout()` unwraps
297
+ // the strips and re-runs `buildStrips()` from scratch before re-measuring,
298
+ // so a mutation that adds or removes a page-context run is seen. The inline
309
299
  // editing work depends on it (docs/inline-editing-plan.md, ADR 0009
310
- // decision 4 as revised). The perf comparison above still stands on its
311
- // own for the full-document reload path.
300
+ // decision 4).
312
301
 
313
302
  function markActiveReady() {
314
303
  onReady(active, function () {
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-mv1mhpkk.js";
9
+ } from "./cli-d2he8zph.js";
10
10
  import {
11
11
  log
12
- } from "./cli-79tsqp3p.js";
12
+ } from "./cli-1kdtzgxm.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,8 +17,8 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-b36za51e.js";
21
- import"./cli-30j93gzt.js";
20
+ } from "./cli-ny50k8md.js";
21
+ import"./cli-tjhn2mdn.js";
22
22
  import {
23
23
  BuildError
24
24
  } from "./cli-46ycxe6r.js";
@@ -11,17 +11,17 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-mv1mhpkk.js";
14
+ } from "./cli-d2he8zph.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-79tsqp3p.js";
18
+ } from "./cli-1kdtzgxm.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-b36za51e.js";
24
- import"./cli-30j93gzt.js";
23
+ } from "./cli-ny50k8md.js";
24
+ import"./cli-tjhn2mdn.js";
25
25
  import {
26
26
  EXIT_CODES
27
27
  } from "./cli-46ycxe6r.js";
package/dist/render.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * gutterpress/render — the PURE, node-free render core.
3
3
  *
4
- * §1/§8 / ADR 0004: this entry deliberately exposes ONLY the browser-safe
4
+ * §1/§8: this entry deliberately exposes ONLY the browser-safe
5
5
  * markdown→HTML→book.html pieces. It transitively imports markdown-it + its
6
6
  * plugins, Gutterpress's inlined marker parser (`markers.js`), and pure helpers
7
- * — and NOTHING from `node:*`/`fs`/`path`/`url`. This is what the desktop's WebAdapter imports
8
- * (a *value* import that stays PWA-clean), so the in-browser live preview (#33)
9
- * can render the opened project entirely client-side with no localhost server
10
- * and no headless Chromium.
7
+ * — and NOTHING from `node:*`/`fs`/`path`/`url`. It has no browser consumer
8
+ * today; it is kept node-free (scripts/check-render-pure.mjs) because any
9
+ * future browser build of the app needs to render a book client-side, and that
10
+ * discipline is far harder to restore than to keep.
11
11
  *
12
12
  * The CLI build path keeps using `renderChapters` / `renderChaptersToFile` from
13
13
  * `./lib/markdown/index.ts` (the node wrapper around this same core).
package/dist/render.js CHANGED
@@ -894,8 +894,8 @@ var GUTTERPRESS_CSS = `
894
894
 
895
895
  /* the per-shape decisions the paragraph above deliberately leaves to the
896
896
  author, named instead of left as raw CSS every book was reinventing
897
- (2026-09-01 CSS architecture review, findings C1/C7 — CLAUDE.md §0:
898
- "behavior broadly useful to non-technical authors belongs in core").
897
+ (CLAUDE.md §0: "behavior broadly useful to non-technical authors belongs
898
+ in core").
899
899
  Permanent vocabulary, standard properties verbatim — same rationale as
900
900
  the column/grid runs above. One name each, no aliases:
901
901
  .gp-columns-all column-span: all a heading or block that
@@ -206,11 +206,11 @@ export interface ResolvedExtensionConfig {
206
206
  }
207
207
  /**
208
208
  * Fully-resolved config. Every field is a concrete default with one
209
- * deliberate exception: `styles` (ARCH finding #2). There is no preset
209
+ * deliberate exception: `styles`. There is no preset
210
210
  * default for it — `undefined` means "the manifest didn't set one", and
211
211
  * `resolveActiveStyles` (style-resolver.ts) is the single source of truth for
212
212
  * what that resolves to (styles/book.css, else the first discovered `.css`,
213
- * else `[]`). Baking a preset default in here defeated that fallback chain.
213
+ * else `[]`). Baking a preset default in here would defeat that fallback chain.
214
214
  */
215
215
  export interface ResolvedConfig {
216
216
  title: string;
@@ -10,8 +10,7 @@
10
10
  * - The `log` facade object (`log.info`/`log.warn`/`log.error`/`log.success`)
11
11
  * — clean, prefix-styled command output used by the CLI commands and
12
12
  * re-exported from `index.ts` for the desktop. Always emitted (it carries
13
- * user-facing command results, not diagnostic chatter), preserving the
14
- * historical `lib/logger` behavior verbatim.
13
+ * user-facing command results, not diagnostic chatter).
15
14
  */
16
15
  export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
17
16
  /**
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-mv1mhpkk.js";
3
+ } from "./cli-d2he8zph.js";
4
4
  import {
5
5
  log
6
- } from "./cli-79tsqp3p.js";
6
+ } from "./cli-1kdtzgxm.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-b36za51e.js";
12
- import"./cli-30j93gzt.js";
11
+ } from "./cli-ny50k8md.js";
12
+ import"./cli-tjhn2mdn.js";
13
13
  import {
14
14
  EXIT_CODES
15
15
  } from "./cli-46ycxe6r.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.10",
3
+ "version": "0.11.11-alpha.2",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",
@@ -67,7 +67,7 @@
67
67
  "glob": "^13.0.0",
68
68
  "htmlhint": "^1.9.2",
69
69
  "isomorphic-git": "^1.38.4",
70
- "markdown-it": "^14.1.0",
70
+ "markdown-it": "^14.3.2",
71
71
  "markdown-it-abbr": "^2.0.0",
72
72
  "markdown-it-attrs": "^4.3.0",
73
73
  "markdown-it-deflist": "^3.0.1",
@@ -81,7 +81,7 @@
81
81
  "postcss": "^8.5.18",
82
82
  "resolve.exports": "2.0.3",
83
83
  "semver": "7.8.0",
84
- "tar": "7.5.20",
84
+ "tar": "7.5.22",
85
85
  "unpdf": "^1.6.2",
86
86
  "ws": "^8.21.0",
87
87
  "yaml": "^2.8.2"