gutterpress 0.11.23-alpha.2 → 0.11.23-alpha.3

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 (55) hide show
  1. package/README.md +6 -7
  2. package/dist/{README-8ef336pp.md → README-8db7btxb.md} +13 -19
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +4 -4
  5. package/dist/{build-encgnv0c.js → build-m78jcpf7.js} +7 -7
  6. package/dist/{cli-xq6djqbj.js → cli-1h17ktsg.js} +84 -113
  7. package/dist/{cli-kh8v4vvw.js → cli-5jzyw06s.js} +1 -1
  8. package/dist/{cli-sdmv4bba.js → cli-h4x81x97.js} +112 -8
  9. package/dist/{cli-evg8sbht.js → cli-n2tsxqpn.js} +1 -1
  10. package/dist/{cli-aqh6brew.js → cli-s6djx4ma.js} +1 -1
  11. package/dist/{cli-pwwfxdgb.js → cli-t8be9yeb.js} +9 -20
  12. package/dist/{cli-pz4bn98t.js → cli-xsrffqzt.js} +19 -13
  13. package/dist/cli.js +12 -12
  14. package/dist/{doctor-5gtpgpd1.js → doctor-j71jqce3.js} +3 -3
  15. package/dist/engine/shared/gcpm-extract.d.ts +8 -0
  16. package/dist/engine/shared/print-area.d.ts +16 -4
  17. package/dist/{engine-tn13gk0b.js → engine-1r6q80tq.js} +2 -2
  18. package/dist/{engine-mc5br8g8.js → engine-m4y10shc.js} +1 -1
  19. package/dist/{ext-wn2a5afq.js → ext-fq2hg9va.js} +7 -7
  20. package/dist/{extension-restore-s3x1618g.js → extension-restore-9dh7c4tp.js} +2 -2
  21. package/dist/{extension-restore-g8jnmmzq.js → extension-restore-tmxtzkm0.js} +4 -4
  22. package/dist/{gutterpress-viewer-701vf4qp.js → gutterpress-viewer-mafsby5k.js} +9 -2
  23. package/dist/{index-2bjcghze.js → index-0wee4408.js} +9 -20
  24. package/dist/{index-x3ema2w3.js → index-7pck1q9c.js} +124 -17
  25. package/dist/{index-0skhjx3e.js → index-nedd07bk.js} +19 -13
  26. package/dist/{index-4x6y8krm.js → index-nvmf22rt.js} +1 -1
  27. package/dist/{index-j5z86r4a.js → index-prqvz3xm.js} +213 -276
  28. package/dist/index.js +5 -5
  29. package/dist/lib/extension-manager.d.ts +8 -7
  30. package/dist/lib/extension-manifest.d.ts +1 -13
  31. package/dist/lib/manifest.d.ts +15 -1
  32. package/dist/lib/markdown/assemble.d.ts +4 -3
  33. package/dist/lib/page-size.d.ts +34 -27
  34. package/dist/lib/printsafe.d.ts +1 -0
  35. package/dist/lib/project-templates.d.ts +9 -0
  36. package/dist/manifest-8afhrk30.yaml +19 -0
  37. package/dist/manifest-d4f9q79e.yaml +23 -0
  38. package/dist/{manifest-eagfhba6.yaml → manifest-gf13sjp8.yaml} +10 -2
  39. package/dist/manifest-my7mdv8j.yaml +23 -0
  40. package/dist/{manifest.schema-t7nx2stg.json → manifest.schema-650pe0d1.json} +15 -3
  41. package/dist/{new-mhs46pw3.js → new-s4zcaepg.js} +7 -7
  42. package/dist/{package-aenwjew3.json → package-awwkt7sa.json} +0 -1
  43. package/dist/{page-rules-nsj3aw42.css → page-rules-fj09xct5.css} +12 -40
  44. package/dist/{plugin.test.js-xgxrd8hf.tpl → plugin.test.js-gxzqqfdh.tpl} +0 -1
  45. package/dist/{plugins-1sxe0dkz.js → plugins-4vaf27n9.js} +3 -3
  46. package/dist/{plugins-2cek4mz1.js → plugins-80emnf31.js} +1 -1
  47. package/dist/{preflight-3k2b1x8e.js → preflight-c7w1xdn3.js} +7 -7
  48. package/dist/{preview-6p9v6rht.js → preview-mzytcwyk.js} +7 -7
  49. package/dist/{publish-qgsngy8s.js → publish-fez14ad4.js} +7 -7
  50. package/dist/render.js +24 -561
  51. package/dist/schema/manifest.types.d.ts +22 -0
  52. package/dist/{tokens-7984461t.css → tokens-ye64p899.css} +5 -12
  53. package/dist/{validate-0v7xw6wa.js → validate-p2chr6hr.js} +7 -7
  54. package/package.json +1 -1
  55. package/dist/manifest-n1gh3qw5.yaml +0 -9
package/dist/index.js CHANGED
@@ -116,7 +116,7 @@ import {
116
116
  substituteVariables,
117
117
  syncProject,
118
118
  testRemoteAccess
119
- } from "./index-j5z86r4a.js";
119
+ } from "./index-prqvz3xm.js";
120
120
  import {
121
121
  DEFAULT_NPM_REGISTRY,
122
122
  MANIFEST_FILENAMES,
@@ -146,7 +146,7 @@ import {
146
146
  setExtensionEnabled,
147
147
  updateExtensions,
148
148
  validateProjectExtensions
149
- } from "./index-x3ema2w3.js";
149
+ } from "./index-7pck1q9c.js";
150
150
  import {
151
151
  BUNDLED_EXTENSIONS,
152
152
  EXTENSION_MANIFEST_FILENAME,
@@ -162,12 +162,12 @@ import {
162
162
  pathEscapesFolder,
163
163
  readExtensionMeta,
164
164
  resolveExtension
165
- } from "./index-2bjcghze.js";
165
+ } from "./index-0wee4408.js";
166
166
  import"./index-ht1bcqdd.js";
167
167
  import {
168
168
  BUILD_DIAGNOSTIC_CODES
169
- } from "./index-4x6y8krm.js";
170
- import"./index-0skhjx3e.js";
169
+ } from "./index-nvmf22rt.js";
170
+ import"./index-nedd07bk.js";
171
171
  import {
172
172
  BuildError
173
173
  } from "./index-46ycxe6r.js";
@@ -27,14 +27,11 @@ export interface ProjectExtensionEntry {
27
27
  author?: string;
28
28
  /** Preview image path relative to the extension folder, when declared. */
29
29
  preview?: string | null;
30
- /** The sheet carrying the `:root` token surface, relative to the folder,
31
- * when declared (package.json's `gutterpress.tokensFile`). */
32
- tokensFile?: string;
33
30
  /** Declared stylesheets relative to the folder, in cascade order
34
31
  * (`gutterpress.styles`). Absent when the folder declares none. */
35
32
  styles?: string[];
36
- /** What the extension declares — the desktop shows styles-carrying entries
37
- * in its Look view and markdown-carrying ones in Features; one list. */
33
+ /** What the extension declares — the badges on the desktop's Looks &
34
+ * features list. */
38
35
  carries: ExtensionCarries;
39
36
  /** Absolute folder its metadata was read from. Absent for a bundled name,
40
37
  * a bare JS file, an uninstalled npm entry, or a missing path. */
@@ -208,7 +205,11 @@ export declare function uniqueExtensionId(projectDir: string, base: string): Pro
208
205
  export declare function validateProjectExtensions(projectDir: string): Promise<ExtensionValidationResult[]>;
209
206
  /**
210
207
  * Read an extension's stylesheets, concatenated in cascade order, for a
211
- * preview thumbnail (the renderer never touches fs). Read-only, so
212
- * concatenation is fine even though it would not be for an editable surface.
208
+ * preview thumbnail (the renderer never touches fs). A bundled look answers
209
+ * whether or not the manifest lists it, so a book can show what a look is
210
+ * before adding it. Read-only, so concatenation is fine even though it would
211
+ * not be for an editable surface.
213
212
  */
214
213
  export declare function readExtensionCss(projectDir: string, use: string): Promise<string>;
214
+ /** The same, for an entry already described (`listProjectExtensions`), so a caller with the list in hand reads each once. */
215
+ export declare function readEntryCss(entry: Pick<ProjectExtensionEntry, "use" | "dir">): Promise<string>;
@@ -42,11 +42,6 @@ export interface ExtensionMetadata {
42
42
  * its sheets, it does not get an implicit `theme.css`.
43
43
  */
44
44
  styles?: string[];
45
- /** `gutterpress.tokensFile` — which declared sheet (a path from `styles`)
46
- * carries the author-facing `:root` token surface for the Design panel's
47
- * guided editor. Purely advisory — nothing in this module enforces or
48
- * existence-checks it. */
49
- tokensFile?: string;
50
45
  /**
51
46
  * `gutterpress.markdown` — an EXPLICIT markdown-it entry, relative to the
52
47
  * extension folder, for the one case npm's `main` can't express: a package
@@ -138,7 +133,7 @@ export declare function assertPublishOnly(meta: ExtensionMetadata, ref: string):
138
133
  * Exported (#242) for `snippets.ts`'s installed-extension snippet merge,
139
134
  * which needs this SAME single-field check but cannot use
140
135
  * {@link assertExtensionContained}: that guard throws on ANY escaping field
141
- * (styles/markdown/components/tokensFile too), which is correct for a
136
+ * (styles/markdown/components too), which is correct for a
142
137
  * write-boundary check at install/apply time but wrong for a tolerant
143
138
  * listing — an extension with a broken, unrelated `styles` entry must not
144
139
  * make its perfectly fine `snippets` folder disappear from the picker (the
@@ -158,8 +153,6 @@ export declare function assertSupportsThisGutterpress(meta: ExtensionMetadata, r
158
153
  * extension is self-contained by contract (apply/install copies the whole
159
154
  * folder), and an imported/vendored package is untrusted input: a `../` or
160
155
  * absolute entry would make apply/load read a file from anywhere on disk.
161
- * `tokensFile` is included even though it is advisory/unenforced elsewhere —
162
- * defense in depth against a future consumer reading it unchecked.
163
156
  *
164
157
  * A WRITE-BOUNDARY guard, not a read-path check: callers invoke this before
165
158
  * copying anything or wiring a manifest, never from a plain listing/read
@@ -176,17 +169,12 @@ export declare function assertExtensionContained(meta: ExtensionMetadata): void;
176
169
  * `styles` already resolve through, so a broken declaration (a missing file)
177
170
  * throws HERE, at load/apply time, instead of failing silently deep in the
178
171
  * render pipeline (or never, for `components`, which nothing yet reads).
179
- *
180
- * `tokensFile` is the one exception, kept as declared (relative, not
181
- * existence-checked) — it stays purely advisory.
182
172
  */
183
173
  export interface ResolvedExtension {
184
174
  /** Absolute path to the markdown-it entry module, when the folder has one. */
185
175
  markdown?: string;
186
176
  /** Absolute paths, in cascade order, when any are declared. */
187
177
  styles?: string[];
188
- /** Declared-relative path of the `:root` token surface — advisory. */
189
- tokensFile?: string;
190
178
  /** Absolute path to the component catalog file, when declared (#242). */
191
179
  components?: string;
192
180
  /** Absolute path to the snippets folder, when declared. */
@@ -1,4 +1,4 @@
1
- import type { GutterpressManifest, ResolvedConfig } from "../schema/manifest.types";
1
+ import type { GutterpressManifest, PageMargin, ResolvedConfig } from "../schema/manifest.types";
2
2
  /**
3
3
  * The manifest file name Gutterpress recognizes.
4
4
  * Single source of truth — any code that needs to find/match a manifest file
@@ -44,3 +44,17 @@ export declare function resolveConfig(cliOverrides: Partial<GutterpressManifest>
44
44
  * the same sovereignty rule presets follow.
45
45
  */
46
46
  export declare function resolveConfigForTarget(cliOverrides: Partial<GutterpressManifest>, manifest: GutterpressManifest, targetId: string): ResolvedConfig;
47
+ /** The edges of `page.margin`, in the order they are shown and written. */
48
+ export declare const MARGIN_EDGES: readonly ["top", "bottom", "inside", "outside"];
49
+ /**
50
+ * The one rule for a legal `page.margin`, applied to a manifest as it is
51
+ * resolved and to a margin Book setup is about to write: all four edges or
52
+ * nothing (a partial block would leave the missing edges to whatever a
53
+ * stylesheet says, the two-places split the field exists to end), each a
54
+ * finite point value of 0 or more, and the four leaving room for text on the
55
+ * page. Returns the margin typed, or throws the message both callers show.
56
+ */
57
+ export declare function validatePageMargin(margin: unknown, size: {
58
+ width: number;
59
+ height: number;
60
+ }): PageMargin;
@@ -138,9 +138,10 @@ export interface AssembleBookHtmlOptions {
138
138
  * preset id and printer spec — written as `<meta name="gp-print-area">`
139
139
  * (engine/shared/print-area.ts) into EVERY assembled book, preview, build
140
140
  * and publish alike: the preview's print overlays judge the ink against it,
141
- * and both engines place pinned art on its art-safe line. Metadata only,
142
- * with one exception: its `width` x `height` is also the page size the book
143
- * prints at when no stylesheet sets one — see {@link defaultPageSizeCss}.
141
+ * and both engines place pinned art on its art-safe line. Metadata, except
142
+ * that its `width` x `height` is also the page size the book prints at, and
143
+ * its `margin`, when the manifest sets one, the margins — see
144
+ * {@link pageGeometryCss}.
144
145
  */
145
146
  printArea?: PrintAreaSource;
146
147
  plugins?: LoadedPlugin[];
@@ -1,7 +1,9 @@
1
1
  import type { Document } from "yaml";
2
2
  import { type PresetId } from "./presets.ts";
3
- import { formatPageSize, isPositivePoints } from "../engine/shared/print-area.ts";
4
- export { formatPageSize, isPositivePoints };
3
+ import { isPositivePoints } from "../engine/shared/print-area.ts";
4
+ import type { PageMargin } from "../schema/manifest.types.ts";
5
+ export { isPositivePoints };
6
+ export type { PageMargin };
5
7
  /** Page bounds written to the manifest's `page:` block (points; 72pt = 1in). */
6
8
  export interface CustomPageOptions {
7
9
  /** Trim width in points (72pt = 1in). */
@@ -16,24 +18,33 @@ export interface PageSizePoints {
16
18
  width: number;
17
19
  height: number;
18
20
  }
19
- /** What an author picks: a preset, plus the trim when the preset is `custom`. */
21
+ /** What an author picks for the size: a preset, and the trim when the preset is `custom`. */
20
22
  export interface PageSizeChoice {
21
23
  preset: PresetId;
22
24
  /** Required for `custom`; ignored for `dtrpg` / `book` (the preset owns it). */
23
25
  page?: PageSizePoints;
24
26
  }
25
- /** A book's page size as currently recorded, read back for the settings UI. */
27
+ /**
28
+ * A change to a book's page geometry: the size, the margins, or both. A part
29
+ * left out stays as recorded, so a margin edit never rewrites the size (nor
30
+ * drops a preset's `page:` override) and a size change never invents margins.
31
+ */
32
+ export interface PageSetupChange {
33
+ size?: PageSizeChoice;
34
+ margins?: PageMargin;
35
+ }
36
+ /** A book's page geometry as currently recorded, read back for the settings UI. */
26
37
  export interface PageSetup {
27
38
  /** The manifest's preset (`dtrpg` when undeclared — the product default); null when unrecognised. */
28
39
  preset: PresetId | null;
29
- /** The validation bounds the manifest resolves to, or null (custom with no `page:`). */
40
+ /** The size the book prints at and is checked against, or null (custom with no `page:`). */
30
41
  bounds: (PageSizePoints & {
31
42
  tolerance: number;
32
43
  }) | null;
33
- /** What the book's stylesheets print at, or null when no plain `@page` sets a size (the book then prints at `bounds`). */
34
- css: (PageSizePoints & {
35
- file: string;
36
- }) | null;
44
+ /** The margins the book prints with (points, `inside` at the binding edge). */
45
+ margins: PageMargin;
46
+ /** Where `margins` come from: the manifest's `page.margin` (the author's own), a stylesheet's `@page` (a look's design), or the engine's 1in default. */
47
+ marginsFrom: "manifest" | "stylesheet" | "default";
37
48
  }
38
49
  /**
39
50
  * Write the manifest's `page:` bounds. `tolerance` defaults to whatever the
@@ -42,26 +53,22 @@ export interface PageSetup {
42
53
  * that is not a map is replaced.
43
54
  */
44
55
  export declare function writePageBounds(doc: Document, page: CustomPageOptions): void;
56
+ /** Remove the bounds a preset owns and nothing else; a `page:` left empty goes too. */
57
+ export declare function deletePageBounds(doc: Document): void;
58
+ /** Write the manifest's `page.margin`, all four edges; the block's other keys stay as written. */
59
+ export declare function writePageMargin(doc: Document, margin: PageMargin): void;
60
+ /** A positive point value at `keyPath` in the manifest, or undefined. */
61
+ export declare function numberAt(doc: Document, keyPath: string[]): number | undefined;
45
62
  /** The size (points) a choice means: `custom` brings its own, the others are the preset's. */
46
63
  export declare function pageSizeForChoice(choice: PageSizeChoice): PageSizePoints;
47
- /** The size the LAST plain `@page { size }` in the stylesheet sets (cascade: last wins), or null. */
48
- export declare function readPageSizeFromCss(css: string): PageSizePoints | null;
49
- /**
50
- * Point a stylesheet's plain `@page` rule at `page`. Rewrites the existing
51
- * `size` declaration's value in place — comments, whitespace, other rules and
52
- * `!important` untouched — or, when no plain `@page` sets a size, appends a
53
- * minimal rule. An already-matching size leaves the text exactly as it was.
54
- * Throws when the CSS does not parse (it must never be half-rewritten).
55
- */
56
- export declare function setPageSizeInCss(css: string, page: PageSizePoints): string;
57
- /** Read a book's page size back from its manifest and stylesheets. */
64
+ /** Read a book's page geometry back from its manifest (and, for margins it does not set, its stylesheets). */
58
65
  export declare function readPageSetup(projectDir: string): Promise<PageSetup>;
59
66
  /**
60
- * Change a book's page size: the manifest's `preset:` + `page:` bounds and the
61
- * stylesheet's `@page` size, together. Mirrors what the wizard records —
62
- * `dtrpg` / `book` carry their own bounds (the `page:` block's width, height
63
- * and tolerance are removed so they cannot shadow them; its print-area keys
64
- * stay), `custom` records the typed trim. Nothing is written
65
- * unless the whole choice validates and the stylesheet parses.
67
+ * Change a book's page geometry in the manifest. The size mirrors what the
68
+ * wizard records — `dtrpg` / `book` carry their own bounds (the `page:` block's
69
+ * width, height and tolerance are removed so they cannot shadow them; its
70
+ * other keys stay), `custom` records the typed trim. The margins go to
71
+ * `page.margin`, validated against the size being written, else the recorded
72
+ * one. Nothing is written unless the whole change validates.
66
73
  */
67
- export declare function setPageSetup(projectDir: string, choice: PageSizeChoice): Promise<PageSetup>;
74
+ export declare function setPageSetup(projectDir: string, change: PageSetupChange): Promise<PageSetup>;
@@ -3,6 +3,7 @@ export declare const ruleRiskyProps = "printsafe/no-risky-print-effects";
3
3
  export declare const ruleSyntax = "printsafe/syntax-error";
4
4
  export declare const rulePageContainment = "printsafe/page-containment";
5
5
  export declare const rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
6
+ export declare const rulePageSizeInStylesheet = "printsafe/page-size-in-stylesheet";
6
7
  export interface PrintSafeWarning {
7
8
  rule: string;
8
9
  severity: "error" | "warning";
@@ -22,6 +22,15 @@ export interface TemplateInfo {
22
22
  preset?: string;
23
23
  /** The `targets:` this template's manifest declares, when it has one. */
24
24
  targets?: string[];
25
+ /**
26
+ * The `page:` size (points) this template's manifest declares, when it has
27
+ * one. A `custom`-preset template says what size it is designed for here,
28
+ * and the creation flows show that size pre-filled.
29
+ */
30
+ page?: {
31
+ width: number;
32
+ height: number;
33
+ };
25
34
  }
26
35
  export declare const BUILT_IN_META: Record<ProjectTemplateId, {
27
36
  label: string;
@@ -0,0 +1,19 @@
1
+ title: "{{TITLE}}"
2
+ authors:
3
+ - "{{AUTHOR}}"
4
+ source:
5
+ files:
6
+ - chapter-01.md
7
+ styles:
8
+ - styles/book.css
9
+ extensions:
10
+ - clean-book
11
+ # Page margins in points (72 = 1 in): top, bottom, inside (the binding edge)
12
+ # and outside. Change them, and the page size, under Setup > Details.
13
+ page:
14
+ margin:
15
+ top: 61.2
16
+ bottom: 61.2
17
+ inside: 54
18
+ outside: 54
19
+ preset: book
@@ -0,0 +1,23 @@
1
+ title: "{{TITLE}}"
2
+ authors:
3
+ - "{{AUTHOR}}"
4
+ source:
5
+ files:
6
+ - chapter-01.md
7
+ styles:
8
+ - styles/book.css
9
+ extensions:
10
+ - zine
11
+ # Page size and margins in points (72 = 1 in): Digest (5.5 x 8.5 in) with
12
+ # 0.4 in margins; text stays 0.25 in (18) inside the trim. Change them under
13
+ # Setup > Details.
14
+ page:
15
+ width: 396
16
+ height: 612
17
+ safe: 18
18
+ margin:
19
+ top: 28.8
20
+ bottom: 28.8
21
+ inside: 28.8
22
+ outside: 28.8
23
+ preset: custom
@@ -7,13 +7,21 @@ source:
7
7
  - chapter-01.md
8
8
  styles:
9
9
  - styles/book.css
10
- # A paper prints on US Letter; for A4 set width 595 and height 842.
10
+ # Page size and margins in points (72 = 1 in): US Letter with 1 in margins
11
+ # (A4 is width 595, height 842); text stays 0.25 in (18) inside the trim.
12
+ # Change them under Setup > Details.
11
13
  page:
12
14
  width: 612
13
15
  height: 792
16
+ safe: 18
17
+ margin:
18
+ top: 72
19
+ bottom: 72
20
+ inside: 72
21
+ outside: 72
14
22
  extensions:
15
23
  - markdown-it-sup
16
24
  - gutterpress-math
17
25
  # The built-in academic look. Style over it from styles/book.css.
18
26
  - academic
19
- preset: book
27
+ preset: custom
@@ -0,0 +1,23 @@
1
+ title: "{{TITLE}}"
2
+ authors:
3
+ - "{{AUTHOR}}"
4
+ source:
5
+ files:
6
+ - chapter-01.md
7
+ styles:
8
+ - styles/book.css
9
+ extensions:
10
+ - technical-doc
11
+ # Page size and margins in points (72 = 1 in): US Letter (8.5 x 11 in) with
12
+ # 0.9 in top and bottom and 1 in side margins; text stays 0.25 in (18) inside
13
+ # the trim. Change them under Setup > Details.
14
+ page:
15
+ width: 612
16
+ height: 792
17
+ safe: 18
18
+ margin:
19
+ top: 64.8
20
+ bottom: 64.8
21
+ inside: 72
22
+ outside: 72
23
+ preset: custom
@@ -207,17 +207,17 @@
207
207
  },
208
208
  "page": {
209
209
  "type": "object",
210
- "description": "Page geometry, in POINTS (1in = 72pt): the trim size the book prints at. The build writes it as the default `@page { size }` unless a stylesheet's own plain `@page` rule sets a size, in which case that rule wins, and the `pdf.print.page-size` check compares the produced PDF against these numbers either way. A bleed-inclusive @page size legitimately differs from the trim recorded here.",
210
+ "description": "Page geometry, in POINTS (1in = 72pt): the trim size the book prints at and, with `margin`, the margins. The build writes them as the `@page` rule after every stylesheet, so a `size` in a stylesheet's own `@page` is overridden (the print-safety check reports it); the `pdf.print.page-size` check compares the produced PDF against these numbers. A bleed-inclusive page size legitimately differs from the trim recorded here.",
211
211
  "properties": {
212
212
  "width": {
213
213
  "type": "number",
214
- "description": "Page width in points (1in = 72pt): the trim the book prints at unless a stylesheet's plain @page rule sets its own, and what validation compares against. Preset defaults: dtrpg 621, book 432 (6in).",
214
+ "description": "Page width in points (1in = 72pt): the trim the book prints at and what validation compares against. Preset defaults: dtrpg 621, book 432 (6in).",
215
215
  "exclusiveMinimum": 0,
216
216
  "examples": [612, 621, 432]
217
217
  },
218
218
  "height": {
219
219
  "type": "number",
220
- "description": "Page height in points (1in = 72pt): the trim the book prints at unless a stylesheet's plain @page rule sets its own, and what validation compares against. Preset defaults: dtrpg 810, book 648 (9in).",
220
+ "description": "Page height in points (1in = 72pt): the trim the book prints at and what validation compares against. Preset defaults: dtrpg 810, book 648 (9in).",
221
221
  "exclusiveMinimum": 0,
222
222
  "examples": [792, 810, 648]
223
223
  },
@@ -250,6 +250,18 @@
250
250
  "description": "The binding allowance, in points: how far content stays from the spine, measured from the trim at the binding edge (the left edge of a right-hand page, the right edge of a left-hand one). It stands in for `safe` there, for text and for art alike: the preview's overlay draws and judges that edge against it, the `pdf.print.safe-margins` check fails text or an image closer to the spine than it, and an image pinned to the binding edge (`.gp-pin`) sits on it. The other edges keep `safe` and `artSafe`. Use it when your book keeps its text further from the spine than the printer's minimum, for example 54 (0.75in) where DriveThruRPG asks for 0.5in. Absent = the same as `safe`. Preset default: dtrpg 36 (0.5in, DriveThruRPG's rule for text and non-bleeding art at the binding — the same as its `safe`, declared so a book inherits it visibly); book and custom none.",
251
251
  "minimum": 0,
252
252
  "examples": [54]
253
+ },
254
+ "margin": {
255
+ "type": "object",
256
+ "description": "Page margins, in points (1in = 72pt): the space between the trim and the text on each edge. `inside` is the binding edge (the left of a right-hand page, the right of a left-hand one), `outside` the other. When set, the book prints with these margins and any `@page` margin in a stylesheet is overridden; when left out, the stylesheets' `@page` margins apply (a look's design), else 1in. Setup > Details in the app edits it. All four edges are required together.",
257
+ "properties": {
258
+ "top": { "type": "number", "description": "Top margin in points.", "minimum": 0, "examples": [61.2, 72] },
259
+ "bottom": { "type": "number", "description": "Bottom margin in points.", "minimum": 0, "examples": [61.2, 72] },
260
+ "inside": { "type": "number", "description": "Binding-edge margin in points: the left of a right-hand page, the right of a left-hand one.", "minimum": 0, "examples": [54, 72] },
261
+ "outside": { "type": "number", "description": "Outer-edge margin in points.", "minimum": 0, "examples": [54, 72] }
262
+ },
263
+ "required": ["top", "bottom", "inside", "outside"],
264
+ "additionalProperties": false
253
265
  }
254
266
  }
255
267
  },
@@ -3,12 +3,12 @@ import {
3
3
  EXTENSION_KINDS,
4
4
  scaffoldExtension,
5
5
  scaffoldProject
6
- } from "./cli-xq6djqbj.js";
6
+ } from "./cli-1h17ktsg.js";
7
7
  import {
8
8
  resolveGhostscript
9
- } from "./cli-evg8sbht.js";
9
+ } from "./cli-n2tsxqpn.js";
10
10
  import"./cli-ht1bcqdd.js";
11
- import"./cli-pz4bn98t.js";
11
+ import"./cli-xsrffqzt.js";
12
12
  import"./cli-2ppw6q9v.js";
13
13
  import"./cli-eyyggect.js";
14
14
  import {
@@ -22,15 +22,15 @@ import {
22
22
  TARGET_IDS,
23
23
  loadManifest,
24
24
  resolveConfig
25
- } from "./cli-sdmv4bba.js";
26
- import"./cli-pwwfxdgb.js";
27
- import"./cli-aqh6brew.js";
25
+ } from "./cli-h4x81x97.js";
26
+ import"./cli-t8be9yeb.js";
27
+ import"./cli-s6djx4ma.js";
28
28
  import {
29
29
  UsageError,
30
30
  rejectExtraPositionals,
31
31
  rejectUnknownFlags
32
32
  } from "./cli-d8xjns66.js";
33
- import"./cli-kh8v4vvw.js";
33
+ import"./cli-5jzyw06s.js";
34
34
  import {
35
35
  EXIT_CODES
36
36
  } from "./cli-46ycxe6r.js";
@@ -20,7 +20,6 @@
20
20
  "styles/page-rules.css",
21
21
  "styles/book.css"
22
22
  ],
23
- "tokensFile": "styles/tokens.css",
24
23
  "components": "components.yaml",
25
24
  "snippets": "snippets",
26
25
  "preview": null
@@ -2,9 +2,16 @@
2
2
  * {{NAME}} — 5. PAGE RULES
3
3
  *
4
4
  * OWNS
5
- * The CSS `@page` at-rules: the sheet's own geometry (margins), the margin
6
- * boxes that carry running heads and folios, and the named-page wiring that
7
- * connects a markdown `@page .name` to a CSS `@page name`.
5
+ * The CSS `@page` at-rules: the margin boxes that carry running heads and
6
+ * folios, and the named-page wiring that connects a markdown `@page .name`
7
+ * to a CSS `@page name`.
8
+ *
9
+ * NOT the page geometry. The trim size and the margins are the BOOK's,
10
+ * recorded in its manifest (`preset:`, `page:` — Setup > Details in the
11
+ * app) and written by the build after every stylesheet, so a `size` here
12
+ * would be ignored (and reported) and a `margin` here would hold only
13
+ * until the author sets margins in the app. Design the furniture below to
14
+ * sit in whatever margin the book gives it.
8
15
  *
9
16
  * This is standard CSS Paged Media and GCPM. Everything here is what the
10
17
  * spec says, not something Gutterpress invented — `string-set`,
@@ -26,39 +33,9 @@
26
33
  * ======================================================================== */
27
34
 
28
35
  @layer pages {
29
- /* -------------------------------------------------------------------------
30
- * TRIM SIZE. This is the physical size of the printed page, and it has to
31
- * be declared in CSS: with no `size:` anywhere, Chromium falls back to US
32
- * Letter and prints a book at the wrong trim with no error at all. That is
33
- * the single most expensive silent failure in this whole stack, which is
34
- * why it is the first thing in the file.
35
- *
36
- * KEEP IT IN STEP WITH THE MANIFEST. The book's `preset:`/`page:` is what
37
- * validation, the publish targets and the PDF/X boxes measure against, and
38
- * `gutterpress validate` fails the build when the produced PDF does not
39
- * match it. So the manifest is the source of truth and this line is the
40
- * instruction that satisfies it — change one and change the other.
41
- *
42
- * 6in × 9in is the neutral trade-book size the `book` preset declares.
43
- * Common alternatives: 5.5in 8.5in (digest), 8.5in 11in (US Letter),
44
- * 210mm 297mm (A4).
45
- *
46
- * Deliberately a literal, not `var(--…)`. A custom property does resolve
47
- * here, but an indirection whose failure mode is "silently prints Letter"
48
- * is not worth the tidiness.
49
- * ---------------------------------------------------------------------- */
50
- @page {
51
- size: 6in 9in;
52
- }
53
-
54
- /* Mirrored margins for a bound book: the inner edge needs the extra room,
55
- and which edge is "inner" flips every page. */
36
+ /* The furniture of a bound book: the folio on the outer corner and the
37
+ running head, which swap sides with the page. */
56
38
  @page :left {
57
- margin: var(--{{PREFIX}}page-margin-top)
58
- var(--{{PREFIX}}page-margin-inner)
59
- var(--{{PREFIX}}page-margin-bottom)
60
- var(--{{PREFIX}}page-margin-outer);
61
-
62
39
  @bottom-left {
63
40
  content: counter(page);
64
41
  font-family: var(--{{PREFIX}}font-body);
@@ -82,11 +59,6 @@
82
59
  }
83
60
 
84
61
  @page :right {
85
- margin: var(--{{PREFIX}}page-margin-top)
86
- var(--{{PREFIX}}page-margin-outer)
87
- var(--{{PREFIX}}page-margin-bottom)
88
- var(--{{PREFIX}}page-margin-inner);
89
-
90
62
  @bottom-right {
91
63
  content: counter(page);
92
64
  font-family: var(--{{PREFIX}}font-body);
@@ -126,7 +126,6 @@ describe("loader contract", () => {
126
126
  ...(gp.styles ?? []),
127
127
  gp.components,
128
128
  gp.snippets,
129
- gp.tokensFile,
130
129
  ].filter(Boolean);
131
130
 
132
131
  expect(declared.length).toBeGreaterThan(0);
@@ -8,9 +8,9 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./cli-pwwfxdgb.js";
12
- import"./cli-aqh6brew.js";
13
- import"./cli-kh8v4vvw.js";
11
+ } from "./cli-t8be9yeb.js";
12
+ import"./cli-s6djx4ma.js";
13
+ import"./cli-5jzyw06s.js";
14
14
  import"./cli-y28j019d.js";
15
15
  import"./cli-37x76zdn.js";
16
16
  export {
@@ -8,7 +8,7 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./index-2bjcghze.js";
11
+ } from "./index-0wee4408.js";
12
12
  import"./index-5x42amh8.js";
13
13
  import"./index-37x76zdn.js";
14
14
  export {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  executeValidation,
3
3
  reportMissingTools
4
- } from "./cli-xq6djqbj.js";
5
- import"./cli-evg8sbht.js";
4
+ } from "./cli-1h17ktsg.js";
5
+ import"./cli-n2tsxqpn.js";
6
6
  import"./cli-ht1bcqdd.js";
7
- import"./cli-pz4bn98t.js";
7
+ import"./cli-xsrffqzt.js";
8
8
  import"./cli-2ppw6q9v.js";
9
9
  import"./cli-eyyggect.js";
10
10
  import"./cli-asap4t1z.js";
@@ -12,17 +12,17 @@ import"./cli-a35w7wzg.js";
12
12
  import"./cli-v5mp7a6q.js";
13
13
  import {
14
14
  publishTargetFor
15
- } from "./cli-sdmv4bba.js";
16
- import"./cli-pwwfxdgb.js";
15
+ } from "./cli-h4x81x97.js";
16
+ import"./cli-t8be9yeb.js";
17
17
  import {
18
18
  log
19
- } from "./cli-aqh6brew.js";
19
+ } from "./cli-s6djx4ma.js";
20
20
  import {
21
21
  UsageError,
22
22
  rejectExtraPositionals,
23
23
  rejectUnknownFlags
24
24
  } from "./cli-d8xjns66.js";
25
- import"./cli-kh8v4vvw.js";
25
+ import"./cli-5jzyw06s.js";
26
26
  import {
27
27
  BuildError,
28
28
  EXIT_CODES
@@ -3,23 +3,23 @@ import {
3
3
  runBuild,
4
4
  splitOutPath,
5
5
  startPreviewServer
6
- } from "./cli-xq6djqbj.js";
7
- import"./cli-evg8sbht.js";
6
+ } from "./cli-1h17ktsg.js";
7
+ import"./cli-n2tsxqpn.js";
8
8
  import {
9
9
  previewArgs
10
10
  } from "./cli-75qkn0k5.js";
11
11
  import"./cli-ht1bcqdd.js";
12
- import"./cli-pz4bn98t.js";
12
+ import"./cli-xsrffqzt.js";
13
13
  import"./cli-2ppw6q9v.js";
14
14
  import"./cli-eyyggect.js";
15
15
  import"./cli-asap4t1z.js";
16
16
  import"./cli-a35w7wzg.js";
17
17
  import"./cli-v5mp7a6q.js";
18
- import"./cli-sdmv4bba.js";
19
- import"./cli-pwwfxdgb.js";
18
+ import"./cli-h4x81x97.js";
19
+ import"./cli-t8be9yeb.js";
20
20
  import {
21
21
  log
22
- } from "./cli-aqh6brew.js";
22
+ } from "./cli-s6djx4ma.js";
23
23
  import {
24
24
  UsageError,
25
25
  parseFormat,
@@ -28,7 +28,7 @@ import {
28
28
  rejectUnknownFlags,
29
29
  resolvePort
30
30
  } from "./cli-d8xjns66.js";
31
- import"./cli-kh8v4vvw.js";
31
+ import"./cli-5jzyw06s.js";
32
32
  import {
33
33
  BuildError
34
34
  } from "./cli-46ycxe6r.js";