gutterpress 0.11.22 → 0.11.23-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +23 -24
  2. package/dist/{README-3nj919rm.md → README-8ef336pp.md} +1 -1
  3. package/dist/api/index.d.ts +4 -4
  4. package/dist/api/index.js +16 -22
  5. package/dist/{build-53bw14h7.js → build-encgnv0c.js} +9 -10
  6. package/dist/{chapter-01-rkpyvjna.md → chapter-01-gsbjt584.md} +1 -1
  7. package/dist/chapter-01-s71xrf5q.md +69 -0
  8. package/dist/{chapter-01-7jwqg1ct.md → chapter-01-wgjfz4w6.md} +1 -1
  9. package/dist/checks/source/character-table.d.ts +12 -6
  10. package/dist/{cli-habkc5ng.js → cli-a35w7wzg.js} +5 -4
  11. package/dist/{cli-kx4hbnx2.js → cli-aqh6brew.js} +1 -1
  12. package/dist/{cli-1hxxwbaq.js → cli-evg8sbht.js} +1 -1
  13. package/dist/{cli-cjftezxr.js → cli-kh8v4vvw.js} +2 -1
  14. package/dist/{cli-ntwqmdnw.js → cli-pwwfxdgb.js} +846 -13
  15. package/dist/{cli-8kg0wjdt.js → cli-pz4bn98t.js} +185 -9
  16. package/dist/{cli-zj5xaepv.js → cli-sdmv4bba.js} +57 -117
  17. package/dist/{cli-ke86zsz6.js → cli-xq6djqbj.js} +51 -54
  18. package/dist/{index-r7jcsk6h.js → cli-y28j019d.js} +19 -10
  19. package/dist/cli.js +12 -12
  20. package/dist/{doctor-xka1frnx.js → doctor-5gtpgpd1.js} +3 -3
  21. package/dist/{engine-p93f06gj.js → engine-mc5br8g8.js} +1 -2
  22. package/dist/{engine-0ndvy8vj.js → engine-tn13gk0b.js} +3 -3
  23. package/dist/{ext-dwfqyy1j.js → ext-wn2a5afq.js} +18 -33
  24. package/dist/{extension-restore-ct1wk344.js → extension-restore-g8jnmmzq.js} +5 -6
  25. package/dist/{extension-restore-7vp3vh77.js → extension-restore-s3x1618g.js} +4 -4
  26. package/dist/{index-apc8w9w8.js → index-0skhjx3e.js} +180 -1
  27. package/dist/{index-4v7e621e.js → index-2bjcghze.js} +847 -13
  28. package/dist/index-46ycxe6r.js +18 -0
  29. package/dist/{index-53nrm5j1.js → index-4x6y8krm.js} +2 -4
  30. package/dist/{cli-h3phdstf.js → index-5x42amh8.js} +19 -10
  31. package/dist/{index-93pqkvty.js → index-h6yk5tjx.js} +5 -4
  32. package/dist/{index-kh7fayjv.js → index-j5z86r4a.js} +57 -54
  33. package/dist/{index-6cmn2wpd.js → index-x3ema2w3.js} +69 -128
  34. package/dist/index.js +17 -23
  35. package/dist/lib/build-runner.d.ts +1 -1
  36. package/dist/lib/extension-manager.d.ts +16 -43
  37. package/dist/lib/extension-scaffold.d.ts +3 -3
  38. package/dist/lib/extension-specifier.d.ts +23 -6
  39. package/dist/lib/markdown/assemble.d.ts +7 -1
  40. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  41. package/dist/lib/markdown/index.d.ts +4 -0
  42. package/dist/lib/markdown/math.d.ts +43 -0
  43. package/dist/lib/markdown/renderer.d.ts +11 -8
  44. package/dist/lib/markdown/table.d.ts +46 -0
  45. package/dist/lib/markdown/typography.d.ts +12 -0
  46. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  47. package/dist/lib/plugin-vendor.d.ts +11 -3
  48. package/dist/lib/presets.d.ts +1 -1
  49. package/dist/lib/project-gitignore.d.ts +2 -2
  50. package/dist/lib/project-scaffold.d.ts +6 -8
  51. package/dist/lib/project-templates.d.ts +6 -1
  52. package/dist/lib/snippets.d.ts +2 -1
  53. package/dist/lib/style-resolver.d.ts +1 -2
  54. package/dist/manifest-eagfhba6.yaml +19 -0
  55. package/dist/{manifest.schema-399bx3vc.json → manifest.schema-t7nx2stg.json} +12 -5
  56. package/dist/{new-1pawj2qf.js → new-mhs46pw3.js} +22 -21
  57. package/dist/{plugins-fr4fnzhb.js → plugins-1sxe0dkz.js} +4 -4
  58. package/dist/{plugins-g4dgw7mn.js → plugins-2cek4mz1.js} +2 -2
  59. package/dist/{preflight-ept9q57t.js → preflight-3k2b1x8e.js} +9 -10
  60. package/dist/preview/file-watcher.d.ts +1 -0
  61. package/dist/{preview-fxpnjv7k.js → preview-6p9v6rht.js} +9 -10
  62. package/dist/{publish-zhqqsgmr.js → publish-qgsngy8s.js} +9 -10
  63. package/dist/render.js +470 -11
  64. package/dist/schema/manifest.types.d.ts +8 -0
  65. package/dist/{source-provider-qbfknhyy.js → source-provider-98k5deda.js} +2 -2
  66. package/dist/{source-provider-2r74a7wp.js → source-provider-knmcam50.js} +2 -2
  67. package/dist/{validate-t68gvckq.js → validate-0v7xw6wa.js} +9 -10
  68. package/package.json +2 -1
  69. package/dist/cli-1ngqzg8s.js +0 -196
  70. package/dist/index-vay3ff1f.js +0 -213
  71. package/dist/package-97gm31sg.json +0 -10
  72. package/dist/package-gag6ykwn.json +0 -10
  73. package/dist/package-nrhkwf2m.json +0 -10
  74. package/dist/theme-h5recz6c.css +0 -61
  75. package/dist/theme-j2bagrfx.css +0 -59
  76. package/dist/theme-nn6d53zy.css +0 -71
@@ -151,7 +151,7 @@
151
151
  * carrying gp-left could start shifting; that is standards-tracking per
152
152
  * CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
153
153
  */
154
- export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-size-10 { width: 10%; max-width: 100%; }\n.gp-size-20 { width: 20%; max-width: 100%; }\n.gp-size-30 { width: 30%; max-width: 100%; }\n.gp-size-40 { width: 40%; max-width: 100%; }\n.gp-size-50 { width: 50%; max-width: 100%; }\n.gp-size-60 { width: 60%; max-width: 100%; }\n.gp-size-70 { width: 70%; max-width: 100%; }\n.gp-size-80 { width: 80%; max-width: 100%; }\n.gp-size-90 { width: 90%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, emitted by the core @columns marker\n (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay\n content out in columns, and @column-break moves to the next column.\n The classes below are the CSS that marker emits, not author vocabulary.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set (the CSS initial balance applies).\n Themes style a run through two tokens instead of writing column rules:\n --gp-column-gap default 1.5em\n --gp-column-rule default none (any column-rule shorthand value)\n Both hooks sit at zero specificity (:where), so any author rule wins.\n .gp-columns is two columns on its own; a count class (bare @columns\n emits gp-columns-2) wins because it comes later at the same specificity. */\n:where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {\n column-gap: var(--gp-column-gap, 1.5em);\n column-rule: var(--gp-column-rule, none);\n}\n.gp-columns { columns: 2; }\n.gp-columns-1 { columns: 1; }\n.gp-columns-2 { columns: 2; }\n.gp-columns-3 { columns: 3; }\n.gp-columns-4 { columns: 4; }\n.gp-columns-5 { columns: 5; }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n The four insets are the pin box: 0 (the text block's edge) unless an\n engine set --gp-pin-<edge> on the page root for an edge a pin uses (the\n art-safe line). Non-auto insets and the explicit centers are\n load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n top: var(--gp-pin-top, 0px);\n right: var(--gp-pin-right, 0px);\n bottom: var(--gp-pin-bottom, 0px);\n left: var(--gp-pin-left, 0px);\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* the escape hatch \u2014 the paper's edge, past the printer's safe area: the\n pin box loses its art-safe insets. Set on the pin itself, so it beats the\n values the page root hands down. */\n.gp-pin.gp-flush {\n --gp-pin-top: 0px;\n --gp-pin-right: 0px;\n --gp-pin-bottom: 0px;\n --gp-pin-left: 0px;\n}\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
154
+ export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-size-10 { width: 10%; max-width: 100%; }\n.gp-size-20 { width: 20%; max-width: 100%; }\n.gp-size-30 { width: 30%; max-width: 100%; }\n.gp-size-40 { width: 40%; max-width: 100%; }\n.gp-size-50 { width: 50%; max-width: 100%; }\n.gp-size-60 { width: 60%; max-width: 100%; }\n.gp-size-70 { width: 70%; max-width: 100%; }\n.gp-size-80 { width: 80%; max-width: 100%; }\n.gp-size-90 { width: 90%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, emitted by the core @columns marker\n (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay\n content out in columns, and @column-break moves to the next column.\n The classes below are the CSS that marker emits, not author vocabulary.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set (the CSS initial balance applies).\n Themes style a run through two tokens instead of writing column rules:\n --gp-column-gap default 1.5em\n --gp-column-rule default none (any column-rule shorthand value)\n Both hooks sit at zero specificity (:where), so any author rule wins.\n .gp-columns is two columns on its own; a count class (bare @columns\n emits gp-columns-2) wins because it comes later at the same specificity. */\n:where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {\n column-gap: var(--gp-column-gap, 1.5em);\n column-rule: var(--gp-column-rule, none);\n}\n.gp-columns { columns: 2; }\n.gp-columns-1 { columns: 1; }\n.gp-columns-2 { columns: 2; }\n.gp-columns-3 { columns: 3; }\n.gp-columns-4 { columns: 4; }\n.gp-columns-5 { columns: 5; }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n The four insets are the pin box: 0 (the text block's edge) unless an\n engine set --gp-pin-<edge> on the page root for an edge a pin uses (the\n art-safe line). Non-auto insets and the explicit centers are\n load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n top: var(--gp-pin-top, 0px);\n right: var(--gp-pin-right, 0px);\n bottom: var(--gp-pin-bottom, 0px);\n left: var(--gp-pin-left, 0px);\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* the escape hatch \u2014 the paper's edge, past the printer's safe area: the\n pin box loses its art-safe insets. Set on the pin itself, so it beats the\n values the page root hands down. */\n.gp-pin.gp-flush {\n --gp-pin-top: 0px;\n --gp-pin-right: 0px;\n --gp-pin-bottom: 0px;\n --gp-pin-left: 0px;\n}\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n\n/* Math (the bundled gutterpress-math feature, math.ts): MathJax's own rules\n for the <mjx-container> SVG output it emits, from its SVG output jax's\n stylesheet, less the ones for its interactive menu and tooltips. Inert in\n a book without formulas. */\nmjx-container[jax=\"SVG\"] { direction: ltr; }\nmjx-container[jax=\"SVG\"] > svg { overflow: visible; min-height: 1px; min-width: 1px; }\nmjx-container[jax=\"SVG\"] > svg a { fill: blue; stroke: blue; }\nmjx-container[jax=\"SVG\"][display=\"true\"] { display: block; text-align: center; margin: 1em 0; }\nmjx-container[jax=\"SVG\"][display=\"true\"][width=\"full\"] { display: flex; }\nmjx-container[jax=\"SVG\"][justify=\"left\"] { text-align: left; }\nmjx-container[jax=\"SVG\"][justify=\"right\"] { text-align: right; }\nmjx-container[jax=\"SVG\"] path[data-c], mjx-container[jax=\"SVG\"] use[data-c] { stroke-width: 3; }\nmjx-assistive-mml {\n position: absolute !important;\n top: 0px; left: 0px;\n clip: rect(1px, 1px, 1px, 1px);\n padding: 1px 0px 0px 0px !important;\n border: 0px !important;\n display: block !important;\n width: auto !important;\n overflow: hidden !important;\n user-select: none;\n}\nmjx-assistive-mml[display=\"block\"] { width: 100% !important; }\ng[data-mml-node=\"merror\"] > g { fill: red; stroke: red; }\ng[data-mml-node=\"merror\"] > rect[data-background] { fill: yellow; stroke: none; }\ng[data-mml-node=\"mtable\"] > line[data-line], svg[data-table] > g > line[data-line] { stroke-width: 70px; fill: none; }\ng[data-mml-node=\"mtable\"] > rect[data-frame], svg[data-table] > g > rect[data-frame] { stroke-width: 70px; fill: none; }\ng[data-mml-node=\"mtable\"] > .mjx-dashed, svg[data-table] > g > .mjx-dashed { stroke-dasharray: 140; }\ng[data-mml-node=\"mtable\"] > .mjx-dotted, svg[data-table] > g > .mjx-dotted { stroke-linecap: round; stroke-dasharray: 0,140; }\ng[data-mml-node=\"mtable\"] > g > svg { overflow: visible; }\n";
155
155
  /**
156
156
  * GP_CLASSES — every class an author may legitimately write with a `gp-`
157
157
  * prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
@@ -32,6 +32,8 @@ export declare function resolveActiveMarkdownFiles(inputDir: string, configuredF
32
32
  */
33
33
  export declare function renderChapters(inputDir: string, opts?: {
34
34
  title?: string;
35
+ /** See {@link AssembleBookHtmlOptions.language}. */
36
+ language?: string;
35
37
  /** See {@link AssembleBookHtmlOptions.print}. */
36
38
  print?: PrintPadding;
37
39
  /** See {@link AssembleBookHtmlOptions.printArea}. */
@@ -73,6 +75,8 @@ export declare function renderChapters(inputDir: string, opts?: {
73
75
  */
74
76
  export declare function renderChaptersToFile(inputDir: string, outDir: string, opts?: {
75
77
  title?: string;
78
+ /** See {@link AssembleBookHtmlOptions.language}. */
79
+ language?: string;
76
80
  /** See {@link AssembleBookHtmlOptions.print}. */
77
81
  print?: PrintPadding;
78
82
  /** See {@link AssembleBookHtmlOptions.printArea}. */
@@ -0,0 +1,43 @@
1
+ import type MarkdownIt from "markdown-it";
2
+ /**
3
+ * LaTeX math — `$…$` in a line, `$$…$$` on its own lines — typeset by
4
+ * MathJax (`mathjax-full`, the same engine arXiv and Wikipedia print with).
5
+ *
6
+ * Bundled, OPT-IN markdown feature (`gutterpress-math` in
7
+ * `BUILTIN_OPTIONAL_PLUGINS`, renderer.ts; "Math" in
8
+ * `BUNDLED_EXTENSIONS`, extension-specifier.ts), exactly like the Callouts
9
+ * feature: a book that never lists it renders `$` as the character it is.
10
+ *
11
+ * WHY SVG. Each formula is written as a drawing of its own glyphs, so the
12
+ * PDF needs no math font on the machine that builds it, no stylesheet and
13
+ * no font files in the project, prints the same everywhere, and is laid
14
+ * out when the Markdown is rendered — so the live preview paginates exactly
15
+ * like the printed PDF (docs/native-parity-gate.md). The MathML and HTML
16
+ * outputs of KaTeX and MathJax fail one or other of those: measured on a
17
+ * machine with no math font, KaTeX's MathML printed broken fractions, and
18
+ * its HTML output needs ~20 font files copied into every book.
19
+ *
20
+ * The `$` grammar is Pandoc's, as every markdown-it math plugin implements
21
+ * it (ported from markdown-it-katex, MIT, Copyright (c) 2016 Waylon Flinn,
22
+ * via markdown-it-mathjax3): an opening `$` is not followed by a space, a
23
+ * closing one is not preceded by a space and not followed by a digit. That
24
+ * is what keeps "$5 and $10" prose. It lives here rather than in a
25
+ * dependency because the table rule (table.ts) must know where a formula
26
+ * ends, so a `|` inside one does not split the cell — one scanner, two users.
27
+ *
28
+ * ERRORS are never silent: a formula MathJax cannot typeset renders in red,
29
+ * as MathJax does, AND is reported on `env.layoutWarnings` (`math_error`,
30
+ * with the line), the channel the marker parser uses, so the Problems panel
31
+ * and `gutterpress validate` name the mistake — "Missing close brace",
32
+ * "Undefined control sequence \fraction". MathJax's `noerrors` and
33
+ * `noundefined` packages, which would swallow exactly those, are left out.
34
+ *
35
+ * `\newcommand` definitions persist for the rest of the book's render and no
36
+ * further: the TeX state is created per renderer, so one build cannot see
37
+ * another's macros. Styling is in `GUTTERPRESS_CSS` (gutterpress-css.ts),
38
+ * MathJax's own rules for its output.
39
+ */
40
+ /** Index of the `$` that closes the inline formula opened at `start`; -1 when
41
+ * it opens none; -2 for an empty `$$`, which is never inline math. */
42
+ export declare function findInlineMathEnd(src: string, start: number, max: number): number;
43
+ export default function gpMath(md: MarkdownIt): void;
@@ -319,13 +319,14 @@ export interface LoadedPlugin {
319
319
  * error. (attrs/footnote/deflist are NOT here — they are always-on defaults
320
320
  * applied unconditionally below.)
321
321
  *
322
- * `gutterpress-gfm-alerts` (#237) is keyed differently from its four
323
- * siblings: it is not a real npm package, it is Gutterpress's OWN code
324
- * (gfm-alerts.ts) registered under a name that reads like one, matching the
325
- * shape "keyed by npm name" is written for. There is nothing to install
326
- * either way — the lookup below always wins before any npm resolution is
327
- * attempted (`plugins.ts`'s `loadPlugin`) — so a real npm package never
328
- * existing under this exact name costs nothing.
322
+ * `gutterpress-gfm-alerts` (#237) and `gutterpress-math` are keyed
323
+ * differently from the four markdown-it-* entries: they are not real npm
324
+ * packages, they are Gutterpress's OWN code (gfm-alerts.ts, math.ts)
325
+ * registered under names that read like one, matching the shape "keyed by
326
+ * npm name" is written for. There is nothing to install either way — the
327
+ * lookup below always wins before any npm resolution is attempted
328
+ * (`plugins.ts`'s `loadPlugin`) — so a real npm package never existing
329
+ * under either name costs nothing.
329
330
  */
330
331
  export declare const BUILTIN_OPTIONAL_PLUGINS: Record<string, GutterpressPlugin>;
331
332
  /**
@@ -376,7 +377,9 @@ export declare const BUILTIN_OPTIONAL_PLUGINS: Record<string, GutterpressPlugin>
376
377
  *
377
378
  * @param customPlugins - Optional array of custom plugins to load
378
379
  */
379
- export declare function createMarkdownRenderer(customPlugins?: LoadedPlugin[]): MarkdownIt;
380
+ export declare function createMarkdownRenderer(customPlugins?: LoadedPlugin[], options?: {
381
+ language?: string;
382
+ }): MarkdownIt;
380
383
  /**
381
384
  * Apply loaded plugins to a markdown-it instance.
382
385
  *
@@ -0,0 +1,46 @@
1
+ import type MarkdownIt from "markdown-it";
2
+ /**
3
+ * GFM tables, with one change to how a row is split into cells.
4
+ *
5
+ * This is markdown-it's own table rule (`lib/rules_block/table.mjs`, MIT,
6
+ * Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin), re-registered under the
7
+ * same name with its `escapedSplit` replaced by {@link splitCells}:
8
+ *
9
+ * - markdown-it splits a row at every unescaped `|` BEFORE any inline
10
+ * syntax is parsed (the GFM spec's rule), so a `|` inside a code span
11
+ * or a formula splits the cell, and the cells past the header's count
12
+ * are silently dropped. Here an inline span is opaque to the split: a
13
+ * code span always, and a `$…$` formula when the math feature is on
14
+ * (math.ts re-registers this rule with its own scanner). `\|` still
15
+ * escapes a pipe in plain cell text; inside a span nothing is touched,
16
+ * so `\|` in a formula is LaTeX's own `\|`.
17
+ * - a row with MORE cells than the header is reported
18
+ * (`table_extra_cells` on `env.layoutWarnings`, the channel Gutterpress's
19
+ * marker parser uses) instead of losing them without a word.
20
+ *
21
+ * Everything else is the upstream rule, verbatim where possible, so a table
22
+ * renders and paginates exactly as before.
23
+ */
24
+ /**
25
+ * Where to resume scanning a row after the span that starts at `start`
26
+ * (the character there is the span's opening delimiter): just past the
27
+ * span, or just past the opening delimiter when it opens nothing. Always
28
+ * greater than `start`.
29
+ */
30
+ export type SpanScanner = (src: string, start: number, max: number) => number;
31
+ /** The scanners a row is split with, by the character code that opens each span. */
32
+ export type SpanScanners = Readonly<Record<number, SpanScanner>>;
33
+ /** A CommonMark code span: a run of backticks closed by a run of the same length. */
34
+ export declare const scanCodeSpan: SpanScanner;
35
+ /** What core splits with: code spans. The math feature adds `$` (math.ts). */
36
+ export declare const DEFAULT_SPANS: SpanScanners;
37
+ /**
38
+ * Replace markdown-it's table rule with the span-aware one. Core applies it
39
+ * with code spans; a feature that adds an inline span (math) applies it
40
+ * again with its own scanner added, and the later registration wins.
41
+ * `at` resets the rule's `alt` chains, so upstream's are given again: a
42
+ * table may interrupt a paragraph.
43
+ */
44
+ export declare function gpTable(md: MarkdownIt, options?: {
45
+ spans?: SpanScanners;
46
+ }): void;
@@ -0,0 +1,12 @@
1
+ import type MarkdownIt from "markdown-it";
2
+ /**
3
+ * The quotation marks straight quotes print as — markdown-it's `quotes`
4
+ * option: outer open, outer close, inner open, inner close — for the book's
5
+ * language (manifest `language`, a BCP 47 tag). A language not listed gets
6
+ * the English marks. Only the languages whose convention is settled are
7
+ * here; add one with its source, not from memory. The desktop's selection
8
+ * search must fold every mark here; its test reads this table.
9
+ */
10
+ export declare const QUOTES: Record<string, string | string[]>;
11
+ export declare function quotesFor(language?: string): string | string[];
12
+ export declare function gpTypography(md: MarkdownIt): void;
@@ -22,7 +22,7 @@ export interface InstalledNpmPlugin {
22
22
  installRoot: string;
23
23
  /** Previous same-version directory held until the manifest commits. */
24
24
  backupRoot: string | null;
25
- /** Folders THIS install created (`plugins/`, `plugins/npm/`, `plugins/npm/<name>/`), outermost first. */
25
+ /** Folders THIS install created (`extensions/`, `extensions/npm/`, `extensions/npm/<name>/`), outermost first. */
26
26
  createdDirs: string[];
27
27
  warnings: string[];
28
28
  }
@@ -1,5 +1,7 @@
1
- /** Project-relative folder shared by local and vendored npm plugins. */
2
- export declare const PLUGINS_DIR = "plugins";
1
+ /** Project-relative folder every extension a book carries lives under: vendored npm copies in `npm/`, imports beside them. */
2
+ export declare const EXTENSIONS_DIR = "extensions";
3
+ /** Where books vendored before 0.11.23 keep their npm copies. Read, never written. */
4
+ export declare const LEGACY_PLUGINS_DIR = "plugins";
3
5
  export declare const VENDORED_NPM_DIR = "npm";
4
6
  export interface ParsedNpmPluginSpec {
5
7
  name: string;
@@ -15,8 +17,14 @@ export declare function isValidNpmPackageName(name: string): boolean;
15
17
  /** Parse `name`, `name@selector`, `@scope/name`, or `@scope/name@selector`. */
16
18
  export declare function parseNpmPluginSpec(input: string): ParsedNpmPluginSpec;
17
19
  export declare function isExactNpmVersion(version: string): boolean;
18
- /** Root containing one pinned package's private `node_modules` tree. */
20
+ /** Root containing one pinned package's private `node_modules` tree — where an install writes. */
19
21
  export declare function vendoredNpmPluginRoot(projectDir: string, name: string, version: string): string;
22
+ /**
23
+ * Every root a pinned package's copy may live at: the current folder first,
24
+ * then the one books vendored before 0.11.23 used. Resolution, removal and
25
+ * pruning all go through this list so they agree on where a copy can be.
26
+ */
27
+ export declare function vendoredNpmPluginRoots(projectDir: string, name: string, version: string): string[];
20
28
  /** npm-compatible package location within a versioned vendor root. */
21
29
  export declare function vendoredNpmPluginPackageDir(installRoot: string, name: string): string;
22
30
  export declare function toPosixPath(value: string): string;
@@ -8,7 +8,7 @@ import type { ResolvedConfig } from "../schema/manifest.types";
8
8
  * Every preset value is overridable from the manifest, leaf by leaf
9
9
  * (resolveConfig's mergeShape; precedence cli > manifest > target > preset).
10
10
  */
11
- export interface VendorPreset extends Omit<ResolvedConfig, "title" | "authors" | "preset" | "targets" | "page"> {
11
+ export interface VendorPreset extends Omit<ResolvedConfig, "title" | "authors" | "language" | "preset" | "targets" | "page"> {
12
12
  /** This preset's id: what the manifest's `preset:` names and a resolved config reports. */
13
13
  id: PresetId;
14
14
  /**
@@ -1,7 +1,7 @@
1
1
  /** Entries every book ignores, in the order they are appended. */
2
- export declare const PROJECT_GITIGNORE_ENTRIES: readonly ["dist/", "plugins/npm/"];
2
+ export declare const PROJECT_GITIGNORE_ENTRIES: readonly ["dist/", "extensions/npm/"];
3
3
  /**
4
- * Make sure `<projectDir>/.gitignore` ignores `dist/` and `plugins/npm/`,
4
+ * Make sure `<projectDir>/.gitignore` ignores `dist/` and `extensions/npm/`,
5
5
  * creating the file or appending what is missing. Returns the entries the
6
6
  * author's own rules re-include (left alone) so a caller can warn.
7
7
  */
@@ -6,7 +6,7 @@ import { type CustomPageOptions } from "./page-size.ts";
6
6
  * into the binary via `embedded-assets.ts`. `"book"` is the default; the others
7
7
  * give non-technical authors a head start for common formats (#29).
8
8
  */
9
- export type ProjectTemplateId = "book" | "zine" | "technical";
9
+ export type ProjectTemplateId = "book" | "zine" | "technical" | "paper";
10
10
  /**
11
11
  * How (or whether) to put the new project under local version history.
12
12
  *
@@ -43,12 +43,10 @@ export interface CreateProjectOptions {
43
43
  */
44
44
  templateDir?: string;
45
45
  /**
46
- * Which vendor preset the new book is designed for (ADR 0008). REQUIRED
47
- * when scaffolding a built-in template — creation flows make the author
48
- * choose; the resolveConfig dtrpg fallback exists for hand-written
49
- * manifests, not for tooling. Written into the generated manifest as an
50
- * explicit `preset:` line. Ignored with `templateDir`: a saved template's
51
- * manifest carries its preset as part of the captured design.
46
+ * Which preset the new book is designed for (ADR 0008), written into the
47
+ * generated manifest as an explicit `preset:` line. Optional: left out, the
48
+ * template's own `preset:` stands (every starter carries one, and a saved
49
+ * template's manifest carries its preset as part of the captured design).
52
50
  */
53
51
  preset?: PresetId;
54
52
  /**
@@ -137,7 +135,7 @@ export interface AdoptFolderOptions {
137
135
  * Adopt an EXISTING folder as a gutterpress project, in place (no new subfolder).
138
136
  * Writes a `manifest.yaml` (using the folder's existing top-level `.md` files as
139
137
  * `source.files`, or scaffolding a `chapter-01.md` when there are none), copies
140
- * a starter `styles/book.css`, ensures `dist/` and `plugins/npm/` are gitignored
138
+ * a starter `styles/book.css`, ensures `dist/` and `extensions/npm/` are gitignored
141
139
  * (see `ensureProjectGitignore`), and optionally initialises local version history.
142
140
  *
143
141
  * NON-DESTRUCTIVE (global never-overwrite rule): refuses if the folder is
@@ -1,5 +1,6 @@
1
+ import type { ProjectTemplateId } from "./project-scaffold.ts";
1
2
  /** The built-in templates shipped as embedded assets. */
2
- export declare const BUILT_IN_TEMPLATE_IDS: readonly ["book", "zine", "technical"];
3
+ export declare const BUILT_IN_TEMPLATE_IDS: readonly ["book", "zine", "technical", "paper"];
3
4
  /** Author-friendly metadata for one template (built-in or custom). */
4
5
  export interface TemplateInfo {
5
6
  /** Stable id. For built-ins, the {@link ProjectTemplateId}; for custom, a slug. */
@@ -22,6 +23,10 @@ export interface TemplateInfo {
22
23
  /** The `targets:` this template's manifest declares, when it has one. */
23
24
  targets?: string[];
24
25
  }
26
+ export declare const BUILT_IN_META: Record<ProjectTemplateId, {
27
+ label: string;
28
+ description: string;
29
+ }>;
25
30
  /**
26
31
  * List the built-in templates. The `preset`/`targets` each one starts a book
27
32
  * from are read from that template's own embedded manifest — one source of
@@ -74,7 +74,8 @@ export declare function deleteSnippet(projectDir: string, fileName: string): Pro
74
74
  /**
75
75
  * One plugin-declared marker (a "component", `export const markers`) the
76
76
  * project can use, with the example snippet the editor inserts for it.
77
- * Unrelated to the `gutterpress.components` catalog file, which nothing reads.
77
+ * Unrelated to the `gutterpress.components` catalog file, which the desktop's
78
+ * class catalog reads for class completions.
78
79
  */
79
80
  export interface MarkerComponent {
80
81
  /** The marker name, without `@` — e.g. `term-box`. */
@@ -14,8 +14,7 @@ export declare function relDisplay(projectDir: string, absPath: string): string;
14
14
  * BOTH the renderer (to `<link>` them) and the editor (to edit them), so they
15
15
  * can never disagree. Returns project-relative paths:
16
16
  * 1. the manifest `styles:` list, if it has any entries; else
17
- * 2. the first conventional stylesheet the project actually has
18
- * (`FALLBACK_PRIORITY`); else
17
+ * 2. `styles/book.css`, when the project has it; else
19
18
  * 3. the first discovered `.css` anywhere we scan (deterministic); else
20
19
  * 4. `[]` — the project has no stylesheet (an honest empty, never a phantom
21
20
  * link to a missing file).
@@ -0,0 +1,19 @@
1
+ title: "{{TITLE}}"
2
+ authors:
3
+ - "{{AUTHOR}}"
4
+ language: en
5
+ source:
6
+ files:
7
+ - chapter-01.md
8
+ styles:
9
+ - styles/book.css
10
+ # A paper prints on US Letter; for A4 set width 595 and height 842.
11
+ page:
12
+ width: 612
13
+ height: 792
14
+ extensions:
15
+ - markdown-it-sup
16
+ - gutterpress-math
17
+ # The built-in academic look. Style over it from styles/book.css.
18
+ - academic
19
+ preset: book
@@ -11,6 +11,13 @@
11
11
  "default": "Document",
12
12
  "examples": ["My Book Title", "Technical Documentation", "Game Module"]
13
13
  },
14
+ "language": {
15
+ "type": "string",
16
+ "description": "The book's language as a BCP 47 tag. It is the lang of the built HTML, which hyphenation follows, and sets the quotation marks straight quotes print as.",
17
+ "default": "en",
18
+ "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$",
19
+ "examples": ["en", "de", "de-CH", "fr", "pt-BR"]
20
+ },
14
21
  "authors": {
15
22
  "type": "array",
16
23
  "description": "List of document authors.",
@@ -37,7 +44,7 @@
37
44
  },
38
45
  "styles": {
39
46
  "type": "array",
40
- "description": "Project-relative CSS files to <link> into the rendered book, applied in order. If omitted, Gutterpress discovers one: styles/book.css (what `gutterpress new` scaffolds), else four legacy names kept only for pre-existing projects — css/print.css, css/index.css, css/style.css, css/main.css — else the first discovered .css, else none. New projects should set this explicitly or use styles/book.css.",
47
+ "description": "Project-relative CSS files to <link> into the rendered book, applied in order. If omitted, Gutterpress discovers one: styles/book.css (what `gutterpress new` scaffolds), else the first .css file found in the project root, styles/ or css/, else none. New projects should set this explicitly or use styles/book.css.",
41
48
  "items": {
42
49
  "type": "string",
43
50
  "minLength": 1,
@@ -51,7 +58,7 @@
51
58
  },
52
59
  "extensions": {
53
60
  "type": "array",
54
- "description": "Extensions, in load order: markdown-it plugins, looks (stylesheets), component libraries — anything from npm or a folder. Each entry is a specifier: a bundled feature name (markdown-it-mark, markdown-it-sub, markdown-it-sup, markdown-it-abbr, gutterpress-gfm-alerts), a path starting with ./ or ../ (relative to this file), or an npm package name, optionally pinned as name@version. A later entry loads later: its markdown runs after earlier entries' and its CSS wins ties. The project's own `styles` always load after every extension.",
61
+ "description": "Extensions, in load order: markdown-it plugins, looks (stylesheets), component libraries — anything from npm or a folder. Each entry is a specifier: a bundled name (a markdown feature: markdown-it-mark, markdown-it-sub, markdown-it-sup, markdown-it-abbr, gutterpress-gfm-alerts, gutterpress-math; or a built-in look: clean-book, zine, technical-doc, academic), a path starting with ./ or ../ (relative to this file), or an npm package name, optionally pinned as name@version. A later entry loads later: its markdown runs after earlier entries' and its CSS wins ties. The project's own `styles` always load after every extension.",
55
62
  "items": {
56
63
  "oneOf": [
57
64
  {
@@ -200,17 +207,17 @@
200
207
  },
201
208
  "page": {
202
209
  "type": "object",
203
- "description": "Expected page geometry, in POINTS. VALIDATION BOUNDS ONLY — these values do NOT set the trim size. The real PDF page size comes from the CSS `@page` rule in your stylesheet; these numbers are only what the `pdf.print.page-size` check compares the produced PDF against. Set them to the trim size you expect so a CSS mistake gets caught — and note that a bleed-inclusive @page size will legitimately differ from the trim size recorded here.",
210
+ "description": "Page geometry, in POINTS (1in = 72pt): the trim size the book prints at. The build writes it as the default `@page { size }` unless a stylesheet's own plain `@page` rule sets a size, in which case that rule wins, and the `pdf.print.page-size` check compares the produced PDF against these numbers either way. A bleed-inclusive @page size legitimately differs from the trim recorded here.",
204
211
  "properties": {
205
212
  "width": {
206
213
  "type": "number",
207
- "description": "Expected page width in points (1in = 72pt). Validation bound only — the CSS @page rule sets the real width. Preset defaults: dtrpg 621, book 432 (6in).",
214
+ "description": "Page width in points (1in = 72pt): the trim the book prints at unless a stylesheet's plain @page rule sets its own, and what validation compares against. Preset defaults: dtrpg 621, book 432 (6in).",
208
215
  "exclusiveMinimum": 0,
209
216
  "examples": [612, 621, 432]
210
217
  },
211
218
  "height": {
212
219
  "type": "number",
213
- "description": "Expected page height in points (1in = 72pt). Validation bound only — the CSS @page rule sets the real height. Preset defaults: dtrpg 810, book 648 (9in).",
220
+ "description": "Page height in points (1in = 72pt): the trim the book prints at unless a stylesheet's plain @page rule sets its own, and what validation compares against. Preset defaults: dtrpg 810, book 648 (9in).",
214
221
  "exclusiveMinimum": 0,
215
222
  "examples": [792, 810, 648]
216
223
  },
@@ -3,38 +3,38 @@ import {
3
3
  EXTENSION_KINDS,
4
4
  scaffoldExtension,
5
5
  scaffoldProject
6
- } from "./cli-ke86zsz6.js";
6
+ } from "./cli-xq6djqbj.js";
7
7
  import {
8
8
  resolveGhostscript
9
- } from "./cli-1hxxwbaq.js";
9
+ } from "./cli-evg8sbht.js";
10
10
  import"./cli-ht1bcqdd.js";
11
- import"./cli-8kg0wjdt.js";
11
+ import"./cli-pz4bn98t.js";
12
12
  import"./cli-2ppw6q9v.js";
13
13
  import"./cli-eyyggect.js";
14
14
  import {
15
15
  isToolAvailable
16
16
  } from "./cli-asap4t1z.js";
17
- import"./cli-habkc5ng.js";
17
+ import"./cli-a35w7wzg.js";
18
18
  import"./cli-v5mp7a6q.js";
19
19
  import {
20
- PRESETS,
21
20
  PRESET_IDS,
22
21
  TARGETS,
23
- TARGET_IDS
24
- } from "./cli-zj5xaepv.js";
25
- import"./cli-ntwqmdnw.js";
26
- import"./cli-kx4hbnx2.js";
22
+ TARGET_IDS,
23
+ loadManifest,
24
+ resolveConfig
25
+ } from "./cli-sdmv4bba.js";
26
+ import"./cli-pwwfxdgb.js";
27
+ import"./cli-aqh6brew.js";
27
28
  import {
28
29
  UsageError,
29
30
  rejectExtraPositionals,
30
31
  rejectUnknownFlags
31
32
  } from "./cli-d8xjns66.js";
32
- import"./cli-cjftezxr.js";
33
- import"./cli-1ngqzg8s.js";
33
+ import"./cli-kh8v4vvw.js";
34
34
  import {
35
35
  EXIT_CODES
36
36
  } from "./cli-46ycxe6r.js";
37
- import"./cli-h3phdstf.js";
37
+ import"./cli-y28j019d.js";
38
38
  import"./cli-37x76zdn.js";
39
39
 
40
40
  // src/commands/new.ts
@@ -52,15 +52,15 @@ var newArgs = {
52
52
  },
53
53
  prefix: {
54
54
  type: "string",
55
- description: 'Class/custom-property prefix an extension claims (default: its slug, e.g. "field-notes-"); --kind plugin|theme only'
55
+ description: 'Class/custom-property prefix an extension claims (default: its slug, e.g. "field-notes-"); --kind plugin|look only'
56
56
  },
57
57
  description: {
58
58
  type: "string",
59
- description: "One-line description recorded in the extension's metadata; --kind plugin|theme only"
59
+ description: "One-line description recorded in the extension's metadata; --kind plugin|look only"
60
60
  },
61
61
  preset: {
62
62
  type: "string",
63
- description: `Vendor preset the book is designed for: ${PRESET_IDS.join(", ")} (required; custom also needs --page-width/--page-height)`
63
+ description: `Preset the book is designed for: ${PRESET_IDS.join(", ")} (default: the template's; custom also needs --page-width/--page-height)`
64
64
  },
65
65
  author: {
66
66
  type: "string",
@@ -192,7 +192,7 @@ async function runExtensionScaffold(kind, name, parentDir, args) {
192
192
  var new_default = defineCommand({
193
193
  meta: {
194
194
  name: "new",
195
- description: "Create a new Gutterpress book, plugin or theme from a starter template"
195
+ description: "Create a new Gutterpress book, plugin or look from a starter template"
196
196
  },
197
197
  args: newArgs,
198
198
  async run({ args, rawArgs }) {
@@ -228,8 +228,8 @@ var new_default = defineCommand({
228
228
  template = args.template;
229
229
  }
230
230
  const preset = typeof args.preset === "string" && args.preset ? args.preset : undefined;
231
- if (!preset || !PRESET_IDS.includes(preset)) {
232
- console.error(preset ? `Unknown preset "${preset}". Choose one of: ${PRESET_IDS.join(", ")}.` : `A preset is required: --preset <${PRESET_IDS.join("|")}>. ` + `Use "dtrpg" for DriveThruRPG print-on-demand, "book" for a neutral 6x9in trade book, ` + `or "custom" with --page-width/--page-height (points; 72pt = 1in).`);
231
+ if (preset && !PRESET_IDS.includes(preset)) {
232
+ console.error(`Unknown preset "${preset}". Choose one of: ${PRESET_IDS.join(", ")}. ` + `"dtrpg" is DriveThruRPG print-on-demand, "book" a neutral 6x9in trade book, ` + `"custom" takes --page-width/--page-height (points; 72pt = 1in).`);
233
233
  process.exit(EXIT_CODES.USAGE);
234
234
  }
235
235
  let targets;
@@ -261,10 +261,11 @@ var new_default = defineCommand({
261
261
  customPage,
262
262
  versionHistory: args.git === false ? "none" : "local-git"
263
263
  });
264
- const effectiveTargets = targets ?? [...PRESETS[preset].defaultTargets];
264
+ const written = resolveConfig({}, await loadManifest(result.projectDir));
265
265
  console.log(`Created book: ${result.projectDir}`);
266
266
  console.log(` manifest: ${result.manifestPath}`);
267
- console.log(` publish targets: ${effectiveTargets.length > 0 ? effectiveTargets.join(", ") : "none"}`);
267
+ console.log(` preset: ${written.preset}`);
268
+ console.log(` publish targets: ${written.targets.length > 0 ? written.targets.join(", ") : "none"}`);
268
269
  console.log(` start writing in: ${result.openFile}`);
269
270
  if (result.versionHistory === "local-git") {
270
271
  console.log(" version history: enabled (local snapshots)");
@@ -272,7 +273,7 @@ var new_default = defineCommand({
272
273
  console.log(` version history: not enabled (${result.versionHistoryError})`);
273
274
  }
274
275
  const neededTools = [
275
- ...new Set(effectiveTargets.flatMap((id) => TARGETS[id]?.requiredTools ?? []))
276
+ ...new Set(written.targets.flatMap((id) => TARGETS[id]?.requiredTools ?? []))
276
277
  ];
277
278
  const missingTools = [];
278
279
  for (const tool of neededTools) {
@@ -8,10 +8,10 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./cli-ntwqmdnw.js";
12
- import"./cli-kx4hbnx2.js";
13
- import"./cli-cjftezxr.js";
14
- import"./cli-h3phdstf.js";
11
+ } from "./cli-pwwfxdgb.js";
12
+ import"./cli-aqh6brew.js";
13
+ import"./cli-kh8v4vvw.js";
14
+ import"./cli-y28j019d.js";
15
15
  import"./cli-37x76zdn.js";
16
16
  export {
17
17
  loadPublishFunction,
@@ -8,8 +8,8 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./index-4v7e621e.js";
12
- import"./index-r7jcsk6h.js";
11
+ } from "./index-2bjcghze.js";
12
+ import"./index-5x42amh8.js";
13
13
  import"./index-37x76zdn.js";
14
14
  export {
15
15
  loadPublishFunction,
@@ -1,34 +1,33 @@
1
1
  import {
2
2
  executeValidation,
3
3
  reportMissingTools
4
- } from "./cli-ke86zsz6.js";
5
- import"./cli-1hxxwbaq.js";
4
+ } from "./cli-xq6djqbj.js";
5
+ import"./cli-evg8sbht.js";
6
6
  import"./cli-ht1bcqdd.js";
7
- import"./cli-8kg0wjdt.js";
7
+ import"./cli-pz4bn98t.js";
8
8
  import"./cli-2ppw6q9v.js";
9
9
  import"./cli-eyyggect.js";
10
10
  import"./cli-asap4t1z.js";
11
- import"./cli-habkc5ng.js";
11
+ import"./cli-a35w7wzg.js";
12
12
  import"./cli-v5mp7a6q.js";
13
13
  import {
14
14
  publishTargetFor
15
- } from "./cli-zj5xaepv.js";
16
- import"./cli-ntwqmdnw.js";
15
+ } from "./cli-sdmv4bba.js";
16
+ import"./cli-pwwfxdgb.js";
17
17
  import {
18
18
  log
19
- } from "./cli-kx4hbnx2.js";
19
+ } from "./cli-aqh6brew.js";
20
20
  import {
21
21
  UsageError,
22
22
  rejectExtraPositionals,
23
23
  rejectUnknownFlags
24
24
  } from "./cli-d8xjns66.js";
25
- import"./cli-cjftezxr.js";
26
- import"./cli-1ngqzg8s.js";
25
+ import"./cli-kh8v4vvw.js";
27
26
  import {
28
27
  BuildError,
29
28
  EXIT_CODES
30
29
  } from "./cli-46ycxe6r.js";
31
- import"./cli-h3phdstf.js";
30
+ import"./cli-y28j019d.js";
32
31
  import"./cli-37x76zdn.js";
33
32
 
34
33
  // src/commands/preflight.ts