@suzumiyaaoba/mdxr 0.1.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 (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +2902 -0
  5. package/dist/components.d.mts +791 -0
  6. package/dist/components.mjs +2 -0
  7. package/dist/config-SI9IyFiC.mjs +184 -0
  8. package/dist/doc-context-CEqzMYKv.d.mts +568 -0
  9. package/dist/index.d.mts +20 -0
  10. package/dist/index.mjs +4 -0
  11. package/dist/ui-GKFD0mx5.mjs +15986 -0
  12. package/package.json +131 -0
  13. package/skill/SKILL.md +49 -0
  14. package/skill/references/components/charts.md +172 -0
  15. package/skill/references/components/document.md +98 -0
  16. package/skill/references/components/forms.md +35 -0
  17. package/skill/references/components/investigation.md +198 -0
  18. package/skill/references/components/layout.md +68 -0
  19. package/skill/references/components/output.md +158 -0
  20. package/skill/references/components/planning.md +155 -0
  21. package/skill/references/components/reports.md +252 -0
  22. package/skill/references/components/shadcn.md +19 -0
  23. package/skill/references/components.md +186 -0
  24. package/skill/references/extending.md +48 -0
  25. package/src/ask-sheet.ts +40 -0
  26. package/src/assets/css.ts +392 -0
  27. package/src/assets/scripts.ts +78 -0
  28. package/src/catalog.ts +287 -0
  29. package/src/cli.ts +178 -0
  30. package/src/client/doc-events.ts +1108 -0
  31. package/src/client/entry.ts +27 -0
  32. package/src/client-js.ts +47 -0
  33. package/src/component-map.ts +47 -0
  34. package/src/components/ui/accordion.tsx +77 -0
  35. package/src/components/ui/alert-dialog.tsx +185 -0
  36. package/src/components/ui/alert.tsx +76 -0
  37. package/src/components/ui/aspect-ratio.tsx +22 -0
  38. package/src/components/ui/attachment.tsx +208 -0
  39. package/src/components/ui/avatar.tsx +106 -0
  40. package/src/components/ui/badge.tsx +52 -0
  41. package/src/components/ui/breadcrumb.tsx +121 -0
  42. package/src/components/ui/bubble.tsx +128 -0
  43. package/src/components/ui/button-group.tsx +88 -0
  44. package/src/components/ui/button.tsx +58 -0
  45. package/src/components/ui/calendar.tsx +226 -0
  46. package/src/components/ui/card.tsx +102 -0
  47. package/src/components/ui/carousel.tsx +246 -0
  48. package/src/components/ui/chart.tsx +379 -0
  49. package/src/components/ui/checkbox.tsx +27 -0
  50. package/src/components/ui/collapsible.tsx +19 -0
  51. package/src/components/ui/combobox.tsx +298 -0
  52. package/src/components/ui/command.tsx +193 -0
  53. package/src/components/ui/context-menu.tsx +271 -0
  54. package/src/components/ui/dialog.tsx +159 -0
  55. package/src/components/ui/direction.tsx +4 -0
  56. package/src/components/ui/drawer.tsx +227 -0
  57. package/src/components/ui/dropdown-menu.tsx +269 -0
  58. package/src/components/ui/empty.tsx +104 -0
  59. package/src/components/ui/field.tsx +237 -0
  60. package/src/components/ui/fieldset.tsx +32 -0
  61. package/src/components/ui/frame.tsx +87 -0
  62. package/src/components/ui/hover-card.tsx +50 -0
  63. package/src/components/ui/input-group.tsx +159 -0
  64. package/src/components/ui/input-otp.tsx +83 -0
  65. package/src/components/ui/input.tsx +19 -0
  66. package/src/components/ui/item.tsx +202 -0
  67. package/src/components/ui/kbd.tsx +26 -0
  68. package/src/components/ui/label.tsx +19 -0
  69. package/src/components/ui/marker.tsx +71 -0
  70. package/src/components/ui/menubar.tsx +284 -0
  71. package/src/components/ui/message-scroller.tsx +128 -0
  72. package/src/components/ui/message.tsx +91 -0
  73. package/src/components/ui/meter.tsx +80 -0
  74. package/src/components/ui/native-select.tsx +64 -0
  75. package/src/components/ui/navigation-menu.tsx +170 -0
  76. package/src/components/ui/pagination.tsx +133 -0
  77. package/src/components/ui/popover.tsx +87 -0
  78. package/src/components/ui/progress.tsx +82 -0
  79. package/src/components/ui/questionnaire.tsx +328 -0
  80. package/src/components/ui/radio-group.tsx +35 -0
  81. package/src/components/ui/resizable.tsx +49 -0
  82. package/src/components/ui/scroll-area.tsx +50 -0
  83. package/src/components/ui/select.tsx +201 -0
  84. package/src/components/ui/separator.tsx +22 -0
  85. package/src/components/ui/sheet.tsx +135 -0
  86. package/src/components/ui/sidebar.tsx +730 -0
  87. package/src/components/ui/skeleton.tsx +13 -0
  88. package/src/components/ui/slider.tsx +51 -0
  89. package/src/components/ui/spinner.tsx +16 -0
  90. package/src/components/ui/switch.tsx +31 -0
  91. package/src/components/ui/table.tsx +113 -0
  92. package/src/components/ui/tabs.tsx +82 -0
  93. package/src/components/ui/textarea.tsx +17 -0
  94. package/src/components/ui/toast.tsx +229 -0
  95. package/src/components/ui/toggle-group.tsx +87 -0
  96. package/src/components/ui/toggle.tsx +43 -0
  97. package/src/components/ui/tooltip.tsx +65 -0
  98. package/src/components.ts +78 -0
  99. package/src/config.ts +56 -0
  100. package/src/define.ts +122 -0
  101. package/src/doc-context.ts +20 -0
  102. package/src/editor.ts +94 -0
  103. package/src/format-error.ts +78 -0
  104. package/src/guards.ts +84 -0
  105. package/src/hooks/use-mobile.ts +21 -0
  106. package/src/html.ts +91 -0
  107. package/src/hydrate/export-index.ts +232 -0
  108. package/src/hydrate/import-scan.ts +169 -0
  109. package/src/hydrate/plugins.ts +118 -0
  110. package/src/hydrate/runtime-module.ts +145 -0
  111. package/src/hydrate-runtime.ts +67 -0
  112. package/src/hydrate.ts +147 -0
  113. package/src/index.ts +16 -0
  114. package/src/init.ts +57 -0
  115. package/src/langs.ts +148 -0
  116. package/src/lines.ts +53 -0
  117. package/src/load-user-module.ts +191 -0
  118. package/src/mdx.ts +248 -0
  119. package/src/paths.ts +36 -0
  120. package/src/rehype/shiki.ts +533 -0
  121. package/src/remark/alerts.ts +53 -0
  122. package/src/remark/ast.ts +96 -0
  123. package/src/remark/callouts.ts +25 -0
  124. package/src/remark/code-file.ts +85 -0
  125. package/src/remark/code-meta.ts +21 -0
  126. package/src/remark/directives.ts +170 -0
  127. package/src/remark/file-paths.ts +64 -0
  128. package/src/remark/headings.ts +131 -0
  129. package/src/remark/no-js.ts +40 -0
  130. package/src/render.ts +337 -0
  131. package/src/serve.ts +322 -0
  132. package/src/styles/globals.css +134 -0
  133. package/src/styles/shadcn.css +641 -0
  134. package/src/tailwind.ts +119 -0
  135. package/src/ui/approvals.tsx +76 -0
  136. package/src/ui/ask-question.tsx +386 -0
  137. package/src/ui/ask.tsx +206 -0
  138. package/src/ui/attrs.ts +51 -0
  139. package/src/ui/audit.tsx +139 -0
  140. package/src/ui/bar-chart.tsx +334 -0
  141. package/src/ui/benchmarks.tsx +143 -0
  142. package/src/ui/bits.tsx +537 -0
  143. package/src/ui/board.tsx +173 -0
  144. package/src/ui/bridge.tsx +207 -0
  145. package/src/ui/bumps.tsx +178 -0
  146. package/src/ui/callout.tsx +106 -0
  147. package/src/ui/changes.tsx +89 -0
  148. package/src/ui/chart-bits.tsx +52 -0
  149. package/src/ui/chart.ts +577 -0
  150. package/src/ui/checks.tsx +203 -0
  151. package/src/ui/child-index.tsx +44 -0
  152. package/src/ui/children.ts +43 -0
  153. package/src/ui/chips.tsx +49 -0
  154. package/src/ui/cmd.tsx +27 -0
  155. package/src/ui/columns.tsx +31 -0
  156. package/src/ui/comments.tsx +544 -0
  157. package/src/ui/compare.tsx +67 -0
  158. package/src/ui/decision.tsx +79 -0
  159. package/src/ui/deps.tsx +78 -0
  160. package/src/ui/details.tsx +43 -0
  161. package/src/ui/diff-parse.ts +307 -0
  162. package/src/ui/diff.tsx +458 -0
  163. package/src/ui/diffstat.tsx +58 -0
  164. package/src/ui/due.tsx +65 -0
  165. package/src/ui/effort.tsx +29 -0
  166. package/src/ui/endpoints.tsx +118 -0
  167. package/src/ui/envvars.tsx +96 -0
  168. package/src/ui/figure.tsx +37 -0
  169. package/src/ui/file-icon.ts +1020 -0
  170. package/src/ui/file-link.ts +32 -0
  171. package/src/ui/file-ref.tsx +39 -0
  172. package/src/ui/files.tsx +112 -0
  173. package/src/ui/findings.tsx +85 -0
  174. package/src/ui/flow.tsx +73 -0
  175. package/src/ui/funnel.tsx +121 -0
  176. package/src/ui/gantt.tsx +443 -0
  177. package/src/ui/gauges.tsx +135 -0
  178. package/src/ui/glossary.tsx +29 -0
  179. package/src/ui/graph-layout.ts +149 -0
  180. package/src/ui/graph-specs.tsx +102 -0
  181. package/src/ui/graph.tsx +278 -0
  182. package/src/ui/grid.tsx +119 -0
  183. package/src/ui/hypothesis.tsx +94 -0
  184. package/src/ui/icon.tsx +80 -0
  185. package/src/ui/incident.tsx +130 -0
  186. package/src/ui/index.ts +342 -0
  187. package/src/ui/ins-del.tsx +41 -0
  188. package/src/ui/json.tsx +190 -0
  189. package/src/ui/layout.ts +9 -0
  190. package/src/ui/line-chart.tsx +251 -0
  191. package/src/ui/matrix.tsx +208 -0
  192. package/src/ui/meta.tsx +73 -0
  193. package/src/ui/option.tsx +64 -0
  194. package/src/ui/owner.tsx +40 -0
  195. package/src/ui/packages.tsx +104 -0
  196. package/src/ui/pathway.tsx +96 -0
  197. package/src/ui/phase.tsx +41 -0
  198. package/src/ui/pie-chart.tsx +170 -0
  199. package/src/ui/plan.tsx +77 -0
  200. package/src/ui/pre.tsx +175 -0
  201. package/src/ui/priority.tsx +51 -0
  202. package/src/ui/props.tsx +77 -0
  203. package/src/ui/quadrant.tsx +172 -0
  204. package/src/ui/radar.tsx +200 -0
  205. package/src/ui/ref.tsx +160 -0
  206. package/src/ui/release.tsx +178 -0
  207. package/src/ui/req.tsx +42 -0
  208. package/src/ui/review.tsx +133 -0
  209. package/src/ui/risk.tsx +67 -0
  210. package/src/ui/sankey.tsx +287 -0
  211. package/src/ui/scatter.tsx +237 -0
  212. package/src/ui/schema.tsx +113 -0
  213. package/src/ui/score.tsx +105 -0
  214. package/src/ui/search.tsx +120 -0
  215. package/src/ui/series.tsx +38 -0
  216. package/src/ui/severity.tsx +69 -0
  217. package/src/ui/shadcn.tsx +216 -0
  218. package/src/ui/spark.tsx +86 -0
  219. package/src/ui/stack.tsx +57 -0
  220. package/src/ui/stats.tsx +63 -0
  221. package/src/ui/status-badge.tsx +66 -0
  222. package/src/ui/statuspage.tsx +238 -0
  223. package/src/ui/steps.tsx +74 -0
  224. package/src/ui/summary.tsx +41 -0
  225. package/src/ui/symbol-ref.tsx +73 -0
  226. package/src/ui/terminal.tsx +117 -0
  227. package/src/ui/tests.tsx +218 -0
  228. package/src/ui/timeline.tsx +63 -0
  229. package/src/ui/toc.tsx +56 -0
  230. package/src/ui/tones.ts +187 -0
  231. package/src/ui/trace.tsx +69 -0
  232. package/src/ui/tree.tsx +281 -0
  233. package/src/ui/treemap.tsx +128 -0
  234. package/src/ui/venn.tsx +258 -0
  235. package/src/ui/verdict.tsx +78 -0
  236. package/src/ui/waterfall.tsx +142 -0
@@ -0,0 +1,218 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent, flattenChildren } from "../define.js";
5
+ import { isOneOf, nonEmpty } from "../guards.js";
6
+ import { LINK_LINES_PROPS, NUMISH } from "./attrs.js";
7
+ import { CaptionBar, ListPanel, LocLink, TrimBody } from "./bits.js";
8
+ import { isEl, propOf } from "./children.js";
9
+ import { Icon } from "./icon.js";
10
+ import {
11
+ CHIP_BORDER_CLS,
12
+ COUNT_CHIP_CLS,
13
+ LOC_CLS,
14
+ SUNKEN_CLS,
15
+ TEXT,
16
+ TONE_TEXT,
17
+ } from "./tones.js";
18
+
19
+ export const TEST_STATUSES = ["pass", "fail", "skip", "todo"] as const;
20
+ export type TestStatus = (typeof TEST_STATUSES)[number];
21
+
22
+ const isTestStatus = isOneOf(TEST_STATUSES);
23
+
24
+ const STYLES: Record<TestStatus, { cls: string; icon: string; label: string }> =
25
+ {
26
+ fail: {
27
+ cls: TONE_TEXT.red,
28
+ icon: "lucide:circle-x",
29
+ label: "failed",
30
+ },
31
+ pass: {
32
+ cls: TONE_TEXT.emerald,
33
+ icon: "lucide:circle-check",
34
+ label: "passed",
35
+ },
36
+ skip: {
37
+ cls: TONE_TEXT.neutral,
38
+ icon: "lucide:circle-minus",
39
+ label: "skipped",
40
+ },
41
+ todo: {
42
+ cls: TONE_TEXT.violet,
43
+ icon: "lucide:circle-dashed",
44
+ label: "todo",
45
+ },
46
+ };
47
+
48
+ /** `"120ms"`/`"1.2s"`/`"2m"` → milliseconds; unparseable → undefined. */
49
+ export const parseDuration = (x: unknown): number | undefined => {
50
+ if (typeof x === "number") {
51
+ return Number.isFinite(x) ? x : undefined;
52
+ }
53
+ if (typeof x !== "string") {
54
+ return undefined;
55
+ }
56
+ const m = /^(?<n>\d+(?:\.\d+)?)\s*(?<u>ms|s|m)?$/u.exec(x.trim());
57
+ if (m?.groups === undefined) {
58
+ return undefined;
59
+ }
60
+ const n = Number(m.groups.n);
61
+ const unit = m.groups.u ?? "ms";
62
+ if (unit === "s") {
63
+ return n * 1000;
64
+ }
65
+ if (unit === "m") {
66
+ return n * 60_000;
67
+ }
68
+ return n;
69
+ };
70
+
71
+ export const formatDuration = (ms: number): string => {
72
+ if (ms >= 10_000) {
73
+ return `${Math.round(ms / 1000)}s`;
74
+ }
75
+ if (ms >= 1000) {
76
+ return `${(ms / 1000).toFixed(1)}s`;
77
+ }
78
+ return `${Math.round(ms)}ms`;
79
+ };
80
+
81
+ export const Test = defineComponent(
82
+ {
83
+ description:
84
+ "テスト結果1行。name は必須、status は pass|fail|skip|todo。duration に所要時間、file/lines で実ファイルへのエディタリンク。children は失敗時の詳細 (エラー出力など)",
85
+ schema: v.looseObject({
86
+ ...LINK_LINES_PROPS,
87
+ duration: v.optional(NUMISH),
88
+ file: v.optional(v.string()),
89
+ name: v.string(),
90
+ status: v.optional(v.picklist(TEST_STATUSES), "pass"),
91
+ }),
92
+ },
93
+ ({ name, status, duration, file, lines, href, children }) => {
94
+ const s = STYLES[status];
95
+ return (
96
+ <div className="px-4 py-2.5">
97
+ <div className="flex flex-wrap items-baseline gap-x-2.5 gap-y-1">
98
+ <Icon
99
+ className={`h-4 w-4 shrink-0 self-center ${s.cls}`}
100
+ label={s.label}
101
+ name={s.icon}
102
+ />
103
+ <span className="text-sm">{name}</span>
104
+ {nonEmpty(file) ? (
105
+ <LocLink href={href} lines={lines} path={file} />
106
+ ) : null}
107
+ {duration === undefined || duration === "" ? null : (
108
+ <span className={`${LOC_CLS} ml-auto tabular-nums`}>
109
+ {String(duration)}
110
+ </span>
111
+ )}
112
+ </div>
113
+ <TrimBody
114
+ className={`mt-1.5 rounded-md border-l-2 px-3 py-2 text-xs ${TEXT.body} ${
115
+ status === "fail"
116
+ ? "border-red-400/60 bg-red-500/5"
117
+ : `${CHIP_BORDER_CLS} ${SUNKEN_CLS}`
118
+ }`}
119
+ >
120
+ {children}
121
+ </TrimBody>
122
+ </div>
123
+ );
124
+ }
125
+ );
126
+
127
+ interface TestSummary {
128
+ counts: Map<TestStatus, number>;
129
+ /** False when any <Test> carried an unparseable duration — hides the total. */
130
+ durationOk: boolean;
131
+ total: number;
132
+ totalMs: number;
133
+ }
134
+
135
+ /** Aggregates status counts and total duration over <Test> children. */
136
+ const summarizeTests = (children: ReactNode): TestSummary => {
137
+ const counts = new Map<TestStatus, number>();
138
+ let totalMs = 0;
139
+ let total = 0;
140
+ let durationOk = true;
141
+ for (const node of flattenChildren(children)) {
142
+ if (!isEl(node, Test)) {
143
+ continue;
144
+ }
145
+ const st = propOf(node, "status");
146
+ const status = isTestStatus(st) ? st : "pass";
147
+ counts.set(status, (counts.get(status) ?? 0) + 1);
148
+ total += 1;
149
+ const rawDuration = propOf(node, "duration");
150
+ const d = parseDuration(rawDuration);
151
+ if (d !== undefined) {
152
+ totalMs += d;
153
+ continue;
154
+ }
155
+ if (rawDuration !== undefined && rawDuration !== "") {
156
+ durationOk = false;
157
+ }
158
+ }
159
+ return { counts, durationOk, total, totalMs };
160
+ };
161
+
162
+ const TestsCaption = ({
163
+ summary,
164
+ title,
165
+ tool,
166
+ }: {
167
+ summary: TestSummary;
168
+ title: string | undefined;
169
+ tool: string | undefined;
170
+ }): ReactElement => (
171
+ <CaptionBar className="flex flex-wrap items-center gap-x-2.5 gap-y-1">
172
+ <Icon className="h-3.5 w-3.5" name="lucide:flask-conical" />
173
+ {nonEmpty(title) ? (
174
+ <span className="font-medium">{title}</span>
175
+ ) : (
176
+ <span className="font-medium">Tests</span>
177
+ )}
178
+ {nonEmpty(tool) ? <span className={COUNT_CHIP_CLS}>{tool}</span> : null}
179
+ <span className="ml-auto flex items-center gap-x-2 font-medium">
180
+ {TEST_STATUSES.map((st) => {
181
+ const c = summary.counts.get(st);
182
+ return c === undefined ? null : (
183
+ <span className={STYLES[st].cls} key={st}>
184
+ {c} {STYLES[st].label}
185
+ </span>
186
+ );
187
+ })}
188
+ {summary.durationOk && summary.totalMs > 0 ? (
189
+ <span className="font-mono tabular-nums">
190
+ {formatDuration(summary.totalMs)}
191
+ </span>
192
+ ) : null}
193
+ </span>
194
+ </CaptionBar>
195
+ );
196
+
197
+ export const Tests = defineComponent(
198
+ {
199
+ description:
200
+ "テスト実行レポートのコンテナ。<Test> を並べる。title はキャプション、tool はランナー名チップ。status 別の件数と合計時間を自動集計",
201
+ schema: v.looseObject({
202
+ title: v.optional(v.string()),
203
+ tool: v.optional(v.string()),
204
+ }),
205
+ },
206
+ ({ title, tool, children }) => {
207
+ const summary = summarizeTests(children);
208
+ const caption = nonEmpty(title) || nonEmpty(tool) || summary.total > 0;
209
+ return (
210
+ <ListPanel>
211
+ {caption ? (
212
+ <TestsCaption summary={summary} title={title} tool={tool} />
213
+ ) : null}
214
+ {children}
215
+ </ListPanel>
216
+ );
217
+ }
218
+ );
@@ -0,0 +1,63 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { TITLE_PROP } from "./attrs.js";
6
+ import { Section } from "./bits.js";
7
+ import { Icon } from "./icon.js";
8
+ import {
9
+ STATUS_ICON_CLS,
10
+ STATUS_ICONS,
11
+ STATUS_PROP,
12
+ StatusBadge,
13
+ } from "./status-badge.js";
14
+ import { RAIL_CLS, TEXT } from "./tones.js";
15
+
16
+ export const Timeline = defineComponent(
17
+ {
18
+ description: "時系列のイベント/マイルストーンリスト。<Event> を並べる",
19
+ schema: v.looseObject(TITLE_PROP),
20
+ },
21
+ ({ title, children }) => (
22
+ <Section title={title}>
23
+ <div className={`space-y-4 border-l-2 pl-4 ${RAIL_CLS}`}>{children}</div>
24
+ </Section>
25
+ )
26
+ );
27
+
28
+ export const Event = defineComponent(
29
+ {
30
+ description:
31
+ "タイムラインの1項目。date は必須、status は todo|doing|done|blocked",
32
+ schema: v.looseObject({
33
+ date: v.string(),
34
+ status: STATUS_PROP,
35
+ title: v.optional(v.string()),
36
+ }),
37
+ },
38
+ ({ date, status, title, children }) => (
39
+ <div className="relative">
40
+ {status === undefined ? (
41
+ <span
42
+ className="absolute top-1.5 -left-[1.42rem] h-2.5 w-2.5 rounded-full bg-neutral-400 dark:bg-neutral-500"
43
+ aria-hidden
44
+ />
45
+ ) : (
46
+ <Icon
47
+ className={`bg-background absolute top-0.5 -left-[1.61rem] h-4 w-4 ${STATUS_ICON_CLS[status]}`}
48
+ name={STATUS_ICONS[status]}
49
+ />
50
+ )}
51
+ <div className="flex flex-wrap items-baseline gap-2">
52
+ <time className={`font-mono text-xs ${TEXT.muted}`}>{date}</time>
53
+ {status === undefined ? null : <StatusBadge status={status} />}
54
+ {nonEmpty(title) ? <span className="font-medium">{title}</span> : null}
55
+ </div>
56
+ {children === undefined ? null : (
57
+ <div className="text-sm [&>*:first-child]:mt-1.5 [&>*:last-child]:mb-0">
58
+ {children}
59
+ </div>
60
+ )}
61
+ </div>
62
+ )
63
+ );
package/src/ui/toc.tsx ADDED
@@ -0,0 +1,56 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent, flattenChildren } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { attrFalse, BOOLISH_PROP } from "./attrs.js";
6
+ import { PANEL_CLS } from "./bits.js";
7
+ import { Icon } from "./icon.js";
8
+ import { BORDER_CLS, SURFACE_CLS, TEXT } from "./tones.js";
9
+
10
+ const closed = attrFalse;
11
+
12
+ /**
13
+ * Table of contents. The `:::toc` directive / `<Toc>` element is a marker —
14
+ * the remark plugin collects the document's headings, gives them `id`s, and
15
+ * injects the link list as this component's children. `depth`/`min` are read
16
+ * by the plugin at that point; the component itself frames the list as a
17
+ * collapsible outline (native <details> — works without client JS).
18
+ */
19
+ export const Toc = defineComponent(
20
+ {
21
+ description:
22
+ '目次。見出し (h2〜) へのリンク一覧を自動生成する。depth="4" で深さ変更 (デフォルト 3)。open="false" で初期状態を折りたたみ。:::toc でも書ける',
23
+ schema: v.looseObject({
24
+ depth: v.optional(v.string()),
25
+ min: v.optional(v.string()),
26
+ open: BOOLISH_PROP,
27
+ title: v.optional(v.string()),
28
+ }),
29
+ },
30
+ ({ title, open, children }) => {
31
+ if (flattenChildren(children).length === 0) {
32
+ return null;
33
+ }
34
+ return (
35
+ <nav aria-label="Table of contents" className={`mdxr-toc ${PANEL_CLS}`}>
36
+ <details open={!closed(open)}>
37
+ <summary
38
+ className={`flex cursor-pointer items-center gap-1.5 ${SURFACE_CLS} px-4 py-2 text-xs font-semibold ${TEXT.muted} transition-colors select-none hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-800/70 dark:active:bg-neutral-800`}
39
+ >
40
+ <Icon className="h-3.5 w-3.5" name="lucide:list-tree" />
41
+ <span className="flex-1">
42
+ {nonEmpty(title) ? title : "Contents"}
43
+ </span>
44
+ <Icon
45
+ className="mdxr-chev h-3.5 w-3.5 opacity-50"
46
+ name="lucide:chevron-right"
47
+ />
48
+ </summary>
49
+ <div className={`mdxr-toc-body border-t px-3 py-2.5 ${BORDER_CLS}`}>
50
+ {children}
51
+ </div>
52
+ </details>
53
+ </nav>
54
+ );
55
+ }
56
+ );
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Semantic color pairs for pill/chip surfaces — `bg-{c}-100 text-{c}-700`
3
+ * with the dark-mode counterparts. Shared so every status badge, risk chip,
4
+ * and confidence pill stays visually consistent (and one edit restyles all).
5
+ */
6
+ export const TONE = {
7
+ amber: "bg-amber-100 text-amber-700 dark:bg-amber-900/60 dark:text-amber-300",
8
+ emerald:
9
+ "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/60 dark:text-emerald-300",
10
+ indigo:
11
+ "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/60 dark:text-indigo-300",
12
+ neutral:
13
+ "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300",
14
+ orange:
15
+ "bg-orange-100 text-orange-700 dark:bg-orange-900/60 dark:text-orange-300",
16
+ red: "bg-red-100 text-red-700 dark:bg-red-900/60 dark:text-red-300",
17
+ sky: "bg-sky-100 text-sky-700 dark:bg-sky-900/60 dark:text-sky-300",
18
+ teal: "bg-teal-100 text-teal-700 dark:bg-teal-900/60 dark:text-teal-300",
19
+ violet:
20
+ "bg-violet-100 text-violet-700 dark:bg-violet-900/60 dark:text-violet-300",
21
+ } as const;
22
+
23
+ export type Tone = keyof typeof TONE;
24
+
25
+ /**
26
+ * Neutral text-tone pairs, strongest→faintest. Use these instead of open-
27
+ * coding `text-neutral-* dark:text-neutral-*` so the muted-text hierarchy
28
+ * stays consistent across the catalog.
29
+ */
30
+ export const TEXT = {
31
+ /** Secondary body text (neutral-600/300). */
32
+ body: "text-neutral-600 dark:text-neutral-300",
33
+ /** Interactive chip/control labels (neutral-700/200). */
34
+ chip: "text-neutral-700 dark:text-neutral-200",
35
+ /** Code/mono-context body (neutral-800/200). */
36
+ code: "text-neutral-800 dark:text-neutral-200",
37
+ /** Faint placeholders, locations, icons (neutral-400/500). */
38
+ faint: "text-neutral-400 dark:text-neutral-500",
39
+ /** Ghosted context lines, empty cells (neutral-300/600). */
40
+ ghost: "text-neutral-300 dark:text-neutral-600",
41
+ /** Muted annotations, captions, meta (neutral-500/400). */
42
+ muted: "text-neutral-500 dark:text-neutral-400",
43
+ /** Headings, term names, primary labels (neutral-900/100). */
44
+ strong: "text-neutral-900 dark:text-neutral-100",
45
+ } as const;
46
+
47
+ /**
48
+ * Text-only accent pairs (`text-{c}-600 dark:text-{c}-400`) — the single
49
+ * accent scale for tag labels, status icons, +/- counts, and graph edges.
50
+ * `neutral` maps to `TEXT.muted`. Standalone fills (dots, bars) keep
51
+ * `bg-{c}-500` and need no dark variant.
52
+ */
53
+ export const TONE_TEXT = {
54
+ amber: "text-amber-600 dark:text-amber-400",
55
+ emerald: "text-emerald-600 dark:text-emerald-400",
56
+ indigo: "text-indigo-600 dark:text-indigo-400",
57
+ neutral: "text-neutral-500 dark:text-neutral-400",
58
+ orange: "text-orange-600 dark:text-orange-400",
59
+ red: "text-red-600 dark:text-red-400",
60
+ sky: "text-sky-600 dark:text-sky-400",
61
+ teal: "text-teal-600 dark:text-teal-400",
62
+ violet: "text-violet-600 dark:text-violet-400",
63
+ } as const;
64
+
65
+ /**
66
+ * Lightly tinted bands (`bg-{c}-50 text-{c}-700`, dark `950/40` + `300`) —
67
+ * inline alert strips inside panels: Trace errors, Before/After headers,
68
+ * diff hunk markers. Callout bodies stay on their own deeper `950/100`
69
+ * variant since they carry prose.
70
+ */
71
+ export const TONE_BAND = {
72
+ amber: "bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",
73
+ emerald:
74
+ "bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300",
75
+ indigo:
76
+ "bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-300",
77
+ neutral:
78
+ "bg-neutral-100 text-neutral-700 dark:bg-neutral-800/70 dark:text-neutral-300",
79
+ orange:
80
+ "bg-orange-50 text-orange-700 dark:bg-orange-950/40 dark:text-orange-300",
81
+ red: "bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300",
82
+ sky: "bg-sky-50 text-sky-700 dark:bg-sky-950/40 dark:text-sky-300",
83
+ teal: "bg-teal-50 text-teal-700 dark:bg-teal-950/40 dark:text-teal-300",
84
+ violet:
85
+ "bg-violet-50 text-violet-700 dark:bg-violet-950/40 dark:text-violet-300",
86
+ } as const;
87
+
88
+ /**
89
+ * 1px tinted card borders (`border-{c}-200 dark:border-{c}-900/60`) — the
90
+ * outline for tone-tinted cards (Option, Before/After). `neutral` is the
91
+ * standard panel border. Left-accent bars (Callout/Risk `border-l-4`) keep
92
+ * the stronger `border-{c}-500` since a 4px strip is the accent itself.
93
+ */
94
+ export const TONE_BORDER = {
95
+ amber: "border-amber-200 dark:border-amber-900/60",
96
+ emerald: "border-emerald-200 dark:border-emerald-900/60",
97
+ indigo: "border-indigo-200 dark:border-indigo-900/60",
98
+ neutral: "border-neutral-200 dark:border-neutral-800",
99
+ orange: "border-orange-200 dark:border-orange-900/60",
100
+ red: "border-red-200 dark:border-red-900/60",
101
+ sky: "border-sky-200 dark:border-sky-900/60",
102
+ teal: "border-teal-200 dark:border-teal-900/60",
103
+ violet: "border-violet-200 dark:border-violet-900/60",
104
+ } as const;
105
+
106
+ /** Tiny chip/tick-label size — counts, method badges, axis labels (10.4px). */
107
+ export const TEXT_MICRO = "text-[0.65rem]";
108
+
109
+ /** Small secondary line — sub-labels under names, dense meta rows (11.2px). */
110
+ export const TEXT_SUB = "text-[0.7rem]";
111
+
112
+ /** Muted mono chip text — the `path:lines` location label shared by
113
+ * file-referencing components (Flow, Trace, Tests, Search, Approvals). */
114
+ export const LOC_CLS = `font-mono text-xs ${TEXT.faint}`;
115
+
116
+ /** Inline `<code>` look inside list rows — `font-mono` on the body text color. */
117
+ export const MONO_CLS = `font-mono text-[0.85em] ${TEXT.code}`;
118
+
119
+ /** Trim first/last child margins inside MDX-rendered body regions. */
120
+ export const TRIM_CLS = "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0";
121
+
122
+ /** Standard neutral border — panels, cards, dividers (neutral-200/800). */
123
+ export const BORDER_CLS = "border-neutral-200 dark:border-neutral-800";
124
+
125
+ /** CommentStrip's class list — shared with the `<template>` clone in
126
+ * `<Comments>` so strips the client inserts match SSR output. `bleed`
127
+ * stretches the strip across a parent's `px-4` padding (annotated code
128
+ * blocks sit inside one). */
129
+ export const commentStripCls = (bleed?: boolean): string =>
130
+ `border-t ${BORDER_CLS} space-y-3 bg-white py-3 font-sans text-sm whitespace-normal dark:bg-neutral-950 ${
131
+ bleed === true ? "-mx-4 px-4" : "px-4"
132
+ }`;
133
+
134
+ /** Stronger border for controls and chips — inputs, code chips, marked
135
+ * squares (neutral-300/700). */
136
+ export const CHIP_BORDER_CLS = "border-neutral-300 dark:border-neutral-700";
137
+
138
+ /** Caption-strip surface — header/footer bars inside panels (neutral-50/900). */
139
+ export const SURFACE_CLS = "bg-neutral-50 dark:bg-neutral-900";
140
+
141
+ /** Sunken content surface — file trees, JSON/code viewers, graph canvases
142
+ * (neutral-50 light, softer `900/60` dark so it sits under the card bg). */
143
+ export const SUNKEN_CLS = "bg-neutral-50 dark:bg-neutral-900/60";
144
+
145
+ /** Connector/guide lines in border form — timeline rails, JSON indent guides
146
+ * (a hair darker than BORDER_CLS in dark mode since they are functional). */
147
+ export const RAIL_CLS = "border-neutral-200 dark:border-neutral-700";
148
+
149
+ /** Connector/guide lines in background form — flow stems, chart gridlines,
150
+ * tree rails. */
151
+ export const RAIL_BG_CLS = "bg-neutral-200 dark:bg-neutral-700";
152
+
153
+ /** Sunken track behind progress/timing bars — Summary, Gantt, Waterfall. */
154
+ export const TRACK_CLS = "bg-neutral-100 dark:bg-neutral-800";
155
+
156
+ /** Inherit-color link that underlines on hover — chips/labels inside panels. */
157
+ export const LINK_CLS = "text-inherit no-underline hover:underline";
158
+
159
+ /** Indented tree-line row — Json nodes and file-Tree entries. */
160
+ export const TREE_ROW_CLS = "flex items-baseline gap-1.5 py-px";
161
+
162
+ /** Clickable inline fold row — `<summary>` of a tree-line <details> (Tree, Json). */
163
+ export const DISCLOSURE_ROW_CLS =
164
+ "flex cursor-pointer items-baseline gap-1.5 py-px select-none";
165
+
166
+ /** CaptionBar layout for icon+title headers (Graph/Gantt/Waterfall/Endpoints). */
167
+ export const CAPTION_TITLE_CLS = "flex items-center gap-2 font-medium";
168
+
169
+ /** Right-aligned mono digits in a titled caption (Gantt/Waterfall). */
170
+ export const MONO_NUM_CLS = "font-mono font-normal tabular-nums";
171
+
172
+ /** Soft inner divider rows inside panels (Ask questions, Matrix, Props). */
173
+ export const DIVIDE_CLS =
174
+ "divide-y divide-neutral-100 dark:divide-neutral-800/60";
175
+
176
+ /** Tiny pill shape — counts, tags, diff file kinds. Callers add a TONE/bg. */
177
+ export const MINI_CHIP_CLS = `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-px font-medium ${TEXT_MICRO}`;
178
+
179
+ /** Muted count/tag chip on tinted surfaces — Json key counts, Tests tool
180
+ * tags, Board lane counts (neutral-200/70 so it reads on neutral-50/100). */
181
+ export const COUNT_CHIP_CLS = `${MINI_CHIP_CLS} bg-neutral-200/70 font-mono ${TEXT.muted} dark:bg-neutral-800`;
182
+
183
+ /** Bordered mono tag — search tool names, graph edge labels. */
184
+ export const MONO_TAG_CLS = `inline-flex shrink-0 items-center rounded border ${CHIP_BORDER_CLS} bg-white px-1.5 py-0.5 font-mono ${TEXT_SUB} ${TEXT.muted} dark:bg-neutral-900`;
185
+
186
+ /** Left-accent panel shell shared by Callout and Risk. */
187
+ export const EDGE_PANEL_CLS = "my-6 rounded-r-lg border-l-4 px-4 py-3 text-sm";
@@ -0,0 +1,69 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { FILE_LINK_PROPS } from "./attrs.js";
6
+ import { ListPanel, ListRow, LocLink, RowNote, Tag } from "./bits.js";
7
+ import { indexChildren, useChildIndex } from "./child-index.js";
8
+ import { Icon } from "./icon.js";
9
+ import { LOC_CLS, MONO_CLS, TEXT, TONE_BAND } from "./tones.js";
10
+
11
+ export const FRAME_KINDS = ["app", "lib"] as const;
12
+ export type FrameKind = (typeof FRAME_KINDS)[number];
13
+
14
+ export const Trace = defineComponent(
15
+ {
16
+ description:
17
+ "スタックトレースのコンテナ。<TraceFrame> を先頭(最新フレーム)から順に並べる。error で例外メッセージ行を先頭に表示、title でキャプションバー",
18
+ schema: v.looseObject({
19
+ error: v.optional(v.string()),
20
+ title: v.optional(v.string()),
21
+ }),
22
+ },
23
+ ({ error, title, children }) => (
24
+ <ListPanel title={title}>
25
+ {nonEmpty(error) ? (
26
+ <div
27
+ className={`flex items-center gap-2 px-4 py-2 font-mono text-[0.85em] font-medium ${TONE_BAND.red}`}
28
+ >
29
+ <Icon className="h-3.5 w-3.5 shrink-0" name="lucide:circle-alert" />
30
+ {error}
31
+ </div>
32
+ ) : null}
33
+ {indexChildren(children)}
34
+ </ListPanel>
35
+ )
36
+ );
37
+
38
+ export const TraceFrame = defineComponent(
39
+ {
40
+ description:
41
+ "スタックフレーム1行。#番号はコンテナ内で自動採番(先頭=#0)。name=シンボル名、path/lines=発生箇所(実在すればエディタリンク、href で上書き可)、kind=app|lib(lib は淡色+タグ)。children は注記",
42
+ schema: v.looseObject({
43
+ ...FILE_LINK_PROPS,
44
+ kind: v.optional(v.picklist(FRAME_KINDS), "app"),
45
+ name: v.string(),
46
+ }),
47
+ },
48
+ ({ name, path, lines, kind, href, children }) => {
49
+ const { n } = useChildIndex();
50
+ const lib = kind === "lib";
51
+ return (
52
+ <ListRow className={lib ? "opacity-60" : ""}>
53
+ <span className={`${LOC_CLS} w-7 shrink-0`}>
54
+ {n > 0 ? `#${n - 1}` : "·"}
55
+ </span>
56
+ <code className={MONO_CLS}>{name}</code>
57
+ {nonEmpty(path) ? (
58
+ <LocLink href={href} lines={lines} path={path} />
59
+ ) : null}
60
+ {lib ? (
61
+ <Tag className={TEXT.faint} icon="lucide:package">
62
+ lib
63
+ </Tag>
64
+ ) : null}
65
+ <RowNote>{children}</RowNote>
66
+ </ListRow>
67
+ );
68
+ }
69
+ );