gutterpress 0.11.23-alpha.2 → 0.11.23-alpha.4

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 (82) hide show
  1. package/README.md +10 -13
  2. package/dist/{README-8ef336pp.md → README-syg2hn83.md} +13 -19
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +5 -5
  5. package/dist/{build-encgnv0c.js → build-c48eer6f.js} +8 -8
  6. package/dist/checks/asset/extensions.d.ts +1 -1
  7. package/dist/checks/asset/index.d.ts +0 -1
  8. package/dist/checks/policy.d.ts +5 -4
  9. package/dist/checks/source/config-file.d.ts +9 -6
  10. package/dist/checks/source/dangling-links.d.ts +1 -1
  11. package/dist/checks/types.d.ts +2 -2
  12. package/dist/{cli-pz4bn98t.js → cli-54xt7pjg.js} +19 -13
  13. package/dist/{index-2ppw6q9v.js → cli-5crpmz42.js} +80 -1
  14. package/dist/{cli-xq6djqbj.js → cli-93mkaw6v.js} +166 -308
  15. package/dist/{cli-evg8sbht.js → cli-d9mm3vvh.js} +7 -2
  16. package/dist/{cli-kh8v4vvw.js → cli-gnpea9dx.js} +1 -1
  17. package/dist/{cli-aqh6brew.js → cli-pspjxnc8.js} +1 -1
  18. package/dist/{cli-pwwfxdgb.js → cli-rqh295j9.js} +15 -21
  19. package/dist/{index-x3ema2w3.js → cli-y6d1arem.js} +321 -272
  20. package/dist/cli.js +12 -12
  21. package/dist/{doctor-5gtpgpd1.js → doctor-ptb346k8.js} +4 -3
  22. package/dist/engine/shared/gcpm-extract.d.ts +8 -0
  23. package/dist/engine/shared/print-area.d.ts +16 -4
  24. package/dist/{engine-mc5br8g8.js → engine-4rzdx6af.js} +1 -1
  25. package/dist/{engine-tn13gk0b.js → engine-6txhxmmv.js} +2 -2
  26. package/dist/{ext-wn2a5afq.js → ext-gy8szg3x.js} +8 -8
  27. package/dist/{extension-restore-s3x1618g.js → extension-restore-dymzcasz.js} +2 -2
  28. package/dist/{extension-restore-g8jnmmzq.js → extension-restore-m8tj43e7.js} +4 -4
  29. package/dist/{gutterpress-viewer-701vf4qp.js → gutterpress-viewer-mafsby5k.js} +9 -2
  30. package/dist/{cli-2ppw6q9v.js → index-5crpmz42.js} +80 -1
  31. package/dist/{index-0skhjx3e.js → index-5kh4c78h.js} +19 -13
  32. package/dist/{index-2bjcghze.js → index-8ywkd2sv.js} +15 -21
  33. package/dist/{index-j5z86r4a.js → index-ea94zbq4.js} +329 -488
  34. package/dist/{cli-sdmv4bba.js → index-kaks3mm2.js} +448 -156
  35. package/dist/{index-4x6y8krm.js → index-yc3xj266.js} +1 -1
  36. package/dist/index.js +6 -6
  37. package/dist/lib/build-preflight.d.ts +4 -10
  38. package/dist/lib/build-runner.d.ts +1 -1
  39. package/dist/lib/extension-manager.d.ts +8 -7
  40. package/dist/lib/extension-manifest.d.ts +1 -13
  41. package/dist/lib/ghostscript.d.ts +5 -2
  42. package/dist/lib/lint-rules.d.ts +5 -1
  43. package/dist/lib/manifest-config.d.ts +3 -2
  44. package/dist/lib/manifest-old-spellings.d.ts +64 -0
  45. package/dist/lib/manifest.d.ts +15 -1
  46. package/dist/lib/markdown/assemble.d.ts +4 -3
  47. package/dist/lib/markdown/chapter-id.d.ts +1 -1
  48. package/dist/lib/markdown/index.d.ts +1 -1
  49. package/dist/lib/markdown/table.d.ts +5 -3
  50. package/dist/lib/page-size.d.ts +45 -33
  51. package/dist/lib/pdf-inspect.d.ts +27 -0
  52. package/dist/lib/presets.d.ts +10 -8
  53. package/dist/lib/printsafe.d.ts +2 -1
  54. package/dist/lib/project-scaffold.d.ts +1 -1
  55. package/dist/lib/project-templates.d.ts +9 -0
  56. package/dist/lib/remote-auth/sync-messages.d.ts +2 -2
  57. package/dist/lib/targets.d.ts +4 -4
  58. package/dist/lib/validation-exec.d.ts +4 -4
  59. package/dist/manifest-2kc56wvw.yaml +22 -0
  60. package/dist/manifest-41ddzm13.yaml +18 -0
  61. package/dist/manifest-79zsgkpn.yaml +26 -0
  62. package/dist/manifest-sqzm4681.yaml +22 -0
  63. package/dist/{manifest.schema-t7nx2stg.json → manifest.schema-w86c1xw1.json} +30 -308
  64. package/dist/{new-mhs46pw3.js → new-t2t28ccb.js} +9 -15
  65. package/dist/{package-aenwjew3.json → package-awwkt7sa.json} +0 -1
  66. package/dist/{page-rules-nsj3aw42.css → page-rules-ytj9p04v.css} +12 -40
  67. package/dist/{pdf-inspect-wnw4za71.js → pdf-inspect-b3mf81ex.js} +3 -1
  68. package/dist/{plugin.test.js-xgxrd8hf.tpl → plugin.test.js-gxzqqfdh.tpl} +0 -1
  69. package/dist/{plugins-2cek4mz1.js → plugins-fec8t2bh.js} +1 -1
  70. package/dist/{plugins-1sxe0dkz.js → plugins-sqa4msdk.js} +3 -3
  71. package/dist/{preflight-3k2b1x8e.js → preflight-srq8fktp.js} +8 -8
  72. package/dist/preview/file-watcher.d.ts +1 -3
  73. package/dist/{preview-6p9v6rht.js → preview-zsp9hstd.js} +8 -8
  74. package/dist/{publish-qgsngy8s.js → publish-hhy31gr9.js} +8 -8
  75. package/dist/render.js +30 -562
  76. package/dist/schema/manifest.types.d.ts +60 -105
  77. package/dist/{tokens-7984461t.css → tokens-n82t4k9h.css} +12 -12
  78. package/dist/{validate-0v7xw6wa.js → validate-r9jdz85h.js} +8 -8
  79. package/package.json +1 -1
  80. package/dist/checks/asset/approved-fonts.d.ts +0 -3
  81. package/dist/manifest-eagfhba6.yaml +0 -19
  82. package/dist/manifest-n1gh3qw5.yaml +0 -9
@@ -8,17 +8,16 @@ import {
8
8
  stampCreator,
9
9
  stripAnnotations,
10
10
  transparentPages
11
- } from "./cli-evg8sbht.js";
11
+ } from "./cli-d9mm3vvh.js";
12
12
  import {
13
13
  blackTextToGray
14
14
  } from "./cli-ht1bcqdd.js";
15
15
  import {
16
+ DEFAULT_PAGE_MARGIN_PT,
16
17
  MARGIN_BOX_IGNORED_PROPERTIES,
17
- extract,
18
18
  getAssetPath,
19
- lastPageMode,
20
- resolvePage
21
- } from "./cli-pz4bn98t.js";
19
+ lastPageMode
20
+ } from "./cli-54xt7pjg.js";
22
21
  import {
23
22
  countLinkAnnotations,
24
23
  getImageResolutions,
@@ -31,7 +30,7 @@ import {
31
30
  loadPdf,
32
31
  pageHasMarks,
33
32
  retainPdfCache
34
- } from "./cli-2ppw6q9v.js";
33
+ } from "./cli-5crpmz42.js";
35
34
  import {
36
35
  INSTALL_HINTS,
37
36
  execCapture,
@@ -57,11 +56,17 @@ import {
57
56
  TARGET_IDS,
58
57
  addExtension,
59
58
  applyDefaultPdfStrictChecks,
59
+ cssLength,
60
60
  ensureProjectGitignore,
61
+ formatPageSize,
62
+ isPositivePoints,
63
+ judgeRect,
61
64
  loadManifest,
62
65
  loadManifestDoc,
63
66
  loadManifestWithPath,
64
67
  npmRegistryUrl,
68
+ pageBoxes,
69
+ printAreaMeta,
65
70
  publishTargetFor,
66
71
  resolveConfig,
67
72
  resolveConfigForTarget,
@@ -70,7 +75,7 @@ import {
70
75
  uniqueExtensionId,
71
76
  withFetchTimeout,
72
77
  writeManifestDoc
73
- } from "./cli-sdmv4bba.js";
78
+ } from "./cli-y6d1arem.js";
74
79
  import {
75
80
  EXTENSION_MANIFEST_FILENAME,
76
81
  GUTTERPRESS_CSS,
@@ -91,7 +96,7 @@ import {
91
96
  readExtensionMeta,
92
97
  resolveDeclaredStyles,
93
98
  slugify
94
- } from "./cli-pwwfxdgb.js";
99
+ } from "./cli-rqh295j9.js";
95
100
  import {
96
101
  PACKAGE_META,
97
102
  PACKAGE_VERSION,
@@ -101,7 +106,7 @@ import {
101
106
  log,
102
107
  setLogLevel,
103
108
  warn
104
- } from "./cli-aqh6brew.js";
109
+ } from "./cli-pspjxnc8.js";
105
110
  import {
106
111
  DEBOUNCE,
107
112
  UsageError,
@@ -184,10 +189,7 @@ function selectorToRegex(selector) {
184
189
  // src/checks/policy.ts
185
190
  var INSPECT_FAILED_CODE = "inspect-failed";
186
191
  function isCheckEnabled(check, config) {
187
- const entry = config.validate.checks[check.id];
188
- if (entry === false)
189
- return false;
190
- if (typeof entry === "object" && entry.enabled === false)
192
+ if (config.checks[check.id] === "off")
191
193
  return false;
192
194
  if (check.enabledWhen && !check.enabledWhen(config))
193
195
  return false;
@@ -281,84 +283,6 @@ var check2 = {
281
283
  };
282
284
  registerCheck(check2);
283
285
 
284
- // src/engine/shared/print-area.ts
285
- function isPositivePoints(value) {
286
- return typeof value === "number" && Number.isFinite(value) && value > 0;
287
- }
288
- var round3 = (n) => Math.round(n * 1000) / 1000;
289
- function cssLength(pt) {
290
- const inches = round3(pt / 72);
291
- return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
292
- }
293
- function formatPageSize(page) {
294
- return `${cssLength(page.width)} ${cssLength(page.height)}`;
295
- }
296
- function printAreaMeta(page) {
297
- const area = JSON.stringify({
298
- width: page.width,
299
- height: page.height,
300
- bleed: page.bleed ?? 0,
301
- safe: page.safe ?? 0,
302
- artSafe: page.artSafe,
303
- gutter: page.gutter,
304
- preset: page.preset,
305
- spec: page.spec
306
- });
307
- return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
308
- }
309
- function pageBoxes(area, side, scale = 1) {
310
- const w = area.width * scale;
311
- const h = area.height * scale;
312
- const b = Math.max(0, area.bleed) * scale;
313
- const s = Math.max(0, area.safe) * scale;
314
- const a = Math.max(0, area.artSafe) * scale;
315
- const g = Math.max(0, area.gutter) * scale;
316
- const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
317
- const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
318
- const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
319
- return {
320
- sheet: { x: 0, y: 0, w, h },
321
- trim,
322
- safe: {
323
- x: trim.x + textLeft,
324
- y: trim.y + s,
325
- w: Math.max(0, trim.w - textLeft - textRight),
326
- h: Math.max(0, trim.h - 2 * s)
327
- },
328
- safeArt: {
329
- x: trim.x + artLeft,
330
- y: trim.y + a,
331
- w: Math.max(0, trim.w - artLeft - artRight),
332
- h: Math.max(0, trim.h - 2 * a)
333
- },
334
- bleed: b
335
- };
336
- }
337
- var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
338
- function judgeRect(r, boxes, rule, tol = 0) {
339
- const { sheet, trim } = boxes;
340
- const safe = rule === "art" ? boxes.safeArt : boxes.safe;
341
- const sides = [
342
- ["left", -r.x, -safe.x, -trim.x, -sheet.x],
343
- ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
344
- ["top", -r.y, -safe.y, -trim.y, -sheet.y],
345
- ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
346
- ];
347
- let worst = { kind: "inside", gap: 0, need: 0, edge: null };
348
- let beyond = 0;
349
- for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
350
- const past = ink - limit;
351
- if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
352
- continue;
353
- const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
354
- if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
355
- worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
356
- beyond = past;
357
- }
358
- }
359
- return worst;
360
- }
361
-
362
286
  // src/checks/pdf/safe-margins.ts
363
287
  var TOLERANCE_PT = 0.5;
364
288
  var ABOVE_BASELINE = 0.8;
@@ -846,7 +770,7 @@ var check8 = {
846
770
  severity: "warning",
847
771
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
848
772
  file: ctx.pdfPath,
849
- detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `lint.rules: { pdf.print.black-text: off }` in manifest.yaml.",
773
+ detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `checks: { pdf.print.black-text: off }` in manifest.yaml.",
850
774
  code: "rich-black-text",
851
775
  data: { runs: found.runs, pages: found.pages }
852
776
  })
@@ -926,8 +850,6 @@ var check10 = {
926
850
  async run(ctx) {
927
851
  if (!ctx.pdfPath)
928
852
  return [];
929
- if (!ctx.config.validate.pdf.requireBookmarks)
930
- return [];
931
853
  const doc = await loadPdf(ctx.pdfPath);
932
854
  if (!doc) {
933
855
  return [
@@ -960,8 +882,6 @@ var check11 = {
960
882
  async run(ctx) {
961
883
  if (!ctx.pdfPath)
962
884
  return [];
963
- if (!ctx.config.validate.pdf.requireTocLinks)
964
- return [];
965
885
  const doc = await loadPdf(ctx.pdfPath);
966
886
  if (!doc) {
967
887
  return [
@@ -1099,8 +1019,6 @@ var check15 = {
1099
1019
  async run(ctx) {
1100
1020
  if (!ctx.pdfPath)
1101
1021
  return [];
1102
- if (!ctx.config.validate.pdf.forbidTransparency)
1103
- return [];
1104
1022
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
1105
1023
  const found = [];
1106
1024
  if (bytes.includes("/Transparency"))
@@ -1141,8 +1059,6 @@ var check16 = {
1141
1059
  async run(ctx) {
1142
1060
  if (!ctx.pdfPath)
1143
1061
  return [];
1144
- if (!ctx.config.validate.pdf.requireBleed)
1145
- return [];
1146
1062
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
1147
1063
  if (!bytes) {
1148
1064
  return [
@@ -1233,18 +1149,20 @@ import { parse as parseYaml } from "yaml";
1233
1149
 
1234
1150
  // src/checks/source/config-file.ts
1235
1151
  import { existsSync } from "node:fs";
1236
- import { resolve } from "node:path";
1237
- function findConfigFile(inputDir, names, explicit) {
1238
- if (explicit) {
1239
- const p = resolve(inputDir, explicit);
1240
- return existsSync(p) ? p : null;
1241
- }
1242
- for (const name of names) {
1243
- const p = resolve(inputDir, name);
1244
- if (existsSync(p))
1245
- return p;
1152
+ import { dirname, resolve } from "node:path";
1153
+ function findConfigFile(startDir, names) {
1154
+ let dir = resolve(startDir);
1155
+ for (;; ) {
1156
+ for (const name of names) {
1157
+ const p = resolve(dir, name);
1158
+ if (existsSync(p))
1159
+ return p;
1160
+ }
1161
+ const parent = dirname(dir);
1162
+ if (existsSync(resolve(dir, ".git")) || parent === dir)
1163
+ return null;
1164
+ dir = parent;
1246
1165
  }
1247
- return null;
1248
1166
  }
1249
1167
 
1250
1168
  // src/checks/source/markdownlint.ts
@@ -1286,17 +1204,13 @@ var check18 = {
1286
1204
  category: "source",
1287
1205
  phase: "pre-build",
1288
1206
  async run(ctx) {
1289
- const sourceConfig = ctx.config.validate.source;
1290
- if (sourceConfig.markdownlint === false)
1291
- return [];
1292
1207
  const files = ctx.markdownFiles;
1293
1208
  if (!files || files.length === 0)
1294
1209
  return [];
1295
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
1296
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
1297
- if (!resolvedConfig && !configPath)
1210
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
1211
+ if (!resolvedConfig)
1298
1212
  return [];
1299
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
1213
+ const config = await loadConfig(resolvedConfig);
1300
1214
  const results = lint({ files, config });
1301
1215
  const out = [];
1302
1216
  for (const [file, violations] of Object.entries(results)) {
@@ -1332,14 +1246,10 @@ var check19 = {
1332
1246
  category: "source",
1333
1247
  phase: "pre-build",
1334
1248
  async run(ctx) {
1335
- const sourceConfig = ctx.config.validate.source;
1336
- if (sourceConfig.htmlhint === false)
1337
- return [];
1338
1249
  if (!ctx.htmlPath || !existsSync2(ctx.htmlPath))
1339
1250
  return [];
1340
- const configPath = typeof sourceConfig.htmlhint === "string" ? sourceConfig.htmlhint : null;
1341
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
1342
- if (!resolvedConfig && !configPath)
1251
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2);
1252
+ if (!resolvedConfig)
1343
1253
  return [];
1344
1254
  const ruleset = resolvedConfig ? JSON.parse(await readFile4(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
1345
1255
  const html = await readFile4(ctx.htmlPath, "utf8");
@@ -1367,7 +1277,7 @@ function ruleLevel(rules, id) {
1367
1277
  const v = rules?.[id];
1368
1278
  if (v === "off" || v === false)
1369
1279
  return "off";
1370
- if (v === "warn" || v === "warning")
1280
+ if (v === "warn" || v === "warning" || v === "info")
1371
1281
  return "warn";
1372
1282
  if (v === "error")
1373
1283
  return "error";
@@ -1388,6 +1298,7 @@ var ruleRiskyProps = "printsafe/no-risky-print-effects";
1388
1298
  var ruleSyntax = "printsafe/syntax-error";
1389
1299
  var rulePageContainment = "printsafe/page-containment";
1390
1300
  var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
1301
+ var rulePageSizeInStylesheet = "printsafe/page-size-in-stylesheet";
1391
1302
  function extractUrls(value) {
1392
1303
  const urls = [];
1393
1304
  const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
@@ -1436,6 +1347,12 @@ function isInPageBox(decl) {
1436
1347
  const page = decl.parent;
1437
1348
  return !!page && page.type === "atrule" && page.name.toLowerCase() === "page";
1438
1349
  }
1350
+ function isInBookPageRule(decl) {
1351
+ if (!isInPageBox(decl))
1352
+ return false;
1353
+ const params = decl.parent.params.trim();
1354
+ return params === "" || /^(:[a-z-]+\s*)+$/i.test(params);
1355
+ }
1439
1356
  function isDroppedPageGradient(decl) {
1440
1357
  const prop = decl.prop.toLowerCase();
1441
1358
  if (prop !== "background" && prop !== "background-image")
@@ -1675,6 +1592,15 @@ function checkCssRaw(css, from) {
1675
1592
  root.walkAtRules((at) => reportRemoteUrls(at.params || "", at));
1676
1593
  root.walkDecls((decl) => {
1677
1594
  const prop = decl.prop.toLowerCase();
1595
+ if (prop === "size" && isInBookPageRule(decl)) {
1596
+ warnings.push({
1597
+ rule: rulePageSizeInStylesheet,
1598
+ severity: "warning",
1599
+ message: `"size" in a stylesheet's @page rule is ignored: the page size comes from the manifest (preset:, or page: width and height — Setup > Book in the app), which is written after every stylesheet. Remove this declaration; to print at another size, change the manifest.`,
1600
+ ...nodeLoc(decl)
1601
+ });
1602
+ return;
1603
+ }
1678
1604
  if (isInertValue(prop, decl.value))
1679
1605
  return;
1680
1606
  if (marginBoxIgnoredProperties.has(prop) && isInPageMarginBox(decl)) {
@@ -1770,10 +1696,7 @@ var check20 = {
1770
1696
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
1771
1697
  category: "source",
1772
1698
  phase: "pre-build",
1773
- enabledWhen: (config) => config.validate.source.stylelint !== false,
1774
1699
  async run(ctx) {
1775
- if (ctx.config.validate.source.stylelint === false)
1776
- return [];
1777
1700
  const files = ctx.cssFiles;
1778
1701
  if (!files || files.length === 0)
1779
1702
  return [];
@@ -1791,7 +1714,7 @@ var check20 = {
1791
1714
  });
1792
1715
  continue;
1793
1716
  }
1794
- for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
1717
+ for (const w of checkCss(css, file, { rules: ctx.config.checks })) {
1795
1718
  results.push({
1796
1719
  checkId: check20.id,
1797
1720
  severity: w.severity,
@@ -1987,15 +1910,11 @@ var check21 = {
1987
1910
  description: "Checks an optional per-project CSS ownership contract (.gutterpress/css-contract.yaml): which file may declare which properties/at-rules, and which selectors must never appear unscoped.",
1988
1911
  category: "source",
1989
1912
  phase: "pre-build",
1990
- enabledWhen: (config) => config.validate.source.cssOwnership !== false,
1991
1913
  async run(ctx) {
1992
- if (ctx.config.validate.source.cssOwnership === false)
1993
- return [];
1994
1914
  const files = ctx.cssFiles;
1995
1915
  if (!files || files.length === 0)
1996
1916
  return [];
1997
- const explicit = typeof ctx.config.validate.source.cssOwnership === "string" ? ctx.config.validate.source.cssOwnership : null;
1998
- const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3, explicit);
1917
+ const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3);
1999
1918
  if (!configPath)
2000
1919
  return [];
2001
1920
  let contract;
@@ -2062,7 +1981,7 @@ registerCheck(check21);
2062
1981
 
2063
1982
  // src/checks/source/local-refs.ts
2064
1983
  import { existsSync as existsSync4 } from "node:fs";
2065
- import { dirname, resolve as resolve2 } from "node:path";
1984
+ import { dirname as dirname2, resolve as resolve2 } from "node:path";
2066
1985
 
2067
1986
  // src/lib/asset-inline.ts
2068
1987
  import { createHash } from "node:crypto";
@@ -2488,7 +2407,7 @@ function filesystemRef(ref) {
2488
2407
  }
2489
2408
  function localRefExists(ref, kind, sourceFile, projectRoot) {
2490
2409
  const cleaned = filesystemRef(ref);
2491
- const fileRelative = resolve2(dirname(sourceFile), cleaned);
2410
+ const fileRelative = resolve2(dirname2(sourceFile), cleaned);
2492
2411
  const rootRelative = resolve2(projectRoot, cleaned);
2493
2412
  switch (kind) {
2494
2413
  case "image":
@@ -3590,8 +3509,6 @@ var check30 = {
3590
3509
  category: "asset",
3591
3510
  phase: "pre-build",
3592
3511
  async run(ctx) {
3593
- if (ctx.config.validate.assets.allowAlpha)
3594
- return [];
3595
3512
  if (ctx.config.pdfx.transparency === "preserve-text")
3596
3513
  return [];
3597
3514
  const files = shippedImages(ctx, ["png", "tiff", "tif"]);
@@ -3614,58 +3531,6 @@ var check30 = {
3614
3531
  };
3615
3532
  registerCheck(check30);
3616
3533
 
3617
- // src/checks/asset/approved-fonts.ts
3618
- var check31 = {
3619
- id: "asset.font.approved-files",
3620
- name: "Approved Font Files",
3621
- description: "Checks font files against the approved file patterns",
3622
- category: "asset",
3623
- phase: "pre-build",
3624
- async run(ctx) {
3625
- const patterns = ctx.config.validate.assets.approvedFontFiles;
3626
- if (!patterns || patterns.length === 0)
3627
- return [];
3628
- const { glob } = await import("glob");
3629
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
3630
- const allFonts = [];
3631
- for (const dir of dirs) {
3632
- const matches = await glob(`**/*.{${FONT_EXTS2.join(",")}}`, {
3633
- cwd: dir,
3634
- absolute: true,
3635
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
3636
- });
3637
- allFonts.push(...matches);
3638
- }
3639
- if (allFonts.length === 0)
3640
- return [];
3641
- const approvedFonts = new Set;
3642
- for (const dir of dirs) {
3643
- for (const pattern of patterns) {
3644
- const matches = await glob(pattern, {
3645
- cwd: dir,
3646
- absolute: true,
3647
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
3648
- });
3649
- for (const m of matches)
3650
- approvedFonts.add(m);
3651
- }
3652
- }
3653
- const results = [];
3654
- for (const font of allFonts) {
3655
- if (!approvedFonts.has(font)) {
3656
- results.push({
3657
- checkId: check31.id,
3658
- severity: "warning",
3659
- message: "Font file not in approved list",
3660
- file: font
3661
- });
3662
- }
3663
- }
3664
- return results;
3665
- }
3666
- };
3667
- registerCheck(check31);
3668
-
3669
3534
  // src/checks/asset/font-license.ts
3670
3535
  import { existsSync as existsSync5 } from "node:fs";
3671
3536
  import { resolve as resolve3 } from "node:path";
@@ -3679,15 +3544,13 @@ var LICENSE_NAMES = [
3679
3544
  "OFL-1.1.txt",
3680
3545
  "COPYING"
3681
3546
  ];
3682
- var check32 = {
3547
+ var check31 = {
3683
3548
  id: "asset.font.license",
3684
3549
  name: "Font License",
3685
3550
  description: "Checks for license files in font directories",
3686
3551
  category: "asset",
3687
3552
  phase: "pre-build",
3688
3553
  async run(ctx) {
3689
- if (!ctx.config.validate.assets.requireFontLicense)
3690
- return [];
3691
3554
  const { glob } = await import("glob");
3692
3555
  const dirs = ctx.assetDirs ?? [ctx.inputDir];
3693
3556
  const fontDirs = new Set;
@@ -3706,7 +3569,7 @@ var check32 = {
3706
3569
  const hasLicense = LICENSE_NAMES.some((name) => existsSync5(resolve3(fontDir, name)));
3707
3570
  if (!hasLicense) {
3708
3571
  results.push({
3709
- checkId: check32.id,
3572
+ checkId: check31.id,
3710
3573
  severity: "warning",
3711
3574
  message: `No font license file found in directory`,
3712
3575
  file: fontDir
@@ -3716,10 +3579,10 @@ var check32 = {
3716
3579
  return results;
3717
3580
  }
3718
3581
  };
3719
- registerCheck(check32);
3582
+ registerCheck(check31);
3720
3583
 
3721
3584
  // src/checks/heuristic/text-density.ts
3722
- var check33 = {
3585
+ var check32 = {
3723
3586
  id: "heuristic.whitespace.text-density",
3724
3587
  name: "Text Density",
3725
3588
  description: "Checks characters-per-page ratio",
@@ -3748,7 +3611,7 @@ var check33 = {
3748
3611
  });
3749
3612
  if (lowPages.length > 0) {
3750
3613
  results.push({
3751
- checkId: check33.id,
3614
+ checkId: check32.id,
3752
3615
  severity: "info",
3753
3616
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
3754
3617
  file: ctx.pdfPath
@@ -3756,7 +3619,7 @@ var check33 = {
3756
3619
  }
3757
3620
  if (highPages.length > 0) {
3758
3621
  results.push({
3759
- checkId: check33.id,
3622
+ checkId: check32.id,
3760
3623
  severity: "info",
3761
3624
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
3762
3625
  file: ctx.pdfPath
@@ -3765,11 +3628,11 @@ var check33 = {
3765
3628
  return results;
3766
3629
  }
3767
3630
  };
3768
- registerCheck(check33);
3631
+ registerCheck(check32);
3769
3632
 
3770
3633
  // src/checks/heuristic/section-density.ts
3771
3634
  import { readFile as readFile15 } from "node:fs/promises";
3772
- var check34 = {
3635
+ var check33 = {
3773
3636
  id: "heuristic.chunking.section-density",
3774
3637
  name: "Section Density",
3775
3638
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -3795,7 +3658,7 @@ var check34 = {
3795
3658
  const line = lines[i];
3796
3659
  if (/^#{1,6}\s/.test(line)) {
3797
3660
  if (paragraphCount > maxParas) {
3798
- results.push(finding(check34.id, {
3661
+ results.push(finding(check33.id, {
3799
3662
  severity: "info",
3800
3663
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
3801
3664
  file,
@@ -3815,7 +3678,7 @@ var check34 = {
3815
3678
  }
3816
3679
  }
3817
3680
  if (paragraphCount > maxParas) {
3818
- results.push(finding(check34.id, {
3681
+ results.push(finding(check33.id, {
3819
3682
  severity: "info",
3820
3683
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
3821
3684
  file,
@@ -3823,7 +3686,7 @@ var check34 = {
3823
3686
  }));
3824
3687
  }
3825
3688
  } catch {
3826
- results.push(inspectionFailed(check34.id, `Could not read source file: ${file}`, {
3689
+ results.push(inspectionFailed(check33.id, `Could not read source file: ${file}`, {
3827
3690
  file
3828
3691
  }));
3829
3692
  }
@@ -3831,10 +3694,10 @@ var check34 = {
3831
3694
  return results;
3832
3695
  }
3833
3696
  };
3834
- registerCheck(check34);
3697
+ registerCheck(check33);
3835
3698
 
3836
3699
  // src/checks/heuristic/layer-count.ts
3837
- var check35 = {
3700
+ var check34 = {
3838
3701
  id: "heuristic.decoration.layer-count",
3839
3702
  name: "Layer Count",
3840
3703
  description: "Counts image objects per page",
@@ -3859,7 +3722,7 @@ var check35 = {
3859
3722
  heavyPages.sort((a, b) => a - b);
3860
3723
  return [
3861
3724
  {
3862
- checkId: check35.id,
3725
+ checkId: check34.id,
3863
3726
  severity: "info",
3864
3727
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
3865
3728
  file: ctx.pdfPath
@@ -3869,10 +3732,10 @@ var check35 = {
3869
3732
  return [];
3870
3733
  }
3871
3734
  };
3872
- registerCheck(check35);
3735
+ registerCheck(check34);
3873
3736
 
3874
3737
  // src/checks/heuristic/placement-variance.ts
3875
- var check36 = {
3738
+ var check35 = {
3876
3739
  id: "heuristic.layout.placement-variance",
3877
3740
  name: "Placement Variance",
3878
3741
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -3890,7 +3753,7 @@ var check36 = {
3890
3753
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
3891
3754
  return [
3892
3755
  {
3893
- checkId: check36.id,
3756
+ checkId: check35.id,
3894
3757
  severity: "info",
3895
3758
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
3896
3759
  file: ctx.pdfPath
@@ -3898,21 +3761,21 @@ var check36 = {
3898
3761
  ];
3899
3762
  }
3900
3763
  };
3901
- registerCheck(check36);
3764
+ registerCheck(check35);
3902
3765
 
3903
3766
  // src/checks/tool-check.ts
3904
3767
  async function checkToolAvailability(config, opts = {}) {
3905
3768
  const { checks: checks2 } = selectChecks(opts, config);
3906
3769
  const toolToChecks = new Map;
3907
- for (const check37 of checks2) {
3908
- if (!check37.requiredTools?.length)
3770
+ for (const check36 of checks2) {
3771
+ if (!check36.requiredTools?.length)
3909
3772
  continue;
3910
- for (const tool of check37.requiredTools) {
3773
+ for (const tool of check36.requiredTools) {
3911
3774
  const existing = toolToChecks.get(tool);
3912
3775
  if (existing) {
3913
- existing.push(check37.id);
3776
+ existing.push(check36.id);
3914
3777
  } else {
3915
- toolToChecks.set(tool, [check37.id]);
3778
+ toolToChecks.set(tool, [check36.id]);
3916
3779
  }
3917
3780
  }
3918
3781
  }
@@ -3928,11 +3791,11 @@ async function checkToolAvailability(config, opts = {}) {
3928
3791
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
3929
3792
  const missingSet = new Set(missing);
3930
3793
  const skippedChecks = [];
3931
- for (const check37 of checks2) {
3932
- if (!check37.requiredTools?.length)
3794
+ for (const check36 of checks2) {
3795
+ if (!check36.requiredTools?.length)
3933
3796
  continue;
3934
- if (check37.requiredTools.some((t) => missingSet.has(t))) {
3935
- skippedChecks.push(check37.id);
3797
+ if (check36.requiredTools.some((t) => missingSet.has(t))) {
3798
+ skippedChecks.push(check36.id);
3936
3799
  }
3937
3800
  }
3938
3801
  return { available, missing, skippedChecks, toolToChecks };
@@ -3952,7 +3815,7 @@ function reportMissingTools(result) {
3952
3815
  // src/lib/validation-exec.ts
3953
3816
  import { existsSync as existsSync6 } from "node:fs";
3954
3817
  import { readFile as readFile18 } from "node:fs/promises";
3955
- import { dirname as dirname2, join as join2, resolve as resolve4 } from "node:path";
3818
+ import { dirname as dirname3, join as join2, resolve as resolve4 } from "node:path";
3956
3819
 
3957
3820
  // src/lib/output-paths.ts
3958
3821
  import path5 from "node:path";
@@ -3995,16 +3858,25 @@ function canonicalChapterId(p) {
3995
3858
  }
3996
3859
 
3997
3860
  // src/lib/markdown/assemble.ts
3998
- function defaultPageSizeCss(css, page) {
3861
+ function pageGeometryCss(page) {
3999
3862
  if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
4000
3863
  return null;
4001
- try {
4002
- if (resolvePage(extract(css)).decls.size !== undefined)
4003
- return null;
4004
- } catch {
3864
+ const size2 = `size: ${formatPageSize(page)};`;
3865
+ const m = page.margin;
3866
+ if (!m)
3867
+ return `@page { ${size2} }`;
3868
+ const [top, bottom, inside, outside] = [m.top, m.bottom, m.inside, m.outside].map(cssLength);
3869
+ const recto = `margin: ${top} ${outside} ${bottom} ${inside};`;
3870
+ const verso = `margin: ${top} ${inside} ${bottom} ${outside};`;
3871
+ return [`@page { ${size2} ${recto} }`, `@page :left { ${verso} }`, `@page :right { ${recto} }`].join(`
3872
+ `);
3873
+ }
3874
+ function defaultPageMarginCss(page) {
3875
+ if (!page || page.margin)
4005
3876
  return null;
4006
- }
4007
- return `@page { size: ${formatPageSize(page)}; }`;
3877
+ return `@layer gp.vocab {
3878
+ @page { margin: ${cssLength(DEFAULT_PAGE_MARGIN_PT)}; }
3879
+ }`;
4008
3880
  }
4009
3881
  async function assembleBookHtml(opts) {
4010
3882
  const title = opts.title ?? "Document";
@@ -4044,8 +3916,8 @@ async function assembleBookHtml(opts) {
4044
3916
  imageRefs.add(ref);
4045
3917
  opts.onImageRefs([...imageRefs]);
4046
3918
  }
4047
- const pageSize = defaultPageSizeCss(`${pluginCss}
4048
- ${projectCss}`, opts.printArea);
3919
+ const pageGeometry = pageGeometryCss(opts.printArea);
3920
+ const defaultMargin = defaultPageMarginCss(opts.printArea);
4049
3921
  const inlineCss = [
4050
3922
  "@layer gp.marker, gp.vocab;",
4051
3923
  `/* gutterpress markers */
@@ -4057,14 +3929,16 @@ ${TWO_VERSIONS_CSS.trim()}
4057
3929
  @layer gp.vocab {
4058
3930
  ${GUTTERPRESS_CSS.trim()}
4059
3931
  }`,
4060
- pageSize ? `/* page size (manifest) */
4061
- ${pageSize}` : null,
3932
+ defaultMargin ? `/* page margins (default) */
3933
+ ${defaultMargin}` : null,
4062
3934
  pluginCss ? `/* extension css */
4063
3935
  ${pluginCss.trim()}` : null,
4064
3936
  `/* column alignment */
4065
3937
  ${TABLE_ALIGN_CSS.trim()}`,
4066
3938
  projectCss ? `/* project css */
4067
- ${projectCss.trim()}` : null
3939
+ ${projectCss.trim()}` : null,
3940
+ pageGeometry ? `/* page geometry (manifest) */
3941
+ ${pageGeometry}` : null
4068
3942
  ].filter(Boolean).join(`
4069
3943
 
4070
3944
  `);
@@ -4186,8 +4060,7 @@ async function uncommittedAmong(inputDir, files) {
4186
4060
  return files.filter((file) => changed.has(repoSubPath(repoRoot, file)));
4187
4061
  }
4188
4062
  async function applyMarkdownlintFixes(ctx) {
4189
- const sourceConfig = ctx.config.validate.source;
4190
- if (sourceConfig.markdownlint === false) {
4063
+ if (ctx.config.checks["source.markdownlint"] === "off") {
4191
4064
  log.warn("--fix: source.markdownlint is disabled for this book — no files were fixed.");
4192
4065
  return [];
4193
4066
  }
@@ -4196,13 +4069,12 @@ async function applyMarkdownlintFixes(ctx) {
4196
4069
  log.warn("--fix: no markdown files to fix.");
4197
4070
  return [];
4198
4071
  }
4199
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
4200
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
4201
- if (!resolvedConfig && !configPath) {
4202
- log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
4072
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
4073
+ if (!resolvedConfig) {
4074
+ log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} or a folder above it (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
4203
4075
  return [];
4204
4076
  }
4205
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
4077
+ const config = await loadConfig(resolvedConfig);
4206
4078
  const results = lint2({ files, config });
4207
4079
  const pending = [];
4208
4080
  for (const [file, violations] of Object.entries(results)) {
@@ -4291,15 +4163,10 @@ function formatLocation(file, line, column) {
4291
4163
 
4292
4164
  // src/checks/runner.ts
4293
4165
  function getCheckSeverityOverride(checkId, config) {
4294
- const entry = config.validate.checks[checkId];
4295
- if (typeof entry === "object" && entry.severity)
4296
- return entry.severity;
4297
- return;
4166
+ const level = config.checks[checkId];
4167
+ return level === "off" || level === "on" ? undefined : level;
4298
4168
  }
4299
4169
  async function runChecks(ctx, opts = {}) {
4300
- if (ctx.config.validate.enabled === false) {
4301
- return emptyReport();
4302
- }
4303
4170
  const { checks: selected, unmatched: unmatchedSelectors } = selectChecks(opts, ctx.config);
4304
4171
  let checks2 = selected;
4305
4172
  if (opts.skipMissingTools && opts.skipMissingTools.length > 0) {
@@ -4317,32 +4184,25 @@ async function runChecks(ctx, opts = {}) {
4317
4184
  }
4318
4185
  const release = retainPdfCache();
4319
4186
  try {
4320
- for (const check37 of checks2) {
4187
+ for (const check36 of checks2) {
4321
4188
  try {
4322
- let results = await check37.run(ctx);
4323
- const severityOverride = getCheckSeverityOverride(check37.id, ctx.config);
4189
+ const results = await check36.run(ctx);
4190
+ const severityOverride = getCheckSeverityOverride(check36.id, ctx.config);
4324
4191
  if (severityOverride) {
4325
4192
  for (const r of results) {
4326
4193
  r.severity = severityOverride;
4327
4194
  }
4328
4195
  }
4329
- const lintLevel = ruleLevel(ctx.config.lint.rules, check37.id);
4330
- if (lintLevel === "off")
4331
- results = [];
4332
- else if (lintLevel) {
4333
- for (const r of results)
4334
- r.severity = lintLevel === "warn" ? "warning" : "error";
4335
- }
4336
4196
  if (results.length === 0) {
4337
- passed.push(check37.id);
4197
+ passed.push(check36.id);
4338
4198
  } else {
4339
4199
  allResults.push(...results);
4340
4200
  }
4341
4201
  } catch (err) {
4342
4202
  allResults.push({
4343
- checkId: check37.id,
4203
+ checkId: check36.id,
4344
4204
  severity: "error",
4345
- message: `Check "${check37.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4205
+ message: `Check "${check36.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4346
4206
  });
4347
4207
  }
4348
4208
  }
@@ -4367,16 +4227,6 @@ async function runChecks(ctx, opts = {}) {
4367
4227
  }
4368
4228
  };
4369
4229
  }
4370
- function emptyReport() {
4371
- return {
4372
- results: [],
4373
- errors: [],
4374
- warnings: [],
4375
- infos: [],
4376
- passed: [],
4377
- summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
4378
- };
4379
- }
4380
4230
 
4381
4231
  // src/lib/validation-exec.ts
4382
4232
  var SHIPPED_ASSET_EXTS = new Set([
@@ -4432,7 +4282,7 @@ async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
4432
4282
  const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
4433
4283
  if (!SHIPPED_ASSET_EXTS.has(ext))
4434
4284
  continue;
4435
- const dir = dirname2(resolve4(file));
4285
+ const dir = dirname3(resolve4(file));
4436
4286
  if (escapesProjectRoot(projectRoot, dir))
4437
4287
  dirs.add(dir);
4438
4288
  }
@@ -4593,7 +4443,7 @@ function planRuns(opts) {
4593
4443
  async function executeValidation(args) {
4594
4444
  const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
4595
4445
  const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
4596
- const config = resolveConfig(args.skipStylelint ? { validate: { source: { stylelint: false } } } : {}, manifest);
4446
+ const config = resolveConfig(args.skipStylelint ? { checks: { "source.stylelint": "off" } } : {}, manifest);
4597
4447
  const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
4598
4448
  const pdfPath = typeof args.pdf === "string" ? resolve4(args.pdf) : undefined;
4599
4449
  const inputDir = typeof args.input === "string" ? resolve4(args.input) : undefined;
@@ -4626,7 +4476,7 @@ async function executeValidation(args) {
4626
4476
  let htmlPath;
4627
4477
  const outDir = resolveOutputDir(manifestDir, config.title);
4628
4478
  if (inputDir) {
4629
- const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
4479
+ const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.chapters);
4630
4480
  markdownFiles = relMarkdown.map((f) => join2(manifestDir, canonicalChapterId(f)));
4631
4481
  const relStyles = await resolveActiveStyles(manifestDir, config.styles);
4632
4482
  const projectCssFiles = relStyles.map((rel) => resolve4(manifestDir, rel));
@@ -4662,7 +4512,7 @@ async function executeValidation(args) {
4662
4512
  };
4663
4513
  if (args.fix) {
4664
4514
  const selected = selectChecks(runnerOptions, config).checks;
4665
- if (selected.some((check37) => check37.id === MARKDOWNLINT_CHECK_ID)) {
4515
+ if (selected.some((check36) => check36.id === MARKDOWNLINT_CHECK_ID)) {
4666
4516
  await applyMarkdownlintFixes(context);
4667
4517
  } else {
4668
4518
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -4940,16 +4790,16 @@ ${list}
4940
4790
 
4941
4791
  Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter 7 (System Setup) at examples/gutterpress-user-guide/07-system-setup.md for the full per-feature matrix.`, 2);
4942
4792
  }
4943
- function computeGates(format, opts, config) {
4793
+ function computeGates(format, opts) {
4944
4794
  if (format === "html") {
4945
4795
  if (opts.skipLint || opts.skipPreValidate || opts.skipPostValidate) {
4946
4796
  log.info("Validation/lint flags ignored for --format html (no validation phases apply)");
4947
4797
  }
4948
4798
  return { skipStylelint: false, preValidate: false, postValidate: false };
4949
4799
  }
4950
- const skipStylelint = !!opts.skipLint || config.lint.enabled === false;
4951
- const preValidate = !opts.skipPreValidate && config.validate.enabled !== false;
4952
- const postValidate = format === "pdfx" && !opts.skipPostValidate && config.validate.enabled !== false;
4800
+ const skipStylelint = !!opts.skipLint;
4801
+ const preValidate = !opts.skipPreValidate;
4802
+ const postValidate = format === "pdfx" && !opts.skipPostValidate;
4953
4803
  return { skipStylelint, preValidate, postValidate };
4954
4804
  }
4955
4805
 
@@ -4985,7 +4835,7 @@ async function resolveBuildContext(opts) {
4985
4835
  }, manifest);
4986
4836
  const target = opts.pdfFileOverride ? { kind: "file", file: path7.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
4987
4837
  const outDir = target.kind === "file" ? path7.dirname(target.file) : target.dir;
4988
- const gates = computeGates(format, opts, config);
4838
+ const gates = computeGates(format, opts);
4989
4839
  const workDir = target.kind === "project" ? path7.join(path7.dirname(target.dir), `.${path7.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path7.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
4990
4840
  return {
4991
4841
  opts,
@@ -5041,8 +4891,8 @@ async function runQualityGates(ctx) {
5041
4891
  }
5042
4892
  async function renderBook(ctx) {
5043
4893
  const { config, gates, renderDir, workDir, opts, format } = ctx;
5044
- if (config.source.files && config.source.files.length > 0) {
5045
- log.info(`Using specified files (${config.source.files.length} total)`);
4894
+ if (config.chapters && config.chapters.length > 0) {
4895
+ log.info(`Using specified files (${config.chapters.length} total)`);
5046
4896
  } else {
5047
4897
  log.info("Using all .md files in alphabetical order");
5048
4898
  }
@@ -5055,7 +4905,7 @@ async function renderBook(ctx) {
5055
4905
  print: config.print,
5056
4906
  printArea: { ...config.page, preset: config.preset, spec: config.spec },
5057
4907
  styles: config.styles,
5058
- files: config.source.files,
4908
+ files: config.chapters,
5059
4909
  plugins,
5060
4910
  pluginStyles,
5061
4911
  onChapterWarnings: (file, warnings) => {
@@ -5206,7 +5056,7 @@ class PdfOutput {
5206
5056
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5207
5057
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5208
5058
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5209
- const { buildNativePdf } = await import("./engine-mc5br8g8.js");
5059
+ const { buildNativePdf } = await import("./engine-4rzdx6af.js");
5210
5060
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5211
5061
  title: config.title,
5212
5062
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5243,7 +5093,7 @@ class PdfOutput {
5243
5093
  }
5244
5094
  const flattenPages = config.pdfx.flattenPages;
5245
5095
  if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
5246
- const { unsplittableTextReasons } = await import("./pdf-inspect-wnw4za71.js");
5096
+ const { unsplittableTextReasons } = await import("./pdf-inspect-b3mf81ex.js");
5247
5097
  const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
5248
5098
  log.warn(`pdfx.transparency: preserve-text could not inspect this PDF's text (${err instanceof Error ? err.message : String(err)}), so every page that uses transparency is flattened whole, as with flatten-page.`);
5249
5099
  return null;
@@ -5545,7 +5395,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
5545
5395
  ...config,
5546
5396
  printArea: config.page && { ...config.page, preset: config.preset, spec: config.spec }
5547
5397
  }, {
5548
- files: config.source?.files ?? null,
5398
+ files: config.chapters ?? null,
5549
5399
  onCssAssets: (copies) => {
5550
5400
  for (const copy of copies)
5551
5401
  nextAssets.set(copy.to, copy.from);
@@ -6501,32 +6351,41 @@ import { constants as FS, existsSync as existsSync7 } from "node:fs";
6501
6351
  import path12 from "node:path";
6502
6352
 
6503
6353
  // src/lib/page-size.ts
6504
- import postcss5 from "postcss";
6505
- import { isMap, Scalar } from "yaml";
6354
+ import { isMap } from "yaml";
6506
6355
  function writePageBounds(doc, page) {
6507
- const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
6508
- if (!isMap(doc.get("page", true)))
6509
- doc.set("page", doc.createNode({}));
6356
+ ensurePageMap(doc);
6510
6357
  doc.setIn(["page", "width"], page.width);
6511
6358
  doc.setIn(["page", "height"], page.height);
6512
- if (tolerance !== undefined)
6513
- doc.setIn(["page", "tolerance"], tolerance);
6359
+ doc.deleteIn(["page", "tolerance"]);
6360
+ }
6361
+ function deletePageBounds(doc) {
6362
+ const block = doc.get("page", true);
6363
+ if (!isMap(block)) {
6364
+ doc.delete("page");
6365
+ return;
6366
+ }
6367
+ for (const key of ["width", "height", "tolerance"])
6368
+ block.delete(key);
6369
+ if (block.items.length === 0)
6370
+ doc.delete("page");
6514
6371
  }
6515
- function numberAt(doc, keyPath) {
6516
- const v = doc.getIn(keyPath);
6517
- return isPositivePoints(v) ? v : undefined;
6372
+ function ensurePageMap(doc) {
6373
+ if (!isMap(doc.get("page", true)))
6374
+ doc.set("page", doc.createNode({}));
6518
6375
  }
6519
6376
 
6520
6377
  // src/lib/project-scaffold.ts
6521
6378
  function starterOverridesCss() {
6522
6379
  return `/**
6523
- * Your book's own styles.
6380
+ * Your book's own styles. Rules here win over your look's.
6524
6381
  *
6525
- * This file loads AFTER every extension, so any rule you add here overrides
6526
- * a look's defaults. A new book has no look until its template or you add
6527
- * one:
6528
- * gutterpress ext add academic <book> (a built-in look)
6529
- * gutterpress ext add <package> <book> (a look from npm)
6382
+ * In the app you rarely need this file: Setup > Look chooses a look and
6383
+ * changes its fonts and colors (those changes are saved here as :root
6384
+ * custom properties), Setup > Advanced lists every setting, and Setup > Book
6385
+ * sets the page size and margins (in the manifest).
6386
+ *
6387
+ * To change the look from the command line: gutterpress ext remove <old>
6388
+ * <book>, then gutterpress ext add <look> <book>.
6530
6389
  */
6531
6390
  `;
6532
6391
  }
@@ -6562,9 +6421,6 @@ function requirePreset(preset, customPage) {
6562
6421
  } else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
6563
6422
  throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
6564
6423
  }
6565
- if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
6566
- throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
6567
- }
6568
6424
  return preset;
6569
6425
  }
6570
6426
  function resolveScaffoldTargets(requested, preset) {
@@ -6649,6 +6505,8 @@ async function scaffoldProject(options) {
6649
6505
  if (preset) {
6650
6506
  doc.set("preset", preset);
6651
6507
  doc.set("targets", doc.createNode(targets ?? []));
6508
+ if (preset !== "custom")
6509
+ deletePageBounds(doc);
6652
6510
  }
6653
6511
  if (options.customPage)
6654
6512
  writePageBounds(doc, options.customPage);
@@ -6699,7 +6557,7 @@ async function firstSourceFile(manifestPath) {
6699
6557
  } catch {
6700
6558
  return;
6701
6559
  }
6702
- const filesIdx = text.search(/^\s*files:\s*$/m);
6560
+ const filesIdx = text.search(/^(?:chapters|\s+files):\s*$/m);
6703
6561
  if (filesIdx === -1)
6704
6562
  return;
6705
6563
  const rest = text.slice(filesIdx);
@@ -9020,7 +8878,7 @@ async function resolvePublishProvider(id, book) {
9020
8878
  const ext = await bookExtensions(book);
9021
8879
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
9022
8880
  if (npmEntry) {
9023
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-g8jnmmzq.js");
8881
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-m8tj43e7.js");
9024
8882
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
9025
8883
  }
9026
8884
  const publishers = await discover(ext);
@@ -9058,7 +8916,7 @@ function infoFor(p) {
9058
8916
  }
9059
8917
  function pluginProvider(p) {
9060
8918
  let loading;
9061
- const load = () => loading ??= import("./plugins-1sxe0dkz.js").then((m) => m.loadPublishFunction(p));
8919
+ const load = () => loading ??= import("./plugins-sqa4msdk.js").then((m) => m.loadPublishFunction(p));
9062
8920
  return {
9063
8921
  info: infoFor(p),
9064
8922
  async authenticate() {
@@ -9332,10 +9190,10 @@ async function disconnectPublishCredential(key, deps, options = {}) {
9332
9190
  }
9333
9191
  }
9334
9192
  // src/lib/manifest-config.ts
9335
- import { isSeq as isSeq2, isMap as isMap3, YAMLSeq, Scalar as Scalar2 } from "yaml";
9193
+ import { isSeq as isSeq2, isMap as isMap3, YAMLSeq, Scalar } from "yaml";
9336
9194
  // src/lib/version-changes.ts
9337
9195
  import git3 from "isomorphic-git";
9338
- import postcss6 from "postcss";
9196
+ import postcss5 from "postcss";
9339
9197
 
9340
9198
  // src/lib/remote-auth/transport.ts
9341
9199
  import git2 from "isomorphic-git";