mosage 0.1.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 (146) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +16 -217
  3. package/bin.js +2 -0
  4. package/dist/build-C7NW_3Pk.js +14 -0
  5. package/dist/check-CP4873Wx.js +41 -0
  6. package/dist/cli/bin.d.ts +1 -0
  7. package/dist/cli/bin.js +228 -0
  8. package/dist/config-DPm1BBAb.js +2619 -0
  9. package/dist/config-TlTe7Ona.d.ts +24 -0
  10. package/dist/context-BqsdSrAQ.js +1084 -0
  11. package/dist/dev-Biz42qlu.js +17 -0
  12. package/dist/diagram-xlVDekYk.js +763 -0
  13. package/dist/export-Bi6nuxjT.js +31 -0
  14. package/dist/import-D2jNB07F.js +25 -0
  15. package/dist/index.d.ts +455 -0
  16. package/dist/index.js +693 -0
  17. package/dist/init-Bbtj2pxF.js +262 -0
  18. package/dist/preview-CLm51aRt.js +19 -0
  19. package/dist/sdk-DjpX6mCv.js +51 -0
  20. package/dist/vite/index.d.ts +25 -0
  21. package/dist/vite/index.js +2 -0
  22. package/env.d.ts +83 -0
  23. package/package.json +84 -64
  24. package/skills/apply-comments/SKILL.md +43 -56
  25. package/skills/create-doc/SKILL.md +106 -0
  26. package/skills/create-theme/SKILL.md +184 -0
  27. package/skills/current-doc/SKILL.md +120 -0
  28. package/skills/doc-authoring/SKILL.md +434 -0
  29. package/skills/doc-authoring/references/assets.md +47 -0
  30. package/skills/doc-authoring/references/design-system.md +81 -0
  31. package/skills/doc-authoring/references/long-form.md +131 -0
  32. package/skills/doc-authoring/references/pagination.md +118 -0
  33. package/skills/doc-authoring/references/tables-and-charts.md +161 -0
  34. package/src/app/app.tsx +42 -0
  35. package/src/app/components/data-table.tsx +196 -0
  36. package/src/app/components/design-panel/design-panel.tsx +318 -0
  37. package/src/app/components/design-panel/design-provider.tsx +121 -0
  38. package/src/app/components/design-panel/use-design.ts +85 -0
  39. package/src/app/components/diagram.tsx +76 -0
  40. package/src/app/components/doc-assets.tsx +129 -0
  41. package/src/app/components/doc-search.tsx +248 -0
  42. package/src/app/components/doc-sidebar.tsx +162 -0
  43. package/src/app/components/flow-page.tsx +93 -0
  44. package/src/app/components/footnote.tsx +204 -0
  45. package/src/app/components/image-placeholder.tsx +50 -0
  46. package/src/app/components/inspector/inspector.tsx +518 -0
  47. package/src/app/components/numbering.tsx +224 -0
  48. package/src/app/components/page-frame.tsx +70 -0
  49. package/src/app/components/sidebar/folder-item.tsx +212 -0
  50. package/src/app/components/sidebar/icon-picker.tsx +99 -0
  51. package/src/app/components/sidebar/sidebar.tsx +252 -0
  52. package/src/app/components/table-of-contents.tsx +93 -0
  53. package/src/app/components/theme-toggle.tsx +50 -0
  54. package/src/app/components/themes/markdown.tsx +249 -0
  55. package/src/app/components/themes/theme-preview.tsx +74 -0
  56. package/src/app/components/ui/menu.tsx +143 -0
  57. package/src/app/index.html +12 -0
  58. package/src/app/lib/agent-bridge.ts +140 -0
  59. package/src/app/lib/assets.ts +151 -0
  60. package/src/app/lib/design-presets.ts +109 -0
  61. package/src/app/lib/design.ts +88 -0
  62. package/src/app/lib/diagnostics.ts +282 -0
  63. package/src/app/lib/doc-preview.tsx +29 -0
  64. package/src/app/lib/docs.ts +26 -0
  65. package/src/app/lib/docx/extract.ts +1623 -0
  66. package/src/app/lib/docx/fonts.test.ts +136 -0
  67. package/src/app/lib/docx/fonts.ts +166 -0
  68. package/src/app/lib/docx/media.ts +102 -0
  69. package/src/app/lib/docx/model.ts +206 -0
  70. package/src/app/lib/docx/paragraph.test.ts +92 -0
  71. package/src/app/lib/docx/paragraph.ts +107 -0
  72. package/src/app/lib/docx/props.ts +187 -0
  73. package/src/app/lib/docx/styles.ts +306 -0
  74. package/src/app/lib/docx/units.ts +35 -0
  75. package/src/app/lib/docx/write.test.ts +507 -0
  76. package/src/app/lib/docx/write.ts +581 -0
  77. package/src/app/lib/docx/xml.ts +39 -0
  78. package/src/app/lib/export-docx.ts +289 -0
  79. package/src/app/lib/export-dom.ts +318 -0
  80. package/src/app/lib/export-html.ts +156 -0
  81. package/src/app/lib/export-image.ts +70 -0
  82. package/src/app/lib/export-pdf.ts +165 -0
  83. package/src/app/lib/flow-measure.test.ts +31 -0
  84. package/src/app/lib/flow-measure.ts +183 -0
  85. package/src/app/lib/flow.test.ts +110 -0
  86. package/src/app/lib/flow.ts +136 -0
  87. package/src/app/lib/folders.ts +192 -0
  88. package/src/app/lib/footnotes.test.tsx +102 -0
  89. package/src/app/lib/footnotes.ts +94 -0
  90. package/src/app/lib/inspector/fiber.ts +99 -0
  91. package/src/app/lib/labels.test.ts +18 -0
  92. package/src/app/lib/labels.ts +181 -0
  93. package/src/app/lib/outline.ts +118 -0
  94. package/src/app/lib/page-context.tsx +43 -0
  95. package/src/app/lib/page-range.test.ts +95 -0
  96. package/src/app/lib/page-range.ts +90 -0
  97. package/src/app/lib/print-ready.ts +69 -0
  98. package/src/app/lib/rasterize.ts +173 -0
  99. package/src/app/lib/scan.ts +26 -0
  100. package/src/app/lib/sdk.test.ts +32 -0
  101. package/src/app/lib/sdk.ts +115 -0
  102. package/src/app/lib/themes.ts +31 -0
  103. package/src/app/lib/use-doc-module.ts +53 -0
  104. package/src/app/lib/use-doc-pages.ts +147 -0
  105. package/src/app/lib/utils.ts +6 -0
  106. package/src/app/lib/view-mode.test.ts +91 -0
  107. package/src/app/lib/view-mode.ts +104 -0
  108. package/src/app/main.tsx +14 -0
  109. package/src/app/routes/assets.tsx +257 -0
  110. package/src/app/routes/doc.tsx +877 -0
  111. package/src/app/routes/home-shell.tsx +203 -0
  112. package/src/app/routes/home.tsx +269 -0
  113. package/src/app/routes/themes.tsx +121 -0
  114. package/src/app/styles.css +97 -0
  115. package/src/app/virtual.d.ts +30 -0
  116. package/template/AGENTS.md +27 -0
  117. package/template/README.md +39 -0
  118. package/template/docs/getting-started/index.tsx +230 -0
  119. package/template/mosage.config.ts +5 -0
  120. package/template/package.json +24 -0
  121. package/template/tsconfig.json +17 -0
  122. package/README.en.md +0 -57
  123. package/dist/cli.js +0 -4545
  124. package/dist/web/assets/index-Czg2WeHe.js +0 -182
  125. package/dist/web/assets/index-DKAyt92W.css +0 -1
  126. package/dist/web/index.html +0 -15
  127. package/skills/current-position/SKILL.md +0 -65
  128. package/skills/kickoff/SKILL.md +0 -105
  129. package/skills/mosage-reference/SKILL.md +0 -164
  130. package/skills/outline/SKILL.md +0 -79
  131. package/skills/review/SKILL.md +0 -64
  132. package/skills/write-chapter/SKILL.md +0 -58
  133. package/template/book/STYLE.md +0 -6
  134. package/template/book/assets/.gitkeep +0 -0
  135. package/template/book/book.yaml +0 -42
  136. package/template/book/brief.md +0 -6
  137. package/template/book/chapters/.gitkeep +0 -0
  138. package/template/book/notes/README.md +0 -7
  139. package/template/project/AGENTS.md +0 -82
  140. package/template/project/CLAUDE.md +0 -1
  141. package/template/project/README.md +0 -48
  142. package/template/project/books/.gitkeep +0 -0
  143. package/template/project/gitignore +0 -5
  144. package/template/project/mosage.yaml +0 -23
  145. package/template/project/notes/README.md +0 -8
  146. package/template/project/package.json +0 -14
@@ -0,0 +1,204 @@
1
+ import {
2
+ type Context,
3
+ type CSSProperties,
4
+ createContext,
5
+ type ReactNode,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
13
+ import { LABEL_ATTR, LABEL_ID_ATTR, useDocLabel, useLabelVocabulary } from '../lib/labels';
14
+
15
+ /** Stands in for a number the scan has not produced yet, or never will. */
16
+ const UNNUMBERED = '\u2022';
17
+
18
+ export type CollectedNote = { id: string; content: ReactNode };
19
+
20
+ type Collector = {
21
+ /** Registration order, which is document order. */
22
+ ids: string[];
23
+ /**
24
+ * Note bodies live outside React state on purpose. A `ReactNode` is a new
25
+ * object on every render, so storing one in state would make every commit
26
+ * look like a change — register, re-render, register — and never settle.
27
+ * Ids are stable, so only they drive a re-render; the bodies are read fresh
28
+ * from here in whatever render follows.
29
+ */
30
+ contents: Map<string, ReactNode>;
31
+ register: (id: string, content: ReactNode) => void;
32
+ unregister: (id: string) => void;
33
+ };
34
+
35
+ // Same globalThis treatment as the page context: a document imports the
36
+ // published bundle while the viewer runs the source copy, and a context created
37
+ // twice registers into one instance and reads from the other.
38
+ const GLOBAL_KEY = '__mosage_footnote_context__';
39
+ type GlobalWithCtx = typeof globalThis & { [GLOBAL_KEY]?: Context<Collector | null> };
40
+ const g = globalThis as GlobalWithCtx;
41
+ if (!g[GLOBAL_KEY]) {
42
+ g[GLOBAL_KEY] = createContext<Collector | null>(null);
43
+ }
44
+ const FootnoteContext = g[GLOBAL_KEY];
45
+
46
+ /**
47
+ * Collects the footnotes a fixed page renders, so `<Footnotes />` can print
48
+ * them at its foot. Flow sections never use this: their notes are lifted out of
49
+ * the blocks before measurement, because the space they take decides where the
50
+ * page breaks land.
51
+ */
52
+ export function FootnoteCollector({ children }: { children?: ReactNode }) {
53
+ const contents = useRef<Map<string, ReactNode>>(new Map()).current;
54
+ const [ids, setIds] = useState<string[]>([]);
55
+
56
+ const value = useMemo<Collector>(
57
+ () => ({
58
+ ids,
59
+ contents,
60
+ register: (id, content) => {
61
+ contents.set(id, content);
62
+ setIds((current) => (current.includes(id) ? current : [...current, id]));
63
+ },
64
+ unregister: (id) => {
65
+ contents.delete(id);
66
+ setIds((current) => (current.includes(id) ? current.filter((x) => x !== id) : current));
67
+ },
68
+ }),
69
+ [ids, contents],
70
+ );
71
+
72
+ return <FootnoteContext.Provider value={value}>{children}</FootnoteContext.Provider>;
73
+ }
74
+
75
+ export const FOOTNOTE_MARKER_FLAG = '__odFootnote';
76
+
77
+ export type FootnoteProps = {
78
+ /** Stable id, so `<Ref to>` can point at the note. Generated when omitted. */
79
+ id?: string;
80
+ children?: ReactNode;
81
+ };
82
+
83
+ export function markerStyle(): CSSProperties {
84
+ return {
85
+ fontSize: '0.7em',
86
+ lineHeight: 0,
87
+ verticalAlign: 'super',
88
+ fontVariantNumeric: 'tabular-nums',
89
+ color: 'var(--od-accent)',
90
+ padding: '0 1px',
91
+ };
92
+ }
93
+
94
+ /**
95
+ * The superscript in the text. Its position in the document decides the number.
96
+ *
97
+ * The placeholder is the one the printed note uses, and deliberately not an
98
+ * empty string: the two describe the same unresolved state, a note whose number
99
+ * never arrives has to be visible in both places rather than silently blank,
100
+ * and the packer measures this glyph — an empty marker is narrower than the
101
+ * digit that replaces it.
102
+ */
103
+ export function FootnoteMarker({ id }: { id: string }) {
104
+ const entry = useDocLabel(id);
105
+ return (
106
+ <sup {...{ [LABEL_ATTR]: 'footnote', [LABEL_ID_ATTR]: id }} style={markerStyle()}>
107
+ {entry?.number ?? UNNUMBERED}
108
+ </sup>
109
+ );
110
+ }
111
+
112
+ /**
113
+ * A note anchored to this point in the text. Inside a `flow()` section the
114
+ * framework lifts it to the foot of whatever page the marker lands on; on a
115
+ * fixed page, put a `<Footnotes />` where you want them printed.
116
+ */
117
+ export function Footnote({ id, children }: FootnoteProps) {
118
+ const generated = useId();
119
+ const noteId = id ?? generated;
120
+ const collector = useContext(FootnoteContext);
121
+ const latest = useRef<Collector | null>(null);
122
+
123
+ // Runs after every commit so the body stays current; only a new id changes
124
+ // state, so this settles instead of looping.
125
+ useEffect(() => {
126
+ latest.current = collector;
127
+ collector?.register(noteId, children);
128
+ });
129
+
130
+ useEffect(() => () => latest.current?.unregister(noteId), [noteId]);
131
+
132
+ return <FootnoteMarker id={noteId} />;
133
+ }
134
+
135
+ (Footnote as unknown as Record<string, boolean>)[FOOTNOTE_MARKER_FLAG] = true;
136
+
137
+ export type FootnotesProps = {
138
+ /** Notes to print. Omitted on a fixed page, where they are collected from it. */
139
+ notes?: CollectedNote[];
140
+ style?: CSSProperties;
141
+ className?: string;
142
+ };
143
+
144
+ /** Outside the area's box, so measurement has to add it back by hand. */
145
+ export const FOOTNOTE_AREA_MARGIN_TOP = 12;
146
+
147
+ export const FOOTNOTE_ROW_ATTR = 'data-od-footnote-row';
148
+ export const FOOTNOTE_BODY_ATTR = 'data-od-footnote-body';
149
+ export const FOOTNOTES_ATTR = 'data-od-footnotes';
150
+
151
+ export function footnoteAreaStyle(): CSSProperties {
152
+ return {
153
+ borderTop: '1px solid var(--od-rule)',
154
+ paddingTop: 6,
155
+ marginTop: FOOTNOTE_AREA_MARGIN_TOP,
156
+ fontSize: 'var(--od-size-caption)',
157
+ lineHeight: 1.45,
158
+ color: 'var(--od-muted)',
159
+ };
160
+ }
161
+
162
+ /** One note's row. Shared with the measurement pass so the reserved space is real. */
163
+ export function FootnoteRow({ id, content }: CollectedNote) {
164
+ const entry = useDocLabel(id);
165
+ return (
166
+ <div {...{ [FOOTNOTE_ROW_ATTR]: id }} style={{ display: 'flex', gap: 5, marginBottom: 3 }}>
167
+ <span
168
+ style={{
169
+ flex: 'none',
170
+ fontVariantNumeric: 'tabular-nums',
171
+ color: 'var(--od-accent)',
172
+ }}
173
+ >
174
+ {entry?.number ?? UNNUMBERED}
175
+ </span>
176
+ <span {...{ [FOOTNOTE_BODY_ATTR]: '' }} style={{ minWidth: 0 }}>
177
+ {content}
178
+ </span>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ export function Footnotes({ notes, style, className }: FootnotesProps) {
184
+ const collector = useContext(FootnoteContext);
185
+ const vocabulary = useLabelVocabulary();
186
+ const list =
187
+ notes ?? collector?.ids.map((id) => ({ id, content: collector.contents.get(id) })) ?? [];
188
+ if (list.length === 0) return null;
189
+
190
+ return (
191
+ <div
192
+ {...{ [FOOTNOTES_ATTR]: '' }}
193
+ className={className}
194
+ style={{ ...footnoteAreaStyle(), ...style }}
195
+ >
196
+ {vocabulary.footnotes ? (
197
+ <div style={{ fontWeight: 600, marginBottom: 3 }}>{vocabulary.footnotes}</div>
198
+ ) : null}
199
+ {list.map((note) => (
200
+ <FootnoteRow key={note.id} id={note.id} content={note.content} />
201
+ ))}
202
+ </div>
203
+ );
204
+ }
@@ -0,0 +1,50 @@
1
+ import type { CSSProperties } from 'react';
2
+
3
+ export type ImagePlaceholderProps = {
4
+ /** What the final image should show — the user reads this when replacing it. */
5
+ hint: string;
6
+ width?: number | string;
7
+ height?: number | string;
8
+ style?: CSSProperties;
9
+ className?: string;
10
+ };
11
+
12
+ /**
13
+ * Stands in for an image the user has to supply (a chart from their data, a
14
+ * product screenshot). Use it instead of inventing filler — never for
15
+ * decoration.
16
+ */
17
+ export function ImagePlaceholder({
18
+ hint,
19
+ width = '100%',
20
+ height = 200,
21
+ style,
22
+ className,
23
+ }: ImagePlaceholderProps) {
24
+ return (
25
+ <div
26
+ data-od-image-placeholder=""
27
+ className={className}
28
+ style={{
29
+ width,
30
+ height,
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ justifyContent: 'center',
34
+ textAlign: 'center',
35
+ padding: 16,
36
+ boxSizing: 'border-box',
37
+ border: '1px dashed var(--od-rule, #d4d4d8)',
38
+ borderRadius: 'var(--od-radius, 6px)',
39
+ background: 'repeating-linear-gradient(45deg, rgba(0,0,0,.02) 0 8px, transparent 8px 16px)',
40
+ color: 'var(--od-muted, #6b7280)',
41
+ fontFamily: 'var(--od-font-body, system-ui, sans-serif)',
42
+ fontSize: 'var(--od-size-caption, 10px)',
43
+ lineHeight: 1.4,
44
+ ...style,
45
+ }}
46
+ >
47
+ {hint}
48
+ </div>
49
+ );
50
+ }