mosage 0.2.0 → 0.8.0

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 (121) hide show
  1. package/README.md +16 -15
  2. package/bin.js +2 -0
  3. package/dist/build-C7NW_3Pk.js +14 -0
  4. package/dist/check-CP4873Wx.js +41 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +228 -0
  7. package/dist/config-DPm1BBAb.js +2619 -0
  8. package/dist/config-TlTe7Ona.d.ts +24 -0
  9. package/dist/context-BqsdSrAQ.js +1084 -0
  10. package/dist/dev-Biz42qlu.js +17 -0
  11. package/dist/diagram-xlVDekYk.js +763 -0
  12. package/dist/export-Bi6nuxjT.js +31 -0
  13. package/dist/import-D2jNB07F.js +25 -0
  14. package/dist/index.d.ts +455 -0
  15. package/dist/index.js +693 -0
  16. package/dist/{cli.js → init-Bbtj2pxF.js} +34 -68
  17. package/dist/preview-CLm51aRt.js +19 -0
  18. package/dist/sdk-DjpX6mCv.js +51 -0
  19. package/dist/vite/index.d.ts +25 -0
  20. package/dist/vite/index.js +2 -0
  21. package/env.d.ts +83 -0
  22. package/package.json +59 -12
  23. package/{template/.agents/skills → skills}/create-doc/SKILL.md +1 -1
  24. package/{template/.agents/skills → skills}/create-theme/SKILL.md +1 -1
  25. package/{template/.agents/skills → skills}/doc-authoring/SKILL.md +8 -9
  26. package/{template/.agents/skills → skills}/doc-authoring/references/assets.md +1 -1
  27. package/{template/.agents/skills → skills}/doc-authoring/references/design-system.md +1 -1
  28. package/{template/.agents/skills → skills}/doc-authoring/references/long-form.md +4 -4
  29. package/{template/.agents/skills → skills}/doc-authoring/references/pagination.md +1 -1
  30. package/src/app/app.tsx +42 -0
  31. package/src/app/components/data-table.tsx +196 -0
  32. package/src/app/components/design-panel/design-panel.tsx +318 -0
  33. package/src/app/components/design-panel/design-provider.tsx +121 -0
  34. package/src/app/components/design-panel/use-design.ts +85 -0
  35. package/src/app/components/diagram.tsx +76 -0
  36. package/src/app/components/doc-assets.tsx +129 -0
  37. package/src/app/components/doc-search.tsx +248 -0
  38. package/src/app/components/doc-sidebar.tsx +162 -0
  39. package/src/app/components/flow-page.tsx +93 -0
  40. package/src/app/components/footnote.tsx +204 -0
  41. package/src/app/components/image-placeholder.tsx +50 -0
  42. package/src/app/components/inspector/inspector.tsx +518 -0
  43. package/src/app/components/numbering.tsx +224 -0
  44. package/src/app/components/page-frame.tsx +70 -0
  45. package/src/app/components/sidebar/folder-item.tsx +212 -0
  46. package/src/app/components/sidebar/icon-picker.tsx +99 -0
  47. package/src/app/components/sidebar/sidebar.tsx +252 -0
  48. package/src/app/components/table-of-contents.tsx +93 -0
  49. package/src/app/components/theme-toggle.tsx +50 -0
  50. package/src/app/components/themes/markdown.tsx +249 -0
  51. package/src/app/components/themes/theme-preview.tsx +74 -0
  52. package/src/app/components/ui/menu.tsx +143 -0
  53. package/src/app/index.html +12 -0
  54. package/src/app/lib/agent-bridge.ts +140 -0
  55. package/src/app/lib/assets.ts +151 -0
  56. package/src/app/lib/design-presets.ts +109 -0
  57. package/src/app/lib/design.ts +88 -0
  58. package/src/app/lib/diagnostics.ts +282 -0
  59. package/src/app/lib/doc-preview.tsx +29 -0
  60. package/src/app/lib/docs.ts +26 -0
  61. package/src/app/lib/docx/extract.ts +1623 -0
  62. package/src/app/lib/docx/fonts.test.ts +136 -0
  63. package/src/app/lib/docx/fonts.ts +166 -0
  64. package/src/app/lib/docx/media.ts +102 -0
  65. package/src/app/lib/docx/model.ts +206 -0
  66. package/src/app/lib/docx/paragraph.test.ts +92 -0
  67. package/src/app/lib/docx/paragraph.ts +107 -0
  68. package/src/app/lib/docx/props.ts +187 -0
  69. package/src/app/lib/docx/styles.ts +306 -0
  70. package/src/app/lib/docx/units.ts +35 -0
  71. package/src/app/lib/docx/write.test.ts +507 -0
  72. package/src/app/lib/docx/write.ts +581 -0
  73. package/src/app/lib/docx/xml.ts +39 -0
  74. package/src/app/lib/export-docx.ts +289 -0
  75. package/src/app/lib/export-dom.ts +318 -0
  76. package/src/app/lib/export-html.ts +156 -0
  77. package/src/app/lib/export-image.ts +70 -0
  78. package/src/app/lib/export-pdf.ts +165 -0
  79. package/src/app/lib/flow-measure.test.ts +31 -0
  80. package/src/app/lib/flow-measure.ts +183 -0
  81. package/src/app/lib/flow.test.ts +110 -0
  82. package/src/app/lib/flow.ts +136 -0
  83. package/src/app/lib/folders.ts +192 -0
  84. package/src/app/lib/footnotes.test.tsx +102 -0
  85. package/src/app/lib/footnotes.ts +94 -0
  86. package/src/app/lib/inspector/fiber.ts +99 -0
  87. package/src/app/lib/labels.test.ts +18 -0
  88. package/src/app/lib/labels.ts +181 -0
  89. package/src/app/lib/outline.ts +118 -0
  90. package/src/app/lib/page-context.tsx +43 -0
  91. package/src/app/lib/page-range.test.ts +95 -0
  92. package/src/app/lib/page-range.ts +90 -0
  93. package/src/app/lib/print-ready.ts +69 -0
  94. package/src/app/lib/rasterize.ts +173 -0
  95. package/src/app/lib/scan.ts +26 -0
  96. package/src/app/lib/sdk.test.ts +32 -0
  97. package/src/app/lib/sdk.ts +115 -0
  98. package/src/app/lib/themes.ts +31 -0
  99. package/src/app/lib/use-doc-module.ts +53 -0
  100. package/src/app/lib/use-doc-pages.ts +147 -0
  101. package/src/app/lib/utils.ts +6 -0
  102. package/src/app/lib/view-mode.test.ts +91 -0
  103. package/src/app/lib/view-mode.ts +104 -0
  104. package/src/app/main.tsx +14 -0
  105. package/src/app/routes/assets.tsx +257 -0
  106. package/src/app/routes/doc.tsx +877 -0
  107. package/src/app/routes/home-shell.tsx +203 -0
  108. package/src/app/routes/home.tsx +269 -0
  109. package/src/app/routes/themes.tsx +121 -0
  110. package/src/app/styles.css +97 -0
  111. package/src/app/virtual.d.ts +30 -0
  112. package/template/AGENTS.md +1 -1
  113. package/template/README.md +24 -52
  114. package/template/docs/getting-started/index.tsx +2 -2
  115. package/template/mosage.config.ts +1 -1
  116. package/template/package.json +1 -1
  117. package/template/tsconfig.json +1 -1
  118. package/README.zh-TW.md +0 -28
  119. /package/{template/.agents/skills → skills}/apply-comments/SKILL.md +0 -0
  120. /package/{template/.agents/skills → skills}/current-doc/SKILL.md +0 -0
  121. /package/{template/.agents/skills → skills}/doc-authoring/references/tables-and-charts.md +0 -0
@@ -0,0 +1,88 @@
1
+ export type DesignPalette = {
2
+ bg: string;
3
+ text: string;
4
+ muted: string;
5
+ accent: string;
6
+ rule: string;
7
+ };
8
+
9
+ export type DesignFonts = {
10
+ heading: string;
11
+ body: string;
12
+ mono: string;
13
+ };
14
+
15
+ export type DesignTypeScale = {
16
+ title: number;
17
+ h1: number;
18
+ h2: number;
19
+ h3: number;
20
+ body: number;
21
+ caption: number;
22
+ };
23
+
24
+ export type DesignSystem = {
25
+ palette: DesignPalette;
26
+ fonts: DesignFonts;
27
+ typeScale: DesignTypeScale;
28
+ /** Page margin in CSS px — the printable text block's inset from the sheet edge. */
29
+ margin: number;
30
+ /** Body line-height multiplier. Headings tighten this themselves. */
31
+ leading: number;
32
+ radius: number;
33
+ };
34
+
35
+ export function designToCssVars(d: DesignSystem): Record<string, string> {
36
+ return {
37
+ '--od-bg': d.palette.bg,
38
+ '--od-text': d.palette.text,
39
+ '--od-muted': d.palette.muted,
40
+ '--od-accent': d.palette.accent,
41
+ '--od-rule': d.palette.rule,
42
+ '--od-font-heading': d.fonts.heading,
43
+ '--od-font-body': d.fonts.body,
44
+ '--od-font-mono': d.fonts.mono,
45
+ '--od-size-title': `${d.typeScale.title}px`,
46
+ '--od-size-h1': `${d.typeScale.h1}px`,
47
+ '--od-size-h2': `${d.typeScale.h2}px`,
48
+ '--od-size-h3': `${d.typeScale.h3}px`,
49
+ '--od-size-body': `${d.typeScale.body}px`,
50
+ '--od-size-caption': `${d.typeScale.caption}px`,
51
+ '--od-margin': `${d.margin}px`,
52
+ '--od-leading': String(d.leading),
53
+ '--od-radius': `${d.radius}px`,
54
+ };
55
+ }
56
+
57
+ export function cssVarsToString(vars: Record<string, string>): string {
58
+ return Object.entries(vars)
59
+ .map(([k, v]) => ` ${k}: ${v};`)
60
+ .join('\n');
61
+ }
62
+
63
+ export const defaultDesign: DesignSystem = {
64
+ palette: {
65
+ bg: '#ffffff',
66
+ text: '#16181d',
67
+ muted: '#6b7280',
68
+ accent: '#2563eb',
69
+ rule: '#e5e7eb',
70
+ },
71
+ fonts: {
72
+ heading: '-apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif',
73
+ body: '-apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif',
74
+ mono: 'ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace',
75
+ },
76
+ // px at 96dpi — 1pt ≈ 1.333px, so body 14px reads as ~10.5pt on paper.
77
+ typeScale: {
78
+ title: 44,
79
+ h1: 28,
80
+ h2: 20,
81
+ h3: 16,
82
+ body: 14,
83
+ caption: 10,
84
+ },
85
+ margin: 76,
86
+ leading: 1.55,
87
+ radius: 6,
88
+ };
@@ -0,0 +1,282 @@
1
+ import { PAGE_ATTR, PAGE_INDEX_ATTR } from './outline';
2
+ import type { PageGeometry } from './sdk';
3
+
4
+ export type LayoutRule =
5
+ | 'unresolved-ref'
6
+ | 'page-overflow'
7
+ | 'off-page'
8
+ | 'blank-page'
9
+ | 'oversized-block'
10
+ | 'orphan-heading'
11
+ | 'tiny-text'
12
+ | 'broken-image';
13
+
14
+ export type LayoutSeverity = 'error' | 'warn';
15
+
16
+ export type LayoutFinding = {
17
+ /** 1-based sheet the problem is on. */
18
+ page: number;
19
+ rule: LayoutRule;
20
+ severity: LayoutSeverity;
21
+ message: string;
22
+ /** Tag plus a text snippet, so the caller can recognise the element. */
23
+ element?: string;
24
+ /** `line:column` in the document source, from the inspector's `data-od-loc`. */
25
+ loc?: string;
26
+ };
27
+
28
+ export type DiagnoseOptions = {
29
+ /** Flow blocks the packer reported as taller than one page. */
30
+ oversized?: Array<{ section: number; block: number }>;
31
+ /** Slack before a bounds violation counts, in CSS px. Subpixel layout needs some. */
32
+ tolerance?: number;
33
+ /** Smallest computed font size that still reads on paper. */
34
+ minFontSize?: number;
35
+ /** Findings kept per rule per page, so one broken page can't drown the report. */
36
+ perPageLimit?: number;
37
+ };
38
+
39
+ const DEFAULTS = { tolerance: 1, minFontSize: 7, perPageLimit: 3 } as const;
40
+
41
+ /**
42
+ * The band at the bottom of a sheet where a running footer lives. A heading
43
+ * followed only by content down here is still a dangling heading — the body it
44
+ * introduces went to the next page.
45
+ */
46
+ const FOOTER_ZONE_RATIO = 0.12;
47
+
48
+ const HEADING_SELECTOR = 'h1, h2, h3, h4, [data-od-heading]';
49
+ const MEDIA_TAGS = new Set(['IMG', 'SVG', 'CANVAS', 'VIDEO', 'PICTURE', 'IFRAME', 'OBJECT']);
50
+ const SNIPPET_MAX = 60;
51
+
52
+ function snippet(el: Element): string {
53
+ const text = (el.textContent ?? '').replace(/\s+/g, ' ').trim();
54
+ const tag = el.tagName.toLowerCase();
55
+ if (!text) return tag;
56
+ return `${tag}: ${text.length > SNIPPET_MAX ? `${text.slice(0, SNIPPET_MAX)}…` : text}`;
57
+ }
58
+
59
+ function locOf(el: Element): string | undefined {
60
+ return el.closest('[data-od-loc]')?.getAttribute('data-od-loc') ?? undefined;
61
+ }
62
+
63
+ function hasOwnText(el: Element): boolean {
64
+ for (const node of Array.from(el.childNodes)) {
65
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim() !== '') return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ function isPainted(el: Element, rect: DOMRect): boolean {
71
+ if (rect.width <= 0 || rect.height <= 0) return false;
72
+ const style = getComputedStyle(el);
73
+ return style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
74
+ }
75
+
76
+ /** Keeps the innermost offender: a wrapper that only overflows because its child does is noise. */
77
+ function leafmost(elements: Element[]): Element[] {
78
+ return elements.filter((el) => !elements.some((other) => other !== el && el.contains(other)));
79
+ }
80
+
81
+ function pageNumberOf(frame: Element, fallbackIndex: number): number {
82
+ const declared = Number(frame.getAttribute(PAGE_INDEX_ATTR));
83
+ return (Number.isFinite(declared) ? declared : fallbackIndex) + 1;
84
+ }
85
+
86
+ /**
87
+ * Reads rendered sheets and reports what a person would call a layout mistake:
88
+ * content past the paper edge, an empty sheet, a heading stranded at the foot
89
+ * of a page, type too small to print, an image that never loaded.
90
+ *
91
+ * It runs against the print copy — pages at their true pixel size — so the
92
+ * numbers describe the PDF, not the zoomed-down viewer.
93
+ */
94
+ export function diagnosePages(
95
+ root: ParentNode,
96
+ geometry: PageGeometry,
97
+ opts: DiagnoseOptions = {},
98
+ ): LayoutFinding[] {
99
+ const tolerance = opts.tolerance ?? DEFAULTS.tolerance;
100
+ const minFontSize = opts.minFontSize ?? DEFAULTS.minFontSize;
101
+ const perPageLimit = opts.perPageLimit ?? DEFAULTS.perPageLimit;
102
+
103
+ const findings: LayoutFinding[] = [];
104
+ const frames = Array.from(root.querySelectorAll<HTMLElement>(`[${PAGE_ATTR}]`));
105
+
106
+ frames.forEach((frame, index) => {
107
+ const page = pageNumberOf(frame, index);
108
+ const frameRect = frame.getBoundingClientRect();
109
+ const descendants = Array.from(frame.querySelectorAll<HTMLElement>('*'));
110
+
111
+ const below: Element[] = [];
112
+ const sideways: Element[] = [];
113
+ const tiny: Element[] = [];
114
+ const broken: Element[] = [];
115
+ let hasMedia = false;
116
+
117
+ for (const el of descendants) {
118
+ const rect = el.getBoundingClientRect();
119
+ if (!isPainted(el, rect)) continue;
120
+ if (MEDIA_TAGS.has(el.tagName)) hasMedia = true;
121
+
122
+ if (rect.bottom > frameRect.bottom + tolerance) below.push(el);
123
+ if (rect.right > frameRect.right + tolerance || rect.left < frameRect.left - tolerance) {
124
+ sideways.push(el);
125
+ }
126
+ if (hasOwnText(el) && Number.parseFloat(getComputedStyle(el).fontSize) < minFontSize) {
127
+ tiny.push(el);
128
+ }
129
+ if (el instanceof HTMLImageElement && el.complete && el.naturalWidth === 0) broken.push(el);
130
+ }
131
+
132
+ const text = (frame.textContent ?? '').trim();
133
+ // A page shell always paints — background, borders. What makes a sheet
134
+ // blank is that it carries neither words nor a picture.
135
+ if (text === '' && !hasMedia) {
136
+ findings.push({
137
+ page,
138
+ rule: 'blank-page',
139
+ severity: 'error',
140
+ message: 'The sheet renders nothing — an empty page will still print.',
141
+ });
142
+ }
143
+
144
+ for (const el of leafmost(below).slice(0, perPageLimit)) {
145
+ const overshoot = Math.round(el.getBoundingClientRect().bottom - frameRect.bottom);
146
+ findings.push({
147
+ page,
148
+ rule: 'page-overflow',
149
+ severity: 'error',
150
+ message: `Content runs ${overshoot}px past the bottom of the sheet and is clipped in the PDF.`,
151
+ element: snippet(el),
152
+ loc: locOf(el),
153
+ });
154
+ }
155
+
156
+ for (const el of leafmost(sideways).slice(0, perPageLimit)) {
157
+ const rect = el.getBoundingClientRect();
158
+ const side = rect.right > frameRect.right + tolerance ? 'right' : 'left';
159
+ const overshoot = Math.round(
160
+ side === 'right' ? rect.right - frameRect.right : frameRect.left - rect.left,
161
+ );
162
+ findings.push({
163
+ page,
164
+ rule: 'off-page',
165
+ severity: 'error',
166
+ message: `Content runs ${overshoot}px past the ${side} edge of the sheet.`,
167
+ element: snippet(el),
168
+ loc: locOf(el),
169
+ });
170
+ }
171
+
172
+ for (const el of leafmost(tiny).slice(0, perPageLimit)) {
173
+ const size = Math.round(Number.parseFloat(getComputedStyle(el).fontSize) * 10) / 10;
174
+ findings.push({
175
+ page,
176
+ rule: 'tiny-text',
177
+ severity: 'warn',
178
+ message: `Type is ${size}px — under ${minFontSize}px it stops being readable on paper.`,
179
+ element: snippet(el),
180
+ loc: locOf(el),
181
+ });
182
+ }
183
+
184
+ for (const el of broken.slice(0, perPageLimit)) {
185
+ findings.push({
186
+ page,
187
+ rule: 'broken-image',
188
+ severity: 'error',
189
+ message: `Image failed to load: ${(el as HTMLImageElement).getAttribute('src') ?? 'no src'}`,
190
+ element: snippet(el),
191
+ loc: locOf(el),
192
+ });
193
+ }
194
+
195
+ for (const el of Array.from(
196
+ frame.querySelectorAll<HTMLElement>('[data-od-ref-unresolved]'),
197
+ ).slice(0, perPageLimit)) {
198
+ findings.push({
199
+ page,
200
+ rule: 'unresolved-ref',
201
+ severity: 'error',
202
+ message: `Cross-reference points at "${el.getAttribute('data-od-ref-unresolved')}", which no figure, table, or footnote declares.`,
203
+ element: snippet(el),
204
+ loc: locOf(el),
205
+ });
206
+ }
207
+
208
+ findings.push(...danglingHeadings(frame, frameRect, geometry, page).slice(0, perPageLimit));
209
+ });
210
+
211
+ for (const { section, block } of opts.oversized ?? []) {
212
+ findings.push({
213
+ page: 0,
214
+ rule: 'oversized-block',
215
+ severity: 'error',
216
+ message: `Flow section ${section + 1}, block ${block + 1} is taller than one page. Blocks are never split — break it into smaller siblings.`,
217
+ });
218
+ }
219
+
220
+ return findings.sort((a, b) => a.page - b.page || rank(b.severity) - rank(a.severity));
221
+ }
222
+
223
+ function rank(severity: LayoutSeverity): number {
224
+ return severity === 'error' ? 1 : 0;
225
+ }
226
+
227
+ function danglingHeadings(
228
+ frame: HTMLElement,
229
+ frameRect: DOMRect,
230
+ geometry: PageGeometry,
231
+ page: number,
232
+ ): LayoutFinding[] {
233
+ const footerTop = frameRect.bottom - geometry.height * FOOTER_ZONE_RATIO;
234
+ const bodyText = Array.from(frame.querySelectorAll<HTMLElement>('*')).filter(
235
+ (el) => hasOwnText(el) && isPainted(el, el.getBoundingClientRect()),
236
+ );
237
+
238
+ const out: LayoutFinding[] = [];
239
+ for (const heading of Array.from(frame.querySelectorAll<HTMLElement>(HEADING_SELECTOR))) {
240
+ // A heading kept out of the outline is decoration — a cover title, a stat
241
+ // label — and is supposed to end its page.
242
+ if (heading.getAttribute('data-od-outline') === 'skip') continue;
243
+ const rect = heading.getBoundingClientRect();
244
+ if (!isPainted(heading, rect)) continue;
245
+
246
+ const follows = bodyText.filter(
247
+ (el) =>
248
+ !heading.contains(el) &&
249
+ !el.contains(heading) &&
250
+ el.getBoundingClientRect().top >= rect.bottom - 1,
251
+ );
252
+ // Nothing after it, or nothing except whatever sits in the footer band.
253
+ const hasBody = follows.some((el) => el.getBoundingClientRect().top < footerTop);
254
+ if (hasBody) continue;
255
+
256
+ out.push({
257
+ page,
258
+ rule: 'orphan-heading',
259
+ severity: 'warn',
260
+ message: 'Heading ends the page — the section it opens starts on the next sheet.',
261
+ element: snippet(heading),
262
+ loc: locOf(heading),
263
+ });
264
+ }
265
+ return out;
266
+ }
267
+
268
+ export function summarize(findings: LayoutFinding[]): {
269
+ errors: number;
270
+ warnings: number;
271
+ byRule: Record<string, number>;
272
+ } {
273
+ const byRule: Record<string, number> = {};
274
+ let errors = 0;
275
+ let warnings = 0;
276
+ for (const finding of findings) {
277
+ byRule[finding.rule] = (byRule[finding.rule] ?? 0) + 1;
278
+ if (finding.severity === 'error') errors++;
279
+ else warnings++;
280
+ }
281
+ return { errors, warnings, byRule };
282
+ }
@@ -0,0 +1,29 @@
1
+ import { createElement, type ReactNode } from 'react';
2
+ import { FlowPage } from '../components/flow-page';
3
+ import { type DocEntry, isFlowSection } from './flow';
4
+ import type { DocModule } from './sdk';
5
+
6
+ /**
7
+ * Page 1 of a document without running the flow measurement — a thumbnail only
8
+ * needs what fits above the fold, and the page shell clips the rest.
9
+ */
10
+ export function coverContent(doc: DocModule | null | undefined): ReactNode {
11
+ const entry = (doc?.default as DocEntry[] | undefined)?.[0];
12
+ if (!entry) return null;
13
+ if (!isFlowSection(entry)) return createElement(entry);
14
+ return createElement(FlowPage, {
15
+ section: entry,
16
+ design: doc?.design,
17
+ blockIndices: entry.blocks.map((_, index) => index),
18
+ });
19
+ }
20
+
21
+ /** "9 pages" for fixed docs; flow docs read "3 sections" until they are opened. */
22
+ export function pageCountLabel(doc: DocModule | null | undefined): string {
23
+ const entries = (doc?.default as DocEntry[] | undefined) ?? [];
24
+ if (entries.length === 0) return '0 pages';
25
+ const flowCount = entries.filter(isFlowSection).length;
26
+ if (flowCount === 0) return `${entries.length} pages`;
27
+ const fixed = entries.length - flowCount;
28
+ return fixed > 0 ? `${fixed} + flow` : 'flow';
29
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ docCreatedAt as createdAt,
3
+ docIds as ids,
4
+ loadDoc as load,
5
+ docThemes as themes,
6
+ } from 'virtual:mosage/docs';
7
+ import type { DocModule } from './sdk';
8
+
9
+ export const docIds: string[] = ids;
10
+ export const docCreatedAt: Record<string, number> = createdAt;
11
+ export const docThemes: Record<string, string> = themes;
12
+
13
+ export function docsByTheme(themeId: string): string[] {
14
+ return docIds.filter((id) => docThemes[id] === themeId);
15
+ }
16
+
17
+ export async function loadDoc(id: string): Promise<DocModule> {
18
+ return load(id);
19
+ }
20
+
21
+ export function docChangeIncludes(data: unknown, docId: string): boolean {
22
+ if (!data || typeof data !== 'object') return false;
23
+ const payload = data as { docId?: unknown; docIds?: unknown };
24
+ if (payload.docId === docId) return true;
25
+ return Array.isArray(payload.docIds) && payload.docIds.includes(docId);
26
+ }