gutterpress 0.11.23-alpha.7 → 0.11.23-beta.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 (69) hide show
  1. package/README.md +3 -1
  2. package/dist/{README-syg2hn83.md → README-8rct3axy.md} +2 -2
  3. package/dist/api/index.d.ts +3 -3
  4. package/dist/api/index.js +8 -6
  5. package/dist/{build-w572wq10.js → build-3e18wnqj.js} +9 -8
  6. package/dist/{cli-mmgtqnmr.js → cli-0ep11b0q.js} +1 -1
  7. package/dist/cli-65gy8ask.js +82 -0
  8. package/dist/{cli-6pv84kf3.js → cli-88arpdke.js} +6 -84
  9. package/dist/{cli-eatdqx3z.js → cli-9n9kg58f.js} +1 -1
  10. package/dist/{cli-58fsk6d7.js → cli-pd2cje5b.js} +187 -44
  11. package/dist/{cli-3cvq3n1c.js → cli-pnrararp.js} +189 -85
  12. package/dist/{cli-wyg7qstd.js → cli-tgnyvra3.js} +15 -5
  13. package/dist/{cli-31anr0r9.js → cli-v0evdpdf.js} +68 -28
  14. package/dist/{cli-5ba6bm6z.js → cli-x7gcdg6w.js} +1 -1
  15. package/dist/cli.js +12 -12
  16. package/dist/{doctor-2f3zqmza.js → doctor-2f4s9dyn.js} +3 -3
  17. package/dist/engine/compiler/postprocess.d.ts +3 -0
  18. package/dist/engine/shared/page-vars.d.ts +32 -0
  19. package/dist/engine/shared/pdf-inspect.d.ts +33 -0
  20. package/dist/{engine-4rabsgwq.js → engine-c4pxz79g.js} +2 -1
  21. package/dist/{engine-wm4fggac.js → engine-cs5g2ez0.js} +3 -3
  22. package/dist/{ext-0cgky8n3.js → ext-70gv86v5.js} +9 -8
  23. package/dist/{extension-restore-vghc58vx.js → extension-restore-chj5nx3t.js} +3 -3
  24. package/dist/{extension-restore-e7je32pr.js → extension-restore-pd87p8r2.js} +5 -4
  25. package/dist/{gutterpress-agent-d29ry3ak.js → gutterpress-agent-wyryqehx.js} +13 -4
  26. package/dist/{gutterpress-viewer-mafsby5k.js → gutterpress-viewer-ywkf49q5.js} +90 -166
  27. package/dist/{index-nfysshcc.js → index-0k43evt0.js} +309 -155
  28. package/dist/{index-wm4x7xbf.js → index-2t1gdma7.js} +178 -35
  29. package/dist/{index-cw03e9p4.js → index-7j3hkwya.js} +68 -28
  30. package/dist/{index-fp7xwvpr.js → index-80vg3ed6.js} +15 -5
  31. package/dist/{index-2vxzxyyj.js → index-swfdgn2n.js} +10 -10
  32. package/dist/index-tayzfr9c.js +99 -0
  33. package/dist/{index-hphcn7th.js → index-ytjkvwy5.js} +5 -85
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +234 -7
  36. package/dist/lib/book-css.d.ts +56 -0
  37. package/dist/lib/markdown/assemble.d.ts +58 -0
  38. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  39. package/dist/lib/publish/google-drive.d.ts +12 -4
  40. package/dist/lib/publish/manifest-publish.d.ts +6 -0
  41. package/dist/lib/publish/providers/gdrive-folder.d.ts +30 -0
  42. package/dist/lib/publish/run-publish.d.ts +6 -1
  43. package/dist/lib/publish/types.d.ts +52 -5
  44. package/dist/lib/remote-auth/converge-merge.d.ts +3 -3
  45. package/dist/lib/remote-auth/sync-messages.d.ts +2 -2
  46. package/dist/lib/root-tokens.d.ts +76 -0
  47. package/dist/lib/source-provider.d.ts +27 -0
  48. package/dist/lib/version-changes.d.ts +26 -16
  49. package/dist/{manifest-79zsgkpn.yaml → manifest-0vwaskj2.yaml} +1 -1
  50. package/dist/{manifest-2kc56wvw.yaml → manifest-aya31jwt.yaml} +1 -1
  51. package/dist/{manifest-41ddzm13.yaml → manifest-mffah3hr.yaml} +1 -1
  52. package/dist/{manifest-sqzm4681.yaml → manifest-wbcv8cbp.yaml} +2 -2
  53. package/dist/{manifest.schema-93sd7zje.json → manifest.schema-0jgj0jt1.json} +1 -1
  54. package/dist/{new-88rkrv71.js → new-d61n5cn0.js} +9 -8
  55. package/dist/{page-rules-ytj9p04v.css → page-rules-vnja6grt.css} +2 -2
  56. package/dist/{plugins-3aj0wc5h.js → plugins-0hhw6s17.js} +1 -1
  57. package/dist/{plugins-sprxsz5d.js → plugins-e0n46s6m.js} +3 -3
  58. package/dist/{preflight-11dg1qkk.js → preflight-fjayhjr5.js} +9 -8
  59. package/dist/{preview-nftp782j.js → preview-h815jrfb.js} +9 -8
  60. package/dist/{publish-6tgxfck9.js → publish-ft39zgma.js} +25 -9
  61. package/dist/render.js +77 -26
  62. package/dist/schema/manifest.types.d.ts +2 -1
  63. package/dist/{source-provider-n7vehh6t.js → source-provider-j1w7byeq.js} +5 -1
  64. package/dist/{source-provider-tgzet3j5.js → source-provider-tjaf303m.js} +5 -1
  65. package/dist/test-helpers/vocabulary.d.ts +12 -0
  66. package/dist/{tokens-n82t4k9h.css → tokens-a7grv940.css} +4 -5
  67. package/dist/{validate-pktqrye5.js → validate-b201dbrb.js} +9 -8
  68. package/package.json +1 -1
  69. package/dist/index-46ycxe6r.js +0 -18
@@ -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\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";
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, plus one\n bound of ours on the hidden MathML copy (#469, below). Inert in a book\n 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 /* Ours (#469). The browser lays the copy out in whatever math font it has,\n so it can be wider than the formula's SVG; ending a line, it crossed the\n column edge and Chromium shrank the whole print to fit. MathJax makes the\n container position: relative with the copy at its top left, so 100% is\n the formula's width. Clip and overflow still hide the copy and its MathML\n still reaches screen readers; only its box changes. Not the 1px\n visually-hidden box MathJax 2 used: its docs note VoiceOver on iOS could\n not reach a formula by touch in it. */\n max-width: 100% !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
@@ -38,17 +38,25 @@ export interface DriveFolder {
38
38
  * page — an author with more than one page of app-created folders would
39
39
  * otherwise see the destinations picker silently miss folders past the
40
40
  * first 100. (`ensureFolder`'s find-by-name lookup uses its own single
41
- * server-side query, {@link findFolderByName} below, and doesn't paginate.) */
41
+ * server-side query, {@link findFoldersByName} below, and doesn't paginate.) */
42
42
  export declare function listFolders(fetchImpl: typeof fetch, accessToken: string): Promise<DriveFolder[]>;
43
- /** Look up one folder by id (for verifying a manifest-recorded `folderId`
44
- * still exists and isn't trashed). Returns null when it's gone. */
43
+ /** Look up one folder by id (for verifying a recorded `folderId` is reachable
44
+ * with this account). Returns null when it isn't: trashed or deleted, or never
45
+ * opened through Gutterpress by this account — `drive.file` answers 404 for a
46
+ * folder this connection can't see, so the cases are one (#452). */
45
47
  export declare function getFolderById(fetchImpl: typeof fetch, accessToken: string, folderId: string): Promise<DriveFolder | null>;
46
48
  /** Create a folder at My Drive root. */
47
49
  export declare function createFolder(fetchImpl: typeof fetch, accessToken: string, name: string): Promise<DriveFolder>;
50
+ /** App-visible folders with exactly this name, most recently modified first,
51
+ * at most `limit` of them — a single server-side query (the same `q=` pattern
52
+ * as {@link findFileInFolder}), not a client-side scan of every app-created
53
+ * folder. `ensureFolder` asks for one; a caller that must know whether
54
+ * several share the name (#452: the CLI says which it used) asks for two. */
55
+ export declare function findFoldersByName(fetchImpl: typeof fetch, accessToken: string, name: string, limit?: number): Promise<DriveFolder[]>;
48
56
  /** Find-or-create a folder by name at My Drive root (D5's name-resolution
49
57
  * path, used when the manifest has no recorded `folderId` yet). One query,
50
58
  * not a client-side scan of every app-created folder — see
51
- * {@link findFolderByName}. */
59
+ * {@link findFoldersByName}. */
52
60
  export declare function ensureFolder(fetchImpl: typeof fetch, accessToken: string, name: string): Promise<DriveFolder>;
53
61
  export interface DriveFile {
54
62
  id: string;
@@ -25,6 +25,12 @@ export declare function setPublishProviderConfig(projectDir: string, providerId:
25
25
  * override). A folder inside the book is shared, and is written relative to
26
26
  * it (an absolute in-book path is rewritten) so it means the same thing on
27
27
  * every computer. An absolute path is never written to the manifest.
28
+ * - their own Google Drive folder for the book (`myFolderId`, `myFolder`; #452),
29
+ * chosen when the book's folder is out of their reach or just for them. It is
30
+ * recorded with the book's folder it stands in for (`myFolderFor`: the
31
+ * manifest's `folderId`, else its `folder` name) and the account it was
32
+ * chosen with (`myFolderAccount`), so it lapses when either changes (see
33
+ * providers/gdrive.ts). Clearing `myFolderId` clears the whole record.
28
34
  *
29
35
  * Only the values passed are classified; the manifest is not rewritten unless
30
36
  * one of them changes it. Empty values clear. Returns the settings as the
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Which Google Drive folder one person publishes to (#452), for the `gdrive`
3
+ * provider (gdrive.ts). The book's folder in the manifest is the team's; a
4
+ * person whose Google connection can't reach it publishes to a folder of their
5
+ * own, kept in their private store for the book, and that folder stands in for
6
+ * the book's only while the book's folder and their account are the ones it
7
+ * was chosen for.
8
+ */
9
+ import { type DriveFolder } from "../google-drive.ts";
10
+ import type { PublishDestinationStatus, PublishProduct, PublishRequest } from "../types.ts";
11
+ /** Default folder name when the manifest sets neither `folder` nor
12
+ * `folderId` (D5, ratified: one shared "Gutterpress" folder, not
13
+ * per-book-title folders). */
14
+ export declare const DEFAULT_FOLDER_NAME = "Gutterpress";
15
+ /**
16
+ * Where this person publishes (#452). The book's folder is the team's: an id,
17
+ * checked with the account this person publishes with (`drive.file` answers 404
18
+ * for a folder it can't see, so "not shared with Gutterpress", "another
19
+ * account" and "trashed" are one case), or a name, which each person finds or
20
+ * creates in their own Drive. Their own folder, from their private store,
21
+ * stands in for it while it was chosen for the book's current folder and with
22
+ * the account they publish with now; one recorded against another folder or
23
+ * account no longer applies, and is dropped once the book's folder is
24
+ * reachable. Never writes the manifest or creates a folder.
25
+ */
26
+ export declare function resolveFolder(req: PublishRequest, accessToken: string): Promise<PublishDestinationStatus>;
27
+ /** The folder an upload goes to. An unreachable one fails, naming it and how
28
+ * to publish elsewhere: publishing never creates a folder in its place. */
29
+ export declare function uploadFolder(where: PublishDestinationStatus, fetchImpl: typeof fetch, accessToken: string): Promise<PublishProduct>;
30
+ export declare function toDestination(folder: DriveFolder): PublishProduct;
@@ -10,6 +10,11 @@ export interface RunPublishOptions {
10
10
  artifactPath?: string;
11
11
  /** Stop after preflight — report issues without contacting the platform. */
12
12
  dryRun?: boolean;
13
+ /**
14
+ * Provider settings for this run only, laid over the saved ones and never
15
+ * written anywhere: the CLI's `--folder-id` / `--folder` (#452).
16
+ */
17
+ settings?: Record<string, string>;
13
18
  /**
14
19
  * The provider, when the caller has already resolved it (the CLI does, to
15
20
  * map a failed restore to its own exit code): no second lookup or restore.
@@ -39,7 +44,7 @@ export interface RunPublishResult {
39
44
  * with a preflight error over one bad manifest field.
40
45
  */
41
46
  export declare function resolvePublishFormat(info: PublishProviderInfo, providerConfig: Record<string, unknown>): PublishArtifactFormat;
42
- type RequestOptions = Pick<RunPublishOptions, "projectDir" | "providerId" | "manifestPath" | "artifactPath">;
47
+ type RequestOptions = Pick<RunPublishOptions, "projectDir" | "providerId" | "manifestPath" | "artifactPath" | "settings">;
43
48
  /**
44
49
  * Resolve the {@link PublishRequest} for a BUILT-IN provider from the
45
50
  * project's manifest — for hosts that call individual provider methods
@@ -206,12 +206,13 @@ export interface PublishDeps {
206
206
  credentialAccount?: string;
207
207
  /**
208
208
  * The person's PRIVATE publishing values for a book (see
209
- * {@link PublishSelectionsStore}): which saved account to use and an output
210
- * folder outside the book. When given, they win over the manifest's
211
- * `publish.<id>` values in every request. Absent (the CLI today): the
212
- * manifest alone.
209
+ * {@link PublishSelectionsStore}): which saved account to use, an output
210
+ * folder outside the book, and their own Drive folder when the book's is out
211
+ * of their reach (#452). When given, they win over the manifest's
212
+ * `publish.<id>` values in every request, and a provider may drop one that
213
+ * no longer applies. Absent (the CLI today): the manifest alone.
213
214
  */
214
- selections?: Pick<PublishSelectionsStore, "settings">;
215
+ selections?: Pick<PublishSelectionsStore, "settings" | "setSettings">;
215
216
  }
216
217
  /**
217
218
  * The TokenStore key for a publishing provider credential. Named accounts use a
@@ -252,6 +253,50 @@ export interface PublishProduct {
252
253
  title: string;
253
254
  url?: string;
254
255
  }
256
+ /**
257
+ * Where one person publishes among a provider's destinations, and whether they
258
+ * must choose one (#452, gdrive's folders). The book's destination is the
259
+ * team's, in the manifest; a person whose account can't reach it chooses their
260
+ * own, kept in their private settings for the book.
261
+ */
262
+ export type PublishDestinationStatus =
263
+ /** The book has no destination yet: the first choice is the book's. */
264
+ {
265
+ state: "unset";
266
+ }
267
+ /** The book's destination, reachable with this person's account. */
268
+ | {
269
+ state: "book";
270
+ destination: PublishProduct;
271
+ }
272
+ /** The book names its destination by name only: each person's own of that
273
+ * name. `destination` is this account's most recently changed one (absent:
274
+ * it is created on the first publish); `several` when more share the name. */
275
+ | {
276
+ state: "by-name";
277
+ name: string;
278
+ destination?: PublishProduct;
279
+ several: boolean;
280
+ }
281
+ /** This person's own destination stands in for the book's. */
282
+ | {
283
+ state: "mine";
284
+ destination: PublishProduct;
285
+ book: {
286
+ title: string;
287
+ reachable: boolean;
288
+ };
289
+ }
290
+ /** Out of reach with this person's account: the book's destination, or their
291
+ * own (`mine`, its title) when they have one. They must choose another. */
292
+ | {
293
+ state: "unreachable";
294
+ book: {
295
+ title: string;
296
+ reachable: boolean;
297
+ };
298
+ mine?: string;
299
+ };
255
300
  /** Listing metadata for {@link PublishProvider.updateListing}. */
256
301
  export interface PublishListingMetadata {
257
302
  title?: string;
@@ -304,6 +349,8 @@ export interface PublishProvider {
304
349
  listDestinations?(req: PublishRequest): Promise<PublishProduct[]>;
305
350
  /** Create a new destination (gdrive: a Drive folder at My Drive root). */
306
351
  createDestination?(req: PublishRequest, name: string): Promise<PublishProduct>;
352
+ /** Where this person publishes now, and whether they must choose (#452). */
353
+ destinationStatus?(req: PublishRequest): Promise<PublishDestinationStatus>;
307
354
  }
308
355
  /**
309
356
  * Resolve the credential for a provider: the CI env var wins, then the token
@@ -29,11 +29,11 @@ export interface ConvergeResult {
29
29
  keptBothFiles: KeptBothFile[];
30
30
  }
31
31
  /** Snapshot message for the equalization commit (driver-unreachable cases). */
32
- export declare const CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online version";
32
+ export declare const CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online copy's changes";
33
33
  /** Snapshot message for the post-merge restore commit (kept-both/edits). */
34
- export declare const CONVERGE_RESTORE_MESSAGE = "Kept both versions of the files that can't be combined";
34
+ export declare const CONVERGE_RESTORE_MESSAGE = "Kept both takes of the files that can't be combined";
35
35
  /** The merge commit message. */
36
- export declare const CONVERGE_MERGE_MESSAGE = "Combined your changes with the online version";
36
+ export declare const CONVERGE_MERGE_MESSAGE = "Combined your changes with the online copy's";
37
37
  /**
38
38
  * diff3 merge that NEVER reports a conflict: clean hunks merge exactly like
39
39
  * git's default driver; clashing hunks keep BOTH versions inside standard
@@ -19,8 +19,8 @@ export declare const MSG_BUSY = "The online copy is changing very quickly right
19
19
  * VERSIONS are what broke, and (backing up always has an online copy, so this
20
20
  * always applies here) "Repair the online copy" is the way back.
21
21
  */
22
- export declare const MSG_HISTORY_UNREADABLE = "This book's versions can't be read, so backing up can't continue. Your writing is safe \u2014 every file is still here on this computer. Use \"Repair the online copy\" (in Book setup \u2192 Online copy) to get versions and backing up working again.";
23
- export declare const MSG_UNRELATED = "This book and the online copy have no versions in common, so they can't be combined. Either the online copy's address points at a different book, or this book's own versions were lost and started over. Your writing is safe \u2014 every file is still here on this computer. Check the online copy's address; if it is correct, use \"Repair the online copy\" (in Book setup \u2192 Online copy).";
22
+ export declare const MSG_HISTORY_UNREADABLE = "This book's versions can't be read, so backing up can't continue. Your writing is safe \u2014 every file is still here on this computer. Use \"Repair the online copy\" in Book setup to get versions and backing up working again.";
23
+ export declare const MSG_UNRELATED = "This book and the online copy have no versions in common, so they can't be combined. Either the online copy's address points at a different book, or this book's own versions were lost and started over. Your writing is safe \u2014 every file is still here on this computer. Check the online copy's address; if it is correct, use \"Repair the online copy\" in Book setup.";
24
24
  export declare const MSG_NO_REMOTE = "This book doesn't have an online copy yet.";
25
25
  export declare const MSG_SSH_REMOTE = "This book's online copy uses an SSH address (git@\u2026), which Gutterpress can't back up to. Switch it to the web (HTTPS) address to back up from here.";
26
26
  export declare const MSG_NO_BRANCH = "This book isn't on a named revision, so it can't be backed up right now.";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * A stylesheet's root-level custom properties — the design settings Book
3
+ * setup shows — read and written with postcss (#454).
4
+ *
5
+ * "Root-level" means a declaration in a `:root` rule that is not inside
6
+ * `@media`, `@supports` or any other conditional group (and, postcss being a
7
+ * parser, never one in a comment). A `:root` inside a cascade layer counts,
8
+ * tagged with its layer. Values are read as written, so a `;` inside
9
+ * `url("data:…")` is part of the value, and a write changes only the
10
+ * declarations it names: every other byte of the sheet is kept.
11
+ *
12
+ * Host-side only: postcss never reaches the desktop's SPA (architecture rules
13
+ * §8). The desktop reads these through its `style/book-css` route and writes
14
+ * through `style/edit-tokens` (`book-css.ts`).
15
+ *
16
+ * **Token annotations (issue #244).** Theme authors curate what the guided
17
+ * panel shows with a tiny comment grammar in the `:root` block — no new file
18
+ * format, no schema, and (since it's ordinary CSS comments) no risk to a
19
+ * plain browser or a build that never heard of Gutterpress. Three
20
+ * directives, each written in a block comment right before the declaration it
21
+ * describes:
22
+ *
23
+ * - `@group <name>` — show the token under a "<name>" heading instead of the
24
+ * Fonts/Colors/Sizes/Other bucket its kind would choose.
25
+ * - `@label <text>` — the label shown instead of the default one.
26
+ * - `@internal` — leave the token out of the panel. The CSS is untouched.
27
+ *
28
+ * Each directive applies to the NEXT custom-property declaration only and is
29
+ * consumed by it. A block has defaults too (#435): `@group` and `@internal`
30
+ * in the comments directly BEFORE the `:root` selector apply to every
31
+ * declaration in the block that carries no directive of its own; a
32
+ * declaration's own `@group`, `@label` or `@internal` still wins for that
33
+ * declaration (its own `@group` or `@label` shows it even inside an
34
+ * `@internal` block). Several directives can share one comment (`@label X
35
+ * @group Y`) or be spread across consecutive comments. A comment with none of
36
+ * them is prose and changes nothing.
37
+ */
38
+ /** One root-level custom property, as a stylesheet declares it. */
39
+ export interface RootToken {
40
+ /** The property name, e.g. `--color-accent`. */
41
+ name: string;
42
+ /**
43
+ * The value as written, comments and the space before its `;` included, so
44
+ * writing it back restores the declaration byte for byte (`!important` is
45
+ * not part of it, and stays on the declaration).
46
+ */
47
+ value: string;
48
+ /** An `@label` annotation's text. */
49
+ label?: string;
50
+ /** An `@group` annotation (the declaration's own, else its block's). */
51
+ group?: string;
52
+ /** The cascade layer the `:root` rule sits in, dotted (`ext.look.theme`); absent when unlayered. */
53
+ layer?: string;
54
+ }
55
+ /** One pending change: a token's new value, or null to remove the book's declaration of it. */
56
+ export interface TokenUpdate {
57
+ name: string;
58
+ value: string | null;
59
+ }
60
+ /**
61
+ * Every root-level custom property a stylesheet declares, in source order,
62
+ * with its annotations; an `@internal` one is left out. Throws postcss's
63
+ * `CssSyntaxError` for a sheet it cannot parse.
64
+ */
65
+ export declare function rootTokens(css: string): RootToken[];
66
+ /**
67
+ * Apply a batch of token updates to a book's stylesheets, in cascade order,
68
+ * folding each onto the result of the last so none is lost. A value is set
69
+ * where the book already declares the token — every root-level declaration
70
+ * of it in the LAST sheet that has one, the one that wins — and otherwise
71
+ * added to `sheets[target]`. A null value removes the token from every
72
+ * sheet, so what the looks set shows through. Returns the sheets' new text;
73
+ * a sheet nothing changed comes back as it was given. Throws postcss's
74
+ * `CssSyntaxError` for a sheet it cannot parse.
75
+ */
76
+ export declare function applyTokenUpdates(sheets: readonly string[], target: number, updates: readonly TokenUpdate[]): string[];
@@ -1,3 +1,4 @@
1
+ import { type ReadCommitResult } from "isomorphic-git";
1
2
  import type { ProjectCapabilities, ProjectSource } from "./project-source.ts";
2
3
  /**
3
4
  * isomorphic-git object cache scoped to ONE operation and released with it.
@@ -182,6 +183,32 @@ export declare function hasPendingChanges(dir: string, cache?: GitCache): Promis
182
183
  * as dirty (the WORKDIR-vs-STAGE-only check silently drops it).
183
184
  */
184
185
  export declare function hasUncommittedChanges(dir: string, cache?: GitCache): Promise<boolean>;
186
+ /**
187
+ * A walk back through a repo's history in the order `git.log` lists it (the
188
+ * newest commit time first), visiting each commit once (#472). `git.log` has
189
+ * no visited set: after a merge it lists a commit again, with everything older,
190
+ * and the repeats used up a page. Starting the next page's log at the last id
191
+ * also dropped the side of a merge that id does not descend from. Shared with
192
+ * the online-only versions (`commitsAhead`, #477), which walks two tips at once.
193
+ */
194
+ export interface HistoryWalk {
195
+ /** Commits read but not listed yet, sorted so the newest is last. */
196
+ tips: ReadCommitResult[];
197
+ /** Every commit ever put in `tips`. */
198
+ queued: Set<string>;
199
+ /** `.git/shallow`: commits whose parents a shallow clone does not have. */
200
+ shallow: Set<string>;
201
+ /** Commits read ahead (see {@link nextInHistory}), not queued yet. */
202
+ readAhead: Map<string, ReadCommitResult>;
203
+ }
204
+ /** A walk back from `heads` (the history of each of them, together). */
205
+ export declare function startHistoryWalk(dir: string, cache: GitCache, heads: string[]): Promise<HistoryWalk>;
206
+ /**
207
+ * The walk's next commit, newest first; undefined once the history is done.
208
+ * `needed` is how many more commits the page needs, to bound a read-ahead
209
+ * (none for 1: a caller that cannot tell how far it will go reads one by one).
210
+ */
211
+ export declare function nextInHistory(dir: string, walk: HistoryWalk, cache: GitCache, needed: number): Promise<ReadCommitResult | undefined>;
185
212
  /**
186
213
  * Lock-free snapshot of the full working tree (stage everything + commit).
187
214
  *
@@ -17,10 +17,8 @@ export interface VersionChanges {
17
17
  /** Changed files beyond the ones listed (the list is capped). */
18
18
  more: number;
19
19
  }
20
- /** A history entry with what it changed and where it is kept. */
20
+ /** A history entry and where it is kept. */
21
21
  export interface VersionEntry extends SnapshotEntry {
22
- changes: VersionFileChange[];
23
- moreChanges: number;
24
22
  /** True = online too; false = on this computer only; null = no online copy to compare with. */
25
23
  backedUp: boolean | null;
26
24
  }
@@ -29,30 +27,42 @@ export declare const VERSION_CHANGES_SHOWN = 8;
29
27
  /** The size hint for a text file that changed between two versions. */
30
28
  export declare function changeHint(filepath: string, before: string, after: string): string;
31
29
  /**
32
- * The files `oid` changed against its first parent (every file, for a
33
- * version with no parent), sorted by path and capped to
30
+ * The files version `oid` of the book changed against its first parent
31
+ * (every file, for a version with no parent), sorted by path and capped to
34
32
  * {@link VERSION_CHANGES_SHOWN}.
35
33
  */
36
- export declare function listVersionChanges(dir: string, oid: string, options?: {
37
- cache?: object;
38
- }): Promise<VersionChanges>;
34
+ export declare function listVersionChanges(projectDir: string, oid: string): Promise<VersionChanges>;
39
35
  /**
40
- * A history page with each version's changes and whether it is backed up.
41
- * Versions come newest-first, and everything older than the first backed-up
42
- * one is backed up too, so the ancestry walk runs only over the versions
43
- * that are on this computer only.
36
+ * A history page with whether each version is backed up: whether it is the
37
+ * online tip or one of its earlier versions. Versions come newest-first, and
38
+ * everything older than the first backed-up one is backed up too. The rest
39
+ * share ONE breadth-first walk back from the tip, step for step the walk
40
+ * `git.isDescendent` makes (a shallow commit's parents are not followed; a
41
+ * missing object ends the walk, and a version not reached by then is not
42
+ * backed up): asking `isDescendent` once per version walked the whole history
43
+ * again for every version on this computer only.
44
44
  */
45
45
  export declare function describeVersions(projectDir: string, entries: SnapshotEntry[]): Promise<VersionEntry[]>;
46
46
  /**
47
47
  * The commits reachable from `fromOid` that `baseOid`'s history does not
48
- * have, newest first, down to (not including) their merge base. None when
49
- * the two share no history.
48
+ * have (`git rev-list fromOid ^baseOid`), each once, newest first in the
49
+ * order Version history lists versions, up to `limit`. None when the two
50
+ * share no history: that is another book's online copy, which Sync refuses.
51
+ *
52
+ * One walk back from both tips, newest commit time first (#477). What
53
+ * `baseOid` reaches is marked, and a marked commit marks its parents, even
54
+ * ones already walked: a commit both tips reach is never listed, by whatever
55
+ * path `fromOid` reaches it (a criss-cross merge has two merge bases). The
56
+ * walk goes on while an unmarked commit is queued, so a version dated before
57
+ * the commits it builds on (a collaborator's clock behind) is still found;
58
+ * then until every marked commit queued is {@link CLOCK_SKEW_SECONDS} older
59
+ * than the oldest version listed.
50
60
  */
51
- export declare function commitsAhead(dir: string, fromOid: string, baseOid: string, cache?: object, limit?: number): Promise<SnapshotEntry[]>;
61
+ export declare function commitsAhead(dir: string, fromOid: string, baseOid: string, cache?: Record<string, unknown>, limit?: number): Promise<SnapshotEntry[]>;
52
62
  /**
53
63
  * What is online that this computer has not got: fetch the book's branch
54
64
  * (remote-tracking ref only — no merge, no checkout, safe on a dirty tree)
55
- * and describe the commits above the merge base. Quiet on every failure,
65
+ * and describe the commits this computer has not got. Quiet on every failure,
56
66
  * reporting why, like `checkRemoteUpdates`.
57
67
  */
58
68
  export declare function listOnlineVersions(options: {
@@ -8,7 +8,7 @@ styles:
8
8
  - styles/book.css
9
9
  # Page size and margins in points (72 = 1 in): US Letter with 1 in margins
10
10
  # (A4 is width 595, height 842); text stays 0.25 in (18) inside the trim.
11
- # Change them under Setup > Book.
11
+ # Change them in Book setup.
12
12
  page:
13
13
  width: 612
14
14
  height: 792
@@ -9,7 +9,7 @@ extensions:
9
9
  - technical-doc
10
10
  # Page size and margins in points (72 = 1 in): US Letter (8.5 x 11 in) with
11
11
  # 0.9 in top and bottom and 1 in side margins; text stays 0.25 in (18) inside
12
- # the trim. Change them under Setup > Book.
12
+ # the trim. Change them in Book setup.
13
13
  page:
14
14
  width: 612
15
15
  height: 792
@@ -8,7 +8,7 @@ styles:
8
8
  extensions:
9
9
  - clean-book
10
10
  # Page margins in points (72 = 1 in): top, bottom, inside (the binding edge)
11
- # and outside. Change them, and the page size, under Setup > Book.
11
+ # and outside. Change them, and the page size, in Book setup.
12
12
  page:
13
13
  margin:
14
14
  top: 61.2
@@ -8,8 +8,8 @@ styles:
8
8
  extensions:
9
9
  - zine
10
10
  # Page size and margins in points (72 = 1 in): Digest (5.5 x 8.5 in) with
11
- # 0.4 in margins; text stays 0.25 in (18) inside the trim. Change them under
12
- # Setup > Book.
11
+ # 0.4 in margins; text stays 0.25 in (18) inside the trim. Change them in
12
+ # Book setup.
13
13
  page:
14
14
  width: 396
15
15
  height: 612
@@ -242,7 +242,7 @@
242
242
  },
243
243
  "margin": {
244
244
  "type": "object",
245
- "description": "Page margins, in points (1in = 72pt): the space between the trim and the text on each edge. `inside` is the binding edge (the left of a right-hand page, the right of a left-hand one), `outside` the other. When set, the book prints with these margins and any `@page` margin in a stylesheet is overridden; when left out, the stylesheets' `@page` margins apply (a look's design), else 1in. Setup > Book in the app edits it. All four edges are required together.",
245
+ "description": "Page margins, in points (1in = 72pt): the space between the trim and the text on each edge. `inside` is the binding edge (the left of a right-hand page, the right of a left-hand one), `outside` the other. When set, the book prints with these margins and any `@page` margin in a stylesheet is overridden, except where a named page sets its own (`@page full { margin: 0 }`); when left out, the stylesheets' `@page` margins apply (a look's design), else 1in. Setup > Book in the app edits it. All four edges are required together.",
246
246
  "properties": {
247
247
  "top": { "type": "number", "description": "Top margin in points.", "minimum": 0, "examples": [61.2, 72] },
248
248
  "bottom": { "type": "number", "description": "Bottom margin in points.", "minimum": 0, "examples": [61.2, 72] },
@@ -3,18 +3,18 @@ import {
3
3
  EXTENSION_KINDS,
4
4
  scaffoldExtension,
5
5
  scaffoldProject
6
- } from "./cli-3cvq3n1c.js";
6
+ } from "./cli-pnrararp.js";
7
7
  import {
8
8
  resolveGhostscript
9
- } from "./cli-mmgtqnmr.js";
9
+ } from "./cli-0ep11b0q.js";
10
10
  import"./cli-ht1bcqdd.js";
11
- import"./cli-58fsk6d7.js";
11
+ import"./cli-pd2cje5b.js";
12
12
  import"./cli-5crpmz42.js";
13
13
  import"./cli-eyyggect.js";
14
14
  import {
15
15
  isToolAvailable
16
16
  } from "./cli-asap4t1z.js";
17
- import"./cli-31anr0r9.js";
17
+ import"./cli-v0evdpdf.js";
18
18
  import"./cli-v5mp7a6q.js";
19
19
  import {
20
20
  PRESET_IDS,
@@ -22,15 +22,16 @@ import {
22
22
  TARGET_IDS,
23
23
  loadManifest,
24
24
  resolveConfig
25
- } from "./cli-6pv84kf3.js";
26
- import"./cli-wyg7qstd.js";
27
- import"./cli-eatdqx3z.js";
25
+ } from "./cli-88arpdke.js";
26
+ import"./cli-tgnyvra3.js";
27
+ import"./cli-9n9kg58f.js";
28
28
  import {
29
29
  UsageError,
30
30
  rejectExtraPositionals,
31
31
  rejectUnknownFlags
32
32
  } from "./cli-d8xjns66.js";
33
- import"./cli-5ba6bm6z.js";
33
+ import"./cli-x7gcdg6w.js";
34
+ import"./cli-65gy8ask.js";
34
35
  import {
35
36
  EXIT_CODES
36
37
  } from "./cli-46ycxe6r.js";
@@ -7,8 +7,8 @@
7
7
  * to a CSS `@page name`.
8
8
  *
9
9
  * NOT the page geometry. The trim size and the margins are the BOOK's,
10
- * recorded in its manifest (`preset:`, `page:` — Setup > Book in the
11
- * app) and written by the build after every stylesheet, so a `size` here
10
+ * recorded in its manifest (`preset:`, `page:` — set in Book setup) and
11
+ * written by the build after every stylesheet, so a `size` here
12
12
  * would be ignored (and reported) and a `margin` here would hold only
13
13
  * until the author sets margins in the app. Design the furniture below to
14
14
  * sit in whatever margin the book gives it.
@@ -8,7 +8,7 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./index-fp7xwvpr.js";
11
+ } from "./index-80vg3ed6.js";
12
12
  import"./index-5x42amh8.js";
13
13
  import"./index-37x76zdn.js";
14
14
  export {
@@ -8,9 +8,9 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./cli-wyg7qstd.js";
12
- import"./cli-eatdqx3z.js";
13
- import"./cli-5ba6bm6z.js";
11
+ } from "./cli-tgnyvra3.js";
12
+ import"./cli-9n9kg58f.js";
13
+ import"./cli-x7gcdg6w.js";
14
14
  import"./cli-y28j019d.js";
15
15
  import"./cli-37x76zdn.js";
16
16
  export {