gutterpress 0.9.0-alpha.2 → 0.10.0-alpha.3

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 (138) hide show
  1. package/README.md +18 -4
  2. package/dist/api/index.d.ts +11 -5
  3. package/dist/api/index.js +20 -15
  4. package/dist/assets/preview/scripts/preview-bridge.d.ts +1 -0
  5. package/dist/assets/preview/scripts/preview-interface.d.ts +1 -0
  6. package/dist/{audit-nhn2pjz3.js → audit-xxegv0t0.js} +10 -7
  7. package/dist/{build-san7fv2z.js → build-w0vb6mgq.js} +14 -8
  8. package/dist/checks/source/index.d.ts +1 -0
  9. package/dist/checks/source/layout-markers.d.ts +3 -0
  10. package/dist/checks/source/local-ref-parser.d.ts +28 -0
  11. package/dist/cli-46ycxe6r.js +18 -0
  12. package/dist/{cli-k4bd06sd.js → cli-5czby0dd.js} +4986 -5889
  13. package/dist/cli-c41yr7he.js +241 -0
  14. package/dist/{cli-e5zhb0xs.js → cli-f4rcbt2t.js} +17 -24
  15. package/dist/{cli-najycadg.js → cli-gk3wsdpb.js} +69 -253
  16. package/dist/{index-yzrh708h.js → cli-k1065rkg.js} +2 -5
  17. package/dist/{cli-506tg37g.js → cli-revgt4pr.js} +2 -1
  18. package/dist/{cli-wchtvxvw.js → cli-v5mp7a6q.js} +14 -1
  19. package/dist/cli-yp3p0sf2.js +2343 -0
  20. package/dist/cli.js +20 -18
  21. package/dist/{doctor-hrk0kxxz.js → doctor-ctgn1dnt.js} +4 -2
  22. package/dist/engine/compiler/build.d.ts +139 -0
  23. package/dist/engine/compiler/postprocess.d.ts +23 -0
  24. package/dist/engine/compiler/tier2.d.ts +78 -0
  25. package/dist/engine/shared/cdp.d.ts +104 -0
  26. package/dist/engine/shared/content-value.d.ts +64 -0
  27. package/dist/engine/shared/gcpm-extract.d.ts +119 -0
  28. package/dist/engine/shared/margin-box-support.d.ts +12 -0
  29. package/dist/engine/shared/pdf-inspect.d.ts +24 -0
  30. package/dist/engine/shared/synthesis.d.ts +155 -0
  31. package/dist/engine-eng4xh2q.js +41 -0
  32. package/dist/engine-q5skhgms.js +40 -0
  33. package/dist/gutterpress-agent-1ctgfz92.js +576 -0
  34. package/dist/gutterpress-viewer-cem7dmr5.js +2349 -0
  35. package/dist/index-a4kr77td.js +1907 -0
  36. package/dist/{cli-yzrh708h.js → index-mdefp0y5.js} +1 -1
  37. package/dist/index-mvehxh0e.js +708 -0
  38. package/dist/{index-bynn850m.js → index-s416qpdv.js} +3049 -4015
  39. package/dist/{index-wchtvxvw.js → index-v5mp7a6q.js} +14 -1
  40. package/dist/index.d.ts +3 -1
  41. package/dist/index.js +24 -17
  42. package/dist/lib/asset-inline.d.ts +36 -0
  43. package/dist/lib/browser-pool.d.ts +18 -0
  44. package/dist/lib/build-error.d.ts +1 -1
  45. package/dist/lib/build-preflight.d.ts +35 -2
  46. package/dist/lib/build-runner.d.ts +45 -17
  47. package/dist/lib/build-staging.d.ts +9 -55
  48. package/dist/lib/cli-args.d.ts +8 -0
  49. package/dist/lib/desktop.d.ts +2 -2
  50. package/dist/lib/embedded-assets.d.ts +1 -1
  51. package/dist/lib/engine.d.ts +33 -0
  52. package/dist/lib/ghostscript.d.ts +46 -1
  53. package/dist/lib/markdown/assemble.d.ts +18 -10
  54. package/dist/lib/markdown/gp-pin-scope.d.ts +1 -0
  55. package/dist/lib/markdown/gutterpress-css.d.ts +125 -0
  56. package/dist/lib/markdown/images.d.ts +26 -0
  57. package/dist/lib/markdown/index.d.ts +4 -2
  58. package/dist/lib/markdown/inline-source.d.ts +10 -0
  59. package/dist/lib/markdown/markers.d.ts +32 -0
  60. package/dist/lib/markdown/renderer.d.ts +8 -3
  61. package/dist/lib/markdown/source-range.d.ts +61 -0
  62. package/dist/lib/missing-asset-placeholder.d.ts +52 -0
  63. package/dist/lib/presets.d.ts +1 -1
  64. package/dist/lib/printsafe.d.ts +2 -3
  65. package/dist/lib/remote-auth/converge-merge.d.ts +48 -0
  66. package/dist/lib/remote-auth/image-clash.d.ts +17 -0
  67. package/dist/lib/remote-auth/recovery/classify.d.ts +55 -67
  68. package/dist/lib/remote-auth/recovery/inspect.d.ts +14 -13
  69. package/dist/lib/remote-auth/recovery/locks.d.ts +20 -0
  70. package/dist/lib/remote-auth/recovery/repair.d.ts +29 -0
  71. package/dist/lib/remote-auth/recovery/types.d.ts +8 -204
  72. package/dist/lib/remote-auth/sync-messages.d.ts +2 -3
  73. package/dist/lib/remote-auth/sync-types.d.ts +35 -63
  74. package/dist/lib/remote-auth/sync.d.ts +11 -18
  75. package/dist/lib/remote-auth/transport.d.ts +14 -7
  76. package/dist/lib/theme-import.d.ts +4 -5
  77. package/dist/{lint-96j9hrj4.js → lint-gsh6qwrq.js} +10 -7
  78. package/dist/{manifest.schema-z61rzw44.json → manifest.schema-zxgxnbg7.json} +21 -0
  79. package/dist/{new-8p38wavc.js → new-nkhycqy4.js} +12 -8
  80. package/dist/{plugin-ees6nhkc.js → plugin-129wcs93.js} +10 -7
  81. package/dist/{preflight-1q6c2edh.js → preflight-41hcejg9.js} +10 -7
  82. package/dist/preview/file-watcher.d.ts +13 -17
  83. package/dist/preview/lifecycle.d.ts +1 -1
  84. package/dist/{pagedjs-bridge-vn4hk9fx.js → preview-bridge-fz7vpk8m.js} +8 -0
  85. package/dist/preview-interface-fnqb2y4v.js +1015 -0
  86. package/dist/{preview-y5a2zen1.js → preview-pftsvr9z.js} +16 -9
  87. package/dist/preview-shell-c5mfa3q0.js +346 -0
  88. package/dist/{project-source-p0gn1wd5.js → project-source-ekcyp63q.js} +1 -1
  89. package/dist/{publish-rm9yb3wh.js → publish-dnjvb4jg.js} +10 -7
  90. package/dist/render.d.ts +3 -4
  91. package/dist/render.js +728 -64
  92. package/dist/{repair-zgq7q2g6.js → repair-a3enjttn.js} +45 -79
  93. package/dist/schema/manifest.types.d.ts +38 -0
  94. package/dist/{source-provider-c1rjm2c0.js → source-provider-3tcj6qg2.js} +2 -2
  95. package/dist/source-provider-vanafrt9.js +40 -0
  96. package/dist/{theme-zz2ktzqs.css → theme-h5recz6c.css} +8 -7
  97. package/dist/{theme-570zmh2t.css → theme-j2bagrfx.css} +8 -7
  98. package/dist/{theme-nya4nqh6.css → theme-nn6d53zy.css} +8 -7
  99. package/dist/types.d.ts +7 -0
  100. package/dist/{validate-nr0xa6sa.js → validate-ph1xffqy.js} +10 -7
  101. package/package.json +6 -6
  102. package/dist/cli-yja077f6.js +0 -92
  103. package/dist/git-http-yrb4ag6z.js +0 -17
  104. package/dist/index-yja077f6.js +0 -92
  105. package/dist/lib/markdown/markdown-it-paged.d.ts +0 -30
  106. package/dist/lib/pagedjs-marker.d.ts +0 -42
  107. package/dist/lib/pagedjs.d.ts +0 -26
  108. package/dist/lib/pagination.d.ts +0 -149
  109. package/dist/lib/remote-auth/conflict-resolution.d.ts +0 -29
  110. package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +0 -103
  111. package/dist/lib/remote-auth/recovery/backup.d.ts +0 -113
  112. package/dist/lib/remote-auth/recovery/context.d.ts +0 -47
  113. package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -28
  114. package/dist/lib/remote-auth/recovery/failsafe.d.ts +0 -33
  115. package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +0 -28
  116. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +0 -59
  117. package/dist/lib/remote-auth/recovery/policy.d.ts +0 -47
  118. package/dist/lib/remote-auth/recovery/recover-auth.d.ts +0 -42
  119. package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +0 -37
  120. package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +0 -40
  121. package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +0 -70
  122. package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +0 -23
  123. package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +0 -28
  124. package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +0 -39
  125. package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +0 -34
  126. package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +0 -37
  127. package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +0 -56
  128. package/dist/lib/remote-auth/recovery/recover-network.d.ts +0 -34
  129. package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +0 -27
  130. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -68
  131. package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +0 -44
  132. package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +0 -35
  133. package/dist/lib/remote-auth/resolution-plan.d.ts +0 -64
  134. package/dist/paged.polyfill-n95pbxfn.js +0 -33288
  135. package/dist/pagedjs-interface-qxvzgwd7.js +0 -557
  136. package/dist/preview-shell-6dqexx1m.js +0 -581
  137. /package/dist/assets/{preview/scripts/pagedjs-bridge.d.ts → engine/gutterpress-agent.d.ts} +0 -0
  138. /package/dist/assets/{preview/scripts/pagedjs-interface.d.ts → engine/gutterpress-viewer.d.ts} +0 -0
@@ -1,30 +0,0 @@
1
- export default function plugin(md: any, pluginOptions?: {}): void;
2
- /**
3
- * Minimal Paged.js-friendly CSS for the classes this plugin emits.
4
- * Consumers should inject this into <head> after their user stylesheets so
5
- * the layout contract (page/section/column breaks) wins at equal specificity.
6
- *
7
- * Also ships five author-facing image/block utility classes (CLAUDE.md §0 —
8
- * a behavior broadly useful to non-technical authors belongs in core, not a
9
- * project layer; see UX finding M17). markdown-it-attrs is bundled by
10
- * default (renderer.ts), so `![Art](x.jpg){.full-bleed}` already attaches
11
- * the class to the rendered `<img>` — these rules are what make that class
12
- * actually do something print-safe:
13
- *
14
- * .center — centers a block element (margin-inline: auto).
15
- * .float-left — floats left with clearance margins.
16
- * .float-right — floats right with clearance margins.
17
- * .full-width — fills the page's content width (100%).
18
- * .full-bleed — forces its own page (break-before) and cancels the
19
- * page's LEFT/RIGHT margins via Paged.js's real
20
- * `--pagedjs-margin-left`/`--pagedjs-margin-right` custom
21
- * properties (set per-page by the polyfill from the active
22
- * `@page` rule — see pagedjs/src/polisher/base.js), so
23
- * content spans the page edge-to-edge horizontally. This
24
- * does NOT cancel the top/bottom margins, extend past the
25
- * trim into printer bleed overage, apply a named `@page`
26
- * template, or remove headers/footers — none of that is
27
- * implemented; the custom-property fallback of 0 means it
28
- * degrades to plain full-width outside a Paged.js render.
29
- */
30
- export const PAGED_CSS: "\n.md-page-break { break-before: page; }\n.page { break-before: page; }\n.spread { break-before: page; }\n.section { break-inside: avoid; }\n.section.col-split { break-inside: auto; }\n.md-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }\n\n.center { display: block; margin-left: auto; margin-right: auto; max-width: 100%; }\n.float-left { float: left; margin: 0 1em 1em 0; max-width: 50%; }\n.float-right { float: right; margin: 0 0 1em 1em; max-width: 50%; }\n.full-width { display: block; width: 100%; max-width: 100%; }\n.full-bleed {\n display: block;\n break-before: page;\n max-width: none;\n width: calc(100% + var(--pagedjs-margin-left, 0px) + var(--pagedjs-margin-right, 0px));\n margin-left: calc(-1 * var(--pagedjs-margin-left, 0px));\n margin-right: calc(-1 * var(--pagedjs-margin-right, 0px));\n}\n";
@@ -1,42 +0,0 @@
1
- /**
2
- * Single source of truth for the Paged.js polyfill `<script>` slot.
3
- *
4
- * PURE / node-free by design: this module imports nothing (no `node:*`, no `fs`),
5
- * so it can be shared by BOTH the pure, browser-usable HTML assembler
6
- * (`markdown/assemble.ts`) and the node-side build/preview rewriters
7
- * (`build-runner.ts`, `pagedjs.ts`, `preview/file-watcher.ts`).
8
- *
9
- * The contract between the assembler and every rewriter is the stable
10
- * `data-pagedjs-polyfill` MARKER ATTRIBUTE — never a URL, filename, or version
11
- * substring. Core emits the marker; the rewriters find/strip/replace it by
12
- * attribute. A pagedjs version bump or attribute reorder therefore cannot
13
- * silently break the strip/replace passes, and the un-rewritten `book.html`
14
- * carries NO network dependency (there is no live CDN URL to leak).
15
- */
16
- /**
17
- * Pinned Paged.js version. Bump HERE only — the value is emitted purely as
18
- * documentation on the marker tag; matching never depends on it.
19
- */
20
- export declare const PAGEDJS_VERSION = "0.4.3";
21
- /** Stable marker attribute identifying the Paged.js polyfill script slot. */
22
- export declare const PAGEDJS_POLYFILL_MARKER = "data-pagedjs-polyfill";
23
- /**
24
- * The polyfill `<script>` slot Gutterpress core emits into `book.html`. Carries the
25
- * stable marker (with the intended version as its value, for greppability) and
26
- * NO `src` — an un-rewritten `book.html` has zero network dependency. Every
27
- * consumer replaces this slot with a locally-vendored polyfill before the book
28
- * is ever loaded (build staging, the no-Chromium runtime fallback, live preview).
29
- */
30
- export declare function pagedjsPolyfillTag(version?: string): string;
31
- /**
32
- * Match the polyfill `<script>` slot. Matches EITHER the stable marker attribute
33
- * (core output) OR a `paged.polyfill` `src` (legacy CDN / vendored copy that a
34
- * staging/patch pass has already swapped in) — so the same matcher works at every
35
- * stage of the pipeline. Deliberately matches `paged.polyfill` (the version-
36
- * stable FILENAME), never a bare `pagedjs` substring, so the navigation toolbar
37
- * scripts (`pagedjs-interface.js` / `pagedjs-bridge.js`) are left untouched.
38
- *
39
- * Attribute-order tolerant and version-agnostic. Returns a FRESH RegExp per call
40
- * so the `g` flag's `lastIndex` is never shared between callers.
41
- */
42
- export declare function pagedjsPolyfillTagRegex(): RegExp;
@@ -1,26 +0,0 @@
1
- /**
2
- * Inline script that polyfills Paged.js's missing break-inside: avoid support.
3
- *
4
- * Paged.js has an avoidBreakInside() method but never calls it. This handler
5
- * intercepts onBreakToken and, when the break lands inside an element with
6
- * data-break-inside="avoid", moves the break to before that element.
7
- *
8
- * Uses a data attribute (not CSS) because source nodes are disconnected from
9
- * the DOM when onBreakToken fires, so getComputedStyle returns empty values.
10
- *
11
- * Must be registered via PagedConfig.before (runs before Paged.js renders).
12
- */
13
- export declare const BREAK_INSIDE_HANDLER: string;
14
- /**
15
- * Inject the Paged.js polyfill + render-complete marker into an HTML STRING.
16
- *
17
- * Pure so the build can paginate an in-memory copy: the shipped `book.html`
18
- * must never carry the polyfill `<script src>` (it is a build-time engine, not
19
- * part of the artifact), and the pagination server serves this patched string
20
- * as an overlay instead of writing it to disk.
21
- */
22
- export declare function patchHtmlStringForPagedjs(html: string, vendorPath: string): string;
23
- /**
24
- * File-in-place wrapper around {@link patchHtmlStringForPagedjs}.
25
- */
26
- export declare function patchHtmlForPagedjs(htmlPath: string, vendorPath: string): Promise<void>;
@@ -1,149 +0,0 @@
1
- import type { Page } from "puppeteer-core";
2
- /**
3
- * Render/pagination (ARCH finding #9, extracted from build-runner.ts): drives
4
- * headless Chromium through Paged.js to fully paginate a staged HTML document,
5
- * either printing it to PDF or serializing the paginated DOM to static HTML.
6
- * Owns: the embedded static HTTP file server both render paths need (a local
7
- * origin so relative asset URLs resolve), the shared navigate+wait+liveness
8
- * sequence (`paginateAndCapture`), the default PDF renderer, and build-time
9
- * static-HTML pagination. build-runner.ts's two `OutputStrategy` classes call
10
- * into this module for the actual pagination work and never touch a puppeteer
11
- * `Page` themselves.
12
- */
13
- /** An in-memory response served instead of reading a file from disk. */
14
- export interface ServerOverlay {
15
- body: string | Buffer;
16
- contentType: string;
17
- }
18
- /** Input handed to a PDF renderer: a URL serving the staged HTML + assets. */
19
- export interface PdfRenderInput {
20
- /** URL of the staged HTML on a local HTTP server (assets resolve relative). */
21
- url: string;
22
- /** Absolute path the renderer must write the finished PDF to. */
23
- outPdf: string;
24
- /** Hard ceiling for navigation + pagination + PDF generation. */
25
- timeoutMs: number;
26
- /**
27
- * When set, after printing the PDF the renderer also serializes the
28
- * already-paginated DOM (the same DOM it just printed) and writes it to this
29
- * path as raw static HTML. Lets ONE pagination pass emit BOTH the PDF and the
30
- * static desktop HTML, so screen and PDF come from the same artifact. The
31
- * serialize is read-only and runs AFTER `page.pdf()`, so it cannot affect the
32
- * PDF. Optional; injected renderers that cannot serialize may ignore it.
33
- */
34
- captureStaticHtmlTo?: string;
35
- }
36
- /**
37
- * A PDF renderer drives a browser engine to load `url`, wait for fonts +
38
- * Paged.js (`window.__PAGED_RENDERED__ === true`), measure the first
39
- * `.pagedjs_page`, and write a borderless PDF at that exact page size to
40
- * `outPdf` with backgrounds printed.
41
- *
42
- * The default ({@link puppeteerPdfRenderer}) drives a system/bundled Chromium.
43
- * The Electron desktop injects one backed by its own `webContents.printToPDF`,
44
- * so the packaged app needs no external browser (ADR 0002, Phase 4).
45
- */
46
- export type PdfRenderer = (input: PdfRenderInput) => Promise<void>;
47
- /** Hard ceiling for navigation + pagination + PDF generation. Large books need
48
- * this budget; it is also the puppeteer protocolTimeout for the pooled browser. */
49
- export declare const RENDER_TIMEOUT_MS: number;
50
- /** Tracks the last-seen `.pagedjs_page` count and when it last advanced. */
51
- export interface PaginationLivenessState {
52
- count: number;
53
- lastAdvanceAt: number;
54
- }
55
- export interface PaginationLivenessResult {
56
- stalled: boolean;
57
- state: PaginationLivenessState;
58
- }
59
- /**
60
- * Pure stall-detection decision (finding #19), extracted out of the polling
61
- * loop below so it is unit-testable with a fake page-count source instead of
62
- * a real puppeteer `page`. Given the latest `.pagedjs_page` count poll and the
63
- * previously tracked state, decide whether pagination has stalled — the count
64
- * has not advanced for at least `stallWindowMs` — and return the updated
65
- * state (advancing resets the liveness clock; a flat or regressed count
66
- * leaves `lastAdvanceAt` untouched).
67
- */
68
- export declare function evaluatePaginationLiveness(count: number, now: number, state: PaginationLivenessState, stallWindowMs: number): PaginationLivenessResult;
69
- /**
70
- * Drive a puppeteer `page` to fully paginate the document at `url`: set the
71
- * viewport + timeouts, navigate (waiting for network idle so vendored assets +
72
- * the polyfill load), wait for web fonts, then block until Paged.js signals
73
- * `window.__PAGED_RENDERED__ === true`.
74
- *
75
- * While waiting, a background poller checks the `.pagedjs_page` count every
76
- * `STALL_POLL_INTERVAL_MS` and logs it (so a long build visibly advances
77
- * instead of sitting silent), and fails fast with a BuildError the moment
78
- * `evaluatePaginationLiveness` decides the count has stopped advancing for
79
- * `STALL_WINDOW_MS` — instead of a wedged Paged.js run silently consuming the
80
- * full `timeoutMs` / `RENDER_TIMEOUT_MS` budget (up to an hour) before anyone
81
- * finds out. `timeoutMs` (== `RENDER_TIMEOUT_MS` at every call site) remains
82
- * the outer budget for legitimately slow-but-advancing books.
83
- *
84
- * Policy (owner's call, superseding an earlier "warn and ship partial output"
85
- * behavior): pagination either completes — `__PAGED_RENDERED__` fires — or the
86
- * build FAILS. A stall (count plateaus, whether at zero or after some pages)
87
- * and an outright wait timeout are BOTH treated as an incomplete render and
88
- * throw a `BuildError`; neither path falls through to let a caller print/
89
- * serialize whatever partial DOM exists. A silently-truncated "successful"
90
- * print-ready PDF is worse than a build that fails loudly — non-technical
91
- * authors have no way to notice half their book is missing. The stall check
92
- * still fails fast (within `stallWindowMs` of the last advance) rather than
93
- * waiting out the full `timeoutMs` budget.
94
- *
95
- * Shared navigate+wait sequence for BOTH render paths. Callers keep their own
96
- * tails: the PDF path calls `page.pdf()`; the static-HTML path serializes the
97
- * DOM — neither tail runs unless this function returns normally, i.e. unless
98
- * pagination actually completed. Per-caller knobs (viewport, timeout,
99
- * liveness window) are passed in so behavior is never silently changed;
100
- * `livenessConfig` defaults to the production constants and exists as a seam
101
- * for tests to shrink the poll/stall windows instead of waiting 60+ real
102
- * seconds.
103
- */
104
- export declare function paginateAndCapture(page: Page, url: string, timeoutMs: number, viewport?: {
105
- width: number;
106
- height: number;
107
- }, livenessConfig?: {
108
- pollIntervalMs: number;
109
- stallWindowMs: number;
110
- }): Promise<void>;
111
- /**
112
- * Close a CSS string value that Paged.js deliberately left unterminated.
113
- *
114
- * Paged.js's StringSets handler writes its string-set results as inline custom
115
- * properties with an OPENING quote and NO closing quote (paged.polyfill.js:
116
- * `fragment.style.setProperty(\`--pagedjs-string-first-${name}\`, \`"${…}\`)`).
117
- * Live that is harmless: `setProperty` parses each value in isolation and the
118
- * CSS tokenizer auto-closes a string token at end-of-value. But the moment the
119
- * DOM is SERIALIZED, all four of those properties land in ONE `style` attribute
120
- * joined by `;` — and on reparse the first unterminated string swallows the
121
- * declarations that follow it (`"1; --pagedjs-string-last-guideSection: "`).
122
- * The consumer, `content: "C." var(--pagedjs-string-first-guideSection)`, then
123
- * becomes invalid at computed-value time and the footer disappears.
124
- *
125
- * Given the raw property text, return it with a closing `"` appended when the
126
- * string token does not terminate. Rules follow CSS Syntax §4.3.5 "consume a
127
- * string token": a backslash escapes the next code point, and a backslash at
128
- * end-of-input is dropped (it cannot escape the quote we are about to add).
129
- * Values that are already terminated — including ones this function has already
130
- * fixed — are returned unchanged, so it is idempotent. Values that are not a
131
- * quoted string at all are left alone.
132
- */
133
- export declare function closeUnterminatedCssString(value: string): string;
134
- /**
135
- * Build-time pagination (SSG model): drive headless Chromium to fully paginate
136
- * the book with Paged.js, then serialize the resulting already-fragmented DOM
137
- * to a static HTML string. Paged.js's polisher injects its layout CSS as
138
- * `<style>` elements INTO the DOM, so the serialized markup carries everything
139
- * needed to render the pages with NO runtime pagination engine.
140
- *
141
- * Serves `outDir` itself (images resolve relative to `book.html`, exactly as
142
- * they will in the shipped artifact) with the engine supplied as overlays.
143
- */
144
- export declare function paginateToStaticHtml(htmlFile: string): Promise<string>;
145
- /**
146
- * Render `htmlFile` (in its own `outDir`) to a PDF, serving that directory with
147
- * the pagination engine supplied as in-memory overlays.
148
- */
149
- export declare function renderHtmlToPdf(htmlFile: string, outPdf: string, renderer?: PdfRenderer, captureStaticHtmlTo?: string): Promise<void>;
@@ -1,29 +0,0 @@
1
- import type { ResolveConflictsOptions, SyncOutcome } from "./sync-types.ts";
2
- /**
3
- * `chapter-01.md` → `chapter-01 (online copy).md` (next to the original).
4
- * `counter` ≥ 2 produces `chapter-01 (online copy 2).md`, … — used to avoid
5
- * clobbering a pre-existing file with the same name.
6
- */
7
- export declare function onlineCopyPath(filepath: string, counter?: number): string;
8
- /**
9
- * Apply the author's per-file choices and sync the combined result
10
- * (ADR 0006 D5). The merge commit has TWO PARENTS — the local branch tip and
11
- * the online tip — so both histories remain intact and View History stays
12
- * honest about what was combined.
13
- *
14
- * How each choice is applied WITHOUT conflict markers:
15
- *
16
- * - Files edited in both copies are settled inside the merge itself by a
17
- * custom `mergeDriver` that returns the chosen side's content ("Keep both
18
- * copies" keeps mine and writes the online version to
19
- * `<name> (online copy)<ext>` beforehand, committed on the local side so it
20
- * is part of the merge). Undecided files auto-merge with the same diff3
21
- * algorithm a plain merge uses.
22
- * - Delete-involved conflicts never reach a merge driver, so they are settled
23
- * by equalizing the local side BEFORE the merge (making both sides agree so
24
- * the merge is clean) and, when the author chose the now-removed side, a
25
- * small follow-up commit AFTER the merge restores their choice. The merge
26
- * commit still carries both parents; the around-commits are visible,
27
- * honestly labeled steps in View History.
28
- */
29
- export declare function resolveConflicts(options: ResolveConflictsOptions): Promise<SyncOutcome>;
@@ -1,103 +0,0 @@
1
- /**
2
- * abort-interrupted-operation.ts — shared skeleton for aborting an unfinished
3
- * git operation (merge / cherry-pick / rebase) left on disk.
4
- *
5
- * WHY (DRY): recover-interrupted-merge, recover-interrupted-cherry-pick, and
6
- * recover-interrupted-rebase were ~90% identical — same TOCTOU guard, same
7
- * backup gate, same hadLocalChanges capture, same fault-injection ordering,
8
- * same force-checkout + marker cleanup + re-verify. The ONLY real differences
9
- * are: which marker files signal the state, which files to remove, and how to
10
- * resolve the ref to restore (merge/cherry-pick reset the current branch; a
11
- * rebase may rewind a named branch to a recorded pre-rebase commit OR restore a
12
- * detached HEAD). Those three differences are captured in `AbortConfig`; the
13
- * invariant ordering lives here, once.
14
- *
15
- * Abort algorithm (pure isomorphic-git + node:fs — never the system git binary):
16
- * 1. TOCTOU precondition: if NONE of `markerFiles` exist, the operation was
17
- * already finished/aborted externally and there is nothing to abort. This
18
- * is now enforced by the DISPATCHER (dispatch.ts), which calls each
19
- * kind's exported `stillApplies` — built from `anyMarkerPresent` below —
20
- * INSIDE withRepoLock, before this function is ever invoked. There used
21
- * to be a duplicate hand-rolled copy of this same check at the top of
22
- * this function; it is deleted, not kept alongside the dispatcher probe,
23
- * because — unlike recover-missing-git-dir.ts — this function has no
24
- * SECOND re-check later (no re-check after the backup/confirm wait), so
25
- * the one check and the dispatcher probe cover the exact same window: the
26
- * dispatcher probe runs immediately before this function's synchronous
27
- * entry, with no work in between, just as the deleted local check did.
28
- * 2. withBackupGate (backup → confirm → risky → failsafe). Inside the callback:
29
- * 3. Capture whether the working tree had in-progress edits (best-effort).
30
- * 4. Resolve the restore target via `resolveTarget` (default:
31
- * ctx.branch → git.currentBranch → "HEAD"). resolveTarget MAY throw (e.g.
32
- * the rebase cannot find its pre-op commit) — that surfaces as
33
- * failed_backup_available since the backup is already safe.
34
- * 5. Optionally rewind a named branch ref to a recorded commit, then
35
- * force-checkout the target (resets index + worktree).
36
- * 6. Remove the transient `cleanupFiles` inside .git.
37
- * 7. Verify every `markerFiles` entry is gone; if not → THROW.
38
- *
39
- * Inside the risky callback we call ONLY raw git.* / node:fs — never a
40
- * lock-wrapped lib function — so the dispatcher's per-repo FIFO queue can't
41
- * deadlock. Re-verification uses direct fs.existsSync, not inspectRepo. All
42
- * removed paths live INSIDE the repo's own .git and are captured in the verified
43
- * backup — never user content.
44
- *
45
- * Fault injection points (ctx.faults?.before()):
46
- * after_backup_before_repair — start of the destructive section
47
- * abort_interrupted_operation — start of the abort proper
48
- * checkout_branch — before the force checkout
49
- * remove_operation_state — before deleting the on-disk state files
50
- */
51
- import type { RecoveryContext, RecoveryResult, SyncErrorKind } from "./types.ts";
52
- /**
53
- * True when any of `markerFiles` (gitDir-relative) still exists. Shared by
54
- * each interrupted-* handler's exported `stillApplies` (the dispatcher's
55
- * precondition probe, see types.ts `StillAppliesFn`) so there is ONE
56
- * implementation of "is this abort still needed", not three copies.
57
- */
58
- export declare function anyMarkerPresent(ctx: RecoveryContext, markerFiles: string[]): boolean;
59
- /** Arguments handed to a config's `resolveTarget`. */
60
- export interface AbortResolveArgs {
61
- ctx: RecoveryContext;
62
- /** The git repository root (ctx.repoDir). */
63
- dir: string;
64
- /** The resolved `.git` directory for `dir`. */
65
- gitDir: string;
66
- }
67
- /** Which ref to restore and (optionally) which branch ref to rewind first. */
68
- export interface AbortTargetPlan {
69
- /** The ref to force-checkout (a branch name, a commit sha, or "HEAD"). */
70
- checkoutRef: string;
71
- /**
72
- * When set, `refs/heads/<writeRefBranch>` is force-written to `writeRefValue`
73
- * BEFORE the checkout (used to rewind a named branch to a pre-op commit). Omit
74
- * to leave all refs untouched (merge/cherry-pick and detached-HEAD rebase).
75
- */
76
- writeRefBranch?: string;
77
- writeRefValue?: string;
78
- }
79
- /** The per-operation differences the shared skeleton parameterizes. */
80
- export interface AbortConfig {
81
- /** Recovery kind — drives policy (backup/confirm) and guidance copy. */
82
- kind: SyncErrorKind;
83
- /**
84
- * gitDir-relative marker paths whose presence signals the interrupted state.
85
- * If NONE exist the abort is a benign no-op; after the abort ALL must be gone.
86
- */
87
- markerFiles: string[];
88
- /** gitDir-relative paths removed during the abort (force; missing is fine). */
89
- cleanupFiles: string[];
90
- /**
91
- * Resolve which ref to restore. Defaults to
92
- * ctx.branch → git.currentBranch → "HEAD". MAY throw to abort the repair
93
- * (the verified backup makes that safe → failed_backup_available).
94
- */
95
- resolveTarget?: (args: AbortResolveArgs) => Promise<AbortTargetPlan>;
96
- /** Build the success message; `hadLocalChanges` reports whether edits were reset. */
97
- successMessage: (hadLocalChanges: boolean) => string;
98
- }
99
- /**
100
- * Abort an interrupted git operation per `config`. See the module header for the
101
- * full algorithm and safety invariants.
102
- */
103
- export declare function abortInterruptedOperation(ctx: RecoveryContext, config: AbortConfig): Promise<RecoveryResult>;
@@ -1,113 +0,0 @@
1
- /**
2
- * Temp-dir zip backup creation and verification for the sync-recovery subsystem.
3
- *
4
- * Creates a STORE-method (no compression) ZIP of the project's user files plus
5
- * .git/, writing it to:
6
- * <os.tmpdir()>/print-sync-recovery/<repo-slug>/<ISO-timestamp>-<reason>.zip
7
- *
8
- * The backup root is `os.tmpdir()` (NOT a hardcoded "/tmp") so it is correct on
9
- * Windows/macOS/Linux — the desktop ships on all three (CLAUDE.md §8). A literal
10
- * "/tmp" would make every risky-repair backup throw on Windows.
11
- *
12
- * The ZIP bytes are produced by fflate's streaming Zip/ZipPassThrough (STORE
13
- * method — no compression), replacing the previous hand-rolled writer (#86).
14
- * fflate is pure JS with zero dependencies, no runtime package.json/data
15
- * reads, and no computed-path dynamic imports, so it bundles cleanly under
16
- * bun build --compile (CLAUDE.md §1/§3). It is already a dependency (publish
17
- * providers use its unzipSync).
18
- *
19
- * Verification (assertZipReadable): deliberately NOT fflate. It validates
20
- * EVERY central-directory entry's signature and bounds (not just the first)
21
- * via positioned reads of the EOCD + central directory only — never the file
22
- * data — so it stays memory-safe on multi-GB backups while still catching
23
- * corruption in any later entry. fflate's unzip needs the whole archive in
24
- * memory, so it cannot back this check; the small positioned-read parser
25
- * stays.
26
- *
27
- * Retention: createRecoveryZip prunes stale backups (best-effort, never throws)
28
- * before writing a new one — see pruneOldBackups — so old backups do not fill
29
- * the disk over time.
30
- *
31
- * Exclusions: node_modules/, .print-sync/cache/, and **.git/config**. The git
32
- * config file is deliberately dropped from the backup because it can carry an
33
- * embedded credential (e.g. a tokenized remote URL) and is reconstructable on
34
- * recovery (the remote is reconfigured from the stored connection). All other
35
- * .git/ contents (objects, refs, HEAD) ARE included so recovery still works.
36
- * Inclusions: all user-visible files + .git/ (minus config, for full recovery)
37
- */
38
- import type { RecoveryBackup, RecoveryContext } from "./types.ts";
39
- /**
40
- * Root directory for all sync-recovery backups.
41
- *
42
- * Computed from `os.tmpdir()` (NOT hardcoded "/tmp") so it resolves to a real
43
- * temp dir on Windows, macOS, and Linux. A literal "/tmp" does not exist on
44
- * Windows and would make every risky-repair backup throw there (CLAUDE.md §8).
45
- */
46
- export declare const BACKUP_ROOT: string;
47
- export interface PruneOldBackupsOptions {
48
- /** Backup root to prune under. Defaults to {@link BACKUP_ROOT}. */
49
- root?: string;
50
- /** Restrict pruning to a single repo-slug subfolder. */
51
- slug?: string;
52
- /** Remove zips with an mtime older than this many ms. Default 7 days. */
53
- ttlMs?: number;
54
- /** After TTL pruning, keep at most this many newest zips per slug. */
55
- maxPerSlug?: number;
56
- /** Clock override (tests). Returns epoch ms. */
57
- now?: () => number;
58
- }
59
- /**
60
- * Remove stale recovery backups so they do not accumulate forever and fill the
61
- * disk (BUG 2). Two independent policies are applied per repo-slug folder:
62
- * 1. TTL — delete any zip whose mtime is older than `ttlMs` (default 7 days).
63
- * 2. Cap — keep only the newest `maxPerSlug` zips (default 20), delete the rest.
64
- *
65
- * BEST-EFFORT and SILENT: this never throws and never blocks backup creation.
66
- * Any unreadable dir, racing delete, or stat failure is ignored — a failure to
67
- * prune must never prevent the user's work from being backed up.
68
- */
69
- export declare function pruneOldBackups(opts?: PruneOldBackupsOptions): Promise<void>;
70
- /**
71
- * Create a temp-dir zip backup of the project directory (user files + .git/,
72
- * minus .git/config). Calls ctx.faults?.before("backup_create") then
73
- * ctx.faults?.before("backup_verify") so tests can inject failures at each step.
74
- *
75
- * Returns a RecoveryBackup describing the zip, or throws on failure.
76
- */
77
- export declare function createRecoveryZip(ctx: Pick<RecoveryContext, "repoDir" | "repoSlug" | "faults" | "now">, reason: string): Promise<RecoveryBackup>;
78
- export interface ZipEntryInfo {
79
- name: string;
80
- size: number;
81
- /** The extracted file content. */
82
- data: Uint8Array;
83
- }
84
- /**
85
- * Extract every entry of a ZIP buffer (fflate unzipSync). Returns [] for a
86
- * buffer that is not a parseable zip — matching the old hand-rolled parser's
87
- * "no EOCD found" behavior that some assertions rely on.
88
- */
89
- export declare function parseZipEntries(buf: Buffer): ZipEntryInfo[];
90
- /**
91
- * Assert that a zip file at `zipPath` is readable and parseable.
92
- * Throws with a descriptive error if not.
93
- *
94
- * Validates EVERY central-directory entry — each entry's CD signature
95
- * (0x02014b50) and that its variable-length fields stay within the central
96
- * directory bounds. A zip whose FIRST entry is intact but whose LATER entries
97
- * are corrupt is rejected (BUG 4): checking only entry 0 let truncated/garbled
98
- * tails slip through.
99
- *
100
- * MEMORY-SAFE: reads only the end-of-central-directory record (file tail) and
101
- * the central directory via positioned reads — NEVER the file data. A multi-GB
102
- * backup is verified without reading it back into memory.
103
- */
104
- export declare function assertZipReadable(zipPath: string): Promise<void>;
105
- /**
106
- * Return the list of entries inside a zip file (as ZipEntryInfo objects).
107
- *
108
- * @internal TEST-ONLY: reads the WHOLE zip into memory to expose entry content.
109
- * NEVER call this in a production path — a large backup would OOM. Production
110
- * verification uses {@link assertZipReadable} (positioned reads, memory-safe).
111
- * Used by tests to assert which files were backed up and inspect content.
112
- */
113
- export declare function zipEntries(zipPath: string): Promise<ZipEntryInfo[]>;
@@ -1,47 +0,0 @@
1
- /**
2
- * buildRecoveryContext — the ONE place a RecoveryContext is resolved from a
3
- * project directory. Both hosts consume it (the desktop's recovery bridge and
4
- * `gutterpress repair`); each supplies only its own ConfirmationGate (dialog vs
5
- * terminal prompt). Keeping the resolution here means repo-root, branch,
6
- * credential, and slug rules can never drift between hosts.
7
- *
8
- * Resolution rules (each learned the hard way — see the audit trail):
9
- * - repoDir: the project's OWN repo root via detectProjectSource. NEVER
10
- * findEnclosingRepoDir — it is ancestor-only (skips the project's own
11
- * .git), so a project that IS its own repo root would resolve to a parent
12
- * repo (e.g. ~/.git) and the backup step would zip the entire home
13
- * directory.
14
- * - branch: remote diagnosis first, then the locally detected branch
15
- * (local-only repos on non-"main" branches), then "main".
16
- * - credential: resolved from the token store by remote hostname; stays in
17
- * the calling process — never serialized to a UI layer.
18
- * - repoSlug: last path segment, sanitized for backup file naming.
19
- */
20
- import { detectProjectSource } from "../../project-source.ts";
21
- import { diagnoseProjectRemote } from "../diagnose.ts";
22
- import { type TokenStore } from "../token-store.ts";
23
- import type { ConfirmationGate, RecoveryContext } from "./types.ts";
24
- export interface BuildRecoveryContextOptions {
25
- /** The directory the user opened (may be a subfolder of its repo). */
26
- projectDir: string;
27
- /** Host-specific approval gate (dialog, terminal prompt, …). */
28
- confirmation: ConfirmationGate;
29
- /** Credential store for the remote host, when the host has one. */
30
- tokenStore?: TokenStore;
31
- /** Display name for snapshot commits created during recovery. */
32
- authorName?: string;
33
- /** Email for snapshot commits created during recovery. */
34
- authorEmail?: string;
35
- /** Operation-log file shared with the sync path. */
36
- logFile?: string;
37
- /**
38
- * Classification override (tests only — omit in production). Injected the
39
- * same way as RecoveryContext's `now`/`faults`: bun's mock.module leaks
40
- * across test files, so cross-cutting modules are never module-mocked.
41
- */
42
- classify?: typeof detectProjectSource;
43
- /** Diagnosis override (tests only — omit in production). See `classify`. */
44
- diagnose?: typeof diagnoseProjectRemote;
45
- }
46
- /** Resolve everything a recovery handler needs from a project directory. */
47
- export declare function buildRecoveryContext(options: BuildRecoveryContextOptions): Promise<RecoveryContext>;
@@ -1,28 +0,0 @@
1
- /**
2
- * Recovery dispatcher — INTEGRATE PHASE.
3
- *
4
- * Maps each SyncErrorKind to its recover-<x>.ts handler.
5
- * Also exports the index barrel for the recovery subsystem public surface.
6
- *
7
- * The dispatcher is the single call-site for all recovery attempts. Callers
8
- * (e.g. a guardedSync wrapper) obtain a SyncErrorKind from classifyGitError(),
9
- * build a RecoveryContext, and call recover(kind, ctx, error).
10
- *
11
- * Handler lookup is a plain switch — no dynamic import, no registry object —
12
- * so TypeScript can verify exhaustiveness at compile time and bun build
13
- * --compile can tree-shake unused handlers.
14
- */
15
- import type { RecoveryContext, RecoveryResult, SyncErrorKind } from "./types.ts";
16
- /**
17
- * Dispatch a recovery attempt.
18
- *
19
- * @param kind - The SyncErrorKind from classifyGitError().
20
- * @param ctx - Full RecoveryContext (repoDir, branch, remoteUrl, etc.).
21
- * @param error - The original thrown error, if available.
22
- * @returns RecoveryResult describing what happened.
23
- */
24
- export declare function recover(kind: SyncErrorKind, ctx: RecoveryContext, error?: unknown): Promise<RecoveryResult>;
25
- export type { RecoverFn, RecoveryContext, RecoveryResult, SyncErrorKind, RecoveryRisk, ManualGuidance, RepoHealth, RecoveryBackup, RepairConfirmation, ConfirmationGate, FaultInjector, FaultPoint, } from "./types.ts";
26
- export { classifyGitError, classifyFromHealth, RepoNeedsRecoveryError, isRepoNeedsRecoveryError, } from "./classify.ts";
27
- export { inspectRepo, preflightStructuralReason, buildPreflightDiagnostics, verifyRepoReadable, isUnbornRepo, } from "./inspect.ts";
28
- export { buildRecoveryContext } from "./context.ts";
@@ -1,33 +0,0 @@
1
- /**
2
- * Fail-safe wrapper and no-op fallback for the sync-recovery subsystem.
3
- *
4
- * withBackupGate() enforces the invariant ordering every risky repair must
5
- * follow:
6
- * 1. Look up the policy for the kind.
7
- * 2. If createBackup → createRecoveryZip; on failure → failSafe
8
- * "failed_no_changes_made" with NO subsequent writes.
9
- * 3. If requireConfirmation → confirmRepair; DENIED → "blocked" no-op.
10
- * 4. Run the risky repair callback.
11
- * 5. If the risky callback throws AFTER a backup → failSafe
12
- * "failed_backup_available" (backup is readable, remote is unchanged).
13
- *
14
- * failSafeNoRepair() is the terminal no-op: returns
15
- * - "failed_backup_available" when a backup zip path is supplied
16
- * - "failed_no_changes_made" otherwise
17
- * Both branches include ManualGuidance so the host can show useful copy.
18
- */
19
- import type { RecoveryContext, RecoveryResult, SyncErrorKind } from "./types.ts";
20
- /**
21
- * Terminal no-op: no repair was attempted (or the repair failed after a
22
- * backup was created). Returns a RecoveryResult with the appropriate status.
23
- */
24
- export declare function failSafeNoRepair(ctx: Pick<RecoveryContext, "repoSlug" | "remoteUrl">, kind: SyncErrorKind, backupZipPath?: string, error?: unknown): RecoveryResult;
25
- /**
26
- * Enforce the invariant ordering for risky repairs:
27
- * policy → backup → confirmation → risky → failsafe on throw.
28
- *
29
- * The `risky` callback receives the backup zip path (or undefined) and
30
- * must call ctx.faults?.before("after_backup_before_repair") at the start
31
- * of its destructive section.
32
- */
33
- export declare function withBackupGate(ctx: RecoveryContext, kind: SyncErrorKind, risky: (backupZipPath: string | undefined) => Promise<RecoveryResult>, error?: unknown): Promise<RecoveryResult>;
@@ -1,28 +0,0 @@
1
- /**
2
- * Plain-language guidance builders for the sync-recovery subsystem.
3
- *
4
- * AUTHOR-FACING COPY RULES (non-negotiable):
5
- * - No git words ("branch", "commit", "rebase", "ref", "HEAD", etc.)
6
- * - No tokens or credentials
7
- * - No internal file paths (no .git/, no /tmp paths in userSummary)
8
- * - No technical error codes in user-facing fields
9
- * - supportDetails MAY contain technical info for support tickets
10
- *
11
- * Each SyncErrorKind gets its own copy block. The host maps the
12
- * ManualGuidance to its display surface.
13
- */
14
- import type { ManualGuidance, RecoveryContext, SyncErrorKind } from "./types.ts";
15
- /**
16
- * Build a ManualGuidance for a given error kind. The guidance is shown when
17
- * a repair is blocked, failed, or needs user action. Never throws.
18
- *
19
- * SAFETY-COPY HONESTY: the "a safety copy was saved" reassurance is generated
20
- * in ONE place, from the actual `backupZipPath` — never hardcoded per kind.
21
- * The backup gate creates (and verifies) the zip BEFORE confirmation, so
22
- * whenever this guidance is shown for a backup-requiring kind, a present
23
- * backupZipPath means the copy really exists, and an absent one means backup
24
- * creation FAILED and nothing was changed (failsafe.ts returns
25
- * failed_no_changes_made without any writes). Promising a safety copy in that
26
- * second case would be false — the one moment the promise matters most.
27
- */
28
- export declare function makeManualGuidance(ctx: Pick<RecoveryContext, "repoSlug" | "remoteUrl">, kind: SyncErrorKind, error?: unknown, backupZipPath?: string): ManualGuidance;