@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
package/src/ui/ref.tsx ADDED
@@ -0,0 +1,160 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent } from "../define.js";
5
+ import { safeHref } from "../guards.js";
6
+ import { NUMISH } from "./attrs.js";
7
+ import { CODE_CHIP_CLS } from "./bits.js";
8
+ import { Icon } from "./icon.js";
9
+ import { BORDER_CLS, TEXT } from "./tones.js";
10
+
11
+ export const Ref = defineComponent(
12
+ {
13
+ description: "外部参照リンクカード。href/title は必須。children は説明文",
14
+ schema: v.looseObject({
15
+ href: v.string(),
16
+ title: v.string(),
17
+ }),
18
+ },
19
+ ({ href, title, children }) => (
20
+ <a
21
+ href={safeHref(href)}
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ className={`group not-prose my-6 flex items-start gap-3 rounded-lg border px-4 py-3 text-inherit no-underline transition-colors hover:bg-neutral-50 active:bg-neutral-100 dark:hover:bg-neutral-900 dark:active:bg-neutral-800 ${BORDER_CLS}`}
25
+ >
26
+ <Icon
27
+ className={`mt-0.5 h-4 w-4 shrink-0 ${TEXT.faint} transition-transform duration-150 group-hover:translate-x-0.5 group-hover:-translate-y-0.5`}
28
+ name="lucide:external-link"
29
+ />
30
+ <span className="min-w-0 flex-1">
31
+ <span className={`block text-sm font-medium ${TEXT.strong}`}>
32
+ {title}
33
+ </span>
34
+ {children === undefined ? null : (
35
+ <span className={`mt-0.5 block text-sm ${TEXT.muted}`}>
36
+ {children}
37
+ </span>
38
+ )}
39
+ </span>
40
+ </a>
41
+ )
42
+ );
43
+
44
+ const GH_PATH: Record<"commit" | "issue" | "pr", string> = {
45
+ commit: "commit",
46
+ issue: "issues",
47
+ pr: "pull",
48
+ };
49
+
50
+ /** One repo/host path segment — GitHub names are alnum, `-`, `_`, `.`. */
51
+ const REPO_SEG = /^[\w.-]+$/u;
52
+
53
+ /** `o/r` → github.com; `host/o/r` or a full URL → that host (e.g. GHES). */
54
+ const repoBase = (repo: string): string => {
55
+ const r = repo.replace(/\/+$/u, "");
56
+ // A `proto://` value is used verbatim only for web schemes — a
57
+ // `javascript:`/`data:` repo would otherwise land in `href` unchecked.
58
+ if (r.includes("://")) {
59
+ return /^https?:\/\//iu.test(r) ? r : `https://github.com/${r}`;
60
+ }
61
+ // The host/o/r form needs plausible segments — `javascript:alert(1)//x`
62
+ // has no `://` but is no repo either; treating its first piece as a host
63
+ // would mint `https://javascript:…` links. Degrade to a github.com path,
64
+ // which is at worst a broken link, never a scriptable one.
65
+ const segs = r.split("/");
66
+ if (!segs.every((s) => REPO_SEG.test(s))) {
67
+ return `https://github.com/${r}`;
68
+ }
69
+ return segs.length > 2 ? `https://${r}` : `https://github.com/${r}`;
70
+ };
71
+
72
+ const ghLink = (
73
+ kind: "issue" | "pr" | "commit",
74
+ repo: string,
75
+ id: string
76
+ ): string => `${repoBase(repo)}/${GH_PATH[kind]}/${id}`;
77
+
78
+ const CHIP_CLS = `${CODE_CHIP_CLS} no-underline transition-all hover:bg-neutral-200 active:scale-95 dark:hover:bg-neutral-700`;
79
+
80
+ const refChip = (
81
+ kind: "issue" | "pr",
82
+ repo: string,
83
+ number: string,
84
+ href: string | undefined,
85
+ children: ReactNode
86
+ ): ReactElement => (
87
+ <a
88
+ href={safeHref(href) ?? ghLink(kind, repo, number)}
89
+ target="_blank"
90
+ rel="noopener noreferrer"
91
+ className={CHIP_CLS}
92
+ title={`${repo}#${number}`}
93
+ >
94
+ <Icon
95
+ className="h-3.5 w-3.5 opacity-60"
96
+ name={kind === "pr" ? "lucide:git-pull-request" : "lucide:circle-dot"}
97
+ />
98
+ <span className="font-mono">#{number}</span>
99
+ {children === undefined ? null : (
100
+ <span className={TEXT.body}>{children}</span>
101
+ )}
102
+ </a>
103
+ );
104
+
105
+ const ghRefSchema = () =>
106
+ v.looseObject({
107
+ href: v.optional(v.string()),
108
+ number: NUMISH,
109
+ repo: v.string(),
110
+ });
111
+
112
+ export const Issue = defineComponent(
113
+ {
114
+ description:
115
+ 'GitHub issue 参照チップ。repo="owner/repo"("host/owner/repo" や URL も可)と number は必須。children はタイトル',
116
+ schema: ghRefSchema(),
117
+ },
118
+ ({ repo, number, href, children }) =>
119
+ refChip("issue", repo, String(number), href, children)
120
+ );
121
+
122
+ export const PR = defineComponent(
123
+ {
124
+ description:
125
+ 'GitHub pull request 参照チップ。repo="owner/repo"("host/owner/repo" や URL も可)と number は必須。children はタイトル',
126
+ schema: ghRefSchema(),
127
+ },
128
+ ({ repo, number, href, children }) =>
129
+ refChip("pr", repo, String(number), href, children)
130
+ );
131
+
132
+ export const Commit = defineComponent(
133
+ {
134
+ description:
135
+ 'GitHub コミット参照チップ。repo="owner/repo"("host/owner/repo" や URL も可)と sha は必須。sha は先頭7文字で表示。children はタイトル',
136
+ schema: v.looseObject({
137
+ href: v.optional(v.string()),
138
+ repo: v.string(),
139
+ sha: v.string(),
140
+ }),
141
+ },
142
+ ({ repo, sha, href, children }) => (
143
+ <a
144
+ href={safeHref(href) ?? ghLink("commit", repo, sha)}
145
+ target="_blank"
146
+ rel="noopener noreferrer"
147
+ className={CHIP_CLS}
148
+ title={`${repo}@${sha}`}
149
+ >
150
+ <Icon
151
+ className="h-3.5 w-3.5 opacity-60"
152
+ name="lucide:git-commit-horizontal"
153
+ />
154
+ <span className="font-mono">{sha.slice(0, 7)}</span>
155
+ {children === undefined ? null : (
156
+ <span className={TEXT.body}>{children}</span>
157
+ )}
158
+ </a>
159
+ )
160
+ );
@@ -0,0 +1,178 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import { Fragment } from "react";
3
+ import * as v from "valibot";
4
+
5
+ import { defineComponent, flattenChildren } from "../define.js";
6
+ import { isOneOf, nonEmpty } from "../guards.js";
7
+ import { CaptionBar, Panel } from "./bits.js";
8
+ import { isEl, propOf } from "./children.js";
9
+ import { Icon } from "./icon.js";
10
+ import {
11
+ CAPTION_TITLE_CLS,
12
+ MONO_CLS,
13
+ TEXT,
14
+ TEXT_SUB,
15
+ TONE_TEXT,
16
+ TRIM_CLS,
17
+ } from "./tones.js";
18
+
19
+ export const ENTRY_KINDS = [
20
+ "breaking",
21
+ "added",
22
+ "changed",
23
+ "deprecated",
24
+ "removed",
25
+ "fixed",
26
+ "security",
27
+ ] as const;
28
+ export type EntryKind = (typeof ENTRY_KINDS)[number];
29
+
30
+ const isEntryKind = isOneOf(ENTRY_KINDS);
31
+
32
+ const ENTRY_STYLES: Record<
33
+ EntryKind,
34
+ { icon: string; label: string; text: string }
35
+ > = {
36
+ added: {
37
+ icon: "lucide:plus",
38
+ label: "Added",
39
+ text: TONE_TEXT.emerald,
40
+ },
41
+ breaking: {
42
+ icon: "lucide:zap",
43
+ label: "Breaking",
44
+ text: TONE_TEXT.orange,
45
+ },
46
+ changed: {
47
+ icon: "lucide:pen-line",
48
+ label: "Changed",
49
+ text: TONE_TEXT.sky,
50
+ },
51
+ deprecated: {
52
+ icon: "lucide:archive",
53
+ label: "Deprecated",
54
+ text: TONE_TEXT.amber,
55
+ },
56
+ fixed: {
57
+ icon: "lucide:wrench",
58
+ label: "Fixed",
59
+ text: TONE_TEXT.teal,
60
+ },
61
+ removed: {
62
+ icon: "lucide:minus",
63
+ label: "Removed",
64
+ text: TONE_TEXT.red,
65
+ },
66
+ security: {
67
+ icon: "lucide:shield",
68
+ label: "Security",
69
+ text: TONE_TEXT.violet,
70
+ },
71
+ };
72
+
73
+ export const Entry = defineComponent(
74
+ {
75
+ description:
76
+ "リリースノート項目1行。kind は breaking|added|changed|deprecated|removed|fixed|security、scope で対象スコープチップ。children は項目文",
77
+ schema: v.looseObject({
78
+ kind: v.optional(v.picklist(ENTRY_KINDS), "changed"),
79
+ scope: v.optional(v.string()),
80
+ }),
81
+ },
82
+ ({ kind, scope, children }) => {
83
+ const s = ENTRY_STYLES[kind];
84
+ return (
85
+ <div className="flex flex-wrap items-baseline gap-x-2.5 gap-y-1 px-4 py-1.5">
86
+ <Icon
87
+ className={`h-3.5 w-3.5 shrink-0 self-center ${s.text}`}
88
+ name={s.icon}
89
+ />
90
+ {nonEmpty(scope) ? (
91
+ <code className={`${MONO_CLS} ${TEXT.faint}`}>{scope}:</code>
92
+ ) : null}
93
+ <span className={`min-w-0 flex-1 text-sm ${TRIM_CLS}`}>{children}</span>
94
+ </div>
95
+ );
96
+ }
97
+ );
98
+
99
+ export const Release = defineComponent(
100
+ {
101
+ description:
102
+ "リリースノート/CHANGELOG ブロック。version は必須、date/href (比較リンク) をヘッダに表示。<Entry> を kind 別グループに自動整列。リリース報告・変更履歴ドキュメント向け",
103
+ schema: v.looseObject({
104
+ date: v.optional(v.string()),
105
+ href: v.optional(v.string()),
106
+ title: v.optional(v.string()),
107
+ version: v.string(),
108
+ }),
109
+ },
110
+ ({ version, date, href, title, children }): ReactElement => {
111
+ const groups = new Map<EntryKind, ReactNode[]>();
112
+ const rest: ReactNode[] = [];
113
+ for (const node of flattenChildren(children)) {
114
+ if (!isEl(node, Entry)) {
115
+ rest.push(node);
116
+ continue;
117
+ }
118
+ const k = propOf(node, "kind");
119
+ const kind = isEntryKind(k) ? k : "changed";
120
+ groups.set(kind, [...(groups.get(kind) ?? []), node]);
121
+ }
122
+ return (
123
+ <Panel>
124
+ <CaptionBar className={CAPTION_TITLE_CLS}>
125
+ <Icon className="h-3.5 w-3.5" name="lucide:tag" />
126
+ <code className={`font-mono font-semibold ${TEXT.code}`}>
127
+ {nonEmpty(title) ? `${title} ` : ""}
128
+ {version}
129
+ </code>
130
+ <span className="ml-auto flex items-center gap-x-2 font-normal">
131
+ {nonEmpty(date) ? (
132
+ <span className={`${TEXT_SUB} ${TEXT.faint}`}>{date}</span>
133
+ ) : null}
134
+ {nonEmpty(href) ? (
135
+ <a
136
+ className={`${TEXT_SUB} ${TEXT.muted} no-underline hover:underline`}
137
+ href={href}
138
+ rel="noopener noreferrer"
139
+ target="_blank"
140
+ >
141
+ compare
142
+ </a>
143
+ ) : null}
144
+ </span>
145
+ </CaptionBar>
146
+ <div className="py-2">
147
+ {ENTRY_KINDS.map((kind) => {
148
+ const items = groups.get(kind);
149
+ if (items === undefined || items.length === 0) {
150
+ return null;
151
+ }
152
+ const s = ENTRY_STYLES[kind];
153
+ return (
154
+ <div className="py-1" key={kind}>
155
+ <div
156
+ className={`px-4 py-1 text-xs font-semibold tracking-wide uppercase ${TEXT.muted}`}
157
+ >
158
+ {s.label}
159
+ <span className={`ml-1.5 font-normal ${TEXT.faint}`}>
160
+ {items.length}
161
+ </span>
162
+ </div>
163
+ {items.map((node, i) => (
164
+ // Entries arrive unkeyed from the doc — group position is
165
+ // stable, so index keys on a fragment are safe here.
166
+ <Fragment key={i}>{node}</Fragment>
167
+ ))}
168
+ </div>
169
+ );
170
+ })}
171
+ {rest.length > 0 ? (
172
+ <div className={`px-4 py-2 ${TRIM_CLS}`}>{rest}</div>
173
+ ) : null}
174
+ </div>
175
+ </Panel>
176
+ );
177
+ }
178
+ );
package/src/ui/req.tsx ADDED
@@ -0,0 +1,42 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { NUMISH } from "./attrs.js";
5
+ import { ListPanel, ListRow } from "./bits.js";
6
+ import { Icon } from "./icon.js";
7
+ import { STATUS_PROP, StatusBadge } from "./status-badge.js";
8
+ import { TONE, TRIM_CLS } from "./tones.js";
9
+
10
+ export const Reqs = defineComponent(
11
+ {
12
+ description: "要件/受け入れ基準リストのコンテナ。<Req> を並べる",
13
+ },
14
+ ({ children }) => <ListPanel as="div">{children}</ListPanel>
15
+ );
16
+
17
+ export const Req = defineComponent(
18
+ {
19
+ description:
20
+ '要件1行。id="REQ-1" などの識別子チップ。status (todo|doing|done|blocked) でバッジを付けられる',
21
+ schema: v.looseObject({
22
+ id: NUMISH,
23
+ status: STATUS_PROP,
24
+ }),
25
+ },
26
+ ({ id, status, children }) => (
27
+ <ListRow>
28
+ <span
29
+ className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 font-mono text-xs font-medium ${TONE.neutral}`}
30
+ >
31
+ <Icon className="h-3 w-3" name="lucide:bookmark" />
32
+ {id}
33
+ </span>
34
+ <span className={`min-w-0 flex-1 text-sm ${TRIM_CLS}`}>{children}</span>
35
+ {status === undefined ? null : (
36
+ <span className="ml-auto">
37
+ <StatusBadge status={status} />
38
+ </span>
39
+ )}
40
+ </ListRow>
41
+ )
42
+ );
@@ -0,0 +1,133 @@
1
+ import type { 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 } from "./attrs.js";
7
+ import { IndexedCard, LocLink, Pill, Section } from "./bits.js";
8
+ import { indexChildren, useChildIndex } from "./child-index.js";
9
+ import { isEl, propOf } from "./children.js";
10
+ import {
11
+ SEVERITY_LEVELS,
12
+ SEVERITY_STYLES,
13
+ Severity,
14
+ isSeverity,
15
+ } from "./severity.js";
16
+ import { TONE, TEXT } from "./tones.js";
17
+
18
+ export const REVIEW_VERDICTS = ["approve", "comment", "changes"] as const;
19
+ export type ReviewVerdict = (typeof REVIEW_VERDICTS)[number];
20
+
21
+ const VERDICT_STYLES: Record<
22
+ ReviewVerdict,
23
+ { cls: string; icon: string; label: string }
24
+ > = {
25
+ approve: {
26
+ cls: TONE.emerald,
27
+ icon: "lucide:circle-check-big",
28
+ label: "Approved",
29
+ },
30
+ changes: {
31
+ cls: TONE.amber,
32
+ icon: "lucide:message-square-warning",
33
+ label: "Changes requested",
34
+ },
35
+ comment: {
36
+ cls: TONE.sky,
37
+ icon: "lucide:message-square-text",
38
+ label: "Commented",
39
+ },
40
+ };
41
+
42
+ export const Comment = defineComponent(
43
+ {
44
+ description:
45
+ "レビュー指摘1件。severity は critical|high|medium|low|info、file/lines でコード位置へのエディタリンク。children は説明や修正案 (```diff フェンスで提案 diff も書ける)",
46
+ schema: v.looseObject({
47
+ ...LINK_LINES_PROPS,
48
+ file: v.optional(v.string()),
49
+ severity: v.optional(v.picklist(SEVERITY_LEVELS), "info"),
50
+ title: v.optional(v.string()),
51
+ }),
52
+ },
53
+ ({ severity, title, file, lines, href, children }) => {
54
+ const { n } = useChildIndex();
55
+ return (
56
+ <IndexedCard
57
+ aside={
58
+ nonEmpty(file) ? (
59
+ <LocLink href={href} lines={lines} path={file} />
60
+ ) : undefined
61
+ }
62
+ n={n}
63
+ pill={<Severity level={severity} />}
64
+ title={title}
65
+ >
66
+ {children}
67
+ </IndexedCard>
68
+ );
69
+ }
70
+ );
71
+
72
+ /** Tally `severity` across <Comment> children for the summary line. */
73
+ const countComments = (children: ReactNode): Map<string, number> => {
74
+ const counts = new Map<string, number>();
75
+ for (const node of flattenChildren(children)) {
76
+ if (!isEl(node, Comment)) {
77
+ continue;
78
+ }
79
+ const sev = propOf(node, "severity");
80
+ const key = isSeverity(sev) ? sev : "info";
81
+ counts.set(key, (counts.get(key) ?? 0) + 1);
82
+ }
83
+ return counts;
84
+ };
85
+
86
+ const isReviewVerdict = isOneOf(REVIEW_VERDICTS);
87
+
88
+ export const Review = defineComponent(
89
+ {
90
+ description:
91
+ "コードレビュー結果のコンテナ。<Comment> を列挙し、severity 別の件数を上部に集計。verdict は approve|comment|changes の判定ピル",
92
+ schema: v.looseObject({
93
+ title: v.optional(v.string()),
94
+ verdict: v.optional(v.picklist(REVIEW_VERDICTS)),
95
+ }),
96
+ },
97
+ ({ title, verdict, children }) => {
98
+ const counts = countComments(children);
99
+ const total = [...counts.values()].reduce((a, b) => a + b, 0);
100
+ const style = isReviewVerdict(verdict)
101
+ ? VERDICT_STYLES[verdict]
102
+ : undefined;
103
+ return (
104
+ <Section title={title}>
105
+ {total > 0 || style !== undefined ? (
106
+ <div
107
+ className={`mb-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs ${TEXT.muted}`}
108
+ >
109
+ {style === undefined ? null : (
110
+ <Pill className={style.cls} icon={style.icon}>
111
+ {style.label}
112
+ </Pill>
113
+ )}
114
+ {total > 0 ? (
115
+ <span>
116
+ {total} {total === 1 ? "comment" : "comments"}
117
+ </span>
118
+ ) : null}
119
+ {SEVERITY_LEVELS.map((k) => {
120
+ const count = counts.get(k);
121
+ return count === undefined ? null : (
122
+ <span key={k}>
123
+ · {count} {SEVERITY_STYLES[k].label.toLowerCase()}
124
+ </span>
125
+ );
126
+ })}
127
+ </div>
128
+ ) : null}
129
+ <div className="not-prose space-y-3">{indexChildren(children)}</div>
130
+ </Section>
131
+ );
132
+ }
133
+ );
@@ -0,0 +1,67 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { Pill, TrimBody } from "./bits.js";
6
+ import { BORDER_CLS, EDGE_PANEL_CLS, SUNKEN_CLS, TONE } from "./tones.js";
7
+
8
+ export const RISK_LEVELS = ["low", "medium", "high"] as const;
9
+ export type RiskLevel = (typeof RISK_LEVELS)[number];
10
+
11
+ const STYLES: Record<
12
+ RiskLevel,
13
+ { border: string; chip: string; icon: string; label: string }
14
+ > = {
15
+ high: {
16
+ border: "border-red-500",
17
+ chip: TONE.red,
18
+ icon: "lucide:flame",
19
+ label: "High",
20
+ },
21
+ low: {
22
+ border: "border-neutral-400",
23
+ chip: TONE.neutral,
24
+ icon: "lucide:shield",
25
+ label: "Low",
26
+ },
27
+ medium: {
28
+ border: "border-amber-500",
29
+ chip: TONE.amber,
30
+ icon: "lucide:shield-alert",
31
+ label: "Medium",
32
+ },
33
+ };
34
+
35
+ export const Risk = defineComponent(
36
+ {
37
+ description:
38
+ "リスクブロック。level は low|medium|high。mitigation 属性で緩和策を示せる",
39
+ schema: v.looseObject({
40
+ level: v.optional(v.picklist(RISK_LEVELS), "medium"),
41
+ mitigation: v.optional(v.string()),
42
+ title: v.optional(v.string()),
43
+ }),
44
+ },
45
+ ({ level, title, mitigation, children }) => {
46
+ const s = STYLES[level];
47
+ return (
48
+ <aside className={`${EDGE_PANEL_CLS} ${SUNKEN_CLS} ${s.border}`}>
49
+ <div className="flex flex-wrap items-baseline gap-2">
50
+ <Pill className={s.chip} icon={s.icon}>
51
+ {s.label} risk
52
+ </Pill>
53
+ {nonEmpty(title) ? (
54
+ <span className="font-semibold">{title}</span>
55
+ ) : null}
56
+ </div>
57
+ <TrimBody className="mt-1.5">{children}</TrimBody>
58
+ {nonEmpty(mitigation) ? (
59
+ <div className={`mt-2 border-t pt-2 text-xs ${BORDER_CLS}`}>
60
+ <span className="font-medium">Mitigation: </span>
61
+ {mitigation}
62
+ </div>
63
+ ) : null}
64
+ </aside>
65
+ );
66
+ }
67
+ );