gutterpress 0.10.0 → 0.10.1-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 (57) hide show
  1. package/README.md +3 -16
  2. package/dist/api/index.d.ts +10 -19
  3. package/dist/api/index.js +8 -64
  4. package/dist/{audit-cvarpa72.js → audit-dce99css.js} +5 -5
  5. package/dist/{build-6r502chw.js → build-bj4x0727.js} +5 -5
  6. package/dist/checks/source/index.d.ts +1 -0
  7. package/dist/checks/source/merge-markers.d.ts +3 -0
  8. package/dist/{cli-cqtggsng.js → cli-0z6zf98d.js} +2 -47
  9. package/dist/{cli-eq5naw4m.js → cli-7n848af2.js} +370 -1249
  10. package/dist/{cli-zfcryxg8.js → cli-eewynen9.js} +97 -60
  11. package/dist/{cli-k1065rkg.js → cli-w692g6dx.js} +75 -29
  12. package/dist/{cli-149edp6b.js → cli-ydpf09d9.js} +1 -1
  13. package/dist/cli.js +14 -15
  14. package/dist/{doctor-akvxbtjb.js → doctor-7nd6n2aa.js} +2 -2
  15. package/dist/{engine-ft4cr3ep.js → engine-57vw8zpd.js} +1 -1
  16. package/dist/{engine-b159tbns.js → engine-8kdwy0jz.js} +2 -2
  17. package/dist/{gutterpress-viewer-te8g5grx.js → gutterpress-viewer-tma1qwz2.js} +57 -15
  18. package/dist/{index-ycpvr0am.js → index-7hrvksrb.js} +4 -4
  19. package/dist/{index-mdefp0y5.js → index-dtrftayr.js} +75 -27
  20. package/dist/{index-ge7q9xj3.js → index-h1x7krkg.js} +771 -1482
  21. package/dist/{index-wq3r5pj7.js → index-p0ebr7dh.js} +94 -57
  22. package/dist/index.js +9 -65
  23. package/dist/lib/git-fs.d.ts +43 -0
  24. package/dist/lib/host-policy.d.ts +14 -2
  25. package/dist/lib/remote-auth/clone.d.ts +0 -21
  26. package/dist/lib/remote-auth/converge-merge.d.ts +24 -7
  27. package/dist/lib/remote-auth/generic-auth.d.ts +0 -22
  28. package/dist/lib/remote-auth/git-http.d.ts +1 -1
  29. package/dist/lib/remote-auth/github-auth.d.ts +2 -22
  30. package/dist/lib/remote-auth/operation-log.d.ts +4 -9
  31. package/dist/lib/remote-auth/sync-messages.d.ts +35 -12
  32. package/dist/lib/remote-auth/sync-types.d.ts +30 -101
  33. package/dist/lib/remote-auth/sync.d.ts +19 -21
  34. package/dist/lib/remote-auth/token-store.d.ts +0 -10
  35. package/dist/lib/remote-auth/transport.d.ts +42 -40
  36. package/dist/lib/source-provider.d.ts +12 -0
  37. package/dist/{lint-p2sw53d9.js → lint-c8ze8sqw.js} +5 -5
  38. package/dist/{new-hvq0x91q.js → new-xy2qc300.js} +5 -5
  39. package/dist/{plugin-pssmk0dx.js → plugin-g9hwzjsm.js} +5 -5
  40. package/dist/{preflight-4j00yd0g.js → preflight-prcvdvmk.js} +5 -5
  41. package/dist/{preview-bridge-fz7vpk8m.js → preview-bridge-2nnrs2hy.js} +15 -0
  42. package/dist/{preview-d4s6gk6p.js → preview-h479apj9.js} +5 -5
  43. package/dist/{preview-interface-0ssk8bmm.js → preview-interface-2f5v96hd.js} +411 -88
  44. package/dist/{preview-shell-c5mfa3q0.js → preview-shell-kyjq59d9.js} +80 -25
  45. package/dist/{publish-tkc26en7.js → publish-emk644a8.js} +5 -5
  46. package/dist/{source-provider-3tcj6qg2.js → source-provider-2j5x2hyw.js} +1 -1
  47. package/dist/{source-provider-vanafrt9.js → source-provider-kn41jmbs.js} +1 -1
  48. package/dist/{validate-w9vfefrw.js → validate-be54bdc6.js} +5 -5
  49. package/package.json +1 -1
  50. package/dist/lib/app-heartbeat.d.ts +0 -102
  51. package/dist/lib/remote-auth/image-clash.d.ts +0 -17
  52. package/dist/lib/remote-auth/recovery/classify.d.ts +0 -97
  53. package/dist/lib/remote-auth/recovery/inspect.d.ts +0 -75
  54. package/dist/lib/remote-auth/recovery/locks.d.ts +0 -20
  55. package/dist/lib/remote-auth/recovery/repair.d.ts +0 -29
  56. package/dist/lib/remote-auth/recovery/types.d.ts +0 -39
  57. package/dist/repair-2va4w12t.js +0 -137
@@ -4,7 +4,7 @@ import {
4
4
  launchChromium,
5
5
  marginBoxAlign,
6
6
  marginBoxRectPt
7
- } from "./index-ycpvr0am.js";
7
+ } from "./index-7hrvksrb.js";
8
8
 
9
9
  // src/engine/compiler/build.ts
10
10
  import { readFile } from "node:fs/promises";
@@ -1282,6 +1282,62 @@ var DESC_JS = `(el) => {
1282
1282
  const src = el.tagName === "IMG" ? " src=" + (el.getAttribute("src") || "").slice(-40) : "";
1283
1283
  return el.tagName.toLowerCase() + cls + src;
1284
1284
  }`;
1285
+ var ABS_CONTAINING_BLOCK_JS = `(cs) => {
1286
+ if (cs.position !== "static") return true;
1287
+ for (const prop of [
1288
+ "transform", "translate", "rotate", "scale", "perspective",
1289
+ "filter", "backdropFilter",
1290
+ ]) {
1291
+ const value = cs[prop];
1292
+ if (value && value !== "none") return true;
1293
+ }
1294
+ if (/\\b(layout|paint|strict|content)\\b/.test(cs.contain)) return true;
1295
+ if (cs.containerType && cs.containerType !== "normal") return true;
1296
+ return /\\b(transform|translate|rotate|scale|perspective|filter)\\b/.test(cs.willChange);
1297
+ }`;
1298
+ var CLIP_EDGES_JS = `((establishesAbsContainingBlock) => {
1299
+ const open = () => ({ left: -Infinity, right: Infinity, top: -Infinity, bottom: Infinity });
1300
+ const rootStyle = getComputedStyle(document.documentElement);
1301
+ const propagates = (a, rootOverflow) =>
1302
+ a === document.documentElement ||
1303
+ (a === document.body && rootOverflow === "visible");
1304
+ return (el) => {
1305
+ const edges = open();
1306
+ const chain = [];
1307
+ const elStyle = getComputedStyle(el);
1308
+ const fixed = elStyle.position === "fixed";
1309
+ let binds = !fixed && elStyle.position !== "absolute";
1310
+ for (let a = el.parentElement; a; a = a.parentElement) {
1311
+ const cs = getComputedStyle(a);
1312
+ if (!fixed && !binds && establishesAbsContainingBlock(cs)) binds = true;
1313
+ const own = open();
1314
+ const clipsX =
1315
+ binds && cs.overflowX !== "visible" && !propagates(a, rootStyle.overflowX);
1316
+ const clipsY =
1317
+ binds && cs.overflowY !== "visible" && !propagates(a, rootStyle.overflowY);
1318
+ if (clipsX || clipsY) {
1319
+ const r = a.getBoundingClientRect();
1320
+ const clipMargin = parseFloat(cs.overflowClipMargin) || 0;
1321
+ if (clipsX) {
1322
+ const grow = cs.overflowX === "clip" ? clipMargin : 0;
1323
+ own.left = r.left + parseFloat(cs.borderLeftWidth) - grow;
1324
+ own.right = r.right - parseFloat(cs.borderRightWidth) + grow;
1325
+ edges.left = Math.max(edges.left, own.left);
1326
+ edges.right = Math.min(edges.right, own.right);
1327
+ }
1328
+ if (clipsY) {
1329
+ const grow = cs.overflowY === "clip" ? clipMargin : 0;
1330
+ own.top = r.top + parseFloat(cs.borderTopWidth) - grow;
1331
+ own.bottom = r.bottom - parseFloat(cs.borderBottomWidth) + grow;
1332
+ edges.top = Math.max(edges.top, own.top);
1333
+ edges.bottom = Math.min(edges.bottom, own.bottom);
1334
+ }
1335
+ }
1336
+ chain.push({ ancestor: a, style: cs, edges: own });
1337
+ }
1338
+ return { edges, chain };
1339
+ };
1340
+ })(${ABS_CONTAINING_BLOCK_JS})`;
1285
1341
  function pagePseudoContexts(model) {
1286
1342
  const used = new Set(pseudoVariants(model));
1287
1343
  const standard = new Set(["left", "right", "recto", "verso", "first", "blank"]);
@@ -1762,25 +1818,11 @@ ${mapCss}`;
1762
1818
  return reasons;
1763
1819
  };
1764
1820
 
1765
- // Does this box establish the containing block for an absolutely
1766
- // positioned descendant? Overflow clipping binds an abspos
1767
- // .gp-pin only from its containing block outward MEASURED: a
1768
- // pin whose box lay entirely outside a STATIC overflow:hidden
1769
- // (and overflow:clip) wrapper printed complete and still behind
1770
- // the page text; the wrapper's clip never touched it.
1771
- const establishesAbsContainingBlock = (cs) => {
1772
- if (cs.position !== "static") return true;
1773
- for (const prop of [
1774
- "transform", "translate", "rotate", "scale", "perspective",
1775
- "filter", "backdropFilter",
1776
- ]) {
1777
- const value = cs[prop];
1778
- if (value && value !== "none") return true;
1779
- }
1780
- if (/\\b(layout|paint|strict|content)\\b/.test(cs.contain)) return true;
1781
- if (cs.containerType && cs.containerType !== "normal") return true;
1782
- return /\\b(transform|translate|rotate|scale|perspective|filter)\\b/.test(cs.willChange);
1783
- };
1821
+ // Which ancestors clip a .gp-behind, and where their edges fall —
1822
+ // the same measured rule the pre-print width check clamps against
1823
+ // (see CLIP_EDGES_JS). This pass walks its \`chain\` so it can name
1824
+ // the ancestor doing the cutting.
1825
+ const clipEdges = ${CLIP_EDGES_JS};
1784
1826
 
1785
1827
  for (const el of document.querySelectorAll("*")) {
1786
1828
  const cs = getComputedStyle(el);
@@ -1823,47 +1865,25 @@ ${mapCss}`;
1823
1865
  const boundary = el.closest(".page, .spread");
1824
1866
  if (boundary) {
1825
1867
  const elRect = el.getBoundingClientRect();
1826
- // Clip binding: an in-flow .gp-behind is bound by every
1827
- // ancestor's overflow, but an abspos one only from its
1828
- // containing block outward — static wrappers in between
1829
- // never clip it (measured; see the pass comment above).
1830
- let clipBinds = cs.position !== "absolute" && cs.position !== "fixed";
1831
- for (let ancestor = el.parentElement; ancestor; ancestor = ancestor.parentElement) {
1832
- const ancestorStyle = getComputedStyle(ancestor);
1833
- if (!clipBinds && establishesAbsContainingBlock(ancestorStyle)) clipBinds = true;
1868
+ for (const link of clipEdges(el).chain) {
1869
+ const ancestor = link.ancestor;
1870
+ const ancestorStyle = link.style;
1834
1871
  const reasons = stackingReasons(ancestor, ancestorStyle);
1835
1872
  // Clipping never reorders layers — it can only CUT the
1836
1873
  // art (measured, pass comment above): warn only where the
1837
- // border box crosses a binding ancestor's clip edge on an
1838
- // axis whose overflow is not \`visible\`. The clip edge is
1839
- // the padding box, grown by overflow-clip-margin where
1840
- // that axis's value is \`clip\` (px values only; keyword
1841
- // forms parse NaN -> 0, i.e. the ungrown padding box).
1842
- // 1px epsilon: the measured cut lands exactly at the
1843
- // edge, and sub-pixel layout rounding is not an overhang.
1874
+ // border box crosses THIS ancestor's own clip edges, which
1875
+ // are ±Infinity on any axis it does not bind. 1px epsilon:
1876
+ // the measured cut lands exactly at the edge, and sub-pixel
1877
+ // layout rounding is not an overhang.
1844
1878
  const cuts = [];
1845
- if (clipBinds && (ancestorStyle.overflowX !== "visible" || ancestorStyle.overflowY !== "visible")) {
1846
- const r = ancestor.getBoundingClientRect();
1847
- const clipMargin = parseFloat(ancestorStyle.overflowClipMargin) || 0;
1848
- if (ancestorStyle.overflowX !== "visible") {
1849
- const grow = ancestorStyle.overflowX === "clip" ? clipMargin : 0;
1850
- const left = r.left + parseFloat(ancestorStyle.borderLeftWidth) - grow;
1851
- const right = r.right - parseFloat(ancestorStyle.borderRightWidth) + grow;
1852
- if (elRect.left < left - 1)
1853
- cuts.push(Math.round(left - elRect.left) + "px past its left clip edge");
1854
- if (elRect.right > right + 1)
1855
- cuts.push(Math.round(elRect.right - right) + "px past its right clip edge");
1856
- }
1857
- if (ancestorStyle.overflowY !== "visible") {
1858
- const grow = ancestorStyle.overflowY === "clip" ? clipMargin : 0;
1859
- const top = r.top + parseFloat(ancestorStyle.borderTopWidth) - grow;
1860
- const bottom = r.bottom - parseFloat(ancestorStyle.borderBottomWidth) + grow;
1861
- if (elRect.top < top - 1)
1862
- cuts.push(Math.round(top - elRect.top) + "px past its top clip edge");
1863
- if (elRect.bottom > bottom + 1)
1864
- cuts.push(Math.round(elRect.bottom - bottom) + "px past its bottom clip edge");
1865
- }
1866
- }
1879
+ if (elRect.left < link.edges.left - 1)
1880
+ cuts.push(Math.round(link.edges.left - elRect.left) + "px past its left clip edge");
1881
+ if (elRect.right > link.edges.right + 1)
1882
+ cuts.push(Math.round(elRect.right - link.edges.right) + "px past its right clip edge");
1883
+ if (elRect.top < link.edges.top - 1)
1884
+ cuts.push(Math.round(link.edges.top - elRect.top) + "px past its top clip edge");
1885
+ if (elRect.bottom > link.edges.bottom + 1)
1886
+ cuts.push(Math.round(elRect.bottom - link.edges.bottom) + "px past its bottom clip edge");
1867
1887
  if ((reasons.length || cuts.length) && !seenLayerTraps.has(ancestor)) {
1868
1888
  seenLayerTraps.add(ancestor);
1869
1889
  const effects = [];
@@ -1968,6 +1988,20 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
1968
1988
  const boxes = JSON.parse(await page.evaluate(`(() => {
1969
1989
  const LIMIT = ${limitPx} + 1;
1970
1990
  const desc = ${DESC_JS};
1991
+ // Shrink-to-fit only reacts to overflow that ESCAPES: a clipping or
1992
+ // scrolling ancestor contains its subtree's overflow, so the
1993
+ // document's own width never grows and the book prints at 1.0x.
1994
+ // MEASURED (Chrome 151, a marker word read off \`pdftotext -bbox\`
1995
+ // against an unshrunk control): a 900px box inside a 300px
1996
+ // \`overflow: hidden\` shell printed unshrunk, as did the same box
1997
+ // under clip/auto/scroll — and a real 208-page book whose two
1998
+ // "offenders" both sat under \`overflow-x: clip\` printed
1999
+ // coordinate-identical with them restored. WHICH ancestors clip, and
2000
+ // where their edges fall, is CLIP_EDGES_JS's measured rule — shared
2001
+ // with the layer-containment audit, which measures overhang past the
2002
+ // same edges. Only the x edges matter here: shrink-to-fit is a width
2003
+ // response.
2004
+ const clipEdges = ${CLIP_EDGES_JS};
1971
2005
  const out = [];
1972
2006
  for (const el of document.querySelectorAll("*")) {
1973
2007
  const r = el.getBoundingClientRect();
@@ -1977,6 +2011,9 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
1977
2011
  // width) — both trigger the same whole-document shrink-to-fit,
1978
2012
  // measured: a left bleed alone cost 16%.
1979
2013
  if (right <= LIMIT && r.width <= LIMIT && r.left >= -1) continue;
2014
+ // ...but only where it actually escapes its clipping ancestors.
2015
+ const clip = clipEdges(el).edges;
2016
+ if (Math.min(right, clip.right) <= LIMIT && Math.max(r.left, clip.left) >= -1) continue;
1980
2017
  let deepest = true;
1981
2018
  for (const c of el.children) {
1982
2019
  if (c.getBoundingClientRect().width >= r.width - 1) { deepest = false; break; }
package/dist/index.js CHANGED
@@ -1,16 +1,15 @@
1
1
  import {
2
- APP_HEARTBEAT_FRESH_MS,
3
2
  AUTO_SNAPSHOT_DEFAULT_MINUTES,
4
3
  AUTO_SNAPSHOT_MAX_MINUTES,
5
4
  AUTO_SNAPSHOT_MIN_MINUTES,
6
5
  AUTO_SYNC_DEFAULT_MINUTES,
7
6
  AUTO_SYNC_MAX_MINUTES,
8
7
  AUTO_SYNC_MIN_MINUTES,
8
+ AUTO_SYNC_PUSH_INTERVAL_MINUTES,
9
9
  BUILT_IN_TEMPLATE_IDS,
10
10
  BUILT_IN_THEME_IDS,
11
11
  FileTokenStore,
12
12
  GITHUB_HOST,
13
- GenericTokenAuthProvider,
14
13
  GitHubAuthProvider,
15
14
  MANIFEST_FILENAMES,
16
15
  PLUGINS_DIR,
@@ -18,9 +17,7 @@ import {
18
17
  PRESET_IDS,
19
18
  PublishSelectionsStore,
20
19
  RECOMMENDED_PLUGINS,
21
- RepoNeedsRecoveryError,
22
20
  SNIPPETS_DIR,
23
- STALE_LOCK_MIN_AGE_MS,
24
21
  SYNC_SNAPSHOT_MESSAGE,
25
22
  TARGETS,
26
23
  TARGET_IDS,
@@ -28,14 +25,11 @@ import {
28
25
  addLocalPlugin,
29
26
  addNpmPlugin,
30
27
  adoptFolder,
31
- appHeartbeatPath,
32
28
  applyTheme,
33
29
  autoSnapshotDelayMs,
34
30
  autoSyncDelayMs,
35
- buildPreflightDiagnostics,
36
31
  checkCss,
37
32
  checkToolAvailability,
38
- classifyFromHealth,
39
33
  cloneRepository,
40
34
  connectGenericHost,
41
35
  connectPublishProvider,
@@ -55,20 +49,13 @@ import {
55
49
  getPreviousTheme,
56
50
  getSystemDiagnostics,
57
51
  hasProjectManifest,
58
- heartbeatTtlMs,
59
52
  importTemplateFromFolder,
60
53
  importThemeFromFile,
61
54
  importThemeFromFolder,
62
55
  importThemeFromUrl,
63
56
  inspectImage,
64
- inspectRepo,
65
- isAppHeartbeatFresh,
66
57
  isGitInternalPath,
67
- isLikelyRepoCorruption,
68
- isRepoNeedsRecoveryError,
69
58
  isSshRemoteUrl,
70
- isUnbornRepo,
71
- keepImageVersion,
72
59
  knownForgeTokenUrl,
73
60
  listBuiltInTemplates,
74
61
  listBuiltInThemes,
@@ -85,29 +72,17 @@ import {
85
72
  loadManifest,
86
73
  loadManifestWithPath,
87
74
  log,
88
- migrateUrlCredential,
89
- normalizeForgeHost,
90
75
  openPath,
91
76
  parseRemoteOrigin,
92
- preflightStructuralReason,
93
- provenancePath,
94
77
  publishConnectionStatus,
95
78
  publishCredentialKey,
96
79
  publishProviderFor,
97
80
  publishTargetFor,
98
- pullChanges,
99
- pushChanges,
100
- readAppHeartbeat,
101
- readImageVersion,
102
81
  readManifestFields,
103
- readProjectProvenance,
104
82
  readPublishSettings,
105
83
  readSnippet,
106
84
  readThemeCss,
107
- redactCredential,
108
- removeAppHeartbeat,
109
85
  removeProjectTheme,
110
- repairRepo,
111
86
  reportMissingTools,
112
87
  resolveActiveStyles,
113
88
  resolveBuiltInTheme,
@@ -137,18 +112,16 @@ import {
137
112
  substituteVariables,
138
113
  syncProject,
139
114
  testRemoteAccess,
140
- validateProjectPlugins,
141
- verifyRepoReadable,
142
- writeAppHeartbeat
143
- } from "./index-ge7q9xj3.js";
115
+ validateProjectPlugins
116
+ } from "./index-h1x7krkg.js";
144
117
  import {
145
118
  BUILD_DIAGNOSTIC_CODES
146
- } from "./index-wq3r5pj7.js";
119
+ } from "./index-p0ebr7dh.js";
147
120
  import {
148
121
  BuildError,
149
122
  DEFAULT_PRINT_OPTS,
150
123
  readyProbeExpr
151
- } from "./index-ycpvr0am.js";
124
+ } from "./index-7hrvksrb.js";
152
125
  import {
153
126
  AUTO_SNAPSHOT_MESSAGE,
154
127
  HISTORY_PAGE_LIMIT,
@@ -156,9 +129,8 @@ import {
156
129
  isNoChangesError,
157
130
  providerFor,
158
131
  resolveLogger,
159
- restoreVersionWithBackup,
160
- shortOid
161
- } from "./index-mdefp0y5.js";
132
+ restoreVersionWithBackup
133
+ } from "./index-dtrftayr.js";
162
134
  import {
163
135
  capabilitiesFor,
164
136
  detectProjectSource,
@@ -168,8 +140,6 @@ import {
168
140
  } from "./index-v5mp7a6q.js";
169
141
  import"./index-37x76zdn.js";
170
142
  export {
171
- writeAppHeartbeat,
172
- verifyRepoReadable,
173
143
  validateProjectPlugins,
174
144
  testRemoteAccess,
175
145
  syncProject,
@@ -177,7 +147,6 @@ export {
177
147
  startPreviewServer,
178
148
  splitOutPath,
179
149
  slugifyProjectName,
180
- shortOid,
181
150
  setPublishProviderConfig,
182
151
  setPluginEnabled,
183
152
  setManifestFields,
@@ -205,31 +174,19 @@ export {
205
174
  reportMissingTools,
206
175
  repoSubPath,
207
176
  repoRootForSource,
208
- repairRepo,
209
177
  removeProjectTheme,
210
- removeAppHeartbeat,
211
- redactCredential,
212
178
  readyProbeExpr,
213
179
  readThemeCss,
214
180
  readSnippet,
215
181
  readPublishSettings,
216
- readProjectProvenance,
217
182
  readManifestFields,
218
- readImageVersion,
219
- readAppHeartbeat,
220
- pushChanges,
221
- pullChanges,
222
183
  publishTargetFor,
223
184
  publishProviderFor,
224
185
  publishCredentialKey,
225
186
  publishConnectionStatus,
226
187
  providerFor,
227
- provenancePath,
228
- preflightStructuralReason,
229
188
  parseRemoteOrigin,
230
189
  openPath,
231
- normalizeForgeHost,
232
- migrateUrlCredential,
233
190
  log,
234
191
  loadManifestWithPath,
235
192
  loadManifest,
@@ -246,21 +203,14 @@ export {
246
203
  listBuiltInThemes,
247
204
  listBuiltInTemplates,
248
205
  knownForgeTokenUrl,
249
- keepImageVersion,
250
- isUnbornRepo,
251
206
  isSshRemoteUrl,
252
- isRepoNeedsRecoveryError,
253
207
  isNoChangesError,
254
- isLikelyRepoCorruption,
255
208
  isGitInternalPath,
256
- isAppHeartbeatFresh,
257
- inspectRepo,
258
209
  inspectImage,
259
210
  importThemeFromUrl,
260
211
  importThemeFromFolder,
261
212
  importThemeFromFile,
262
213
  importTemplateFromFolder,
263
- heartbeatTtlMs,
264
214
  hasProjectManifest,
265
215
  getSystemDiagnostics,
266
216
  getPreviousTheme,
@@ -282,15 +232,12 @@ export {
282
232
  connectPublishProvider,
283
233
  connectGenericHost,
284
234
  cloneRepository,
285
- classifyFromHealth,
286
235
  checkToolAvailability,
287
236
  checkCss,
288
237
  capabilitiesFor,
289
- buildPreflightDiagnostics,
290
238
  autoSyncDelayMs,
291
239
  autoSnapshotDelayMs,
292
240
  applyTheme,
293
- appHeartbeatPath,
294
241
  adoptFolder,
295
242
  addNpmPlugin,
296
243
  addLocalPlugin,
@@ -298,9 +245,7 @@ export {
298
245
  TARGET_IDS,
299
246
  TARGETS,
300
247
  SYNC_SNAPSHOT_MESSAGE,
301
- STALE_LOCK_MIN_AGE_MS,
302
248
  SNIPPETS_DIR,
303
- RepoNeedsRecoveryError,
304
249
  RESTORE_BACKUP_MESSAGE,
305
250
  RECOMMENDED_PLUGINS,
306
251
  PublishSelectionsStore,
@@ -310,7 +255,6 @@ export {
310
255
  MANIFEST_FILENAMES,
311
256
  HISTORY_PAGE_LIMIT,
312
257
  GitHubAuthProvider,
313
- GenericTokenAuthProvider,
314
258
  GITHUB_HOST,
315
259
  FileTokenStore,
316
260
  DEFAULT_PRINT_OPTS,
@@ -318,12 +262,12 @@ export {
318
262
  BUILT_IN_THEME_IDS,
319
263
  BUILT_IN_TEMPLATE_IDS,
320
264
  BUILD_DIAGNOSTIC_CODES,
265
+ AUTO_SYNC_PUSH_INTERVAL_MINUTES,
321
266
  AUTO_SYNC_MIN_MINUTES,
322
267
  AUTO_SYNC_MAX_MINUTES,
323
268
  AUTO_SYNC_DEFAULT_MINUTES,
324
269
  AUTO_SNAPSHOT_MIN_MINUTES,
325
270
  AUTO_SNAPSHOT_MESSAGE,
326
271
  AUTO_SNAPSHOT_MAX_MINUTES,
327
- AUTO_SNAPSHOT_DEFAULT_MINUTES,
328
- APP_HEARTBEAT_FRESH_MS
272
+ AUTO_SNAPSHOT_DEFAULT_MINUTES
329
273
  };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `node:fs`, with crash-atomic writes for git's MUTABLE metadata.
3
+ *
4
+ * WHY THIS EXISTS. isomorphic-git writes `.git/index`, `HEAD`, `packed-refs`
5
+ * and loose refs with a plain `fs.writeFile` — an open-truncate-write. If the
6
+ * process dies between the truncate and the write (the writer force-quits the
7
+ * app, the OS kills it, the machine sleeps badly) the file is left EMPTY or
8
+ * half-written, and the repository can no longer be read. That is the one
9
+ * class of history damage Gutterpress itself causes; every other class needs
10
+ * an outside actor. Since isomorphic-git takes `fs` as a parameter, replacing
11
+ * the write with temp-file + `rename` removes the truncation window entirely
12
+ * — a reader sees either the old file or the new one, never a torn one.
13
+ *
14
+ * SCOPE. Only git's mutable metadata is redirected. Working-tree files and
15
+ * content-addressed objects keep the plain write: objects are written once
16
+ * under a hash of their contents (a torn object is a NEW file that simply
17
+ * fails to inflate, it never destroys a good one), and the working tree is
18
+ * the author's own files, which `rename` semantics would not improve.
19
+ *
20
+ * NOT A DURABILITY BARRIER. There is deliberately no `fsync`: this closes the
21
+ * PROCESS-DEATH window, where the page cache survives and `rename` is atomic
22
+ * to every reader. Surviving sudden power loss would mean fsync-ing the temp
23
+ * file and its directory on every ref write, which is a real cost on the
24
+ * snapshot hot path and guards a failure the app does not cause.
25
+ *
26
+ * Everything not named below passes through to `node:fs` unchanged, so
27
+ * `import { gitFs as fs }` is a drop-in for `import * as fs from "node:fs"`.
28
+ */
29
+ import * as nodeFs from "node:fs";
30
+ /** True when `file` is git metadata that must be replaced, never truncated. */
31
+ export declare function needsAtomicWrite(file: unknown): file is string;
32
+ /**
33
+ * `node:fs` with the two writers above swapped in — the object callers import
34
+ * as `fs` and hand to isomorphic-git. Everything else passes through
35
+ * untouched.
36
+ *
37
+ * WHY an object and not `export * from "node:fs"`: bun build with
38
+ * `--packages=external` compiles a star re-export of a BUILTIN into a
39
+ * `__reExport(exports, node_fs)` call whose `node_fs` binding is never
40
+ * emitted, so the built lib throws `ReferenceError: node_fs is not defined`
41
+ * on import. A plain spread has no such hazard.
42
+ */
43
+ export declare const gitFs: typeof nodeFs;
@@ -40,6 +40,17 @@ export interface AutoSyncPolicy {
40
40
  export declare const AUTO_SYNC_MIN_MINUTES = 1;
41
41
  export declare const AUTO_SYNC_MAX_MINUTES: number;
42
42
  export declare const AUTO_SYNC_DEFAULT_MINUTES = 2;
43
+ /**
44
+ * Minutes between PUSH-enabled auto-sync passes (owner decision 2026-08-23).
45
+ * Every ~2-minute tick still PULLS — a collaborator's work keeps arriving
46
+ * promptly — but only a tick whose push window has elapsed also pushes, and
47
+ * the desktop pushes once more on project close/app exit. This is what keeps
48
+ * an actively-typing author from minting a snapshot-and-push every 2 minutes
49
+ * (the "commit wall"): between push windows, ticks with an unmoved remote
50
+ * commit nothing at all. Deliberately a constant, NOT a settings knob — the
51
+ * sync cadence is hidden policy, same as the tick interval's default.
52
+ */
53
+ export declare const AUTO_SYNC_PUSH_INTERVAL_MINUTES = 15;
43
54
  /**
44
55
  * Resolve the debounce delay (ms) for the host's auto-snapshot timer, or
45
56
  * `null` when automatic snapshots are disabled. Pure — the testable core of
@@ -61,8 +72,9 @@ export declare function autoSnapshotDelayMs(policy: Partial<AutoSnapshotPolicy>
61
72
  * the default and is then clamped into [AUTO_SYNC_MIN_MINUTES,
62
73
  * AUTO_SYNC_MAX_MINUTES].
63
74
  *
64
- * Note: the host orchestrator ALSO debounces on file-change triggers; this
65
- * cadence governs only the periodic safety timer (§4.2 "Periodic safety").
75
+ * Note: this is the host orchestrator's ONLY sync trigger — the file-change
76
+ * debounce that used to sit beside it was removed (it could never fire before
77
+ * this interval already had).
66
78
  */
67
79
  export declare function autoSyncDelayMs(policy: Partial<AutoSyncPolicy> | undefined): number | null;
68
80
  /**
@@ -7,22 +7,6 @@ export interface CloneProgressEvent {
7
7
  loaded: number;
8
8
  total?: number;
9
9
  }
10
- /**
11
- * Provider provenance recorded next to a cloned project (ADR 0006 D4):
12
- * metadata for the repo picker / re-auth UX, never consulted by the
13
- * editing/preview/build paths.
14
- */
15
- export interface ProjectProvenance {
16
- provider: "github";
17
- owner: string;
18
- repo: string;
19
- /**
20
- * Legacy GitHub-App installation id. New clones never write it (the OAuth
21
- * App model has no installations — ADR 0006 D1 amendment 2026-06-10); kept
22
- * optional so provenance files written by 0.4.x betas still parse.
23
- */
24
- installationId?: string;
25
- }
26
10
  export interface CloneRepositoryOptions {
27
11
  /** HTTPS clone URL. Tokens embedded in the URL are stripped (D7). */
28
12
  url: string;
@@ -49,8 +33,6 @@ export interface CloneRepositoryOptions {
49
33
  onProgress?: (event: CloneProgressEvent) => void;
50
34
  /** When provided, credentials embedded in `url` are migrated into it (D7). */
51
35
  tokenStore?: TokenStore;
52
- /** Provider provenance to record beside the clone (ADR 0006 D4). */
53
- provenance?: ProjectProvenance;
54
36
  /**
55
37
  * Injectable git HTTP transport for tests (isomorphic-git's `http` client
56
38
  * shape). Defaults to isomorphic-git's node client.
@@ -63,9 +45,6 @@ export interface CloneRepositoryResult {
63
45
  /** The checked-out branch. */
64
46
  branch?: string;
65
47
  }
66
- export declare function provenancePath(projectDir: string): string;
67
- /** Read recorded provider provenance for a project, if any. Never throws. */
68
- export declare function readProjectProvenance(projectDir: string): Promise<ProjectProvenance | null>;
69
48
  /**
70
49
  * Reduce a (possibly renderer-/user-supplied) project folder name to a single
71
50
  * safe path segment: path separators become dashes and leading dots are
@@ -1,5 +1,23 @@
1
- import type { GitCache, ImageClash } from "./sync-types.ts";
2
- export type { ImageClash };
1
+ import { onlineSiblingPath, isOnlineSibling } from "./sync-messages.ts";
2
+ export { onlineSiblingPath, isOnlineSibling };
3
+ import type { GitCache, KeptBothFile } from "./sync-types.ts";
4
+ export type { KeptBothFile };
5
+ /** Type guard exposing MergeConflictError's per-file payload. */
6
+ export declare function isMergeConflictError(e: unknown): e is {
7
+ data: {
8
+ filepaths: string[];
9
+ bothModified: string[];
10
+ deleteByUs: string[];
11
+ deleteByTheirs: string[];
12
+ };
13
+ };
14
+ /**
15
+ * A non-forced `git.checkout` refused to overwrite working-tree files whose
16
+ * content moved after we committed them. isomorphic-git detects this in its
17
+ * analysis pass and throws BEFORE touching the tree, so the refusal is
18
+ * atomic: nothing on disk has been written.
19
+ */
20
+ export declare function isCheckoutConflict(e: unknown): boolean;
3
21
  export interface ConvergeResult {
4
22
  /** The branch tip after the merge (the merge commit, or the ff/no-op tip). */
5
23
  oid: string;
@@ -8,13 +26,13 @@ export interface ConvergeResult {
8
26
  * the writer should blend these. Empty when everything merged cleanly.
9
27
  */
10
28
  combinedFiles: string[];
11
- /** Clashing images (newer version kept) for the non-blocking picker. */
12
- imageClashes: ImageClash[];
29
+ /** Files kept as a pair: ours at `path`, theirs at `onlinePath`. */
30
+ keptBothFiles: KeptBothFile[];
13
31
  }
14
32
  /** Snapshot message for the equalization commit (driver-unreachable cases). */
15
33
  export declare const CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online version";
16
- /** Snapshot message for the post-merge restore commit (newer-wins/edits). */
17
- export declare const CONVERGE_RESTORE_MESSAGE = "Kept the newest version of files that can't be combined";
34
+ /** Snapshot message for the post-merge restore commit (kept-both/edits). */
35
+ export declare const CONVERGE_RESTORE_MESSAGE = "Kept both versions of the files that can't be combined";
18
36
  /** The merge commit message (same wording the old sync used). */
19
37
  export declare const CONVERGE_MERGE_MESSAGE = "Combined your changes with the online version";
20
38
  /**
@@ -44,5 +62,4 @@ export declare function convergeMerge(params: {
44
62
  };
45
63
  authorName?: string;
46
64
  authorEmail?: string;
47
- allowUnrelatedHistories?: boolean;
48
65
  }): Promise<ConvergeResult>;
@@ -15,7 +15,6 @@
15
15
  * SECURITY INVARIANT: token values never appear in error messages or logs.
16
16
  */
17
17
  import type httpNode from "isomorphic-git/http/node";
18
- import type { HostCallbacks, RemoteAuthProvider } from "./github-auth.ts";
19
18
  import { type HostCredential } from "./token-store.ts";
20
19
  /** What the Advanced Setup UI collects for "Connect a Git server". */
21
20
  export interface GenericTokenConnectInput {
@@ -38,14 +37,6 @@ export interface GenericTokenConnectInput {
38
37
  */
39
38
  repoUrl?: string;
40
39
  }
41
- /**
42
- * The {@link RemoteAuthProvider} `connect` input for the generic provider:
43
- * the UI-collected fields plus the standard host callbacks (`onUserCode` is
44
- * part of the shared contract but unused — there is no device code in the
45
- * token flow; pass a no-op, or use {@link connectGenericHost} directly).
46
- */
47
- export interface GenericHostCallbacks extends HostCallbacks, GenericTokenConnectInput {
48
- }
49
40
  export interface GenericAuthOptions {
50
41
  /** Injectable git HTTP transport for tests. */
51
42
  httpClient?: typeof httpNode;
@@ -82,16 +73,3 @@ export declare function knownForgeTokenUrl(host: string): string | null;
82
73
  * a full end-to-end verification of the token against a real repository.
83
74
  */
84
75
  export declare function connectGenericHost(input: GenericTokenConnectInput, options?: GenericAuthOptions): Promise<HostCredential>;
85
- /**
86
- * {@link RemoteAuthProvider} wrapper for the generic token flow, so host apps
87
- * can route any non-GitHub origin through the shared provider contract.
88
- */
89
- export declare class GenericTokenAuthProvider implements RemoteAuthProvider {
90
- private readonly options;
91
- constructor(options?: GenericAuthOptions);
92
- /** Handles any smart-HTTP(S) host that is not github.com (device flow). */
93
- matches(origin: URL): boolean;
94
- connect(callbacks: GenericHostCallbacks): Promise<HostCredential>;
95
- /** Revalidate a stored credential: false only on a definitive rejection. */
96
- validate(credential: HostCredential): Promise<boolean>;
97
- }
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * One timer serves the whole request (re-armed via `refresh()`; no per-chunk
33
33
  * allocation). On timeout the thrown error's message classifies as offline in
34
- * recovery/classify.ts. Known limitation (documented, not fixable at this
34
+ * transport.ts. Known limitation (documented, not fixable at this
35
35
  * layer): isomorphic-git's client accepts no AbortSignal, so an abandoned
36
36
  * timed-out transfer's socket is left to the OS/agent to reap.
37
37
  *