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.
- package/README.md +3 -3
- package/dist/api/index.js +2 -2
- package/dist/{build-83dnh2qe.js → build-pjd42x09.js} +4 -4
- package/dist/checks/asset/extensions.d.ts +2 -2
- package/dist/checks/policy.d.ts +5 -7
- package/dist/checks/register-builtins.d.ts +6 -6
- package/dist/checks/registry.d.ts +1 -1
- package/dist/{cli-79tsqp3p.js → cli-1kdtzgxm.js} +1 -1
- package/dist/{cli-mv1mhpkk.js → cli-d2he8zph.js} +6 -6
- package/dist/{cli-b36za51e.js → cli-ny50k8md.js} +3 -3
- package/dist/{cli-30j93gzt.js → cli-tjhn2mdn.js} +3 -3
- package/dist/cli.js +12 -12
- package/dist/{doctor-sy4gc8r9.js → doctor-qkh9byxa.js} +2 -2
- package/dist/engine/shared/cdp.d.ts +5 -9
- package/dist/engine/shared/pdf-inspect.d.ts +0 -4
- package/dist/{engine-mwshptgf.js → engine-132czsq9.js} +1 -1
- package/dist/{engine-3gwctsty.js → engine-bgd4aa99.js} +2 -2
- package/dist/{ext-a7ffvyc0.js → ext-271xnh70.js} +4 -4
- package/dist/{index-dzp0hc2c.js → index-b6p1fh61.js} +1 -1
- package/dist/{index-r4wvy5v3.js → index-mw8b25yx.js} +3 -3
- package/dist/{index-0cvkvpkg.js → index-ssjh6tnd.js} +7 -7
- package/dist/index.js +3 -3
- package/dist/lib/asset-inline.d.ts +1 -2
- package/dist/lib/build-error.d.ts +4 -6
- package/dist/lib/build-fingerprint.d.ts +3 -4
- package/dist/lib/build-preflight.d.ts +4 -6
- package/dist/lib/build-runner.d.ts +24 -35
- package/dist/lib/build-staging.d.ts +3 -5
- package/dist/lib/cli-args.d.ts +1 -1
- package/dist/lib/desktop.d.ts +2 -8
- package/dist/lib/engine.d.ts +2 -2
- package/dist/lib/exec.d.ts +3 -6
- package/dist/lib/fetch-timeout.d.ts +3 -4
- package/dist/lib/ghostscript.d.ts +1 -1
- package/dist/lib/host-policy.d.ts +6 -8
- package/dist/lib/image-inspect.d.ts +2 -2
- package/dist/lib/install-hints.d.ts +2 -3
- package/dist/lib/manifest-config.d.ts +2 -3
- package/dist/lib/manifest-doc.d.ts +2 -3
- package/dist/lib/manifest.d.ts +4 -7
- package/dist/lib/markdown/assemble.d.ts +7 -13
- package/dist/lib/markdown/gutterpress-css.d.ts +6 -9
- package/dist/lib/markdown/images.d.ts +0 -7
- package/dist/lib/markdown/index.d.ts +7 -11
- package/dist/lib/markdown/markers.d.ts +3 -4
- package/dist/lib/markdown/plugins.d.ts +9 -12
- package/dist/lib/markdown/renderer.d.ts +10 -14
- package/dist/lib/missing-asset-placeholder.d.ts +2 -5
- package/dist/lib/output-paths.d.ts +4 -5
- package/dist/lib/pdf-inspect.d.ts +5 -5
- package/dist/lib/pdf-parse.d.ts +6 -7
- package/dist/lib/presets.d.ts +2 -3
- package/dist/lib/project-source.d.ts +3 -4
- package/dist/lib/publish/command-runner.d.ts +1 -1
- package/dist/lib/publish/connect-google.d.ts +2 -2
- package/dist/lib/publish/types.d.ts +3 -3
- package/dist/lib/remote-auth/clone.d.ts +2 -2
- package/dist/lib/remote-auth/converge-merge.d.ts +1 -1
- package/dist/lib/remote-auth/diagnose.d.ts +3 -3
- package/dist/lib/remote-auth/generic-auth.d.ts +1 -1
- package/dist/lib/remote-auth/git-http.d.ts +5 -5
- package/dist/lib/remote-auth/github-auth.d.ts +3 -3
- package/dist/lib/remote-auth/github-repos.d.ts +4 -4
- package/dist/lib/remote-auth/sync-types.d.ts +1 -1
- package/dist/lib/remote-auth/sync.d.ts +1 -1
- package/dist/lib/remote-auth/test-access.d.ts +1 -1
- package/dist/lib/remote-auth/token-store.d.ts +1 -1
- package/dist/lib/remote-auth/transport.d.ts +3 -3
- package/dist/lib/render-parity-raster.d.ts +28 -0
- package/dist/lib/render-parity.d.ts +4 -1
- package/dist/lib/source-provider.d.ts +4 -5
- package/dist/lib/static-serve.d.ts +8 -13
- package/dist/lib/style-resolver.d.ts +1 -1
- package/dist/lib/targets.d.ts +1 -5
- package/dist/lib/tool-probe.d.ts +2 -3
- package/dist/lib/validation-exec.d.ts +3 -4
- package/dist/{new-994trd8k.js → new-7w4ezfpy.js} +4 -4
- package/dist/platform.d.ts +8 -41
- package/dist/{preflight-2rtrakyf.js → preflight-f3h6arjp.js} +4 -4
- package/dist/preview/file-watcher.d.ts +3 -5
- package/dist/preview/lifecycle.d.ts +9 -17
- package/dist/{preview-bridge-b4jbtb79.js → preview-bridge-8jhd1wpb.js} +1 -1
- package/dist/{preview-interface-9rwz21yw.js → preview-interface-twd6nsf5.js} +74 -13
- package/dist/{preview-shell-tqndvhbx.js → preview-shell-y8yrm3f0.js} +8 -19
- package/dist/{preview-n9s46142.js → preview-y39jrpbs.js} +4 -4
- package/dist/{publish-wte9548r.js → publish-tvj3xh8m.js} +4 -4
- package/dist/render.d.ts +5 -5
- package/dist/render.js +2 -2
- package/dist/schema/manifest.types.d.ts +2 -2
- package/dist/utils/logger.d.ts +1 -2
- package/dist/{validate-jfsh3m1c.js → validate-9enr4ecc.js} +4 -4
- 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)
|
|
23
|
-
*
|
|
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
|
|
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
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* `
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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
|
|
199
|
-
* this existed).
|
|
195
|
+
* `runQualityGates`, gets a fresh load here).
|
|
200
196
|
*
|
|
201
|
-
* Fail-fast (no `onError`)
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
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
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
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
|
|
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
|
|
272
|
-
*
|
|
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
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
package/dist/lib/cli-args.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/lib/desktop.d.ts
CHANGED
|
@@ -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.
|
package/dist/lib/engine.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
25
|
-
*
|
|
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[]>;
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -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
|
|
15
|
-
* literal `:`
|
|
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
|
-
* (
|
|
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
|
|
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` —
|
|
10
|
-
* `signal ?? AbortSignal.timeout(...)` pattern silently
|
|
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
|
|
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) —
|
|
21
|
-
* knob, so there is nothing
|
|
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 —
|
|
56
|
-
* minutes
|
|
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 —
|
|
74
|
-
* debounce
|
|
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
|
|
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` (
|
|
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
|
-
*
|
|
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
|
|
26
|
-
*
|
|
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
|
-
*
|
|
22
|
-
* (manifest-config.ts
|
|
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;
|
package/dist/lib/manifest.d.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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
|
|
28
|
-
* @
|
|
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
|
-
*
|
|
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.
|
|
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
|
|
142
|
-
*
|
|
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
|
|
20
|
+
* project layer). markdown-it-attrs is bundled by
|
|
21
21
|
* default (renderer.ts), so `{.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.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
32
|
-
* exact same render path with
|
|
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
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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 (``, 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
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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: []`)
|
|
64
|
-
*
|
|
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>;
|