gutterpress 0.11.17-alpha.4 → 0.11.17-alpha.6

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 (29) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-fam0tqg6.js → build-yswp3npj.js} +4 -4
  3. package/dist/{cli-8apk8571.js → cli-0wtnw98y.js} +1 -1
  4. package/dist/{cli-2kc34kme.js → cli-an3pxzzk.js} +77 -110
  5. package/dist/{cli-pqcb2nnb.js → cli-jjbeqx94.js} +1 -1
  6. package/dist/{cli-jyy9pqf3.js → cli-ycbg7mxk.js} +5 -5
  7. package/dist/cli.js +12 -12
  8. package/dist/{components-dhgv2sm7.yaml → components-zzqdpt3m.yaml} +1 -1
  9. package/dist/{doctor-zre5k5fg.js → doctor-xjgxr88y.js} +2 -2
  10. package/dist/{engine-3xqgm82d.js → engine-5d1rpxxq.js} +2 -2
  11. package/dist/{engine-zdb41ay1.js → engine-yf2w9hj7.js} +1 -1
  12. package/dist/{ext-hfqcg6ax.js → ext-n664e0ka.js} +4 -4
  13. package/dist/favicon-272ga6y0.ico +0 -0
  14. package/dist/{gutterpress-viewer-1n793gwd.js → gutterpress-viewer-ez7k4ha7.js} +19 -1
  15. package/dist/{index-hrx0gfkj.js → index-09h3n36d.js} +2 -2
  16. package/dist/{index-gp503ze9.js → index-2vd5b3vy.js} +76 -109
  17. package/dist/{index-q1xqxrh9.js → index-vmcfahe2.js} +4 -4
  18. package/dist/index.js +3 -3
  19. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  20. package/dist/lib/markdown/markers.d.ts +14 -6
  21. package/dist/{new-bjdf7bhg.js → new-8xwac8nd.js} +4 -4
  22. package/dist/{page-templates-7md4ev7d.css → page-templates-tx9ryt19.css} +4 -11
  23. package/dist/{preflight-gvw01661.js → preflight-62c2r37w.js} +4 -4
  24. package/dist/{preview-1cschq8s.js → preview-qdby30fx.js} +4 -4
  25. package/dist/{publish-ddqrb8s9.js → publish-v9tdq7kc.js} +4 -4
  26. package/dist/render.js +73 -106
  27. package/dist/{validate-3hjkgxrz.js → validate-9nszbckg.js} +4 -4
  28. package/package.json +1 -1
  29. package/dist/favicon-wkbm9cjn.ico +0 -0
package/dist/api/index.js CHANGED
@@ -135,11 +135,11 @@ import {
135
135
  testRemoteAccess,
136
136
  updateExtensions,
137
137
  validateProjectExtensions
138
- } from "../index-gp503ze9.js";
138
+ } from "../index-2vd5b3vy.js";
139
139
  import"../index-ht1bcqdd.js";
140
140
  import {
141
141
  BuildError
142
- } from "../index-q1xqxrh9.js";
142
+ } from "../index-vmcfahe2.js";
143
143
  import {
144
144
  DEFAULT_PRINT_OPTS,
145
145
  assertMilestone,
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-2kc34kme.js";
4
+ } from "./cli-an3pxzzk.js";
5
5
  import {
6
6
  log
7
- } from "./cli-8apk8571.js";
7
+ } from "./cli-0wtnw98y.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-pqcb2nnb.js";
14
+ } from "./cli-jjbeqx94.js";
15
15
  import"./cli-ht1bcqdd.js";
16
- import"./cli-jyy9pqf3.js";
16
+ import"./cli-ycbg7mxk.js";
17
17
  import {
18
18
  BuildError
19
19
  } from "./cli-46ycxe6r.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-pqcb2nnb.js";
3
+ } from "./cli-jjbeqx94.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -13,12 +13,12 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-8apk8571.js";
16
+ } from "./cli-0wtnw98y.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-pqcb2nnb.js";
21
+ } from "./cli-jjbeqx94.js";
22
22
  import {
23
23
  blackTextToGray
24
24
  } from "./cli-ht1bcqdd.js";
@@ -27,7 +27,7 @@ import {
27
27
  getAssetPath,
28
28
  getAssetsDir,
29
29
  lastPageMode
30
- } from "./cli-jyy9pqf3.js";
30
+ } from "./cli-ycbg7mxk.js";
31
31
  import {
32
32
  BuildError,
33
33
  EXIT_CODES
@@ -1169,6 +1169,16 @@ function resolveContainerShape(name, pluginName, decl) {
1169
1169
  ...check
1170
1170
  };
1171
1171
  }
1172
+ function presetVariantOf(name, pluginName, rawDecl) {
1173
+ const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
1174
+ if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
1175
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
1176
+ }
1177
+ if (preset.variant !== undefined && typeof preset.variant !== "string") {
1178
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
1179
+ }
1180
+ return preset.variant;
1181
+ }
1172
1182
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1173
1183
  const pluginName = originOf.get(name);
1174
1184
  if (typeof rawDecl !== "object" || rawDecl === null || Array.isArray(rawDecl)) {
@@ -1197,22 +1207,22 @@ function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1197
1207
  if (targetRaw.alias !== undefined) {
1198
1208
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is itself an alias. ` + `Chained aliases are not supported — alias the base marker directly.`);
1199
1209
  }
1200
- const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
1201
- if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
1202
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
1203
- }
1204
- if (preset.variant !== undefined && typeof preset.variant !== "string") {
1205
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
1206
- }
1207
1210
  const container = resolveContainerShape(rawDecl.alias, originOf.get(rawDecl.alias), targetRaw);
1208
- return { name, baseKind: rawDecl.alias, presetVariant: preset.variant, ...container };
1211
+ return { name, baseKind: rawDecl.alias, presetVariant: presetVariantOf(name, pluginName, rawDecl), ...container };
1209
1212
  }
1210
- return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
1213
+ const presetVariant = presetVariantOf(name, pluginName, rawDecl);
1214
+ return {
1215
+ name,
1216
+ baseKind: name,
1217
+ ...presetVariant !== undefined ? { presetVariant } : {},
1218
+ ...resolveContainerShape(name, pluginName, rawDecl)
1219
+ };
1211
1220
  }
1212
1221
  var CORE_MARKER_OWNER = "Gutterpress";
1213
1222
  var CORE_DECLARED_MARKERS = {
1214
1223
  columns: {
1215
1224
  class: "gp-columns",
1225
+ preset: { variant: "2" },
1216
1226
  variants: { 1: "gp-columns-1", 2: "gp-columns-2", 3: "gp-columns-3", 4: "gp-columns-4", 5: "gp-columns-5" }
1217
1227
  }
1218
1228
  };
@@ -1388,20 +1398,35 @@ function plugin(md, pluginOptions = {}) {
1388
1398
  const at = declaredFrameIndex(kind);
1389
1399
  if (at === -1)
1390
1400
  return false;
1391
- while (declaredFrames.length > at) {
1392
- const frame = declaredFrames.pop();
1393
- out.push(new state.Token("layout_component_close", frame.tag, -1));
1394
- }
1401
+ drainDeclaredFrames(at);
1395
1402
  return true;
1396
1403
  }
1397
- function drainDeclaredFrames() {
1398
- while (declaredFrames.length) {
1404
+ function sectionAbove(length) {
1405
+ const section = stack.get("section");
1406
+ return !!section && section.inColumns && declaredFrameIndex("columns") === length - 1;
1407
+ }
1408
+ function sectionHostDepth() {
1409
+ const section = stack.get("section");
1410
+ if (section && !section.inColumns)
1411
+ return 0;
1412
+ return declaredFrameIndex("columns") + 1;
1413
+ }
1414
+ function drainDeclaredFrames(keep = 0) {
1415
+ while (declaredFrames.length > keep) {
1416
+ if (sectionAbove(declaredFrames.length)) {
1417
+ stack.close("section");
1418
+ continue;
1419
+ }
1399
1420
  const frame = declaredFrames.pop();
1400
1421
  out.push(new state.Token("layout_component_close", frame.tag, -1));
1401
1422
  }
1402
1423
  }
1403
1424
  function drainDeclaredFramesAtEof() {
1404
1425
  while (declaredFrames.length) {
1426
+ if (sectionAbove(declaredFrames.length)) {
1427
+ stack.close("section");
1428
+ continue;
1429
+ }
1405
1430
  const frame = declaredFrames.pop();
1406
1431
  if (!frame.decl.autoCloseAtEof) {
1407
1432
  warn2(state.env, frame.line || 0, "declared_marker_eof_close", `An open @${frame.kind} reached end-of-document; closing it automatically. Add ` + `@end-${frame.kind} to close it explicitly, or declare autoCloseAt: ["eof"] on this ` + `marker if running to end-of-document is expected.`, null);
@@ -1545,7 +1570,7 @@ function plugin(md, pluginOptions = {}) {
1545
1570
  __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
1546
1571
  };
1547
1572
  }
1548
- function openSection(meta) {
1573
+ function openSection(meta, inColumns = false) {
1549
1574
  const t = new state.Token("layout_section_open", "div", 1);
1550
1575
  const component = meta.__component || null;
1551
1576
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -1558,25 +1583,11 @@ function plugin(md, pluginOptions = {}) {
1558
1583
  kind: "section",
1559
1584
  classes: meta.attrs && meta.attrs.class || "",
1560
1585
  sawContent: false,
1586
+ ...inColumns ? { inColumns: true } : {},
1561
1587
  meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
1562
1588
  openToken: t
1563
1589
  }, t);
1564
1590
  }
1565
- function breakHost() {
1566
- return stack.get("section") || stack.get("page") || stack.get("spread");
1567
- }
1568
- function gridClassOf(frame) {
1569
- if (!frame || !frame.classes)
1570
- return null;
1571
- return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
1572
- }
1573
- function warnBreakInsideGrid(kind, line, meta) {
1574
- const host = breakHost();
1575
- const gridClass = gridClassOf(host);
1576
- if (!gridClass)
1577
- return;
1578
- warn2(state.env, line, "break_inside_grid", `@${kind} inside a grid container: the enclosing @${host.kind} carries .${gridClass}, so this break's <div> becomes a grid ITEM — it takes a cell of its own, corrupts the grid's placement, and print and the live preview then disagree about which page the content after it lands on. Move the break outside the grid @${host.kind}, or remove it — grid rows already flow and fragment across pages on their own.`, meta);
1579
- }
1580
1591
  function warnIfEmptyDecoratedSection(closingKind, closingLine) {
1581
1592
  const sec = stack.get("section");
1582
1593
  if (!sec || sec.sawContent)
@@ -1641,14 +1652,15 @@ function plugin(md, pluginOptions = {}) {
1641
1652
  continue;
1642
1653
  }
1643
1654
  if (kind === "section") {
1644
- drainDeclaredFrames();
1655
+ const keep = sectionHostDepth();
1656
+ drainDeclaredFrames(keep);
1645
1657
  warnIfEmptyDecoratedSection("section", line);
1646
1658
  stack.close("section");
1647
1659
  const spread = stack.get("spread");
1648
1660
  if (spread && spread.noPagesYet) {
1649
1661
  warn2(state.env, line, "spread_without_pages", "@section inside a spread without an explicit @page. Allowed (adhoc spread), but explicit @page markers give stronger control.", meta);
1650
1662
  }
1651
- openSection(meta);
1663
+ openSection(meta, keep > 0);
1652
1664
  continue;
1653
1665
  }
1654
1666
  if (kind === "continue") {
@@ -1657,7 +1669,8 @@ function plugin(md, pluginOptions = {}) {
1657
1669
  warn2(state.env, line, "continue_without_section", "@continue used without an open @section; ignoring marker.", meta);
1658
1670
  continue;
1659
1671
  }
1660
- drainDeclaredFrames();
1672
+ const keep = sectionHostDepth();
1673
+ drainDeclaredFrames(keep);
1661
1674
  const contMeta = {
1662
1675
  name: section.meta.name,
1663
1676
  attrs: { ...section.meta.attrs || {} }
@@ -1672,11 +1685,10 @@ function plugin(md, pluginOptions = {}) {
1672
1685
  cls.push("gp-continued");
1673
1686
  contMeta.attrs.class = cls.join(" ");
1674
1687
  stack.close("section");
1675
- openSection(contMeta);
1688
+ openSection(contMeta, keep > 0);
1676
1689
  continue;
1677
1690
  }
1678
1691
  if (kind === "page-break") {
1679
- warnBreakInsideGrid("page-break", line, meta);
1680
1692
  const t = new state.Token("layout_page_break", "div", 0);
1681
1693
  t.meta = { line };
1682
1694
  t.attrSet("class", "gp-page-break");
@@ -1685,7 +1697,6 @@ function plugin(md, pluginOptions = {}) {
1685
1697
  continue;
1686
1698
  }
1687
1699
  if (kind === "column-break") {
1688
- warnBreakInsideGrid("column-break", line, meta);
1689
1700
  const t = new state.Token("layout_column_break", "div", 0);
1690
1701
  t.meta = { line };
1691
1702
  t.attrSet("class", "gp-column-break");
@@ -1694,7 +1705,7 @@ function plugin(md, pluginOptions = {}) {
1694
1705
  continue;
1695
1706
  }
1696
1707
  if (kind === "end-section") {
1697
- drainDeclaredFrames();
1708
+ drainDeclaredFrames(sectionHostDepth());
1698
1709
  warnIfEmptyDecoratedSection("end-section", line);
1699
1710
  stack.close("section");
1700
1711
  continue;
@@ -1792,11 +1803,20 @@ body { margin: 0; }
1792
1803
  .gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
1793
1804
 
1794
1805
  :where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
1806
+ /* A lead-in paragraph ("**ROLL A DIE!**" above a roll table, "You will need:"
1807
+ above a list) stays with the table or list it introduces, the way a heading
1808
+ stays with what follows it -- otherwise the label can end a column or page
1809
+ with its table starting on the next (#370). Standard break-after; the
1810
+ :has() selector is Chromium's own. :where() keeps specificity at zero so an
1811
+ author rule such as p { break-after: auto } wins. Deliberately table/ul/ol only,
1812
+ also when a @section wraps it (the second rule): the lead-in then keeps with
1813
+ the section that opens with the table or list. */
1814
+ :where(p):has(+ :where(table, ul, ol)) { break-after: avoid; }
1815
+ :where(p):has(+ :where(.section) > :where(table, ul, ol):first-child) { break-after: avoid; }
1795
1816
  :where(img, svg, video) { max-width: 100%; }
1796
1817
  :where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }
1797
1818
  :where(p) > :where(img:not([class])) { max-height: calc(var(--gp-content-h) - 4px); object-fit: contain; }
1798
1819
  :where(figure) { break-inside: avoid; }
1799
- :where(.section, figure) > :where(:first-child) { break-before: avoid; }
1800
1820
 
1801
1821
  `;
1802
1822
 
@@ -1856,26 +1876,24 @@ var GUTTERPRESS_CSS = `
1856
1876
  .gp-tight { --gp-gap: 0.5em; }
1857
1877
  .gp-loose { --gp-gap: 2em; }
1858
1878
 
1859
- /* column runs — plain CSS Multi-column, exposed as author vocabulary so
1860
- "put this in two columns" does not require borrowing a styled container
1861
- from the book's own component layer. That borrowing is what this exists
1862
- to prevent: a book whose theme paints .section chrome by default gives
1863
- every author who opens a section just to start a column run a panel they
1864
- did not ask for, and the book then needs a reset rule to take it back.
1865
- With a neutral primitive the author opts into columns and nothing else.
1879
+ /* column runs — plain CSS Multi-column, emitted by the core @columns marker
1880
+ (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay
1881
+ content out in columns, and @column-break moves to the next column.
1882
+ The classes below are the CSS that marker emits, not author vocabulary.
1866
1883
 
1867
1884
  Permanent vocabulary, not a shim: Chromium implements multicol natively
1868
1885
  and these rules are the standard properties verbatim, so there is no
1869
1886
  spec gap here to remove later. Deliberately minimal — column-fill is
1870
- NOT set, because the correct value depends on whether the run fragments
1871
- across pages (auto packs each page's columns; the CSS initial balance is
1872
- right for a run that fits on one page) and only the author knows which.
1873
- --gp-column-gap is author-settable.
1874
- .gp-columns is what the core @columns marker emits (markers.js); on its
1875
- own it is two columns, and @columns 1 to 5 add the matching count, which
1876
- wins because it comes later at the same specificity. */
1877
- .gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {
1887
+ NOT set (the CSS initial balance applies).
1888
+ Themes style a run through two tokens instead of writing column rules:
1889
+ --gp-column-gap default 1.5em
1890
+ --gp-column-rule default none (any column-rule shorthand value)
1891
+ Both hooks sit at zero specificity (:where), so any author rule wins.
1892
+ .gp-columns is two columns on its own; a count class (bare @columns
1893
+ emits gp-columns-2) wins because it comes later at the same specificity. */
1894
+ :where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {
1878
1895
  column-gap: var(--gp-column-gap, 1.5em);
1896
+ column-rule: var(--gp-column-rule, none);
1879
1897
  }
1880
1898
  .gp-columns { columns: 2; }
1881
1899
  .gp-columns-1 { columns: 1; }
@@ -1884,54 +1902,8 @@ var GUTTERPRESS_CSS = `
1884
1902
  .gp-columns-4 { columns: 4; }
1885
1903
  .gp-columns-5 { columns: 5; }
1886
1904
 
1887
- /* the per-shape decisions the paragraph above deliberately leaves to the
1888
- author, named instead of left as raw CSS every book was reinventing
1889
- (CLAUDE.md §0: "behavior broadly useful to non-technical authors belongs
1890
- in core").
1891
- Permanent vocabulary, standard properties verbatim — same rationale as
1892
- the column/grid runs above. One name each, no aliases:
1893
- .gp-columns-all column-span: all a heading or block that
1894
- spans every column in the
1895
- run it sits inside.
1896
- .gp-columns-flow column-fill: auto a run that FRAGMENTS across
1897
- pages — every page's
1898
- columns fill instead of
1899
- only the last one
1900
- balancing (the dead-column
1901
- collapse the build's
1902
- engine.multicol.dead-column
1903
- warning names this fix
1904
- for).
1905
- .gp-columns-balanced column-fill: balance a run that fits on ONE
1906
- page (the CSS initial
1907
- value — ragged columns
1908
- would be wrong here). */
1909
- .gp-columns-all { column-span: all; }
1910
- .gp-columns-flow { column-fill: auto; }
1911
- .gp-columns-balanced { column-fill: balance; }
1912
-
1913
- /* grid runs — the SLOTTED counterpart to the column runs above. Grid places
1914
- each child into the next cell, across then down (deterministic slots: card
1915
- layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
1916
- down then across. Same neutral-primitive rationale as .gp-columns-*, and
1917
- permanent vocabulary for the same reason: standard CSS Grid verbatim, no
1918
- spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
1919
- grid rows fragment across sheets with EXACT print/viewer parity — 2- and
1920
- 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
1921
- break-inside:avoid, gap geometry — so a grid taller than the page is safe,
1922
- no fit-one-page constraint. Two things to know, not fix:
1923
- - on a min-height page root (MARKER_CSS), default align-content
1924
- stretches rows apart to fill the page — identically in both engines.
1925
- Authors wanting packed rows set align-content: start.
1926
- - a @page-break / @column-break marker DIRECTLY inside a grid container
1927
- becomes a grid item and corrupts placement (the one measured parity
1928
- break); markers.js diagnoses it (break_inside_grid).
1929
- --gp-grid-gap is author-settable. */
1930
- .gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1931
- .gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1932
-
1933
1905
  /* fragmentation controls — generic pagination utilities for any element,
1934
- independent of the column/grid vocabulary above (a book reached for
1906
+ independent of the column vocabulary above (a book reached for
1935
1907
  these just as often outside a multicol run: keeping a card whole,
1936
1908
  forcing a section to start a fresh page). Standard properties only —
1937
1909
  this project is Chromium-only (CLAUDE.md), so there are no legacy
@@ -2089,13 +2061,8 @@ var GP_CLASSES = new Set([
2089
2061
  "gp-columns-3",
2090
2062
  "gp-columns-4",
2091
2063
  "gp-columns-5",
2092
- "gp-columns-all",
2093
- "gp-columns-flow",
2094
- "gp-columns-balanced",
2095
2064
  "gp-no-break",
2096
2065
  "gp-break-before",
2097
- "gp-grid-2",
2098
- "gp-grid-3",
2099
2066
  "gp-shape",
2100
2067
  "gp-pin",
2101
2068
  "gp-top",
@@ -9650,7 +9617,7 @@ class PdfOutput {
9650
9617
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
9651
9618
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
9652
9619
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
9653
- const { buildNativePdf } = await import("./engine-zdb41ay1.js");
9620
+ const { buildNativePdf } = await import("./engine-yf2w9hj7.js");
9654
9621
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
9655
9622
  title: config.title,
9656
9623
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.17-alpha.4",
7
+ version: "0.11.17-alpha.6",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
@@ -1113,7 +1113,7 @@ import { dirname, join, resolve } from "node:path";
1113
1113
  import { fileURLToPath } from "node:url";
1114
1114
 
1115
1115
  // src/assets/favicon.ico
1116
- var favicon_default = "./favicon-wkbm9cjn.ico";
1116
+ var favicon_default = "./favicon-272ga6y0.ico";
1117
1117
 
1118
1118
  // src/assets/manifest.schema.json
1119
1119
  var manifest_schema_default = "./manifest.schema-gh0jjve9.json";
@@ -1131,7 +1131,7 @@ var preview_shell_default = "./preview-shell-xwn84kqr.js";
1131
1131
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
1132
1132
 
1133
1133
  // src/assets/engine/gutterpress-viewer.js
1134
- var gutterpress_viewer_default = "./gutterpress-viewer-1n793gwd.js";
1134
+ var gutterpress_viewer_default = "./gutterpress-viewer-ez7k4ha7.js";
1135
1135
 
1136
1136
  // src/assets/engine/gutterpress-agent.js
1137
1137
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -1185,7 +1185,7 @@ var package_default2 = "./package-aenwjew3.json";
1185
1185
  var README_default2 = "./README-3nj919rm.md";
1186
1186
 
1187
1187
  // src/assets/extension-templates/theme/components.yaml
1188
- var components_default = "./components-dhgv2sm7.yaml";
1188
+ var components_default = "./components-zzqdpt3m.yaml";
1189
1189
 
1190
1190
  // src/assets/extension-templates/theme/snippets/callout.md
1191
1191
  var callout_default = "./callout-hv56m16h.md";
@@ -1200,7 +1200,7 @@ var base_default = "./base-ypqh9n7f.css";
1200
1200
  var components_default2 = "./components-aam0fe2z.css";
1201
1201
 
1202
1202
  // src/assets/extension-templates/theme/styles/page-templates.css
1203
- var page_templates_default = "./page-templates-7md4ev7d.css";
1203
+ var page_templates_default = "./page-templates-tx9ryt19.css";
1204
1204
 
1205
1205
  // src/assets/extension-templates/theme/styles/page-rules.css
1206
1206
  var page_rules_default = "./page-rules-nsj3aw42.css";
@@ -2226,7 +2226,7 @@ ${mapCss}`;
2226
2226
  if (pageBackgrounds.length)
2227
2227
  log(`audit: ${pageBackgrounds.length} unreferenced @page background image(s)`);
2228
2228
  for (const d of multicol)
2229
- diagnose("engine.multicol.dead-column", `${d} runs over more than one page in columns, and only the last page's columns get balanced — earlier pages are left with an empty column. Add .gp-columns-flow to ${d} (column-fill: auto) so every page's columns fill.`);
2229
+ diagnose("engine.multicol.dead-column", `${d} runs over more than one page in columns, and only the last page's columns get balanced — earlier pages are left with an empty column. End the @columns run where the page ends and open a new @columns for the next page, or set column-fill: auto on ${d} in your stylesheet so every page's columns fill.`);
2230
2230
  if (multicol.length)
2231
2231
  log(`audit: ${multicol.length} fragmenting multicol warning(s)`);
2232
2232
  }
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-pqcb2nnb.js";
9
+ } from "./cli-jjbeqx94.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-bjdf7bhg.js").then((m) => m.default),
22
- preview: () => import("./preview-1cschq8s.js").then((m) => m.default),
23
- build: () => import("./build-fam0tqg6.js").then((m) => m.default),
24
- publish: () => import("./publish-ddqrb8s9.js").then((m) => m.default),
25
- validate: () => import("./validate-3hjkgxrz.js").then((m) => m.default),
26
- preflight: () => import("./preflight-gvw01661.js").then((m) => m.default),
27
- doctor: () => import("./doctor-zre5k5fg.js").then((m) => m.default),
28
- ext: () => import("./ext-hfqcg6ax.js").then((m) => m.default)
21
+ new: () => import("./new-8xwac8nd.js").then((m) => m.default),
22
+ preview: () => import("./preview-qdby30fx.js").then((m) => m.default),
23
+ build: () => import("./build-yswp3npj.js").then((m) => m.default),
24
+ publish: () => import("./publish-v9tdq7kc.js").then((m) => m.default),
25
+ validate: () => import("./validate-9nszbckg.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-62c2r37w.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-xjgxr88y.js").then((m) => m.default),
28
+ ext: () => import("./ext-n664e0ka.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-bjdf7bhg.js");
53
+ const { newArgs } = await import("./new-8xwac8nd.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-gvw01661.js");
65
+ const { preflightArgs } = await import("./preflight-62c2r37w.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-hfqcg6ax.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-n664e0ka.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }
@@ -47,5 +47,5 @@ page_templates:
47
47
  - name: reference
48
48
  recipe: "@page .{{PREFIX}}reference"
49
49
  emits: "div.page.{{PREFIX}}reference"
50
- notes: "Two columns with a rule. Prefer core's .gp-columns-2 for plain columns."
50
+ notes: "Ruled headings for a reference page. For columns, use core's @columns 2 ... @end-columns inside it."
51
51
  status: stable
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  getSystemDiagnostics,
3
3
  log
4
- } from "./cli-8apk8571.js";
4
+ } from "./cli-0wtnw98y.js";
5
5
  import {
6
6
  UsageError,
7
7
  rejectExtraPositionals,
8
8
  rejectUnknownFlags
9
- } from "./cli-pqcb2nnb.js";
9
+ } from "./cli-jjbeqx94.js";
10
10
  import"./cli-46ycxe6r.js";
11
11
  import"./cli-7fkhgtfn.js";
12
12
  import"./cli-37x76zdn.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  build
3
- } from "./index-hrx0gfkj.js";
3
+ } from "./index-09h3n36d.js";
4
4
  import {
5
5
  BuildError
6
- } from "./index-q1xqxrh9.js";
6
+ } from "./index-vmcfahe2.js";
7
7
  import"./index-rzm1phg0.js";
8
8
  import"./index-37x76zdn.js";
9
9
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  build
3
- } from "./cli-jyy9pqf3.js";
3
+ } from "./cli-ycbg7mxk.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./cli-46ycxe6r.js";
@@ -12,17 +12,17 @@ import {
12
12
  searchNpmExtensions,
13
13
  setExtensionEnabled,
14
14
  updateExtensions
15
- } from "./cli-2kc34kme.js";
16
- import"./cli-8apk8571.js";
15
+ } from "./cli-an3pxzzk.js";
16
+ import"./cli-0wtnw98y.js";
17
17
  import {
18
18
  UsageError,
19
19
  exitForUsage,
20
20
  rejectExtraPositionals,
21
21
  rejectUnknownFlags,
22
22
  resolveProjectDir
23
- } from "./cli-pqcb2nnb.js";
23
+ } from "./cli-jjbeqx94.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-jyy9pqf3.js";
25
+ import"./cli-ycbg7mxk.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
Binary file
@@ -1415,6 +1415,18 @@
1415
1415
  clearLeadingForcedBreaks(s.el);
1416
1416
  return strips;
1417
1417
  }
1418
+ var AVOID_BREAK = /^avoid(-page|-column)?$/;
1419
+ function keepChainHead(el) {
1420
+ let head = el;
1421
+ for (let prev = head.previousElementSibling;prev instanceof HTMLElement; prev = head.previousElementSibling) {
1422
+ const linked = AVOID_BREAK.test(getComputedStyle(prev).breakAfter) || AVOID_BREAK.test(getComputedStyle(head).breakBefore);
1423
+ if (!linked)
1424
+ break;
1425
+ head = prev;
1426
+ }
1427
+ return head;
1428
+ }
1429
+ var pushTargets = new WeakMap;
1418
1430
  function compensateRepeatedHeaders(strips, maxPasses = 24) {
1419
1431
  const warnings = [];
1420
1432
  let passes = 0;
@@ -1427,6 +1439,10 @@
1427
1439
  for (const shim of Array.from(table.querySelectorAll("tr.gp-thead-shim, tr.gp-tfoot-shim")))
1428
1440
  shim.remove();
1429
1441
  table.style.breakBefore = "";
1442
+ const target = pushTargets.get(table);
1443
+ if (target)
1444
+ target.style.breakBefore = "";
1445
+ pushTargets.delete(table);
1430
1446
  }
1431
1447
  const pushed = new Set;
1432
1448
  const footClaims = new Map;
@@ -1487,7 +1503,9 @@
1487
1503
  shim.remove();
1488
1504
  if (plan.push && !pushed.has(plan.table)) {
1489
1505
  pushed.add(plan.table);
1490
- plan.table.style.breakBefore = "column";
1506
+ const target = keepChainHead(plan.table);
1507
+ target.style.breakBefore = "column";
1508
+ pushTargets.set(plan.table, target);
1491
1509
  touched++;
1492
1510
  continue;
1493
1511
  }
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-q1xqxrh9.js";
22
+ } from "./index-vmcfahe2.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-rzm1phg0.js";
@@ -1329,7 +1329,7 @@ ${mapCss}`;
1329
1329
  if (pageBackgrounds.length)
1330
1330
  log(`audit: ${pageBackgrounds.length} unreferenced @page background image(s)`);
1331
1331
  for (const d of multicol)
1332
- diagnose("engine.multicol.dead-column", `${d} runs over more than one page in columns, and only the last page's columns get balanced — earlier pages are left with an empty column. Add .gp-columns-flow to ${d} (column-fill: auto) so every page's columns fill.`);
1332
+ diagnose("engine.multicol.dead-column", `${d} runs over more than one page in columns, and only the last page's columns get balanced — earlier pages are left with an empty column. End the @columns run where the page ends and open a new @columns for the next page, or set column-fill: auto on ${d} in your stylesheet so every page's columns fill.`);
1333
1333
  if (multicol.length)
1334
1334
  log(`audit: ${multicol.length} fragmenting multicol warning(s)`);
1335
1335
  }