gutterpress 0.11.23-alpha.3 → 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 (69) hide show
  1. package/README.md +4 -6
  2. package/dist/{README-8db7btxb.md → README-syg2hn83.md} +1 -1
  3. package/dist/api/index.js +5 -5
  4. package/dist/{build-m78jcpf7.js → build-c48eer6f.js} +8 -8
  5. package/dist/checks/asset/extensions.d.ts +1 -1
  6. package/dist/checks/asset/index.d.ts +0 -1
  7. package/dist/checks/policy.d.ts +5 -4
  8. package/dist/checks/source/config-file.d.ts +9 -6
  9. package/dist/checks/source/dangling-links.d.ts +1 -1
  10. package/dist/checks/types.d.ts +2 -2
  11. package/dist/{cli-xsrffqzt.js → cli-54xt7pjg.js} +8 -8
  12. package/dist/{cli-2ppw6q9v.js → cli-5crpmz42.js} +80 -1
  13. package/dist/{cli-1h17ktsg.js → cli-93mkaw6v.js} +96 -209
  14. package/dist/{cli-n2tsxqpn.js → cli-d9mm3vvh.js} +7 -2
  15. package/dist/{cli-5jzyw06s.js → cli-gnpea9dx.js} +1 -1
  16. package/dist/{cli-s6djx4ma.js → cli-pspjxnc8.js} +1 -1
  17. package/dist/{cli-t8be9yeb.js → cli-rqh295j9.js} +12 -7
  18. package/dist/{cli-h4x81x97.js → cli-y6d1arem.js} +207 -142
  19. package/dist/cli.js +12 -12
  20. package/dist/{doctor-j71jqce3.js → doctor-ptb346k8.js} +4 -3
  21. package/dist/{engine-m4y10shc.js → engine-4rzdx6af.js} +1 -1
  22. package/dist/{engine-1r6q80tq.js → engine-6txhxmmv.js} +2 -2
  23. package/dist/{ext-fq2hg9va.js → ext-gy8szg3x.js} +8 -8
  24. package/dist/{extension-restore-9dh7c4tp.js → extension-restore-dymzcasz.js} +2 -2
  25. package/dist/{extension-restore-tmxtzkm0.js → extension-restore-m8tj43e7.js} +4 -4
  26. package/dist/{index-2ppw6q9v.js → index-5crpmz42.js} +80 -1
  27. package/dist/{index-nedd07bk.js → index-5kh4c78h.js} +8 -8
  28. package/dist/{index-0wee4408.js → index-8ywkd2sv.js} +12 -7
  29. package/dist/{index-prqvz3xm.js → index-ea94zbq4.js} +132 -228
  30. package/dist/{index-7pck1q9c.js → index-kaks3mm2.js} +206 -141
  31. package/dist/{index-nvmf22rt.js → index-yc3xj266.js} +1 -1
  32. package/dist/index.js +6 -6
  33. package/dist/lib/build-preflight.d.ts +4 -10
  34. package/dist/lib/build-runner.d.ts +1 -1
  35. package/dist/lib/ghostscript.d.ts +5 -2
  36. package/dist/lib/lint-rules.d.ts +5 -1
  37. package/dist/lib/manifest-config.d.ts +3 -2
  38. package/dist/lib/manifest-old-spellings.d.ts +64 -0
  39. package/dist/lib/markdown/chapter-id.d.ts +1 -1
  40. package/dist/lib/markdown/index.d.ts +1 -1
  41. package/dist/lib/markdown/table.d.ts +5 -3
  42. package/dist/lib/page-size.d.ts +14 -9
  43. package/dist/lib/pdf-inspect.d.ts +27 -0
  44. package/dist/lib/presets.d.ts +10 -8
  45. package/dist/lib/printsafe.d.ts +1 -1
  46. package/dist/lib/project-scaffold.d.ts +1 -1
  47. package/dist/lib/remote-auth/sync-messages.d.ts +2 -2
  48. package/dist/lib/targets.d.ts +4 -4
  49. package/dist/lib/validation-exec.d.ts +4 -4
  50. package/dist/{manifest-my7mdv8j.yaml → manifest-2kc56wvw.yaml} +3 -4
  51. package/dist/{manifest-8afhrk30.yaml → manifest-41ddzm13.yaml} +3 -4
  52. package/dist/{manifest-gf13sjp8.yaml → manifest-79zsgkpn.yaml} +3 -4
  53. package/dist/{manifest-d4f9q79e.yaml → manifest-sqzm4681.yaml} +3 -4
  54. package/dist/{manifest.schema-650pe0d1.json → manifest.schema-w86c1xw1.json} +19 -309
  55. package/dist/{new-s4zcaepg.js → new-t2t28ccb.js} +9 -15
  56. package/dist/{page-rules-fj09xct5.css → page-rules-ytj9p04v.css} +1 -1
  57. package/dist/{pdf-inspect-wnw4za71.js → pdf-inspect-b3mf81ex.js} +3 -1
  58. package/dist/{plugins-80emnf31.js → plugins-fec8t2bh.js} +1 -1
  59. package/dist/{plugins-4vaf27n9.js → plugins-sqa4msdk.js} +3 -3
  60. package/dist/{preflight-c7w1xdn3.js → preflight-srq8fktp.js} +8 -8
  61. package/dist/preview/file-watcher.d.ts +1 -3
  62. package/dist/{preview-mzytcwyk.js → preview-zsp9hstd.js} +8 -8
  63. package/dist/{publish-fez14ad4.js → publish-hhy31gr9.js} +8 -8
  64. package/dist/render.js +6 -1
  65. package/dist/schema/manifest.types.d.ts +40 -107
  66. package/dist/{tokens-ye64p899.css → tokens-n82t4k9h.css} +11 -4
  67. package/dist/{validate-p2chr6hr.js → validate-r9jdz85h.js} +8 -8
  68. package/package.json +1 -1
  69. package/dist/checks/asset/approved-fonts.d.ts +0 -3
@@ -37,7 +37,7 @@ import {
37
37
  validatePageMargin,
38
38
  withFetchTimeout,
39
39
  writeManifestDoc
40
- } from "./index-7pck1q9c.js";
40
+ } from "./index-kaks3mm2.js";
41
41
  import {
42
42
  EXTENSION_MANIFEST_FILENAME,
43
43
  GUTTERPRESS_CSS,
@@ -73,7 +73,7 @@ import {
73
73
  setLogLevel,
74
74
  slugify,
75
75
  warn
76
- } from "./index-0wee4408.js";
76
+ } from "./index-8ywkd2sv.js";
77
77
  import {
78
78
  blackTextToGray
79
79
  } from "./index-ht1bcqdd.js";
@@ -84,7 +84,7 @@ import {
84
84
  getAssetPath,
85
85
  lastPageMode,
86
86
  resolvePage
87
- } from "./index-nedd07bk.js";
87
+ } from "./index-5kh4c78h.js";
88
88
  import {
89
89
  BuildError
90
90
  } from "./index-46ycxe6r.js";
@@ -99,8 +99,9 @@ import {
99
99
  isLoadable,
100
100
  loadPdf,
101
101
  pageHasMarks,
102
+ pagesWithVisibleShadingPaints,
102
103
  retainPdfCache
103
- } from "./index-2ppw6q9v.js";
104
+ } from "./index-5crpmz42.js";
104
105
  import {
105
106
  INSTALL_HINTS,
106
107
  enhancedPath,
@@ -156,14 +157,14 @@ var MSG_UP_TO_DATE = "Everything is in sync.";
156
157
  var MSG_UP_TO_DATE_PULLED = "Everything is in sync. The latest online changes were downloaded to this computer.";
157
158
  var MSG_SYNCED = "Your changes are online.";
158
159
  var MSG_SYNCED_MERGED = "Your changes are online, combined with changes from the online copy.";
159
- var MSG_OFFLINE = "Your changes are saved on this computer. gutterpress couldn't reach the online repository — try syncing again when you're back online.";
160
+ var MSG_OFFLINE = "Your changes are saved on this computer. Gutterpress couldn't reach the online repository — try syncing again when you're back online.";
160
161
  var MSG_AUTH = "The online repository didn't accept the saved connection. Reconnect and try again.";
161
162
  var MSG_INSECURE_TRANSPORT = "This book's online address isn't secure, so the saved connection wasn't sent — connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to sync.";
162
163
  var MSG_BUSY = "The online copy is changing very quickly right now. Your work is saved on this computer — try Sync again in a moment.";
163
164
  var MSG_HISTORY_UNREADABLE = "This book's version history can't be read, so syncing can't continue. Your writing is safe — every file is still here on this computer. Use Repair online backup (in Where your work is kept) to get history and syncing working again.";
164
165
  var MSG_UNRELATED = "This book and the online copy have no history in common, so they can't be combined. Either the online address points at a different book, or this book's own history was lost and started over. Your writing is safe — every file is still here on this computer. Check the online address; if it is correct, use Repair online backup (in Where your work is kept).";
165
166
  var MSG_NO_REMOTE = "This book isn't connected to an online repository yet.";
166
- var MSG_SSH_REMOTE = "This book's online address uses SSH (git@…), which gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
167
+ var MSG_SSH_REMOTE = "This book's online address uses SSH (git@…), which Gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
167
168
  var MSG_NO_BRANCH = "This book's version history isn't on a named branch, so it can't be synced right now.";
168
169
  var SYNC_SNAPSHOT_MESSAGE = "Automatic backup of your work";
169
170
  var SYNC_LATE_EDIT_MESSAGE = "Saved the edit you made while syncing";
@@ -955,7 +956,9 @@ async function findPostScriptShadingPages(pdfBytes) {
955
956
  if (usesPostScriptShading(asDict(page.node.Resources())))
956
957
  pages.push(i + 1);
957
958
  });
958
- return pages;
959
+ if (pages.length === 0)
960
+ return pages;
961
+ return pagesWithVisibleShadingPaints(pdfBytes, pages).catch(() => pages);
959
962
  }
960
963
  async function stripAnnotations(pdfPath, stagingDir) {
961
964
  const tmp = join2(stagingDir, "annotations-stripped.pdf");
@@ -1350,7 +1353,7 @@ async function writeBuildFingerprint(input) {
1350
1353
  // src/lib/validation-exec.ts
1351
1354
  import { existsSync as existsSync7 } from "node:fs";
1352
1355
  import { readFile as readFile20 } from "node:fs/promises";
1353
- import { dirname as dirname2, join as join3, resolve as resolve5 } from "node:path";
1356
+ import { dirname as dirname3, join as join3, resolve as resolve5 } from "node:path";
1354
1357
 
1355
1358
  // src/lib/markdownlint-fix.ts
1356
1359
  import { readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
@@ -1359,18 +1362,20 @@ import { lint as lint2 } from "markdownlint/sync";
1359
1362
 
1360
1363
  // src/checks/source/config-file.ts
1361
1364
  import { existsSync as existsSync3 } from "node:fs";
1362
- import { resolve as resolve2 } from "node:path";
1363
- function findConfigFile(inputDir, names, explicit) {
1364
- if (explicit) {
1365
- const p = resolve2(inputDir, explicit);
1366
- return existsSync3(p) ? p : null;
1367
- }
1368
- for (const name of names) {
1369
- const p = resolve2(inputDir, name);
1370
- if (existsSync3(p))
1371
- return p;
1365
+ import { dirname, resolve as resolve2 } from "node:path";
1366
+ function findConfigFile(startDir, names) {
1367
+ let dir = resolve2(startDir);
1368
+ for (;; ) {
1369
+ for (const name of names) {
1370
+ const p = resolve2(dir, name);
1371
+ if (existsSync3(p))
1372
+ return p;
1373
+ }
1374
+ const parent = dirname(dir);
1375
+ if (existsSync3(resolve2(dir, ".git")) || parent === dir)
1376
+ return null;
1377
+ dir = parent;
1372
1378
  }
1373
- return null;
1374
1379
  }
1375
1380
 
1376
1381
  // src/checks/source/markdownlint.ts
@@ -1482,17 +1487,13 @@ var check = {
1482
1487
  category: "source",
1483
1488
  phase: "pre-build",
1484
1489
  async run(ctx) {
1485
- const sourceConfig = ctx.config.validate.source;
1486
- if (sourceConfig.markdownlint === false)
1487
- return [];
1488
1490
  const files = ctx.markdownFiles;
1489
1491
  if (!files || files.length === 0)
1490
1492
  return [];
1491
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
1492
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
1493
- if (!resolvedConfig && !configPath)
1493
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
1494
+ if (!resolvedConfig)
1494
1495
  return [];
1495
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
1496
+ const config = await loadConfig(resolvedConfig);
1496
1497
  const results = lint({ files, config });
1497
1498
  const out = [];
1498
1499
  for (const [file, violations] of Object.entries(results)) {
@@ -1531,8 +1532,7 @@ async function uncommittedAmong(inputDir, files) {
1531
1532
  return files.filter((file) => changed.has(repoSubPath(repoRoot, file)));
1532
1533
  }
1533
1534
  async function applyMarkdownlintFixes(ctx) {
1534
- const sourceConfig = ctx.config.validate.source;
1535
- if (sourceConfig.markdownlint === false) {
1535
+ if (ctx.config.checks["source.markdownlint"] === "off") {
1536
1536
  log.warn("--fix: source.markdownlint is disabled for this book — no files were fixed.");
1537
1537
  return [];
1538
1538
  }
@@ -1541,13 +1541,12 @@ async function applyMarkdownlintFixes(ctx) {
1541
1541
  log.warn("--fix: no markdown files to fix.");
1542
1542
  return [];
1543
1543
  }
1544
- const configPath = typeof sourceConfig.markdownlint === "string" ? sourceConfig.markdownlint : null;
1545
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES, configPath);
1546
- if (!resolvedConfig && !configPath) {
1547
- log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
1544
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES);
1545
+ if (!resolvedConfig) {
1546
+ log.warn(`--fix: no markdownlint config found in ${ctx.inputDir} or a folder above it (looked for ${CONFIG_NAMES.join(", ")}) — no files were fixed.`);
1548
1547
  return [];
1549
1548
  }
1550
- const config = resolvedConfig ? await loadConfig(resolvedConfig) : { default: true };
1549
+ const config = await loadConfig(resolvedConfig);
1551
1550
  const results = lint2({ files, config });
1552
1551
  const pending = [];
1553
1552
  for (const [file, violations] of Object.entries(results)) {
@@ -1581,10 +1580,7 @@ import { readFile as readFile7 } from "node:fs/promises";
1581
1580
  // src/checks/policy.ts
1582
1581
  var INSPECT_FAILED_CODE = "inspect-failed";
1583
1582
  function isCheckEnabled(check2, config) {
1584
- const entry = config.validate.checks[check2.id];
1585
- if (entry === false)
1586
- return false;
1587
- if (typeof entry === "object" && entry.enabled === false)
1583
+ if (config.checks[check2.id] === "off")
1588
1584
  return false;
1589
1585
  if (check2.enabledWhen && !check2.enabledWhen(config))
1590
1586
  return false;
@@ -2327,7 +2323,7 @@ var check9 = {
2327
2323
  severity: "warning",
2328
2324
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
2329
2325
  file: ctx.pdfPath,
2330
- 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.",
2326
+ 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.",
2331
2327
  code: "rich-black-text",
2332
2328
  data: { runs: found.runs, pages: found.pages }
2333
2329
  })
@@ -2407,8 +2403,6 @@ var check11 = {
2407
2403
  async run(ctx) {
2408
2404
  if (!ctx.pdfPath)
2409
2405
  return [];
2410
- if (!ctx.config.validate.pdf.requireBookmarks)
2411
- return [];
2412
2406
  const doc = await loadPdf(ctx.pdfPath);
2413
2407
  if (!doc) {
2414
2408
  return [
@@ -2441,8 +2435,6 @@ var check12 = {
2441
2435
  async run(ctx) {
2442
2436
  if (!ctx.pdfPath)
2443
2437
  return [];
2444
- if (!ctx.config.validate.pdf.requireTocLinks)
2445
- return [];
2446
2438
  const doc = await loadPdf(ctx.pdfPath);
2447
2439
  if (!doc) {
2448
2440
  return [
@@ -2580,8 +2572,6 @@ var check16 = {
2580
2572
  async run(ctx) {
2581
2573
  if (!ctx.pdfPath)
2582
2574
  return [];
2583
- if (!ctx.config.validate.pdf.forbidTransparency)
2584
- return [];
2585
2575
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
2586
2576
  const found = [];
2587
2577
  if (bytes.includes("/Transparency"))
@@ -2622,8 +2612,6 @@ var check17 = {
2622
2612
  async run(ctx) {
2623
2613
  if (!ctx.pdfPath)
2624
2614
  return [];
2625
- if (!ctx.config.validate.pdf.requireBleed)
2626
- return [];
2627
2615
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
2628
2616
  if (!bytes) {
2629
2617
  return [
@@ -2720,14 +2708,10 @@ var check19 = {
2720
2708
  category: "source",
2721
2709
  phase: "pre-build",
2722
2710
  async run(ctx) {
2723
- const sourceConfig = ctx.config.validate.source;
2724
- if (sourceConfig.htmlhint === false)
2725
- return [];
2726
2711
  if (!ctx.htmlPath || !existsSync4(ctx.htmlPath))
2727
2712
  return [];
2728
- const configPath = typeof sourceConfig.htmlhint === "string" ? sourceConfig.htmlhint : null;
2729
- const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
2730
- if (!resolvedConfig && !configPath)
2713
+ const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2);
2714
+ if (!resolvedConfig)
2731
2715
  return [];
2732
2716
  const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
2733
2717
  const html = await readFile10(ctx.htmlPath, "utf8");
@@ -2755,7 +2739,7 @@ function ruleLevel(rules, id) {
2755
2739
  const v = rules?.[id];
2756
2740
  if (v === "off" || v === false)
2757
2741
  return "off";
2758
- if (v === "warn" || v === "warning")
2742
+ if (v === "warn" || v === "warning" || v === "info")
2759
2743
  return "warn";
2760
2744
  if (v === "error")
2761
2745
  return "error";
@@ -3074,7 +3058,7 @@ function checkCssRaw(css, from) {
3074
3058
  warnings.push({
3075
3059
  rule: rulePageSizeInStylesheet,
3076
3060
  severity: "warning",
3077
- message: `"size" in a stylesheet's @page rule is ignored: the page size comes from the manifest (preset:, or page: width and height — Setup > Details in the app), which is written after every stylesheet. Remove this declaration; to print at another size, change the manifest.`,
3061
+ 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.`,
3078
3062
  ...nodeLoc(decl)
3079
3063
  });
3080
3064
  return;
@@ -3174,10 +3158,7 @@ var check20 = {
3174
3158
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
3175
3159
  category: "source",
3176
3160
  phase: "pre-build",
3177
- enabledWhen: (config) => config.validate.source.stylelint !== false,
3178
3161
  async run(ctx) {
3179
- if (ctx.config.validate.source.stylelint === false)
3180
- return [];
3181
3162
  const files = ctx.cssFiles;
3182
3163
  if (!files || files.length === 0)
3183
3164
  return [];
@@ -3195,7 +3176,7 @@ var check20 = {
3195
3176
  });
3196
3177
  continue;
3197
3178
  }
3198
- for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
3179
+ for (const w of checkCss(css, file, { rules: ctx.config.checks })) {
3199
3180
  results.push({
3200
3181
  checkId: check20.id,
3201
3182
  severity: w.severity,
@@ -3336,15 +3317,11 @@ var check21 = {
3336
3317
  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.",
3337
3318
  category: "source",
3338
3319
  phase: "pre-build",
3339
- enabledWhen: (config) => config.validate.source.cssOwnership !== false,
3340
3320
  async run(ctx) {
3341
- if (ctx.config.validate.source.cssOwnership === false)
3342
- return [];
3343
3321
  const files = ctx.cssFiles;
3344
3322
  if (!files || files.length === 0)
3345
3323
  return [];
3346
- const explicit = typeof ctx.config.validate.source.cssOwnership === "string" ? ctx.config.validate.source.cssOwnership : null;
3347
- const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3, explicit);
3324
+ const configPath = findConfigFile(ctx.inputDir, CONFIG_NAMES3);
3348
3325
  if (!configPath)
3349
3326
  return [];
3350
3327
  let contract;
@@ -3411,7 +3388,7 @@ registerCheck(check21);
3411
3388
 
3412
3389
  // src/checks/source/local-refs.ts
3413
3390
  import { existsSync as existsSync5 } from "node:fs";
3414
- import { dirname, resolve as resolve3 } from "node:path";
3391
+ import { dirname as dirname2, resolve as resolve3 } from "node:path";
3415
3392
  var check22 = {
3416
3393
  id: "source.links.local-refs",
3417
3394
  name: "Local Markdown References",
@@ -3451,7 +3428,7 @@ function filesystemRef(ref) {
3451
3428
  }
3452
3429
  function localRefExists(ref, kind, sourceFile, projectRoot) {
3453
3430
  const cleaned = filesystemRef(ref);
3454
- const fileRelative = resolve3(dirname(sourceFile), cleaned);
3431
+ const fileRelative = resolve3(dirname2(sourceFile), cleaned);
3455
3432
  const rootRelative = resolve3(projectRoot, cleaned);
3456
3433
  switch (kind) {
3457
3434
  case "image":
@@ -4527,8 +4504,6 @@ var check30 = {
4527
4504
  category: "asset",
4528
4505
  phase: "pre-build",
4529
4506
  async run(ctx) {
4530
- if (ctx.config.validate.assets.allowAlpha)
4531
- return [];
4532
4507
  if (ctx.config.pdfx.transparency === "preserve-text")
4533
4508
  return [];
4534
4509
  const files = shippedImages(ctx, ["png", "tiff", "tif"]);
@@ -4551,58 +4526,6 @@ var check30 = {
4551
4526
  };
4552
4527
  registerCheck(check30);
4553
4528
 
4554
- // src/checks/asset/approved-fonts.ts
4555
- var check31 = {
4556
- id: "asset.font.approved-files",
4557
- name: "Approved Font Files",
4558
- description: "Checks font files against the approved file patterns",
4559
- category: "asset",
4560
- phase: "pre-build",
4561
- async run(ctx) {
4562
- const patterns = ctx.config.validate.assets.approvedFontFiles;
4563
- if (!patterns || patterns.length === 0)
4564
- return [];
4565
- const { glob } = await import("glob");
4566
- const dirs = ctx.assetDirs ?? [ctx.inputDir];
4567
- const allFonts = [];
4568
- for (const dir of dirs) {
4569
- const matches = await glob(`**/*.{${FONT_EXTS2.join(",")}}`, {
4570
- cwd: dir,
4571
- absolute: true,
4572
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
4573
- });
4574
- allFonts.push(...matches);
4575
- }
4576
- if (allFonts.length === 0)
4577
- return [];
4578
- const approvedFonts = new Set;
4579
- for (const dir of dirs) {
4580
- for (const pattern of patterns) {
4581
- const matches = await glob(pattern, {
4582
- cwd: dir,
4583
- absolute: true,
4584
- ignore: [...ASSET_SCAN_IGNORE_GLOBS]
4585
- });
4586
- for (const m of matches)
4587
- approvedFonts.add(m);
4588
- }
4589
- }
4590
- const results = [];
4591
- for (const font of allFonts) {
4592
- if (!approvedFonts.has(font)) {
4593
- results.push({
4594
- checkId: check31.id,
4595
- severity: "warning",
4596
- message: "Font file not in approved list",
4597
- file: font
4598
- });
4599
- }
4600
- }
4601
- return results;
4602
- }
4603
- };
4604
- registerCheck(check31);
4605
-
4606
4529
  // src/checks/asset/font-license.ts
4607
4530
  import { existsSync as existsSync6 } from "node:fs";
4608
4531
  import { resolve as resolve4 } from "node:path";
@@ -4616,15 +4539,13 @@ var LICENSE_NAMES = [
4616
4539
  "OFL-1.1.txt",
4617
4540
  "COPYING"
4618
4541
  ];
4619
- var check32 = {
4542
+ var check31 = {
4620
4543
  id: "asset.font.license",
4621
4544
  name: "Font License",
4622
4545
  description: "Checks for license files in font directories",
4623
4546
  category: "asset",
4624
4547
  phase: "pre-build",
4625
4548
  async run(ctx) {
4626
- if (!ctx.config.validate.assets.requireFontLicense)
4627
- return [];
4628
4549
  const { glob } = await import("glob");
4629
4550
  const dirs = ctx.assetDirs ?? [ctx.inputDir];
4630
4551
  const fontDirs = new Set;
@@ -4643,7 +4564,7 @@ var check32 = {
4643
4564
  const hasLicense = LICENSE_NAMES.some((name) => existsSync6(resolve4(fontDir, name)));
4644
4565
  if (!hasLicense) {
4645
4566
  results.push({
4646
- checkId: check32.id,
4567
+ checkId: check31.id,
4647
4568
  severity: "warning",
4648
4569
  message: `No font license file found in directory`,
4649
4570
  file: fontDir
@@ -4653,10 +4574,10 @@ var check32 = {
4653
4574
  return results;
4654
4575
  }
4655
4576
  };
4656
- registerCheck(check32);
4577
+ registerCheck(check31);
4657
4578
 
4658
4579
  // src/checks/heuristic/text-density.ts
4659
- var check33 = {
4580
+ var check32 = {
4660
4581
  id: "heuristic.whitespace.text-density",
4661
4582
  name: "Text Density",
4662
4583
  description: "Checks characters-per-page ratio",
@@ -4685,7 +4606,7 @@ var check33 = {
4685
4606
  });
4686
4607
  if (lowPages.length > 0) {
4687
4608
  results.push({
4688
- checkId: check33.id,
4609
+ checkId: check32.id,
4689
4610
  severity: "info",
4690
4611
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
4691
4612
  file: ctx.pdfPath
@@ -4693,7 +4614,7 @@ var check33 = {
4693
4614
  }
4694
4615
  if (highPages.length > 0) {
4695
4616
  results.push({
4696
- checkId: check33.id,
4617
+ checkId: check32.id,
4697
4618
  severity: "info",
4698
4619
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
4699
4620
  file: ctx.pdfPath
@@ -4702,11 +4623,11 @@ var check33 = {
4702
4623
  return results;
4703
4624
  }
4704
4625
  };
4705
- registerCheck(check33);
4626
+ registerCheck(check32);
4706
4627
 
4707
4628
  // src/checks/heuristic/section-density.ts
4708
4629
  import { readFile as readFile19 } from "node:fs/promises";
4709
- var check34 = {
4630
+ var check33 = {
4710
4631
  id: "heuristic.chunking.section-density",
4711
4632
  name: "Section Density",
4712
4633
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -4732,7 +4653,7 @@ var check34 = {
4732
4653
  const line = lines[i];
4733
4654
  if (/^#{1,6}\s/.test(line)) {
4734
4655
  if (paragraphCount > maxParas) {
4735
- results.push(finding(check34.id, {
4656
+ results.push(finding(check33.id, {
4736
4657
  severity: "info",
4737
4658
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
4738
4659
  file,
@@ -4752,7 +4673,7 @@ var check34 = {
4752
4673
  }
4753
4674
  }
4754
4675
  if (paragraphCount > maxParas) {
4755
- results.push(finding(check34.id, {
4676
+ results.push(finding(check33.id, {
4756
4677
  severity: "info",
4757
4678
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
4758
4679
  file,
@@ -4760,7 +4681,7 @@ var check34 = {
4760
4681
  }));
4761
4682
  }
4762
4683
  } catch {
4763
- results.push(inspectionFailed(check34.id, `Could not read source file: ${file}`, {
4684
+ results.push(inspectionFailed(check33.id, `Could not read source file: ${file}`, {
4764
4685
  file
4765
4686
  }));
4766
4687
  }
@@ -4768,10 +4689,10 @@ var check34 = {
4768
4689
  return results;
4769
4690
  }
4770
4691
  };
4771
- registerCheck(check34);
4692
+ registerCheck(check33);
4772
4693
 
4773
4694
  // src/checks/heuristic/layer-count.ts
4774
- var check35 = {
4695
+ var check34 = {
4775
4696
  id: "heuristic.decoration.layer-count",
4776
4697
  name: "Layer Count",
4777
4698
  description: "Counts image objects per page",
@@ -4796,7 +4717,7 @@ var check35 = {
4796
4717
  heavyPages.sort((a, b) => a - b);
4797
4718
  return [
4798
4719
  {
4799
- checkId: check35.id,
4720
+ checkId: check34.id,
4800
4721
  severity: "info",
4801
4722
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
4802
4723
  file: ctx.pdfPath
@@ -4806,10 +4727,10 @@ var check35 = {
4806
4727
  return [];
4807
4728
  }
4808
4729
  };
4809
- registerCheck(check35);
4730
+ registerCheck(check34);
4810
4731
 
4811
4732
  // src/checks/heuristic/placement-variance.ts
4812
- var check36 = {
4733
+ var check35 = {
4813
4734
  id: "heuristic.layout.placement-variance",
4814
4735
  name: "Placement Variance",
4815
4736
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -4827,7 +4748,7 @@ var check36 = {
4827
4748
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
4828
4749
  return [
4829
4750
  {
4830
- checkId: check36.id,
4751
+ checkId: check35.id,
4831
4752
  severity: "info",
4832
4753
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
4833
4754
  file: ctx.pdfPath
@@ -4835,19 +4756,14 @@ var check36 = {
4835
4756
  ];
4836
4757
  }
4837
4758
  };
4838
- registerCheck(check36);
4759
+ registerCheck(check35);
4839
4760
 
4840
4761
  // src/checks/runner.ts
4841
4762
  function getCheckSeverityOverride(checkId, config) {
4842
- const entry = config.validate.checks[checkId];
4843
- if (typeof entry === "object" && entry.severity)
4844
- return entry.severity;
4845
- return;
4763
+ const level = config.checks[checkId];
4764
+ return level === "off" || level === "on" ? undefined : level;
4846
4765
  }
4847
4766
  async function runChecks(ctx, opts = {}) {
4848
- if (ctx.config.validate.enabled === false) {
4849
- return emptyReport();
4850
- }
4851
4767
  const { checks: selected, unmatched: unmatchedSelectors } = selectChecks(opts, ctx.config);
4852
4768
  let checks2 = selected;
4853
4769
  if (opts.skipMissingTools && opts.skipMissingTools.length > 0) {
@@ -4865,32 +4781,25 @@ async function runChecks(ctx, opts = {}) {
4865
4781
  }
4866
4782
  const release = retainPdfCache();
4867
4783
  try {
4868
- for (const check37 of checks2) {
4784
+ for (const check36 of checks2) {
4869
4785
  try {
4870
- let results = await check37.run(ctx);
4871
- const severityOverride = getCheckSeverityOverride(check37.id, ctx.config);
4786
+ const results = await check36.run(ctx);
4787
+ const severityOverride = getCheckSeverityOverride(check36.id, ctx.config);
4872
4788
  if (severityOverride) {
4873
4789
  for (const r of results) {
4874
4790
  r.severity = severityOverride;
4875
4791
  }
4876
4792
  }
4877
- const lintLevel = ruleLevel(ctx.config.lint.rules, check37.id);
4878
- if (lintLevel === "off")
4879
- results = [];
4880
- else if (lintLevel) {
4881
- for (const r of results)
4882
- r.severity = lintLevel === "warn" ? "warning" : "error";
4883
- }
4884
4793
  if (results.length === 0) {
4885
- passed.push(check37.id);
4794
+ passed.push(check36.id);
4886
4795
  } else {
4887
4796
  allResults.push(...results);
4888
4797
  }
4889
4798
  } catch (err) {
4890
4799
  allResults.push({
4891
- checkId: check37.id,
4800
+ checkId: check36.id,
4892
4801
  severity: "error",
4893
- message: `Check "${check37.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4802
+ message: `Check "${check36.id}" threw: ${err instanceof Error ? err.message : String(err)}`
4894
4803
  });
4895
4804
  }
4896
4805
  }
@@ -4915,30 +4824,20 @@ async function runChecks(ctx, opts = {}) {
4915
4824
  }
4916
4825
  };
4917
4826
  }
4918
- function emptyReport() {
4919
- return {
4920
- results: [],
4921
- errors: [],
4922
- warnings: [],
4923
- infos: [],
4924
- passed: [],
4925
- summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
4926
- };
4927
- }
4928
4827
 
4929
4828
  // src/checks/tool-check.ts
4930
4829
  async function checkToolAvailability(config, opts = {}) {
4931
4830
  const { checks: checks2 } = selectChecks(opts, config);
4932
4831
  const toolToChecks = new Map;
4933
- for (const check37 of checks2) {
4934
- if (!check37.requiredTools?.length)
4832
+ for (const check36 of checks2) {
4833
+ if (!check36.requiredTools?.length)
4935
4834
  continue;
4936
- for (const tool of check37.requiredTools) {
4835
+ for (const tool of check36.requiredTools) {
4937
4836
  const existing = toolToChecks.get(tool);
4938
4837
  if (existing) {
4939
- existing.push(check37.id);
4838
+ existing.push(check36.id);
4940
4839
  } else {
4941
- toolToChecks.set(tool, [check37.id]);
4840
+ toolToChecks.set(tool, [check36.id]);
4942
4841
  }
4943
4842
  }
4944
4843
  }
@@ -4954,11 +4853,11 @@ async function checkToolAvailability(config, opts = {}) {
4954
4853
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
4955
4854
  const missingSet = new Set(missing);
4956
4855
  const skippedChecks = [];
4957
- for (const check37 of checks2) {
4958
- if (!check37.requiredTools?.length)
4856
+ for (const check36 of checks2) {
4857
+ if (!check36.requiredTools?.length)
4959
4858
  continue;
4960
- if (check37.requiredTools.some((t) => missingSet.has(t))) {
4961
- skippedChecks.push(check37.id);
4859
+ if (check36.requiredTools.some((t) => missingSet.has(t))) {
4860
+ skippedChecks.push(check36.id);
4962
4861
  }
4963
4862
  }
4964
4863
  return { available, missing, skippedChecks, toolToChecks };
@@ -5029,7 +4928,7 @@ async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
5029
4928
  const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
5030
4929
  if (!SHIPPED_ASSET_EXTS.has(ext))
5031
4930
  continue;
5032
- const dir = dirname2(resolve5(file));
4931
+ const dir = dirname3(resolve5(file));
5033
4932
  if (escapesProjectRoot(projectRoot, dir))
5034
4933
  dirs.add(dir);
5035
4934
  }
@@ -5190,7 +5089,7 @@ function planRuns(opts) {
5190
5089
  async function executeValidation(args) {
5191
5090
  const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
5192
5091
  const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
5193
- const config = resolveConfig(args.skipStylelint ? { validate: { source: { stylelint: false } } } : {}, manifest);
5092
+ const config = resolveConfig(args.skipStylelint ? { checks: { "source.stylelint": "off" } } : {}, manifest);
5194
5093
  const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
5195
5094
  const pdfPath = typeof args.pdf === "string" ? resolve5(args.pdf) : undefined;
5196
5095
  const inputDir = typeof args.input === "string" ? resolve5(args.input) : undefined;
@@ -5223,7 +5122,7 @@ async function executeValidation(args) {
5223
5122
  let htmlPath;
5224
5123
  const outDir = resolveOutputDir(manifestDir, config.title);
5225
5124
  if (inputDir) {
5226
- const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
5125
+ const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.chapters);
5227
5126
  markdownFiles = relMarkdown.map((f) => join3(manifestDir, canonicalChapterId(f)));
5228
5127
  const relStyles = await resolveActiveStyles(manifestDir, config.styles);
5229
5128
  const projectCssFiles = relStyles.map((rel) => resolve5(manifestDir, rel));
@@ -5259,7 +5158,7 @@ async function executeValidation(args) {
5259
5158
  };
5260
5159
  if (args.fix) {
5261
5160
  const selected = selectChecks(runnerOptions, config).checks;
5262
- if (selected.some((check37) => check37.id === MARKDOWNLINT_CHECK_ID)) {
5161
+ if (selected.some((check36) => check36.id === MARKDOWNLINT_CHECK_ID)) {
5263
5162
  await applyMarkdownlintFixes(context);
5264
5163
  } else {
5265
5164
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -5362,16 +5261,16 @@ ${list}
5362
5261
 
5363
5262
  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);
5364
5263
  }
5365
- function computeGates(format, opts, config) {
5264
+ function computeGates(format, opts) {
5366
5265
  if (format === "html") {
5367
5266
  if (opts.skipLint || opts.skipPreValidate || opts.skipPostValidate) {
5368
5267
  log.info("Validation/lint flags ignored for --format html (no validation phases apply)");
5369
5268
  }
5370
5269
  return { skipStylelint: false, preValidate: false, postValidate: false };
5371
5270
  }
5372
- const skipStylelint = !!opts.skipLint || config.lint.enabled === false;
5373
- const preValidate = !opts.skipPreValidate && config.validate.enabled !== false;
5374
- const postValidate = format === "pdfx" && !opts.skipPostValidate && config.validate.enabled !== false;
5271
+ const skipStylelint = !!opts.skipLint;
5272
+ const preValidate = !opts.skipPreValidate;
5273
+ const postValidate = format === "pdfx" && !opts.skipPostValidate;
5375
5274
  return { skipStylelint, preValidate, postValidate };
5376
5275
  }
5377
5276
 
@@ -5407,7 +5306,7 @@ async function resolveBuildContext(opts) {
5407
5306
  }, manifest);
5408
5307
  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) };
5409
5308
  const outDir = target.kind === "file" ? path7.dirname(target.file) : target.dir;
5410
- const gates = computeGates(format, opts, config);
5309
+ const gates = computeGates(format, opts);
5411
5310
  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")}`);
5412
5311
  return {
5413
5312
  opts,
@@ -5463,8 +5362,8 @@ async function runQualityGates(ctx) {
5463
5362
  }
5464
5363
  async function renderBook(ctx) {
5465
5364
  const { config, gates, renderDir, workDir, opts, format } = ctx;
5466
- if (config.source.files && config.source.files.length > 0) {
5467
- log.info(`Using specified files (${config.source.files.length} total)`);
5365
+ if (config.chapters && config.chapters.length > 0) {
5366
+ log.info(`Using specified files (${config.chapters.length} total)`);
5468
5367
  } else {
5469
5368
  log.info("Using all .md files in alphabetical order");
5470
5369
  }
@@ -5477,7 +5376,7 @@ async function renderBook(ctx) {
5477
5376
  print: config.print,
5478
5377
  printArea: { ...config.page, preset: config.preset, spec: config.spec },
5479
5378
  styles: config.styles,
5480
- files: config.source.files,
5379
+ files: config.chapters,
5481
5380
  plugins,
5482
5381
  pluginStyles,
5483
5382
  onChapterWarnings: (file, warnings) => {
@@ -5628,7 +5527,7 @@ class PdfOutput {
5628
5527
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5629
5528
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5630
5529
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5631
- const { buildNativePdf } = await import("./engine-1r6q80tq.js");
5530
+ const { buildNativePdf } = await import("./engine-6txhxmmv.js");
5632
5531
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5633
5532
  title: config.title,
5634
5533
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5665,7 +5564,7 @@ class PdfOutput {
5665
5564
  }
5666
5565
  const flattenPages = config.pdfx.flattenPages;
5667
5566
  if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
5668
- const { unsplittableTextReasons } = await import("./pdf-inspect-wnw4za71.js");
5567
+ const { unsplittableTextReasons } = await import("./pdf-inspect-b3mf81ex.js");
5669
5568
  const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
5670
5569
  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.`);
5671
5570
  return null;
@@ -5938,7 +5837,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
5938
5837
  ...config,
5939
5838
  printArea: config.page && { ...config.page, preset: config.preset, spec: config.spec }
5940
5839
  }, {
5941
- files: config.source?.files ?? null,
5840
+ files: config.chapters ?? null,
5942
5841
  onCssAssets: (copies) => {
5943
5842
  for (const copy of copies)
5944
5843
  nextAssets.set(copy.to, copy.from);
@@ -7175,12 +7074,10 @@ import { readFile as readFile24 } from "node:fs/promises";
7175
7074
  import path13 from "node:path";
7176
7075
  import { isMap } from "yaml";
7177
7076
  function writePageBounds(doc, page) {
7178
- const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
7179
7077
  ensurePageMap(doc);
7180
7078
  doc.setIn(["page", "width"], page.width);
7181
7079
  doc.setIn(["page", "height"], page.height);
7182
- if (tolerance !== undefined)
7183
- doc.setIn(["page", "tolerance"], tolerance);
7080
+ doc.deleteIn(["page", "tolerance"]);
7184
7081
  }
7185
7082
  function deletePageBounds(doc) {
7186
7083
  const block = doc.get("page", true);
@@ -7193,6 +7090,14 @@ function deletePageBounds(doc) {
7193
7090
  if (block.items.length === 0)
7194
7091
  doc.delete("page");
7195
7092
  }
7093
+ function deletePageMargin(doc) {
7094
+ const block = doc.get("page", true);
7095
+ if (!isMap(block))
7096
+ return;
7097
+ block.delete("margin");
7098
+ if (block.items.length === 0)
7099
+ doc.delete("page");
7100
+ }
7196
7101
  function writePageMargin(doc, margin) {
7197
7102
  ensurePageMap(doc);
7198
7103
  if (!isMap(doc.getIn(["page", "margin"], true)))
@@ -7228,11 +7133,12 @@ function manifestGeometry(doc) {
7228
7133
  const base = preset ? PRESETS[preset].page : null;
7229
7134
  const width = numberAt(doc, ["page", "width"]) ?? base?.width;
7230
7135
  const height = numberAt(doc, ["page", "height"]) ?? base?.height;
7231
- const tolerance = numberAt(doc, ["page", "tolerance"]) ?? base?.tolerance ?? 0.5;
7136
+ const tolerance = base?.tolerance ?? 0.5;
7232
7137
  const bounds = width !== undefined && height !== undefined ? { width, height, tolerance } : null;
7138
+ const presetSizeOverridden = base !== null && (numberAt(doc, ["page", "width"]) !== undefined || numberAt(doc, ["page", "height"]) !== undefined);
7233
7139
  const at = (edge) => doc.getIn(["page", "margin", edge]);
7234
7140
  const margins = MARGIN_EDGES.every((edge) => isNonNegativePoints(at(edge))) ? { top: at("top"), bottom: at("bottom"), inside: at("inside"), outside: at("outside") } : undefined;
7235
- return { preset, bounds, ...margins ? { margins } : {} };
7141
+ return { preset, bounds, presetSizeOverridden, ...margins ? { margins } : {} };
7236
7142
  }
7237
7143
  async function stylesheetMargins(projectDir, manifestStyles) {
7238
7144
  const d = DEFAULT_PAGE_MARGIN_PT;
@@ -7267,10 +7173,9 @@ async function stylesheetMargins(projectDir, manifestStyles) {
7267
7173
  }
7268
7174
  async function setupOf(projectDir, doc) {
7269
7175
  const { margins, ...geometry } = manifestGeometry(doc);
7270
- if (margins)
7271
- return { ...geometry, margins, marginsFrom: "manifest" };
7272
7176
  const styles = doc.toJS()?.styles;
7273
- return { ...geometry, ...await stylesheetMargins(projectDir, Array.isArray(styles) ? styles : undefined) };
7177
+ const sheets = await stylesheetMargins(projectDir, Array.isArray(styles) ? styles : undefined);
7178
+ return margins ? { ...geometry, margins, marginsFrom: "manifest", fallbackMarginsFrom: sheets.marginsFrom } : { ...geometry, ...sheets, fallbackMarginsFrom: sheets.marginsFrom };
7274
7179
  }
7275
7180
  async function readPageSetup(projectDir) {
7276
7181
  const { doc } = await loadManifestDoc(projectDir);
@@ -7280,7 +7185,7 @@ async function setPageSetup(projectDir, change) {
7280
7185
  const { doc, file } = await loadManifestDoc(projectDir);
7281
7186
  const size2 = change.size ? pageSizeForChoice(change.size) : null;
7282
7187
  let margins;
7283
- if (change.margins !== undefined) {
7188
+ if (change.margins) {
7284
7189
  const against = size2 ?? manifestGeometry(doc).bounds;
7285
7190
  if (!against)
7286
7191
  throw new UsageError("Choose a page size before setting margins: the manifest records none.");
@@ -7295,6 +7200,8 @@ async function setPageSetup(projectDir, change) {
7295
7200
  }
7296
7201
  if (margins)
7297
7202
  writePageMargin(doc, margins);
7203
+ else if (change.margins === null)
7204
+ deletePageMargin(doc);
7298
7205
  await writeManifestDoc(file, doc);
7299
7206
  return setupOf(projectDir, doc);
7300
7207
  }
@@ -7304,10 +7211,10 @@ function starterOverridesCss() {
7304
7211
  return `/**
7305
7212
  * Your book's own styles. Rules here win over your look's.
7306
7213
  *
7307
- * In the app you rarely need this file: Setup > Looks & features adds a
7308
- * look, Setup > Design changes its fonts, colors and sizes (those changes
7309
- * are saved here as :root custom properties), and Setup > Details sets the
7310
- * page size and margins (in the manifest).
7214
+ * In the app you rarely need this file: Setup > Look chooses a look and
7215
+ * changes its fonts and colors (those changes are saved here as :root
7216
+ * custom properties), Setup > Advanced lists every setting, and Setup > Book
7217
+ * sets the page size and margins (in the manifest).
7311
7218
  *
7312
7219
  * To change the look from the command line: gutterpress ext remove <old>
7313
7220
  * <book>, then gutterpress ext add <look> <book>.
@@ -7346,9 +7253,6 @@ function requirePreset(preset, customPage) {
7346
7253
  } else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
7347
7254
  throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
7348
7255
  }
7349
- if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
7350
- throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
7351
- }
7352
7256
  return preset;
7353
7257
  }
7354
7258
  function resolveScaffoldTargets(requested, preset) {
@@ -7517,7 +7421,7 @@ async function adoptFolder(options) {
7517
7421
  if (!existsSync9(bookCssPath)) {
7518
7422
  await writeFile7(bookCssPath, starterOverridesCss(), "utf8");
7519
7423
  }
7520
- const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
7424
+ const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
7521
7425
  `);
7522
7426
  const manifest = `title: "${escapeYamlScalar(title)}"
7523
7427
  authors:
@@ -7525,8 +7429,7 @@ authors:
7525
7429
  preset: dtrpg
7526
7430
  targets:
7527
7431
  - dtrpg
7528
- source:
7529
- files:
7432
+ chapters:
7530
7433
  ${filesYaml}
7531
7434
  styles:
7532
7435
  - styles/book.css
@@ -7574,7 +7477,7 @@ async function firstSourceFile(manifestPath) {
7574
7477
  } catch {
7575
7478
  return;
7576
7479
  }
7577
- const filesIdx = text.search(/^\s*files:\s*$/m);
7480
+ const filesIdx = text.search(/^(?:chapters|\s+files):\s*$/m);
7578
7481
  if (filesIdx === -1)
7579
7482
  return;
7580
7483
  const rest = text.slice(filesIdx);
@@ -8654,15 +8557,16 @@ async function setManifestFields(projectDir, updates) {
8654
8557
  }
8655
8558
  if (hasKey(updates, "sourceFiles")) {
8656
8559
  const f = updates.sourceFiles;
8657
- if (f === null || Array.isArray(f) && f.length === 0) {
8658
- doc.deleteIn(["source", "files"]);
8659
- const source = doc.get("source", true);
8660
- if (isMap3(source) && source.items.length === 0) {
8560
+ const source = doc.get("source", true);
8561
+ if (isMap3(source)) {
8562
+ source.delete("files");
8563
+ if (source.items.length === 0)
8661
8564
  doc.delete("source");
8662
- }
8663
- } else {
8664
- doc.setIn(["source", "files"], f);
8665
8565
  }
8566
+ if (f === null || Array.isArray(f) && f.length === 0)
8567
+ doc.delete("chapters");
8568
+ else
8569
+ doc.set("chapters", f);
8666
8570
  }
8667
8571
  await writeManifestDoc(file, doc);
8668
8572
  return readManifestFields(projectDir);
@@ -8682,13 +8586,13 @@ async function readManifestFields(projectDir) {
8682
8586
  if (isSeq2(targetsNode)) {
8683
8587
  out.targets = targetsNode.items.map((i) => scalarString(i)).filter((s) => s !== null);
8684
8588
  }
8685
- const filesNode = doc.getIn(["source", "files"], true);
8589
+ const sourceNode = doc.get("source", true);
8590
+ const oldSpelling = isMap3(sourceNode) && sourceNode.has("files");
8591
+ const filesNode = doc.has("chapters") ? doc.get("chapters", true) : doc.getIn(["source", "files"], true);
8686
8592
  if (isSeq2(filesNode)) {
8687
8593
  out.sourceFiles = filesNode.items.map((i) => scalarString(i) ?? "");
8688
- } else {
8689
- const sourceNode = doc.get("source", true);
8690
- if (isMap3(sourceNode) && sourceNode.has("files"))
8691
- out.sourceFiles = null;
8594
+ } else if (doc.has("chapters") || oldSpelling) {
8595
+ out.sourceFiles = null;
8692
8596
  }
8693
8597
  return out;
8694
8598
  } catch {
@@ -10839,7 +10743,7 @@ var FAILURE_MESSAGES = {
10839
10743
  auth: "The Git server didn't accept the saved connection. Reconnect to this server with a current access token and try again.",
10840
10744
  "not-found": "The server answered, but no repository was found at that address. It may have been moved, renamed, or you may no longer have access.",
10841
10745
  unreachable: "Couldn't reach the Git server. Check your internet connection (and VPN, if this is a private server), then try again.",
10842
- "ssh-unsupported": "This book's online address uses SSH (git@…), which gutterpress can't check or sync with. Everything on this computer still works — sync with your usual Git tool.",
10746
+ "ssh-unsupported": "This book's online address uses SSH (git@…), which Gutterpress can't check or sync with. Everything on this computer still works — sync with your usual Git tool.",
10843
10747
  "insecure-transport": "This address isn't secure, so the saved connection wasn't sent — connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to use a saved connection.",
10844
10748
  tls: "The server's security certificate couldn't be verified. If this is a private server with its own certificate, ask its administrator about trusting it (NODE_EXTRA_CA_CERTS).",
10845
10749
  unknown: "The connection test failed unexpectedly. The server may not be a Git server, or it may be temporarily unavailable."
@@ -12569,7 +12473,7 @@ async function resolvePublishProvider(id, book) {
12569
12473
  const ext = await bookExtensions(book);
12570
12474
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12571
12475
  if (npmEntry) {
12572
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-9dh7c4tp.js");
12476
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-dymzcasz.js");
12573
12477
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12574
12478
  }
12575
12479
  const publishers = await discover(ext);
@@ -12607,7 +12511,7 @@ function infoFor(p) {
12607
12511
  }
12608
12512
  function pluginProvider(p) {
12609
12513
  let loading;
12610
- const load = () => loading ??= import("./plugins-80emnf31.js").then((m) => m.loadPublishFunction(p));
12514
+ const load = () => loading ??= import("./plugins-fec8t2bh.js").then((m) => m.loadPublishFunction(p));
12611
12515
  return {
12612
12516
  info: infoFor(p),
12613
12517
  async authenticate() {