gutterpress 0.11.11-alpha.1 → 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-ycaavzsb.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-kgd355eh.js → cli-1kdtzgxm.js} +1 -1
  9. package/dist/{cli-g3xd0x9z.js → cli-d2he8zph.js} +6 -6
  10. package/dist/{cli-wtw5nfsx.js → cli-ny50k8md.js} +1 -1
  11. package/dist/{cli-p40641zw.js → cli-tjhn2mdn.js} +3 -3
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-71dxjkpz.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-fv61tyn7.js → engine-132czsq9.js} +1 -1
  17. package/dist/{engine-sq1kpg5s.js → engine-bgd4aa99.js} +2 -2
  18. package/dist/{ext-rkfntmtv.js → ext-271xnh70.js} +4 -4
  19. package/dist/{index-dxt4t7et.js → index-b6p1fh61.js} +1 -1
  20. package/dist/{index-b7q4wev1.js → index-mw8b25yx.js} +3 -3
  21. package/dist/{index-wdqymw2k.js → index-ssjh6tnd.js} +5 -5
  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-c6pmpvhw.js → new-7w4ezfpy.js} +4 -4
  78. package/dist/platform.d.ts +8 -41
  79. package/dist/{preflight-9vq1ygh4.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-x22vcqyw.js → preview-interface-twd6nsf5.js} +8 -10
  84. package/dist/{preview-shell-tqndvhbx.js → preview-shell-y8yrm3f0.js} +8 -19
  85. package/dist/{preview-6n5ava6c.js → preview-y39jrpbs.js} +4 -4
  86. package/dist/{publish-9an2a1ma.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-0rrjva48.js → validate-9enr4ecc.js} +4 -4
  92. package/package.json +1 -1
@@ -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>;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Pure (node-free) markdown rendering core.
3
3
  *
4
- * §1/§8 / ADR 0004: this module imports ONLY pure JS — markdown-it and its
4
+ * §1/§8: this module imports ONLY pure JS — markdown-it and its
5
5
  * plugins, Gutterpress's inlined marker parser (`markers.js`), and the node-free
6
6
  * leveled logger (console-only). It contains NO `node:*`,
7
7
  * NO `fs`/`path`/`url`, and NO filesystem access, so it can be imported by the
8
- * browser renderer (the PWA WebAdapter, #33) AND bundled into the
8
+ * browser AND bundled into the
9
9
  * `bun build --compile` CLI binary alike.
10
10
  *
11
11
  * The plugin *author* types and the markdown-it factory live here (not in
@@ -201,22 +201,18 @@ export declare const BUILTIN_OPTIONAL_PLUGINS: Record<string, GutterpressPlugin>
201
201
  * `<dl><dt><dd>`. It is not in CommonMark/markdown-it core; this is the
202
202
  * canonical markdown-it plugin for it.
203
203
  *
204
- * Block container syntax (`:::name ... :::`) was removed 2026-05-17 in favor
205
- * of the @marker family. See docs/migrations/2026-05-removing-container-syntax.md
206
- * for the migration mapping.
204
+ * There is no block container syntax (`:::name ... :::`): the @marker family
205
+ * is the wrapped-block surface. See
206
+ * docs/migrations/2026-05-removing-container-syntax.md for the mapping.
207
207
  *
208
- * GFM-style `> [!NOTE]` alerts were also moved into the DC plugin on the
209
- * same date because the emitted classes (dc-alert, dc-vibe-callout, etc.)
210
- * were DC-branded — core should not leak DC identifiers. #237 (0.10.7)
211
- * restored a core-owned, unbranded equivalent as an OPT-IN bundled feature —
208
+ * GFM-style `> [!NOTE]` alerts are an OPT-IN bundled feature (#237) —
212
209
  * `gfm-alerts.ts`, registered below as `gutterpress-gfm-alerts` — emitting
213
210
  * only the standard GitHub five (NOTE/TIP/IMPORTANT/WARNING/CAUTION) as
214
211
  * neutral `gp-alert`/`gp-alert-<type>` structure (see that file's header).
215
- * This does not re-converge with the DC plugin: DC's branded extra types
216
- * (`[!DM]`/`[!VIBE]`/`[!ORIGIN]`, etc.) and its own class names stay exactly
217
- * where they were moved to, layered on top of (or independent from) this
218
- * primitive. A project using neither plugin still renders `> [!NOTE]` as a
219
- * literal blockquote, unchanged — this feature is opt-in, not a default.
212
+ * Core emits no branded identifiers: branded extra types (e.g. DC's
213
+ * `[!DM]`/`[!VIBE]`/`[!ORIGIN]`) and their class names belong in project
214
+ * plugins, layered on top of (or independent from) this primitive. A project
215
+ * using neither renders `> [!NOTE]` as a literal blockquote.
220
216
  *
221
217
  * #240 — before `gutterpressMarkers` is applied, every loaded plugin's
222
218
  * declared `markers` table (if any) is merged into ONE registry via
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * A visible stand-in for an image the book references but does not have.
3
3
  *
4
- * WHY THIS EXISTS: a missing image used to abort the whole build
4
+ * WHY THIS EXISTS: without it, a missing image aborts the whole build
5
5
  * (`Could not copy asset … ENOENT`). For a non-technical author that is the
6
6
  * worst possible failure mode — one stale image path in a 273-page book and
7
7
  * nothing renders at all, with a filesystem error as the only explanation.
8
8
  * Worse, it makes the book unbuildable by anyone who does not already have
9
- * the missing file, which is exactly the state the dc-op-manual field guide
10
- * was in: two chapters referenced art that exists nowhere in the repo, so
11
- * every tool and every reviewer had to hand-patch placeholders in to build
12
- * it at all.
9
+ * the missing file.
13
10
  *
14
11
  * The fix is NOT to substitute something invisible. A silently-blank image
15
12
  * is how a missing illustration ships to print. This paints an unmistakable
@@ -2,11 +2,10 @@
2
2
  * Output location + artifact naming, by convention rather than configuration.
3
3
  *
4
4
  * A book builds to `<manifestDir>/dist/<title-slug>/`, and its artifacts are
5
- * `<title-slug>-<format>.pdf`. This replaces the manifest's `output.dir` /
6
- * `output.filename` / `output.html` block: the slug keeps several books in one
7
- * tree apart, and the format is in the artifact name because the extension
8
- * cannot tell a PDF from a PDF/X one (they previously shared a single
9
- * configured filename, so building both left only the last).
5
+ * `<title-slug>-<format>.pdf` — a convention, not manifest configuration: the
6
+ * slug keeps several books in one tree apart, and the format is in the
7
+ * artifact name because the extension cannot tell a PDF from a PDF/X one (a
8
+ * shared filename would leave only the last one built).
10
9
  *
11
10
  * `--out` remains for per-invocation placement (CI, one-offs).
12
11
  */
@@ -5,7 +5,7 @@
5
5
  * pdfimages, pdftotext) and the general qpdf inspection used by the post-build
6
6
  * validation checks. It has ZERO system dependency and — unlike raw pdfjs-dist,
7
7
  * whose `legacy` build eagerly evaluates canvas/DOMMatrix code — unpdf bundles
8
- * cleanly under `bun build --compile` (verified). See ADR 0002.
8
+ * cleanly under `bun build --compile` (verified).
9
9
  *
10
10
  * NOT replaced here (still system tools, by design):
11
11
  * - Ghostscript (gs): ink coverage + PDF/X CMYK conversion — no JS equivalent.
@@ -13,7 +13,7 @@
13
13
  * catalog/object access pdfjs has no public API for, and qpdf is already
14
14
  * mandatory whenever PDF/X is produced.
15
15
  *
16
- * Fidelity notes (accepted in ADR 0002):
16
+ * Fidelity notes (accepted):
17
17
  * - Structural integrity (`isLoadable`) is a "does it parse" gate, not a deep
18
18
  * `qpdf --check` of xref/stream-length integrity.
19
19
  * - Image DPI requires decoding the image to read its pixel dimensions and is
@@ -25,10 +25,10 @@
25
25
  */
26
26
  import type { PDFDocumentProxy, PDFPageProxy } from "unpdf/pdfjs";
27
27
  /**
28
- * Cap on distinct cached documents (audit B3). One validation run touches a
28
+ * Cap on distinct cached documents. One validation run touches a
29
29
  * single PDF across ~13 checks (one entry), so this only bounds accumulation
30
30
  * ACROSS runs in a long-lived host (the Electron desktop validating many
31
- * projects over a session). Without it, `docCache` grew one never-freed parsed
31
+ * projects over a session). Without it, `docCache` grows one never-freed parsed
32
32
  * document per distinct path ever validated. LRU eviction destroys the evicted
33
33
  * document so its decoded pages/fonts/images are released, not just unreferenced.
34
34
  * (Exported so tests derive their eviction fixtures from the real cap.)
@@ -146,6 +146,6 @@ export declare function getImageResolutions(doc: PDFDocumentProxy): Promise<Arra
146
146
  /**
147
147
  * "Does it parse" gate: load every page. Returns true if the whole document is
148
148
  * traversable. This is NOT a deep xref/stream-length integrity check — it only
149
- * catches PDFs that are actually broken/unparseable (ADR 0002).
149
+ * catches PDFs that are actually broken/unparseable.
150
150
  */
151
151
  export declare function isLoadable(doc: PDFDocumentProxy): Promise<boolean>;
@@ -15,7 +15,7 @@ export declare function readPdfBytes(pdfPath: string): Promise<string>;
15
15
  /**
16
16
  * Parse ink coverage from `gs -sDEVICE=inkcov` output.
17
17
  *
18
- * Ghostscript has no pure-JS equivalent (it stays a system tool — see ADR 0002),
18
+ * Ghostscript has no pure-JS equivalent (it stays a system tool),
19
19
  * so the inkcov path remains shell-based here.
20
20
  */
21
21
  export declare function parseInkCov(out: string): {
@@ -34,12 +34,11 @@ interface InkCoveragePage {
34
34
  tac: number;
35
35
  }
36
36
  /**
37
- * Discriminated result for {@link getPerPageInkCoverage} (finding #51).
38
- * Ghostscript failing (crash, corrupt PDF, missing binary, a Windows PATH
39
- * mismatch — see finding #3) must be distinguishable from a legitimately
40
- * measured, empty-pages result: both used to collapse to the same `[]`,
41
- * which let the ink-coverage check silently PASS a book it never actually
42
- * measured.
37
+ * Discriminated result for {@link getPerPageInkCoverage}. Ghostscript failing
38
+ * (crash, corrupt PDF, missing binary, a Windows PATH mismatch) must be
39
+ * distinguishable from a legitimately measured, empty-pages result:
40
+ * collapsing both to the same `[]` would let the ink-coverage check silently
41
+ * PASS a book it never actually measured.
43
42
  */
44
43
  export type InkCoverageResult = {
45
44
  ok: true;