@dogsbay/format-astro 0.2.0-beta.92 → 0.2.0-beta.98

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 (44) hide show
  1. package/dist/base-path.d.ts +10 -0
  2. package/dist/base-path.d.ts.map +1 -1
  3. package/dist/base-path.js +11 -1
  4. package/dist/base-path.js.map +1 -1
  5. package/dist/blog.d.ts +134 -0
  6. package/dist/blog.d.ts.map +1 -0
  7. package/dist/blog.js +319 -0
  8. package/dist/blog.js.map +1 -0
  9. package/dist/diff-decoration.d.ts +79 -0
  10. package/dist/diff-decoration.d.ts.map +1 -0
  11. package/dist/diff-decoration.js +541 -0
  12. package/dist/diff-decoration.js.map +1 -0
  13. package/dist/granularity.d.ts +83 -0
  14. package/dist/granularity.d.ts.map +1 -0
  15. package/dist/granularity.js +247 -0
  16. package/dist/granularity.js.map +1 -0
  17. package/dist/index.d.ts +17 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +22 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/lead.d.ts.map +1 -1
  22. package/dist/lead.js +30 -6
  23. package/dist/lead.js.map +1 -1
  24. package/dist/llms-txt.d.ts +19 -0
  25. package/dist/llms-txt.d.ts.map +1 -1
  26. package/dist/llms-txt.js +40 -2
  27. package/dist/llms-txt.js.map +1 -1
  28. package/dist/project.d.ts +134 -2
  29. package/dist/project.d.ts.map +1 -1
  30. package/dist/project.js +1149 -137
  31. package/dist/project.js.map +1 -1
  32. package/dist/serialize.d.ts +1 -0
  33. package/dist/serialize.d.ts.map +1 -1
  34. package/dist/serialize.js +302 -138
  35. package/dist/serialize.js.map +1 -1
  36. package/dist/sitemap.d.ts +24 -0
  37. package/dist/sitemap.d.ts.map +1 -1
  38. package/dist/sitemap.js +32 -3
  39. package/dist/sitemap.js.map +1 -1
  40. package/dist/taxonomy.d.ts +24 -1
  41. package/dist/taxonomy.d.ts.map +1 -1
  42. package/dist/taxonomy.js +23 -2
  43. package/dist/taxonomy.js.map +1 -1
  44. package/package.json +8 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-decoration.d.ts","sourceRoot":"","sources":["../src/diff-decoration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,iEAAiE;AACjE,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACjD,UAAU,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,WAAW,GAAI,MAAM,QAAQ,KAAG,SAA8B,CAAC;AAE5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAGpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnE;AAUD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,GAC7C,MAAM,CASR;AAKD,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAsC3F;AAiBD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,GACrC,MAAM,CAmGR;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,2ubAmUpB,CAAC"}
@@ -0,0 +1,541 @@
1
+ export const diffMarksOf = (node) => node;
2
+ export function hasDiffMarks(node) {
3
+ const m = diffMarksOf(node);
4
+ return Boolean(m.diff || m.diffInline);
5
+ }
6
+ /**
7
+ * Inject attributes into the FIRST opening tag of a rendered
8
+ * snippet. Handles components (`<TableRow`), plain tags (`<p`), and
9
+ * skips leading whitespace/JSX expressions.
10
+ */
11
+ export function injectAttrs(rendered, attrs) {
12
+ const m = rendered.match(/<([A-Za-z][\w.-]*)((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>/);
13
+ if (!m || m.index === undefined)
14
+ return rendered;
15
+ const insertAt = m.index + 1 + m[1].length;
16
+ return `${rendered.slice(0, insertAt)} ${attrs}${rendered.slice(insertAt)}`;
17
+ }
18
+ const escapeHtmlText = (s) => s
19
+ .replace(/&/g, "&amp;")
20
+ .replace(/</g, "&lt;")
21
+ .replace(/>/g, "&gt;")
22
+ .replace(/\{/g, "&#123;")
23
+ .replace(/\}/g, "&#125;");
24
+ /**
25
+ * Render the merged word-level segments of a changed prose node.
26
+ * Formatting inside changed prose flattens to text in this view —
27
+ * the marks are the content (the unchanged siblings keep their full
28
+ * inline rendering).
29
+ */
30
+ export function inlineSegmentsToHtml(segments) {
31
+ return segments
32
+ .map((s) => {
33
+ const text = escapeHtmlText(s.text);
34
+ if (s.op === "ins")
35
+ return `<ins class="db-diff-ins">${text}</ins>`;
36
+ if (s.op === "del")
37
+ return `<del class="db-diff-del">${text}</del>`;
38
+ return text;
39
+ })
40
+ .join("");
41
+ }
42
+ /** Quadratic-LCS guard, same rationale as tree-diff's word differ. */
43
+ const CODE_DIFF_MAX_LINES = 1500;
44
+ /**
45
+ * Merge two code texts into ONE unified block, reporting the changed
46
+ * lines BY NUMBER rather than by `[!code]` comment markers.
47
+ *
48
+ * Markers were the first design and they leak: Shiki can only strip a
49
+ * marker it recognizes as a comment in the grammar, so in `plaintext`
50
+ * — which every unknown language (`package-install`, doc-tool tokens)
51
+ * falls back to — the marker renders as literal text in the reader's
52
+ * code. Line numbers are language-independent and can't leak.
53
+ *
54
+ * Returns null over the line cap; callers fall back to before/after.
55
+ */
56
+ export function buildMergedCodeDiff(oldCode, newCode) {
57
+ const a = oldCode.split("\n");
58
+ const b = newCode.split("\n");
59
+ if (a.length > CODE_DIFF_MAX_LINES || b.length > CODE_DIFF_MAX_LINES)
60
+ return null;
61
+ const m = a.length;
62
+ const n = b.length;
63
+ const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
64
+ for (let i = m - 1; i >= 0; i--) {
65
+ for (let j = n - 1; j >= 0; j--) {
66
+ lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
67
+ }
68
+ }
69
+ const out = [];
70
+ const addLines = [];
71
+ const removeLines = [];
72
+ let i = 0;
73
+ let j = 0;
74
+ const emit = (text, kind) => {
75
+ out.push(text);
76
+ if (kind === "add")
77
+ addLines.push(out.length);
78
+ else if (kind === "remove")
79
+ removeLines.push(out.length);
80
+ };
81
+ while (i < m && j < n) {
82
+ if (a[i] === b[j]) {
83
+ emit(b[j]);
84
+ i++;
85
+ j++;
86
+ }
87
+ else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
88
+ emit(a[i], "remove");
89
+ i++;
90
+ }
91
+ else {
92
+ emit(b[j], "add");
93
+ j++;
94
+ }
95
+ }
96
+ while (i < m)
97
+ emit(a[i++], "remove");
98
+ while (j < n)
99
+ emit(b[j++], "add");
100
+ return { code: out.join("\n"), addLines, removeLines };
101
+ }
102
+ /** [2,3,4,7] → "2-4,7" */
103
+ const toLineSpec = (lines) => {
104
+ const parts = [];
105
+ for (let i = 0; i < lines.length;) {
106
+ let j = i;
107
+ while (j + 1 < lines.length && lines[j + 1] === lines[j] + 1)
108
+ j++;
109
+ parts.push(i === j ? String(lines[i]) : `${lines[i]}-${lines[j]}`);
110
+ i = j + 1;
111
+ }
112
+ return parts.join(",");
113
+ };
114
+ const codeTextOf = (node) => node.props?.code ?? node.html ?? "";
115
+ /**
116
+ * Decorate one rendered node. `renderPeer` renders the other side of
117
+ * a changed pair through the same serializer (code before/after).
118
+ */
119
+ export function decorateDiff(node, rendered, renderPeer) {
120
+ const marks = diffMarksOf(node);
121
+ if (!marks.diff && !marks.diffInline)
122
+ return rendered;
123
+ // changed prose with word-level segments: replace the inline body
124
+ if (marks.diffInline && node.type !== "heading") {
125
+ return `<p data-diff="changed" class="db-diff-prose" set:html={${JSON.stringify(inlineSegmentsToHtml(marks.diffInline))}} />`;
126
+ }
127
+ // changed code: ONE unified-diff block (removed lines −, added
128
+ // lines +, via CodeRich's notation-diff pipeline) — the same
129
+ // merged reading view prose uses; the copy button copies the
130
+ // post-change code only. Falls back to a labeled before/after
131
+ // pair when merging is unsafe (size cap, marker collision).
132
+ if (marks.diff === "changed" && marks.diffPeer && node.type === "code") {
133
+ const oldCode = codeTextOf(marks.diffPeer);
134
+ const newCode = codeTextOf(node);
135
+ const merged = buildMergedCodeDiff(oldCode, newCode);
136
+ if (merged !== null) {
137
+ const mergedNode = {
138
+ ...node,
139
+ html: undefined,
140
+ props: {
141
+ ...node.props,
142
+ code: merged.code,
143
+ copyText: newCode,
144
+ diffAdd: toLineSpec(merged.addLines),
145
+ diffRemove: toLineSpec(merged.removeLines),
146
+ },
147
+ };
148
+ // Whitespace-only edits stay FULLY marked (indentation is
149
+ // semantic in YAML/Python/markdown — never silently dropped),
150
+ // but they get NAMED: without the label the reader hunts a
151
+ // 3-space shift across two near-identical lines.
152
+ const wsOnly = oldCode !== newCode && oldCode.replace(/\s+/g, "") === newCode.replace(/\s+/g, "");
153
+ const cls = `db-diff-code${wsOnly ? " db-diff-code-ws" : ""}`;
154
+ // The clean twin is the already-rendered new side: shiki line
155
+ // spans keep their newlines OUTSIDE the span, so hiding removed
156
+ // lines under Hide highlights would leave blank lines — instead
157
+ // the whole merged block swaps for the clean render (CSS only).
158
+ return [
159
+ `<div data-diff="changed" class="${cls}">`,
160
+ `<div class="db-diff-code-merged">`,
161
+ renderPeer(mergedNode),
162
+ `</div>`,
163
+ `<div class="db-diff-code-clean">`,
164
+ rendered,
165
+ `</div>`,
166
+ `</div>`,
167
+ ].join("\n");
168
+ }
169
+ const before = injectAttrs(renderPeer(marks.diffPeer), 'data-diff="removed"');
170
+ const after = injectAttrs(rendered, 'data-diff="added"');
171
+ return [
172
+ `<div class="db-diff-pair" data-diff="changed">`,
173
+ `<div class="db-diff-pair-label">before</div>`,
174
+ before,
175
+ `<div class="db-diff-pair-label">after</div>`,
176
+ after,
177
+ `</div>`,
178
+ ].join("\n");
179
+ }
180
+ const attrs = marks.diff === "moved" && marks.diffMovedFrom
181
+ ? `data-diff="moved" data-diff-from="${marks.diffMovedFrom.join(" › ").replace(/"/g, "&quot;")}"`
182
+ : `data-diff="${marks.diff}"`;
183
+ // Raw HTML renders as <Fragment set:html>, which emits NO wrapper
184
+ // element — an injected attribute vanishes at build time (found via
185
+ // better-auth adapters/mysql: an invisible table change). Marked
186
+ // html blocks get a real wrapper div instead.
187
+ if (node.type === "html") {
188
+ // A CHANGED html block is opaque: we render the new markup
189
+ // faithfully (fidelity is the product), but a bare "changed" border
190
+ // told the reader NOTHING about what went — the mysql table lost a
191
+ // whole column and the page showed no trace of it (external review:
192
+ // "fidelity alone is insufficient if a meaningful deletion becomes
193
+ // invisible"). So the previous version rides along in a disclosure:
194
+ // the deletion is one click away, and the reader is never misled
195
+ // into thinking nothing was removed.
196
+ if (marks.diff === "changed" && marks.diffPeer) {
197
+ return [
198
+ `<div ${attrs} class="db-diff-html">`,
199
+ rendered,
200
+ `<details class="db-diff-html-before">`,
201
+ `<summary>Show previous version</summary>`,
202
+ `<div data-diff="removed">`,
203
+ renderPeer(marks.diffPeer),
204
+ `</div>`,
205
+ `</details>`,
206
+ `</div>`,
207
+ ].join("\n");
208
+ }
209
+ return `<div ${attrs}>\n${rendered}\n</div>`;
210
+ }
211
+ return injectAttrs(rendered, attrs);
212
+ }
213
+ /**
214
+ * The diff stylesheet the generated site carries — theme-token based,
215
+ * both color modes, no hardcoded palette (key-decisions doctrine).
216
+ */
217
+ export const DIFF_CSS = `/* tree-diff decorations — one DiffTree, rendered by the normal components.
218
+ Mark styling is GUARDED by html:not([data-diff-hidden]) rather than
219
+ force-reset under hidden mode: a reset like "border-left: none
220
+ !important" also clobbers a component's OWN border (an added warning
221
+ callout lost its left edge — user find, adapters/mysql). Guarded
222
+ rules simply stop applying, and component styling survives. */
223
+ [data-diff] { position: relative; }
224
+ html:not([data-diff-hidden]) [data-diff="added"] {
225
+ background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 12%, transparent);
226
+ border-left: 3px solid var(--color-success, oklch(0.72 0.19 150));
227
+ padding-left: 0.75rem;
228
+ }
229
+ html:not([data-diff-hidden]) [data-diff="removed"] {
230
+ background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 10%, transparent);
231
+ border-left: 3px solid var(--color-destructive, oklch(0.64 0.21 25));
232
+ padding-left: 0.75rem;
233
+ opacity: 0.75;
234
+ }
235
+ html:not([data-diff-hidden]) [data-diff="removed"] :is(p, li, code, pre) { text-decoration: line-through; text-decoration-thickness: 1px; }
236
+ html:not([data-diff-hidden]) [data-diff="changed"] {
237
+ border-left: 3px solid var(--color-warning, oklch(0.8 0.16 85));
238
+ padding-left: 0.75rem;
239
+ }
240
+ html:not([data-diff-hidden]) [data-diff="moved"] {
241
+ border-left: 3px dashed var(--color-info, oklch(0.7 0.14 240));
242
+ padding-left: 0.75rem;
243
+ }
244
+ html:not([data-diff-hidden]) [data-diff="moved"][data-diff-from]::after {
245
+ content: "moved from " attr(data-diff-from);
246
+ display: block;
247
+ font-size: 0.75rem;
248
+ color: var(--color-muted-foreground, inherit);
249
+ opacity: 0.8;
250
+ }
251
+
252
+ /* Explicit "added"/"removed" label pills straddling the block's top
253
+ edge. Components with their own chrome (callouts, code blocks)
254
+ camouflage a tint-only mark — a green-tinted warning callout reads
255
+ as a callout variant, not as new content (user feedback,
256
+ better-auth adapters/mysql). Table rows are excluded (a pill can't
257
+ straddle a tr); nested marks defer to the outermost label. */
258
+ html:not([data-diff-hidden]) [data-diff="added"]:not(tr, th, td)::before,
259
+ html:not([data-diff-hidden]) [data-diff="removed"]:not(tr, th, td)::before {
260
+ position: absolute;
261
+ top: 0;
262
+ right: 0.6rem;
263
+ transform: translateY(-55%);
264
+ font-size: 0.65rem;
265
+ font-weight: 600;
266
+ letter-spacing: 0.04em;
267
+ line-height: 1.4;
268
+ padding: 0 0.5rem;
269
+ border-radius: 99px;
270
+ z-index: 1;
271
+ text-decoration: none;
272
+ }
273
+ html:not([data-diff-hidden]) [data-diff="added"]:not(tr, th, td)::before {
274
+ content: "added";
275
+ background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 25%, var(--color-background, Canvas));
276
+ border: 1px solid color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 45%, transparent);
277
+ }
278
+ html:not([data-diff-hidden]) [data-diff="removed"]:not(tr, th, td)::before {
279
+ content: "removed";
280
+ background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 20%, var(--color-background, Canvas));
281
+ border: 1px solid color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 45%, transparent);
282
+ }
283
+ [data-diff] [data-diff]::before { content: none !important; }
284
+
285
+ /* Nav change markers (the sidebar is client-rendered from nav.json, so
286
+ the styles ship here rather than with the component). Same contract as
287
+ the tab markers: a GLYPH carries the meaning, colour reinforces it,
288
+ visually-hidden text announces it. */
289
+ .db-nav-mark {
290
+ display: inline-flex;
291
+ align-items: center;
292
+ justify-content: center;
293
+ flex-shrink: 0;
294
+ width: 1rem;
295
+ height: 1rem;
296
+ margin-left: auto;
297
+ border-radius: 99px;
298
+ font-size: 0.75rem;
299
+ font-weight: 700;
300
+ line-height: 1;
301
+ color: var(--color-muted-foreground, currentColor);
302
+ }
303
+ .db-nav-mark[data-nav-mark="added"] { color: var(--color-success, oklch(0.72 0.19 150)); }
304
+ .db-nav-mark[data-nav-mark="changed"] { color: var(--color-warning, oklch(0.8 0.16 85)); }
305
+ .db-nav-mark[data-nav-mark="removed"] { color: var(--color-destructive, oklch(0.64 0.21 25)); }
306
+ .db-nav-mark[data-nav-mark="moved"] { color: var(--color-info, oklch(0.7 0.14 240)); }
307
+ /* a subtree marker is a hint about descendants, not a claim about this row */
308
+ .db-nav-mark[data-nav-mark-subtree] { opacity: 0.55; }
309
+ /* removed pages get a SECOND non-colour signal */
310
+ /* Belt-and-braces: a subtree mark must never strike through its own row. */
311
+ [data-nav-mark-row="removed"]:not([data-nav-mark-subtree]) { text-decoration: line-through; opacity: 0.7; }
312
+ @media (forced-colors: active) {
313
+ .db-nav-mark { color: CanvasText; }
314
+ }
315
+
316
+ /* Which tab changed? A marked panel is invisible until opened, so its
317
+ trigger carries a marker (the group's bar says "in here", the marker
318
+ says "this one"). The marker is a GLYPH, not a colored dot: colour
319
+ alone fails WCAG 1.4.1 and background-tinted dots vanish entirely in
320
+ forced-colors mode. Colour reinforces; shape carries the meaning.
321
+ Visually-hidden text rides along (emitted by the serializer). */
322
+ .db-tab-mark {
323
+ display: inline-block;
324
+ margin-left: 0.35rem;
325
+ font-size: 0.75rem;
326
+ font-weight: 700;
327
+ line-height: 1;
328
+ vertical-align: middle;
329
+ color: var(--color-muted-foreground, currentColor);
330
+ }
331
+ [data-diff-hidden] .db-tab-mark { display: none; }
332
+ .db-tab-mark[data-diff-tab="changed"] { color: var(--color-warning, oklch(0.8 0.16 85)); }
333
+ .db-tab-mark[data-diff-tab="added"] { color: var(--color-success, oklch(0.72 0.19 150)); }
334
+ .db-tab-mark[data-diff-tab="removed"] { color: var(--color-destructive, oklch(0.64 0.21 25)); }
335
+ .db-tab-mark[data-diff-tab="moved"] { color: var(--color-info, oklch(0.7 0.14 240)); }
336
+ @media (forced-colors: active) {
337
+ .db-tab-mark { color: CanvasText; }
338
+ }
339
+ ins.db-diff-ins {
340
+ text-decoration: none;
341
+ background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 22%, transparent);
342
+ border-radius: 2px;
343
+ padding: 0 1px;
344
+ }
345
+ del.db-diff-del {
346
+ background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 18%, transparent);
347
+ border-radius: 2px;
348
+ padding: 0 1px;
349
+ }
350
+ .db-diff-pair { display: grid; gap: 0.25rem; margin-block: 1rem; }
351
+ .db-diff-pair-label {
352
+ font-size: 0.7rem;
353
+ text-transform: uppercase;
354
+ letter-spacing: 0.08em;
355
+ color: var(--color-muted-foreground, inherit);
356
+ opacity: 0.8;
357
+ }
358
+ tr[data-diff] { border-left-width: 3px; }
359
+
360
+ /* Folded unchanged sections are CHROME, not content callouts:
361
+ dashed, dimmed, compact — visually "collapsed context". */
362
+ .db-diff-fold {
363
+ border: 1px dashed color-mix(in oklch, currentColor 25%, transparent) !important;
364
+ border-left-width: 1px !important;
365
+ background: transparent !important;
366
+ opacity: 0.7;
367
+ margin-block: 0.5rem !important;
368
+ }
369
+ .db-diff-fold:hover, .db-diff-fold[open] { opacity: 1; }
370
+ .db-diff-fold summary, .db-diff-fold [data-slot="trigger"] {
371
+ font-size: 0.85rem;
372
+ }
373
+
374
+ /* The comparison banner at the top of each reader page. */
375
+ .db-diff-banner-row { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; align-items: center; width: 100%; }
376
+ .db-diff-banner {
377
+ border: 1px solid color-mix(in oklch, currentColor 15%, transparent);
378
+ border-radius: 8px;
379
+ padding: 0.75rem 1rem;
380
+ margin-block-end: 1.5rem;
381
+ font-size: 0.85rem;
382
+ display: flex;
383
+ flex-wrap: wrap;
384
+ gap: 0.4rem 1rem;
385
+ align-items: center;
386
+ }
387
+ .db-diff-banner .db-chip {
388
+ display: inline-block;
389
+ border-radius: 99px;
390
+ padding: 0.05rem 0.6rem;
391
+ font-size: 0.75rem;
392
+ border: 1px solid transparent;
393
+ }
394
+ .db-chip-added { background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 15%, transparent); }
395
+ .db-chip-removed { background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 12%, transparent); }
396
+ .db-chip-changed { background: color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 15%, transparent); }
397
+ .db-chip-moved { background: color-mix(in oklch, var(--color-info, oklch(0.7 0.14 240)) 12%, transparent); }
398
+ .db-chip-muted { border-color: color-mix(in oklch, currentColor 25%, transparent); opacity: 0.8; }
399
+
400
+ /* The persistent view-switcher bar (Reader / Docs-ops / Agent):
401
+ perspectives on ONE comparison — tabs are links, verification
402
+ lives in the chrome, the Monitor CTA sits next to the value. */
403
+ .db-cmp-bar {
404
+ position: sticky;
405
+ /* BELOW the docs header, not under it: both were sticky at top:0 and
406
+ the header's higher z-index simply swallowed this bar on scroll —
407
+ the chrome carrying the trust contract was the chrome that vanished.
408
+ --db-header-height is declared by DocsLayout (the header sizes itself
409
+ from the same variable, so they cannot drift). */
410
+ top: var(--db-header-height, 3.25rem);
411
+ z-index: 30;
412
+ display: flex;
413
+ flex-wrap: wrap;
414
+ gap: 0.4rem 0.9rem;
415
+ align-items: center;
416
+ padding: 0.5rem 0.9rem;
417
+ margin-block-end: 1rem;
418
+ border: 1px solid color-mix(in oklch, currentColor 15%, transparent);
419
+ border-radius: 8px;
420
+ background: var(--color-background, Canvas);
421
+ font-size: 0.8rem;
422
+ }
423
+ .db-cmp-bar .db-chip {
424
+ display: inline-block;
425
+ border-radius: 99px;
426
+ padding: 0.05rem 0.6rem;
427
+ font-size: 0.75rem;
428
+ border: 1px solid transparent;
429
+ }
430
+ .db-cmp-bar button.db-chip { cursor: pointer; background: transparent; font: inherit; font-size: 0.75rem; color: inherit; }
431
+ .db-cmp-tabs {
432
+ display: inline-flex;
433
+ gap: 0.15rem;
434
+ border: 1px solid color-mix(in oklch, currentColor 20%, transparent);
435
+ border-radius: 99px;
436
+ padding: 0.1rem;
437
+ }
438
+ .db-cmp-tab {
439
+ padding: 0.1rem 0.7rem;
440
+ border-radius: 99px;
441
+ text-decoration: none;
442
+ color: inherit;
443
+ }
444
+ .db-cmp-tab[aria-current="page"] {
445
+ background: color-mix(in oklch, currentColor 12%, transparent);
446
+ font-weight: 600;
447
+ }
448
+ .db-cmp-pairs { position: relative; display: inline-block; }
449
+ .db-cmp-pairs summary { cursor: pointer; }
450
+ .db-cmp-pairs ul {
451
+ position: absolute;
452
+ top: 100%;
453
+ left: 0;
454
+ margin: 0.25rem 0 0;
455
+ padding: 0.35rem 0.75rem;
456
+ list-style: none;
457
+ background: var(--color-background, Canvas);
458
+ border: 1px solid color-mix(in oklch, currentColor 20%, transparent);
459
+ border-radius: 8px;
460
+ white-space: nowrap;
461
+ z-index: 40;
462
+ }
463
+ .db-cmp-monitor { text-decoration: none; border-color: color-mix(in oklch, currentColor 30%, transparent) !important; }
464
+ .db-cmp-monitor:hover { background: color-mix(in oklch, currentColor 10%, transparent); }
465
+
466
+ /* A wholly added/removed PAGE carries one mark around all its content.
467
+ The per-block tint would be overwhelming at page scale, so page level
468
+ gets a stronger edge and a much lighter wash — legible at a glance,
469
+ still readable as prose. The "added"/"removed" pill (the ::before on
470
+ [data-diff]) rides on this wrapper, which is exactly right: it labels
471
+ the page, once. */
472
+ html:not([data-diff-hidden]) [data-diff].db-diff-page {
473
+ border-left-width: 5px;
474
+ padding-left: 1rem;
475
+ padding-block: 0.5rem;
476
+ border-radius: 0 6px 6px 0;
477
+ }
478
+ html:not([data-diff-hidden]) [data-diff="added"].db-diff-page {
479
+ background: color-mix(in oklch, var(--color-success, oklch(0.72 0.19 150)) 5%, transparent);
480
+ }
481
+ html:not([data-diff-hidden]) [data-diff="removed"].db-diff-page {
482
+ background: color-mix(in oklch, var(--color-destructive, oklch(0.64 0.21 25)) 5%, transparent);
483
+ opacity: 1; /* the page is READABLE — the mark says removed, not the dimming */
484
+ }
485
+ /* …and a removed PAGE is not struck through — that is for a deleted line,
486
+ not for a document the reader still needs to read. */
487
+ [data-diff="removed"].db-diff-page :is(p, li, code, pre) { text-decoration: none; }
488
+
489
+ /* Changed raw HTML: the faithful new render, plus the previous version
490
+ one click away. Without it a deletion inside opaque markup (a dropped
491
+ table column) is simply invisible — the reader is told "something
492
+ changed" and shown no evidence. */
493
+ .db-diff-html-before {
494
+ margin-block-start: 0.5rem;
495
+ font-size: 0.8rem;
496
+ border: 1px dashed color-mix(in oklch, currentColor 25%, transparent);
497
+ border-radius: 6px;
498
+ padding: 0.25rem 0.6rem;
499
+ }
500
+ /* Full-strength text: the summary is a CONTROL — dimming it (opacity or
501
+ muted-foreground) drops it below 4.5:1 and axe rightly fails it. */
502
+ .db-diff-html-before summary { cursor: pointer; color: inherit; }
503
+ .db-diff-html-before[open] summary { margin-block-end: 0.5rem; }
504
+ [data-diff-hidden] .db-diff-html-before { display: none; }
505
+
506
+ /* Merged code diffs carry a hidden clean twin (the post-change
507
+ render); Hide highlights swaps them. */
508
+ .db-diff-code { position: relative; }
509
+ .db-diff-code-clean { display: none; }
510
+ [data-diff-hidden] .db-diff-code-clean { display: block; }
511
+ [data-diff-hidden] .db-diff-code-merged { display: none; }
512
+
513
+ /* Whitespace-only code edits are still marked (indentation is
514
+ semantic in YAML/Python/markdown) — but NAMED, so the reader
515
+ is not hunting an invisible 3-space shift. */
516
+ html:not([data-diff-hidden]) .db-diff-code-ws .db-diff-code-merged::before {
517
+ content: "whitespace only";
518
+ position: absolute;
519
+ top: 0;
520
+ right: 0.6rem;
521
+ transform: translateY(-55%);
522
+ z-index: 1;
523
+ font-size: 0.65rem;
524
+ font-weight: 600;
525
+ letter-spacing: 0.04em;
526
+ line-height: 1.4;
527
+ padding: 0 0.5rem;
528
+ border-radius: 99px;
529
+ background: color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 22%, var(--color-background, Canvas));
530
+ border: 1px solid color-mix(in oklch, var(--color-warning, oklch(0.8 0.16 85)) 45%, transparent);
531
+ }
532
+
533
+ /* Hide highlights — read the target version clean. One attribute on
534
+ <html>: the guarded mark rules above stop applying by themselves;
535
+ only content that does not exist in the target version (removed
536
+ blocks, del runs, the "before" code side) needs hiding, plus the
537
+ ins tint. */
538
+ [data-diff-hidden] :is([data-diff="removed"], del.db-diff-del, .db-diff-pair-label) { display: none !important; }
539
+ [data-diff-hidden] ins.db-diff-ins { background: transparent; padding: 0; }
540
+ `;
541
+ //# sourceMappingURL=diff-decoration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-decoration.js","sourceRoot":"","sources":["../src/diff-decoration.ts"],"names":[],"mappings":"AAgCA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAa,EAAE,CAAC,IAAiB,CAAC;AAE5E,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa;IACzD,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACnF,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACjD,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,CAAS,EAAU,EAAE,CAC3C,CAAC;KACE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;KACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;KACxB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA8C;IAE9C,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,EAAE,KAAK,KAAK;YAAE,OAAO,4BAA4B,IAAI,QAAQ,CAAC;QACpE,IAAI,CAAC,CAAC,EAAE,KAAK,KAAK;YAAE,OAAO,4BAA4B,IAAI,QAAQ,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,sEAAsE;AACtE,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAUjC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,OAAe;IAClE,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,CAAC,MAAM,GAAG,mBAAmB,IAAI,CAAC,CAAC,MAAM,GAAG,mBAAmB;QAAE,OAAO,IAAI,CAAC;IAClF,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,MAAM,GAAG,GAAe,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,IAAuB,EAAE,EAAE;QACrD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,IAAI,KAAK,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;aACzC,IAAI,IAAI,KAAK,QAAQ;YAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACX,CAAC,EAAE,CAAC;YACJ,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YACrB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAClB,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IAClC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACzD,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,GAAG,CAAC,KAAe,EAAU,EAAE;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAI,CAAC;QACnC,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,CAAC,EAAE,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAc,EAAU,EAAE,CAC3C,IAAI,CAAC,KAAK,EAAE,IAAe,IAAK,IAAI,CAAC,IAAe,IAAI,EAAE,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAc,EACd,QAAgB,EAChB,UAAsC;IAEtC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC;IAEtD,kEAAkE;IAClE,IAAI,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAChD,OAAO,0DAA0D,IAAI,CAAC,SAAS,CAC7E,oBAAoB,CAAC,KAAK,CAAC,UAAU,CAAC,CACvC,MAAM,CAAC;IACV,CAAC;IAED,+DAA+D;IAC/D,6DAA6D;IAC7D,6DAA6D;IAC7D,8DAA8D;IAC9D,4DAA4D;IAC5D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,UAAU,GAAa;gBAC3B,GAAG,IAAI;gBACP,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE;oBACL,GAAG,IAAI,CAAC,KAAK;oBACb,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;oBACpC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC;iBAC3C;aACF,CAAC;YACF,0DAA0D;YAC1D,8DAA8D;YAC9D,2DAA2D;YAC3D,iDAAiD;YACjD,MAAM,MAAM,GACV,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACrF,MAAM,GAAG,GAAG,eAAe,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC9D,8DAA8D;YAC9D,gEAAgE;YAChE,gEAAgE;YAChE,gEAAgE;YAChE,OAAO;gBACL,mCAAmC,GAAG,IAAI;gBAC1C,mCAAmC;gBACnC,UAAU,CAAC,UAAU,CAAC;gBACtB,QAAQ;gBACR,kCAAkC;gBAClC,QAAQ;gBACR,QAAQ;gBACR,QAAQ;aACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAC9E,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QACzD,OAAO;YACL,gDAAgD;YAChD,8CAA8C;YAC9C,MAAM;YACN,6CAA6C;YAC7C,KAAK;YACL,QAAQ;SACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GACT,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,aAAa;QAC3C,CAAC,CAAC,qCAAqC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG;QACjG,CAAC,CAAC,cAAc,KAAK,CAAC,IAAI,GAAG,CAAC;IAClC,kEAAkE;IAClE,oEAAoE;IACpE,iEAAiE;IACjE,8CAA8C;IAC9C,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,2DAA2D;QAC3D,oEAAoE;QACpE,mEAAmE;QACnE,oEAAoE;QACpE,mEAAmE;QACnE,oEAAoE;QACpE,iEAAiE;QACjE,qCAAqC;QACrC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC/C,OAAO;gBACL,QAAQ,KAAK,wBAAwB;gBACrC,QAAQ;gBACR,uCAAuC;gBACvC,0CAA0C;gBAC1C,2BAA2B;gBAC3B,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC1B,QAAQ;gBACR,YAAY;gBACZ,QAAQ;aACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;QACD,OAAO,QAAQ,KAAK,MAAM,QAAQ,UAAU,CAAC;IAC/C,CAAC;IACD,OAAO,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmUvB,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Granularity views — assemble a whole-book page from topic pages + nav.
3
+ *
4
+ * The additive-MVP core (see plans/granularity-views.md). Given the topic-grained
5
+ * `ExportPage[]` + the `NavItem[]` tree a site already produces, this reassembles
6
+ * an entire nav subtree into ONE "book" page:
7
+ * - each nav node becomes a heading (level = nav depth; book title is the
8
+ * implicit H1, so a top-level node is H2, its children H3, … clamped at H6);
9
+ * - a topic's own body headings are shifted down by `level − 1` so they nest
10
+ * under their topic's title;
11
+ * - every node gets a stable, page-unique anchor, so a topic resolves to
12
+ * `<book>/_book#<anchor>` (the "Read as single page" button target).
13
+ *
14
+ * Pure + source-agnostic: it never touches the network or the original source,
15
+ * only the already-converted `TreeNode` trees ("convert once, project many").
16
+ * The original topic trees are NOT mutated — each is deep-cloned before its
17
+ * headings are shifted.
18
+ *
19
+ * This is a platform generalization of `format-fluidtopics-rest/src/single-page.ts`
20
+ * (which is khub-coupled). The FT `--single-page` branch will fold into this.
21
+ */
22
+ import type { ExportPage, NavItem, TreeNode } from "@dogsbay/types";
23
+ /**
24
+ * Reserved route segment for the book rollup. NOT `_`-prefixed: Astro treats
25
+ * `src/pages/` files/dirs starting with `_` as private (non-routable), so a
26
+ * `_book.astro` would 404. `~book` is collision-proof instead — `slugify`
27
+ * strips `~`, so no content slug can ever produce this segment (no slugifier
28
+ * reservation needed).
29
+ */
30
+ export declare const BOOK_SEGMENT = "~book";
31
+ export interface AssembleBookOptions {
32
+ /** Route prefix the nav hrefs carry (e.g. "/docs" or ""); stripped to get slugs. */
33
+ hrefPrefix?: string;
34
+ /** Book page title; defaults to the first nav item's label. */
35
+ title?: string;
36
+ /** Reserved suffix segment for the rollup route. Default `_book`. */
37
+ bookSegment?: string;
38
+ }
39
+ export interface BookView {
40
+ /** The assembled book page (noindex; excluded from nav/sitemap/search/llms). */
41
+ page: ExportPage;
42
+ /**
43
+ * topic slug → its anchor on the book page. Powers the per-topic
44
+ * "Read as single page" button (`<bookRoute>#<anchor>`).
45
+ */
46
+ topicAnchors: Map<string, string>;
47
+ }
48
+ /**
49
+ * Rough estimate of a TreeNode[]'s textual content size in bytes — the sum of
50
+ * its prose text, pre-rendered html, and string prop values (code, etc.). Used
51
+ * as the granularity size cap's input: it tracks source size closely enough to
52
+ * catch the 10 MB+ rollups (rest_api, installing) that blow up build + DOM,
53
+ * without a full serialize. See docs-dev/granularity-book-perf.md.
54
+ */
55
+ export declare function estimateTreeBytes(tree: TreeNode[]): number;
56
+ /**
57
+ * Assemble one book page from a nav subtree + the topic pages it references.
58
+ * Returns null when the subtree references no actual pages.
59
+ */
60
+ export declare function assembleBook(nav: NavItem[], pages: ExportPage[], opts?: AssembleBookOptions): BookView | null;
61
+ /** The "Open as page ↗" affordance — a link from a book section back to its topic page. */
62
+ export declare function openAsPageNode(slug: string, hrefPrefix: string): TreeNode;
63
+ /** A heading node for a nav entry's title, carrying the explicit anchor id. */
64
+ export declare function bookHeading(level: number, title: string, anchor: string): TreeNode;
65
+ /** Shift every heading in a (topic body) tree down by `offset` ranks (max H6). */
66
+ export declare function shiftHeadings(tree: TreeNode[], offset: number): void;
67
+ /**
68
+ * Rewrite **intra-book** xrefs to in-page anchors so the reader stays in the
69
+ * book. A link is rewritten iff its target slug is in *this* book's
70
+ * `topicAnchors` (the "is it in this book?" test). Cross-book links (target not
71
+ * in the book) and external links are left untouched — they keep pointing at the
72
+ * canonical topic page, which is correct. Mutates `tree` (already a clone).
73
+ */
74
+ export declare function rewriteIntraBookLinks(tree: TreeNode[], topicAnchors: Map<string, string>, hrefPrefix: string): void;
75
+ /**
76
+ * Longest common path prefix (slash-segment-wise) of the member slugs — the
77
+ * book's base route (e.g. the FT book base `…/16.0`). Falls back to `fallback`.
78
+ */
79
+ export declare function bookSlugFrom(slugs: string[], fallback: string): string;
80
+ /** Strip the route prefix + leading slash + anchor/query from a nav href → slug. */
81
+ export declare function slugFromHref(href: string | undefined, hrefPrefix: string): string;
82
+ export declare function slugify(text: string): string;
83
+ //# sourceMappingURL=granularity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"granularity.d.ts","sourceRoot":"","sources":["../src/granularity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAW,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,IAAI,EAAE,UAAU,CAAC;IACjB;;;OAGG;IACH,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,CAoB1D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EAAE,EACd,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,GAAE,mBAAwB,GAC7B,QAAQ,GAAG,IAAI,CAuEjB;AAID,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,QAAQ,CAOzE;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAMlF;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAUpE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,EAAE,EAChB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,UAAU,EAAE,MAAM,GACjB,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUtE;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAKjF;AAuCD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU5C"}