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
@@ -19,9 +19,8 @@ export interface BuildRunnerOptions {
19
19
  manifestPath?: string;
20
20
  stripAnnotations?: boolean;
21
21
  /**
22
- * Skip the CSS print-safety check (#272 — one CSS gate, not two). This used
23
- * to gate a separate lint-runner pass (since deleted); it now disables just the
24
- * `source.stylelint` check inside pre-build validation (see
22
+ * Skip the CSS print-safety check (#272 — one CSS gate, not two): disables
23
+ * just the `source.stylelint` check inside pre-build validation (see
25
24
  * {@link computeGates}'s `skipStylelint`). Has no effect when
26
25
  * `skipPreValidate` is also set — pre-build validation, CSS check
27
26
  * included, does not run at all in that case.
@@ -132,17 +131,15 @@ export interface BuildContext {
132
131
  inputDir: string;
133
132
  /**
134
133
  * THE anchor every manifest-relative path resolves against: `styles:`,
135
- * `source.files`, authored plugin `path:` entries, and the lint gate's own
136
- * stylesheet resolution. Equal to {@link BuildContext.manifestDir}.
134
+ * `source.files`, authored plugin `path:` entries, and the CSS print-safety
135
+ * check's stylesheet resolution. Equal to {@link BuildContext.manifestDir}.
137
136
  *
138
- * These used to resolve against two different roots in one build (2026-07-29
139
- * audit): plugins, the lint gate, and the output dir anchored on
140
- * `manifestDir`, while `styles:`/`source.files` anchored on `inputDir`. They
141
- * are identical in the normative layout (the manifest lives in the book
142
- * folder) and diverge only under an explicit `--manifest` pointing outside
143
- * `--input` — where the docs are unambiguous that both are manifest-relative,
144
- * so the lint gate was checking a different set of stylesheets than the ones
145
- * that shipped.
137
+ * It is NOT `inputDir`. The two are identical in the normative layout (the
138
+ * manifest lives in the book folder) and diverge only under an explicit
139
+ * `--manifest` pointing outside `--input` — where the docs are unambiguous
140
+ * that all of these are manifest-relative. Anchoring any of them on
141
+ * `inputDir` would check a different set of stylesheets than the ones that
142
+ * ship.
146
143
  */
147
144
  renderDir: string;
148
145
  outDir: string;
@@ -195,17 +192,12 @@ export declare function resolveBuildContext(opts: BuildRunnerOptions): Promise<B
195
192
  * `loadPluginsWithCss` directly, and only the first caller does real work —
196
193
  * the other gets the cached result back, whichever stage happens to run
197
194
  * first (a test calling {@link renderBook} directly, without going through
198
- * `runQualityGates`, gets a fresh load here exactly as it would have before
199
- * this existed).
195
+ * `runQualityGates`, gets a fresh load here).
200
196
  *
201
- * Fail-fast (no `onError`), matching `renderBook`'s pre-existing behavior:
202
- * a build/export must never silently omit author-configured formatting (see
203
- * `loadPlugins`'s doc comment in markdown/plugins.ts on the two failure
204
- * modes). One consequence: a plugin that fails to load now aborts the build
205
- * as soon as quality gates start, instead of (as before) the lint gate's own
206
- * degrade-and-report call warning-and-skipping that same plugin only for
207
- * `renderBook` to hard-fail on it moments later — the build failed either
208
- * way, this just stops wasting the lint pass first.
197
+ * Fail-fast (no `onError`): a build/export must never silently omit
198
+ * author-configured formatting (see `loadPlugins`'s doc comment in
199
+ * markdown/plugins.ts on the two failure modes), so a plugin that fails to
200
+ * load aborts the build as soon as quality gates start.
209
201
  *
210
202
  * Resolves plugin `path:` entries against `ctx.renderDir`, which is by
211
203
  * construction identical to `ctx.manifestDir` for the life of one
@@ -222,13 +214,12 @@ export declare function loadBuildPlugins(ctx: BuildContext): Promise<LoadedPlugi
222
214
  * rendered book.html both output strategies then paginate. This is the shared
223
215
  * pre-format work; the per-format tails live in the strategies.
224
216
  *
225
- * ARCH finding #4: Gutterpress's marker parser computes typed, line-numbered
226
- * author-mistake warnings (`env.layoutWarnings`) that every real render path
227
- * used to discard silently. `renderChaptersToFile`'s `onChapterWarnings`
228
- * threads them back out here so a final artifact never omits a marker
229
- * mistake without at least telling the author about it in the build log.
230
- * Exported (not just for the pipeline) so this stage is unit-testable
231
- * without driving the full `runBuild` pagination/PDF machinery.
217
+ * Gutterpress's marker parser computes typed, line-numbered author-mistake
218
+ * warnings (`env.layoutWarnings`). `renderChaptersToFile`'s
219
+ * `onChapterWarnings` threads them back out here so a final artifact never
220
+ * omits a marker mistake without at least telling the author about it in the
221
+ * build log. Exported (not just for the pipeline) so this stage is
222
+ * unit-testable without driving the full `runBuild` pagination/PDF machinery.
232
223
  */
233
224
  export declare function renderBook(ctx: BuildContext): Promise<string>;
234
225
  /**
@@ -265,12 +256,10 @@ export declare function publishToDirectory(workDir: string, targetDir: string, f
265
256
  * The browser is this function's to own: there is ONE launcher
266
257
  * (`engine/shared/cdp.ts`'s `launchChromium`, or the host's injected
267
258
  * `engineBrowser` factory in its place), it is started un-awaited so the
268
- * ~1–2s Chromium cold start overlaps lint + validation + markdown render, it
259
+ * ~1–2s Chromium cold start overlaps validation + markdown render, it
269
260
  * is awaited right before rendering (so a missing or too-old Chromium fails
270
261
  * there, with the launcher's own message, rather than deep inside the engine),
271
- * and it is closed in a `finally` that runs on every exit — finding #50:
272
- * previously the close only happened on the success tail (inside
273
- * `finalizeBuild`), so a pre-launched Chromium leaked whenever a quality gate
274
- * or the render itself threw.
262
+ * and it is closed in a `finally` that runs on every exit, so a pre-launched
263
+ * Chromium never leaks when a quality gate or the render itself throws.
275
264
  */
276
265
  export declare function runBuild(opts: BuildRunnerOptions): Promise<BuildRunnerResult>;
@@ -28,11 +28,9 @@ export interface StagedAssets {
28
28
  *
29
29
  * THE one implementation: `renderBook` (every real build/export) and the
30
30
  * preview/print parity gate both call this, so the gate can never measure a
31
- * document the build would not have produced. It previously hand-rolled a bare
32
- * `copyFile` loop, which (a) hard-crashed with a raw `ENOENT` on any book
33
- * carrying a stale image path — i.e. the tool that enforces preview↔print
34
- * parity could not run on the real books that need it — and (b) skipped
35
- * `inlineShapeUrls`, so `.gp-shape` wrapping silently differed from the build.
31
+ * document the build would not have produced (a hand-rolled `copyFile` loop
32
+ * in the gate would crash on a stale image path and skip `inlineShapeUrls`,
33
+ * silently diverging from the build).
36
34
  *
37
35
  * A missing image is NOT fatal: the same magenta placeholder the build ships
38
36
  * (see missing-asset-placeholder.ts) is substituted and every reference
@@ -23,7 +23,7 @@ type CliArgumentDefinitions = Readonly<Record<string, CliArgumentDefinition>>;
23
23
  */
24
24
  export declare function rejectUnknownFlags(rawArgs: readonly string[], declared: CliArgumentDefinitions, commandName: string): void;
25
25
  /**
26
- * Reject CLI positionals beyond the ones a command declares (UX finding M46).
26
+ * Reject CLI positionals beyond the ones a command declares.
27
27
  *
28
28
  * Citty's parser keeps every raw positional token in `args._` even after
29
29
  * `type: "positional"` args have claimed their share — it shifts values off a
@@ -1,12 +1,6 @@
1
1
  /**
2
- * Output filename constants for the build pipeline.
3
- *
4
- * Historically this file also emitted "desktop chrome" (toolbar + folder
5
- * picker + GH clone modal) into the build output so the directory was a
6
- * self-hostable site. That chrome was removed 2026-05-18 when the desktop
7
- * was extracted into packages/desktop (Electron + SvelteKit). The CLI build
8
- * now produces a "naked" book.html with Gutterpress's native live-viewer and
9
- * preview-interface/bridge scripts injected — no toolbar.
2
+ * Output filename constants for the build pipeline. The build emits no
3
+ * application chrome around book.html — that lives in packages/desktop.
10
4
  *
11
5
  * Power users who want a hosted UI launch the desktop app or write their
12
6
  * own iframe wrapper around book.html.
@@ -21,7 +21,7 @@ export interface NativePdfOptions {
21
21
  *
22
22
  * Returns the build's author-facing diagnostics so the caller can surface
23
23
  * them (the desktop Problems panel, the CLI's own output). Dropping them here
24
- * is what made the engine's print-quality audits invisible in every real
25
- * build path — they only ever reached the engine dev CLI.
24
+ * would make the engine's print-quality audits invisible in every real build
25
+ * path.
26
26
  */
27
27
  export declare function buildNativePdf(htmlFile: string, outPdf: string, options: NativePdfOptions, browser: EngineBrowser): Promise<BuildDiagnostic[]>;
@@ -11,10 +11,8 @@ export declare const localBin: string;
11
11
  * PATH with `localBin` prepended, correctly delimiter-joined. Single source
12
12
  * of truth for every spawn in the lib — `run` and `execCapture` below use
13
13
  * it, and tool-probe.ts's `isToolAvailable`/`findTool` get it for free by
14
- * calling `execCapture` instead of keeping their own copy (previously a
15
- * literal `:` here corrupted PATH on Windows while tool-probe.ts's copy of
16
- * the same logic used `delimiter` correctly — see docs/reviews
17
- * 2026-07-10-architecture-critical-review.md, finding #3).
14
+ * calling `execCapture` instead of keeping their own copy. Joined with
15
+ * `delimiter`, never a literal `:` (which corrupts PATH on Windows).
18
16
  */
19
17
  export declare const enhancedPath: string;
20
18
  /**
@@ -59,8 +57,7 @@ export interface SpawnCaptureOptions {
59
57
  * on settle and `unref()`d so a pending call can never keep the process alive
60
58
  * on its own. `execCapture` below and publish's `defaultCommandRunner` are
61
59
  * thin adapters over this — do not grow parallel spawn loops elsewhere
62
- * (previously four copies existed with different bug profiles — see
63
- * docs/reviews 2026-07-10-architecture-critical-review.md, finding #16).
60
+ * (separate copies drift into different bug profiles).
64
61
  */
65
62
  export declare function spawnCapture(cmd: string, args: string[], opts?: SpawnCaptureOptions): Promise<{
66
63
  code: number | null;
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * Shared network deadline + author-friendly error mapping for the CLI's
3
3
  * direct `fetch` call sites (GitHub auth/API, the butler download). ONE copy
4
- * of the policy, so a fix here reaches every site (the old per-site clones
5
- * let the dropped-timeout bug below survive in two of three copies):
4
+ * of the policy, so a fix here reaches every site:
6
5
  *
7
6
  * - The deadline is a TOTAL budget (`AbortSignal.timeout` keeps ticking
8
7
  * through any body read done inside `run`), COMPOSED with the caller's
9
- * cancellation signal via `AbortSignal.any` — the old
10
- * `signal ?? AbortSignal.timeout(...)` pattern silently DROPPED the
8
+ * cancellation signal via `AbortSignal.any` — a
9
+ * `signal ?? AbortSignal.timeout(...)` pattern would silently DROP the
11
10
  * timeout whenever a caller passed a signal.
12
11
  * - A caller abort ("AbortError") is rethrown untouched, so cancellation
13
12
  * keeps its site-specific handling.
@@ -67,7 +67,7 @@ type PdfxFlavor = "x1a" | "x3";
67
67
  *
68
68
  * Uses pdf-lib (pure JS, MIT) rather than Ghostscript, so the plain-PDF build
69
69
  * path needs no system tool at all — gs is now required only for PDF/X CMYK
70
- * conversion (ADR 0002). `updateMetadata: false` keeps pdf-lib from rewriting
70
+ * conversion. `updateMetadata: false` keeps pdf-lib from rewriting
71
71
  * the ModDate/Producer so the only change is the /Creator field.
72
72
  */
73
73
  export declare function stampCreator(pdfPath: string): Promise<void>;
@@ -17,8 +17,8 @@ export interface AutoSnapshotPolicy {
17
17
  /** Master switch — automatic snapshots default ON. */
18
18
  autoSnapshot: boolean;
19
19
  }
20
- /** Quiet period before a snapshot fires. Fixed (#274) — no longer a settings
21
- * knob, so there is nothing left to clamp. */
20
+ /** Quiet period before a snapshot fires. Fixed (#274) — not a settings
21
+ * knob, so there is nothing to clamp. */
22
22
  export declare const AUTO_SNAPSHOT_DEFAULT_MINUTES = 10;
23
23
  /** User-facing auto-sync policy (mirrors the desktop's settings group). */
24
24
  export interface AutoSyncPolicy {
@@ -52,9 +52,8 @@ export declare const AUTO_SYNC_PUSH_INTERVAL_MINUTES = 15;
52
52
  * `null` when automatic snapshots are disabled. Pure — the testable core of
53
53
  * the trigger policy (the timer itself lives in the Electron main process).
54
54
  *
55
- * The quiet period is fixed at `AUTO_SNAPSHOT_DEFAULT_MINUTES` (#274 — the
56
- * minutes field this used to clamp was deleted from the settings schema, so
57
- * there is nothing left to resolve here beyond the master switch).
55
+ * The quiet period is fixed at `AUTO_SNAPSHOT_DEFAULT_MINUTES` (#274 — there
56
+ * is no minutes setting, so nothing to resolve here beyond the master switch).
58
57
  * Defensive about persisted settings: a missing policy means "defaults"
59
58
  * (enabled, 10 min).
60
59
  */
@@ -70,9 +69,8 @@ export declare function autoSnapshotDelayMs(policy: Partial<AutoSnapshotPolicy>
70
69
  * the default and is then clamped into [AUTO_SYNC_MIN_MINUTES,
71
70
  * AUTO_SYNC_MAX_MINUTES].
72
71
  *
73
- * Note: this is the host orchestrator's ONLY sync trigger — the file-change
74
- * debounce that used to sit beside it was removed (it could never fire before
75
- * this interval already had).
72
+ * Note: this is the host orchestrator's ONLY sync trigger — a file-change
73
+ * debounce beside it could never fire before this interval already had.
76
74
  */
77
75
  export declare function autoSyncDelayMs(policy: Partial<AutoSyncPolicy> | undefined): number | null;
78
76
  /**
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Dependency-free raster image header reader — the in-process replacement for
3
- * ImageMagick `identify` used by the asset checks (Phase 3 of ADR 0002).
3
+ * ImageMagick `identify` used by the asset checks.
4
4
  *
5
5
  * Why not `sharp`? sharp is the most capable option but is a native addon whose
6
6
  * platform binaries are resolved through `node_modules` at runtime — exactly the
7
- * pattern that breaks `bun build --compile` (ADR 0001 §1/§3). A small header
7
+ * pattern that breaks `bun build --compile` (CLAUDE.md §1/§3). A small header
8
8
  * parser for the formats print authors actually use (PNG, JPEG, TIFF) keeps a
9
9
  * single code path that bundles into the standalone binary AND runs in the
10
10
  * Electron desktop, with zero system/native dependency.
@@ -6,9 +6,8 @@
6
6
  * This is the single source of truth. `build-preflight.ts` (preflight error),
7
7
  * `diagnostics.ts` (desktop Help/About dialog + `gutterpress doctor`), and
8
8
  * `chromium.ts` (`requireChromiumExecutable`'s thrown error) all import
9
- * from here instead of hand-copying the per-platform install commands —
10
- * previously three diverging copies existed (see
11
- * docs/reviews/2026-07-10-architecture-critical-review.md, finding #15).
9
+ * from here instead of hand-copying the per-platform install commands, so
10
+ * the copies cannot diverge.
12
11
  */
13
12
  export interface InstallHint {
14
13
  /** Human label for the tool, used in "Install <label>:" headers. */
@@ -22,9 +22,8 @@ export declare function setManifestFields(projectDir: string, updates: ProjectCo
22
22
  /**
23
23
  * Read the author-facing manifest subset for the Config view's Details section.
24
24
  * Loads via the same shared {@link loadManifestDoc} every other manifest reader/
25
- * writer in this module uses (ARCH finding #25 — this used to re-implement
26
- * loadManifestDoc's read-and-parse inline) and surfaces empty/absent fields as
27
- * empty strings so the form inputs are editable.
25
+ * writer in this module uses and surfaces empty/absent fields as empty
26
+ * strings so the form inputs are editable.
28
27
  */
29
28
  export declare function readManifestFields(projectDir: string): Promise<ProjectConfigFields>;
30
29
  /**
@@ -18,8 +18,7 @@ export declare function ensureSeq(doc: Document.Parsed, key: string): YAMLSeq;
18
18
  * plain JS string (as a freshly-constructed `Scalar`'s `.value` or a raw
19
19
  * array entry would be).
20
20
  *
21
- * ARCH finding #25: this was two near-duplicate helpers — `unwrapScalar`
22
- * (manifest-config.ts) and `styleHrefOf` (the theme manager, since retired) — with the same
23
- * shape-sniffing logic. One implementation here, consumed by both.
21
+ * One implementation of this shape-sniffing, shared by every manifest
22
+ * reader (manifest-config.ts, project-templates.ts).
24
23
  */
25
24
  export declare function scalarString(item: unknown): string | null;
@@ -16,16 +16,13 @@ export declare function loadManifest(pathOrDir?: string): Promise<GutterpressMan
16
16
  /**
17
17
  * Load a recognized manifest and return its contents, directory, and resolved path.
18
18
  * Returns an empty manifest, a null path, and the current working directory if none is found —
19
- * UNLESS `explicit` is set (ARCH finding #12/PR #98): a caller that resolved
19
+ * UNLESS `explicit` is set (PR #98): a caller that resolved
20
20
  * `pathOrDir` from a user-supplied `--manifest` flag (as opposed to a project
21
21
  * directory being scanned for a manifest that may legitimately not exist) must
22
22
  * pass `{ explicit: true }` so a missing/typo'd path throws instead of
23
- * silently resolving to an empty manifest. A typo like `--manifest
24
- * ./typo.yaml` previously fell through to the "no manifest" default AND left
25
- * `manifestDir` pointing at the nonexistent path itself
26
- * (`resolve("./typo.yaml")`), so a later `path.resolve(manifestDir,
27
- * config.output.dir)` could create build output beneath a directory named
28
- * after the missing file.
23
+ * silently resolving to an empty manifest whose `manifestDir` is the
24
+ * nonexistent path itself, which later path resolution would then build
25
+ * beneath.
29
26
  */
30
27
  export declare function loadManifestWithPath(pathOrDir?: string, opts?: {
31
28
  explicit?: boolean;
@@ -2,7 +2,7 @@ import { type LoadedPlugin } from "./renderer";
2
2
  /** Reader injected by the host: resolve a project-root-relative file → its text. */
3
3
  export type ReadText = (relPath: string) => Promise<string>;
4
4
  /**
5
- * One author-mistake warning emitted by Gutterpress's marker parser (ARCH finding #4).
5
+ * One author-mistake warning emitted by Gutterpress's marker parser.
6
6
  * Mirrors the shape `markers.js`'s `warn()` pushes onto
7
7
  * `env.layoutWarnings` — see that file's header comment for the warning
8
8
  * `type`s (`ambiguous_marker_token`, `unrecognized_marker_token`,
@@ -24,10 +24,8 @@ export type ReadText = (relPath: string) => Promise<string>;
24
24
  * same type, via a second, unconditional check modeled on `unknown_gp_class`
25
25
  * rather than the core-only `scanForMistypedMarkers`.
26
26
  *
27
- * `section_without_page` and `implicit_page` were REMOVED 2026-08-12: a
28
- * @section with no open @page is valid authoring (audited, 17/17 false
29
- * positives across two real books), and the `implicitPage` option that
30
- * produced the latter was unreachable and latently broken.
27
+ * There is deliberately no `section_without_page`: a @section with no open
28
+ * @page is valid authoring (17/17 false positives across two real books).
31
29
  */
32
30
  export interface LayoutWarning {
33
31
  line: number;
@@ -113,14 +111,11 @@ export interface AssembleBookHtmlOptions {
113
111
  /** Add a layout-neutral source-file id to source-mapped preview blocks. */
114
112
  annotateSourceChapters?: boolean;
115
113
  /**
116
- * ARCH finding #4: per-chapter callback receiving any `env.layoutWarnings`
114
+ * Per-chapter callback receiving any `env.layoutWarnings`
117
115
  * Gutterpress's marker parser computed while rendering `file` (only called
118
116
  * when that chapter produced at least one). `file` is the same canonical
119
117
  * chapter id used for `data-chapter-src`, so a host can attribute a warning
120
- * to the exact source file. Additive/optional — omitting it reproduces the
121
- * prior throwaway-env behavior exactly, so this cannot change output for
122
- * existing callers (e.g. the desktop's WebAdapter, which still gets a plain
123
- * `Promise<string>` back).
118
+ * to the exact source file. Optional, and it never changes output.
124
119
  */
125
120
  onChapterWarnings?: (file: string, warnings: LayoutWarning[]) => void;
126
121
  /**
@@ -138,8 +133,7 @@ export interface AssembleBookHtmlOptions {
138
133
  * Assemble a single `book.html` string from the given markdown files.
139
134
  *
140
135
  * Pure: every input (the file list, their contents via `readText`, the resolved
141
- * CSS hrefs) is supplied by the caller. Mirrors the exact `<head>`/body/CSS
142
- * emission the old `renderChapters` produced, so the CLI output is byte-identical
143
- * for identical inputs.
136
+ * CSS hrefs) is supplied by the caller, so the output is byte-identical for
137
+ * identical inputs.
144
138
  */
145
139
  export declare function assembleBookHtml(opts: AssembleBookHtmlOptions): Promise<string>;
@@ -17,16 +17,13 @@
17
17
  *
18
18
  * Also ships the author-facing `gp-*` image/block vocabulary (CLAUDE.md §0 —
19
19
  * a behavior broadly useful to non-technical authors belongs in core, not a
20
- * project layer; see UX finding M17). markdown-it-attrs is bundled by
20
+ * project layer). markdown-it-attrs is bundled by
21
21
  * default (renderer.ts), so `![Art](x.jpg){.gp-right .gp-small}` already
22
22
  * attaches the classes to the rendered `<img>` — these rules are what make
23
- * them actually do something print-safe. The gp-* vocabulary REPLACED the
24
- * five pre-vocabulary utility names (.center/.float-left/.float-right/
25
- * .full-width/.full-bleed), which were removed rather than kept as
26
- * aliases — one vocabulary, no duplicate way to spell each layout. Books
27
- * migrate by renaming the classes in their markdown; the desktop editor
28
- * recognizes the old names when editing an image and rewrites them to the
29
- * gp-* names in place.
23
+ * them actually do something print-safe. One vocabulary, no aliases: there
24
+ * is no duplicate way to spell each layout (the desktop editor rewrites the
25
+ * pre-gp-* names `.center`/`.float-left`/`.float-right`/`.full-width`/
26
+ * `.full-bleed` to gp-* names in place when editing an image).
30
27
  *
31
28
  * .gp-left — floats left, text wraps.
32
29
  * .gp-right — floats right, text wraps.
@@ -136,7 +133,7 @@
136
133
  * carrying gp-left could start shifting; that is standards-tracking per
137
134
  * CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
138
135
  */
139
- export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable. */\n.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }\n.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }\n\n/* the per-shape decisions the paragraph above deliberately leaves to the\n author, named instead of left as raw CSS every book was reinventing\n (2026-09-01 CSS architecture review, findings C1/C7 \u2014 CLAUDE.md \u00A70:\n \"behavior broadly useful to non-technical authors belongs in core\").\n Permanent vocabulary, standard properties verbatim \u2014 same rationale as\n the column/grid runs above. One name each, no aliases:\n .gp-columns-all column-span: all a heading or block that\n spans every column in the\n run it sits inside.\n .gp-columns-flow column-fill: auto a run that FRAGMENTS across\n pages \u2014 every page's\n columns fill instead of\n only the last one\n balancing (the dead-column\n collapse the build's\n engine.multicol.dead-column\n warning names this fix\n for).\n .gp-columns-balanced column-fill: balance a run that fits on ONE\n page (the CSS initial\n value \u2014 ragged columns\n would be wrong here). */\n.gp-columns-all { column-span: all; }\n.gp-columns-flow { column-fill: auto; }\n.gp-columns-balanced { column-fill: balance; }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column/grid vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
136
+ export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable. */\n.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }\n.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }\n\n/* the per-shape decisions the paragraph above deliberately leaves to the\n author, named instead of left as raw CSS every book was reinventing\n (CLAUDE.md \u00A70: \"behavior broadly useful to non-technical authors belongs\n in core\").\n Permanent vocabulary, standard properties verbatim \u2014 same rationale as\n the column/grid runs above. One name each, no aliases:\n .gp-columns-all column-span: all a heading or block that\n spans every column in the\n run it sits inside.\n .gp-columns-flow column-fill: auto a run that FRAGMENTS across\n pages \u2014 every page's\n columns fill instead of\n only the last one\n balancing (the dead-column\n collapse the build's\n engine.multicol.dead-column\n warning names this fix\n for).\n .gp-columns-balanced column-fill: balance a run that fits on ONE\n page (the CSS initial\n value \u2014 ragged columns\n would be wrong here). */\n.gp-columns-all { column-span: all; }\n.gp-columns-flow { column-fill: auto; }\n.gp-columns-balanced { column-fill: balance; }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column/grid vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
140
137
  /**
141
138
  * GP_CLASSES — every class an author may legitimately write with a `gp-`
142
139
  * prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
@@ -3,13 +3,6 @@ import type MarkdownIt from "markdown-it";
3
3
  * Markdown-it `image` renderer rule that RECORDS every image reference the
4
4
  * document emits, on `env.imageRefs`.
5
5
  *
6
- * This replaces the previous `normalizeImageSrc` rewriter, which collapsed
7
- * `temp/images/…` and `./images/…` to `images/…`. That rewrite had zero
8
- * producers anywhere in the codebase or its history, and it silently broke any
9
- * author who kept art in a folder literally named `temp/images/` — the HTML
10
- * pointed at `images/<file>`, which no code path ever created, and adding the
11
- * folder to the old `source.assets` list could not fix it.
12
- *
13
6
  * Recording instead of rewriting is what lets the build copy exactly the files
14
7
  * the book references (`lib/asset-inline.ts` → `planImageCopies`), so the
15
8
  * author's own folder layout is the layout that ships. Paths are emitted
@@ -6,10 +6,7 @@ export type { LayoutWarning } from "./assemble";
6
6
  * THE canonical "which markdown files make up this book, and in what order?"
7
7
  * resolver — markdown's counterpart to `resolveActiveStyles` (style-resolver.ts).
8
8
  * Both `renderChapters` below AND validation (validation-exec.ts) call this,
9
- * so what gets checked is always what gets rendered
10
- * (2026-07-28 duplication audit — those two used to each re-derive their own
11
- * recursive-glob approximation of "the book's markdown files" instead of
12
- * calling this):
9
+ * so what gets checked is always what gets rendered:
13
10
  * 1. `configuredFiles` (the manifest `source.files` list), if it has entries,
14
11
  * in that order; else
15
12
  * 2. every `.md` file directly inside `inputDir` — NOT recursive, since a
@@ -28,8 +25,8 @@ export declare function resolveActiveMarkdownFiles(inputDir: string, configuredF
28
25
  * This is the thin **Node wrapper** around the pure `assembleBookHtml`
29
26
  * (`./assemble.ts`): it resolves the CSS list + the file list off disk and
30
27
  * supplies a `node:fs/promises`-backed `readText`. The pure assembler owns the
31
- * markdown→HTML→book.html work, so the browser/PWA WebAdapter can reuse the
32
- * exact same render path with a File System Access reader (#33).
28
+ * markdown→HTML→book.html work, so a future browser build can reuse the
29
+ * exact same render path with its own file reader.
33
30
  */
34
31
  export declare function renderChapters(inputDir: string, opts?: {
35
32
  title?: string;
@@ -47,10 +44,9 @@ export declare function renderChapters(inputDir: string, opts?: {
47
44
  /** Add source-file ids to source-mapped preview blocks without wrappers. */
48
45
  annotateSourceChapters?: boolean;
49
46
  /**
50
- * ARCH finding #4: per-chapter author-mistake warnings computed by
51
- * Gutterpress's marker parser (`env.layoutWarnings`), forwarded straight
52
- * through from {@link assembleBookHtml}. See that option's docstring — omitting
53
- * it is fully backward compatible.
47
+ * Per-chapter author-mistake warnings computed by Gutterpress's marker
48
+ * parser (`env.layoutWarnings`), forwarded straight through from
49
+ * {@link assembleBookHtml}. See that option's docstring. Optional.
54
50
  */
55
51
  onChapterWarnings?: (file: string, warnings: LayoutWarning[]) => void;
56
52
  /** See {@link assembleBookHtml}'s option of the same name. */
@@ -76,7 +72,7 @@ export declare function renderChaptersToFile(inputDir: string, outDir: string, o
76
72
  plugins?: LoadedPlugin[];
77
73
  /** See {@link renderChapters}'s option of the same name. */
78
74
  pluginStyles?: PluginStyleGroup[];
79
- /** ARCH finding #4 — see {@link renderChapters}'s option of the same name. */
75
+ /** See {@link renderChapters}'s option of the same name. */
80
76
  onChapterWarnings?: (file: string, warnings: LayoutWarning[]) => void;
81
77
  /** See {@link renderChapters}'s options of the same names. */
82
78
  onImageRefs?: (refs: string[]) => void;
@@ -110,10 +110,9 @@ export default function plugin(md: any, pluginOptions?: {}): void;
110
110
  * `text-align: center` still centers it — `display: block` would not).
111
111
  *
112
112
  * #231 — two engine-generic print fixes adopted from a real book's own
113
- * engine sheet (2026-09-01 CSS architecture review, finding C8), because
114
- * both are written entirely in terms of core's own published contract and
115
- * fix a failure every book with big art or a `<figure>` hits, not a
116
- * DC-brand-specific one:
113
+ * engine sheet, because both are written entirely in terms of core's own
114
+ * published contract and fix a failure every book with big art or a
115
+ * `<figure>` hits, not a DC-brand-specific one:
117
116
  *
118
117
  * - A bare markdown placard (`![Alt](art.jpg)`, no class) taller than the
119
118
  * page content box is monolithic replaced content, and the fragmenter
@@ -25,7 +25,7 @@ export declare function loadPlugin(config: ResolvedExtensionConfig, baseDir: str
25
25
  * shows the plugin error with fix instructions).
26
26
  *
27
27
  * Path plugins are loaded through the mtime cache in `loadPlugin` regardless
28
- * of mode (finding #5): an edited plugin reloads across renders while an
28
+ * of mode: an edited plugin reloads across renders while an
29
29
  * unedited one is never re-imported, correct in both a one-shot CLI build and
30
30
  * the long-lived Electron host.
31
31
  */
@@ -50,18 +50,15 @@ export interface LoadedPluginsWithCss {
50
50
  pluginStylePaths: string[];
51
51
  }
52
52
  /**
53
- * Shared "load plugins -> collect their CSS" preamble (ARCH finding #53).
54
- * Both real render paths — build/export's fail-fast `renderBook`
55
- * (build-runner.ts) and the live preview's degrade-and-report
56
- * `renderPreviewBook` (preview/file-watcher.ts) — did this in lockstep,
57
- * differing ONLY in whether `onError` was supplied. `onError` presence still
58
- * selects fail-fast vs degrade-and-report (see {@link loadPlugins}) and the
59
- * matching path-plugin cache mode; this helper just removes the duplicated
60
- * wiring around it.
53
+ * Shared "load plugins -> collect their CSS" preamble for both real render
54
+ * paths — build/export's fail-fast `renderBook` (build-runner.ts) and the
55
+ * live preview's degrade-and-report `renderPreviewBook`
56
+ * (preview/file-watcher.ts), which differ ONLY in whether `onError` is
57
+ * supplied. `onError` presence selects fail-fast vs degrade-and-report (see
58
+ * {@link loadPlugins}).
61
59
  *
62
60
  * A `configs` of `undefined`/empty short-circuits WITHOUT calling
63
- * `loadPlugins` at all (`plugins: undefined`, `pluginStyles: []`) — matching
64
- * both call sites' prior behavior of never plugin-loading when the manifest
65
- * declares no plugins.
61
+ * `loadPlugins` at all (`plugins: undefined`, `pluginStyles: []`): nothing is
62
+ * plugin-loaded when the manifest declares no plugins.
66
63
  */
67
64
  export declare function loadPluginsWithCss(configs: ResolvedExtensionConfig[] | undefined | null, baseDir: string, onError?: (pluginRef: string, error: Error) => void): Promise<LoadedPluginsWithCss>;