paseo-plugin-genui 0.0.0-stage → 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 (375) hide show
  1. package/LICENSE +202 -0
  2. package/OVERVIEW.md +21 -0
  3. package/README.md +77 -2
  4. package/assets/fonts/Excalifont-OFL.txt +109 -0
  5. package/assets/fonts/Excalifont-Regular-latin.woff2 +0 -0
  6. package/assets/icon.png +0 -0
  7. package/assets/icon.svg +1 -0
  8. package/assets/screenshot-chat.png +0 -0
  9. package/assets/screenshot-gallery.png +0 -0
  10. package/assets/screenshot-playground.png +0 -0
  11. package/client/components/content.tsx +468 -0
  12. package/client/components/data.tsx +967 -0
  13. package/client/components/dev.tsx +998 -0
  14. package/client/components/diagram.tsx +513 -0
  15. package/client/components/inputs.tsx +994 -0
  16. package/client/components/layout.tsx +239 -0
  17. package/client/components/media.tsx +428 -0
  18. package/client/components/project.tsx +641 -0
  19. package/client/components/widgets.tsx +836 -0
  20. package/client/gallery.tsx +449 -0
  21. package/client/live-ui.tsx +525 -0
  22. package/client/loaders.ts +342 -0
  23. package/client/markdown.tsx +172 -0
  24. package/client/node-renderer.tsx +149 -0
  25. package/client/render.tsx +186 -0
  26. package/client/runtime.ts +103 -0
  27. package/client/settings.tsx +57 -0
  28. package/client/web.ts +186 -0
  29. package/gallery/Acceptance-baseline.ui +1 -0
  30. package/gallery/Acceptance-checklist.ui +14 -0
  31. package/gallery/Acceptance-evidence.ui +9 -0
  32. package/gallery/Acceptance.ui +1 -0
  33. package/gallery/Badge.ui +3 -0
  34. package/gallery/Benchmark-context.ui +8 -0
  35. package/gallery/Benchmark-hyperfine.ui +1 -0
  36. package/gallery/Benchmark-runs-list.ui +9 -0
  37. package/gallery/Benchmark-runs.ui +1 -0
  38. package/gallery/Benchmark-vitest.ui +1 -0
  39. package/gallery/Benchmark.ui +9 -0
  40. package/gallery/Board-bb-tasks.ui +1 -0
  41. package/gallery/Board-gh-issues.ui +1 -0
  42. package/gallery/Board-gh-prs.ui +1 -0
  43. package/gallery/Board.ui +12 -0
  44. package/gallery/Button-destructive.ui +4 -0
  45. package/gallery/Button.ui +5 -0
  46. package/gallery/Calendar.ui +15 -0
  47. package/gallery/CalendarHeatmap-short.ui +11 -0
  48. package/gallery/CalendarHeatmap.ui +4 -0
  49. package/gallery/Callout.ui +4 -0
  50. package/gallery/Card.ui +4 -0
  51. package/gallery/Carousel.ui +8 -0
  52. package/gallery/Changes-paths.ui +1 -0
  53. package/gallery/Changes-uncommitted.ui +1 -0
  54. package/gallery/Changes.ui +1 -0
  55. package/gallery/Chart-area.ui +3 -0
  56. package/gallery/Chart-funnel.ui +3 -0
  57. package/gallery/Chart-heatmap.ui +11 -0
  58. package/gallery/Chart-pie.ui +3 -0
  59. package/gallery/Chart-radar.ui +3 -0
  60. package/gallery/Chart-scatter.ui +4 -0
  61. package/gallery/Chart-stacked.ui +5 -0
  62. package/gallery/Chart.ui +3 -0
  63. package/gallery/Checklist.ui +6 -0
  64. package/gallery/Choice.ui +8 -0
  65. package/gallery/Code.ui +3 -0
  66. package/gallery/Commits-limit.ui +1 -0
  67. package/gallery/Commits.ui +1 -0
  68. package/gallery/Compare-conditions.ui +4 -0
  69. package/gallery/Compare-three.ui +7 -0
  70. package/gallery/Compare.ui +4 -0
  71. package/gallery/Coverage-go.ui +1 -0
  72. package/gallery/Coverage-lcov.ui +1 -0
  73. package/gallery/Coverage-python.ui +1 -0
  74. package/gallery/Coverage.ui +1 -0
  75. package/gallery/DateInput.ui +5 -0
  76. package/gallery/Decision-percent.ui +10 -0
  77. package/gallery/Decision.ui +12 -0
  78. package/gallery/Dependencies-audit.ui +1 -0
  79. package/gallery/Dependencies-both.ui +1 -0
  80. package/gallery/Dependencies-list.ui +7 -0
  81. package/gallery/Dependencies-pip.ui +1 -0
  82. package/gallery/Dependencies-pnpm.ui +1 -0
  83. package/gallery/Dependencies.ui +1 -0
  84. package/gallery/Details.ui +4 -0
  85. package/gallery/Diagram-flow.ui +16 -0
  86. package/gallery/Diagram-sequence.ui +15 -0
  87. package/gallery/Diagram.ui +16 -0
  88. package/gallery/Diff.ui +3 -0
  89. package/gallery/Divider.ui +5 -0
  90. package/gallery/EditableTable.ui +9 -0
  91. package/gallery/Entity-list.ui +6 -0
  92. package/gallery/Entity.ui +8 -0
  93. package/gallery/Environments.ui +1 -0
  94. package/gallery/Estimate-points.ui +6 -0
  95. package/gallery/Estimate.ui +8 -0
  96. package/gallery/File.ui +4 -0
  97. package/gallery/Findings-biome.ui +1 -0
  98. package/gallery/Findings-eslint.ui +1 -0
  99. package/gallery/Findings-oxlint.ui +1 -0
  100. package/gallery/Findings-review.ui +1 -0
  101. package/gallery/Findings-ruff.ui +1 -0
  102. package/gallery/Findings-sarif.ui +1 -0
  103. package/gallery/Findings-ship.ui +11 -0
  104. package/gallery/Findings-tsc.ui +1 -0
  105. package/gallery/Findings.ui +7 -0
  106. package/gallery/Flashcards.ui +3 -0
  107. package/gallery/FollowUps.ui +4 -0
  108. package/gallery/Form.ui +8 -0
  109. package/gallery/Gallery-links.ui +8 -0
  110. package/gallery/Gallery-strip.ui +14 -0
  111. package/gallery/Gallery.ui +10 -0
  112. package/gallery/Gauge.ui +7 -0
  113. package/gallery/Grid.ui +8 -0
  114. package/gallery/Hotspots-photo.ui +8 -0
  115. package/gallery/Hotspots.ui +11 -0
  116. package/gallery/Icon.ui +5 -0
  117. package/gallery/Image.ui +4 -0
  118. package/gallery/ImageSearch-aspects.ui +6 -0
  119. package/gallery/ImageSearch.ui +4 -0
  120. package/gallery/Input.ui +5 -0
  121. package/gallery/Json-api.ui +1 -0
  122. package/gallery/Json.ui +8 -0
  123. package/gallery/KeyValue.ui +3 -0
  124. package/gallery/List-dots.ui +8 -0
  125. package/gallery/List.ui +8 -0
  126. package/gallery/ListEditor.ui +5 -0
  127. package/gallery/Logs-jsonl.ui +1 -0
  128. package/gallery/Logs-lines.ui +11 -0
  129. package/gallery/Logs-python.ui +1 -0
  130. package/gallery/Logs.ui +1 -0
  131. package/gallery/Map-city.ui +8 -0
  132. package/gallery/Map.ui +8 -0
  133. package/gallery/Metric.ui +3 -0
  134. package/gallery/OptionCards-multi.ui +10 -0
  135. package/gallery/OptionCards.ui +10 -0
  136. package/gallery/Pipeline-checks.ui +1 -0
  137. package/gallery/Pipeline-passed.ui +1 -0
  138. package/gallery/Pipeline-running.ui +1 -0
  139. package/gallery/Pipeline-stages.ui +13 -0
  140. package/gallery/Pipeline.ui +1 -0
  141. package/gallery/Plan.ui +9 -0
  142. package/gallery/Progress.ui +4 -0
  143. package/gallery/Provenance-deploy.ui +6 -0
  144. package/gallery/Provenance-no-receipt.ui +1 -0
  145. package/gallery/Provenance.ui +1 -0
  146. package/gallery/PullRequest.ui +1 -0
  147. package/gallery/Quiz.ui +6 -0
  148. package/gallery/Radio.ui +5 -0
  149. package/gallery/Rating.ui +5 -0
  150. package/gallery/Roadmap-sprint.ui +8 -0
  151. package/gallery/Roadmap-year.ui +9 -0
  152. package/gallery/Roadmap.ui +12 -0
  153. package/gallery/Row.ui +3 -0
  154. package/gallery/SegmentedBar-status.ui +8 -0
  155. package/gallery/SegmentedBar.ui +8 -0
  156. package/gallery/Select.ui +5 -0
  157. package/gallery/Slider.ui +7 -0
  158. package/gallery/Snippet.ui +3 -0
  159. package/gallery/Sources-chips.ui +4 -0
  160. package/gallery/Sources.ui +8 -0
  161. package/gallery/Sparkline.ui +6 -0
  162. package/gallery/Stack.ui +7 -0
  163. package/gallery/StackTrace-aggregate.ui +1 -0
  164. package/gallery/StackTrace-browser.ui +1 -0
  165. package/gallery/StackTrace-go.ui +1 -0
  166. package/gallery/StackTrace-java.ui +1 -0
  167. package/gallery/StackTrace-python.ui +1 -0
  168. package/gallery/StackTrace-rust.ui +1 -0
  169. package/gallery/StackTrace.ui +1 -0
  170. package/gallery/Status.ui +9 -0
  171. package/gallery/Stepper.ui +5 -0
  172. package/gallery/Steps.ui +8 -0
  173. package/gallery/Swatches-long-names.ui +4 -0
  174. package/gallery/Swatches-picker.ui +5 -0
  175. package/gallery/Swatches.ui +10 -0
  176. package/gallery/Table.ui +7 -0
  177. package/gallery/Tabs.ui +8 -0
  178. package/gallery/Tests-junit.ui +1 -0
  179. package/gallery/Tests-vitest-junit.ui +1 -0
  180. package/gallery/Tests-vitest.ui +1 -0
  181. package/gallery/Tests.ui +9 -0
  182. package/gallery/Text.ui +4 -0
  183. package/gallery/TextArea.ui +5 -0
  184. package/gallery/Threads-ids.ui +1 -0
  185. package/gallery/Threads.ui +1 -0
  186. package/gallery/Timeline.ui +8 -0
  187. package/gallery/Timer.ui +4 -0
  188. package/gallery/Toggle.ui +5 -0
  189. package/gallery/Transcript-text.ui +1 -0
  190. package/gallery/Transcript-untimed.ui +1 -0
  191. package/gallery/Transcript-whisper.ui +1 -0
  192. package/gallery/Transcript.ui +1 -0
  193. package/gallery/Tree.ui +3 -0
  194. package/gallery/Treemap-du.ui +1 -0
  195. package/gallery/Treemap-esbuild.ui +1 -0
  196. package/gallery/Treemap-tree.ui +7 -0
  197. package/gallery/Treemap.ui +18 -0
  198. package/gallery/Walkthrough.ui +9 -0
  199. package/gallery/Waterfall-har.ui +1 -0
  200. package/gallery/Waterfall-trace.ui +1 -0
  201. package/gallery/Waterfall.ui +11 -0
  202. package/gallery/data/acceptance/coil/viewport-lifecycle.json +25 -0
  203. package/gallery/data/acceptance/coil-gate.json +153 -0
  204. package/gallery/data/acceptance/orbit-baseline.json +163 -0
  205. package/gallery/data/acceptance/shots/gate-cell-opened.png +0 -0
  206. package/gallery/data/acceptance/shots/gate-light.png +0 -0
  207. package/gallery/data/acceptance/shots/gate-phone-dark.png +0 -0
  208. package/gallery/data/activity.json +1 -0
  209. package/gallery/data/bb-tasks-orb.json +420 -0
  210. package/gallery/data/biome.json +229 -0
  211. package/gallery/data/compare/status-after.png +0 -0
  212. package/gallery/data/compare/status-before.png +0 -0
  213. package/gallery/data/compare/status-dark-2x.png +0 -0
  214. package/gallery/data/compare/status-rerender.png +0 -0
  215. package/gallery/data/coverage-py.json +1 -0
  216. package/gallery/data/coverage-summary.json +1 -0
  217. package/gallery/data/coverage.out +23 -0
  218. package/gallery/data/deploys.json +1 -0
  219. package/gallery/data/device-runs.json +285 -0
  220. package/gallery/data/du-node-modules.txt +359 -0
  221. package/gallery/data/esbuild-meta.json +292 -0
  222. package/gallery/data/eslint.json +266 -0
  223. package/gallery/data/eslint.sarif +343 -0
  224. package/gallery/data/findings-review.json +76 -0
  225. package/gallery/data/gh-issues-nextjs.json +293 -0
  226. package/gallery/data/gh-pr-checks.json +272 -0
  227. package/gallery/data/gh-prs-nextjs.json +419 -0
  228. package/gallery/data/gh-run-failed.json +1 -0
  229. package/gallery/data/gh-run-passed.json +1 -0
  230. package/gallery/data/gh-run-running.json +1 -0
  231. package/gallery/data/github-release.json +57 -0
  232. package/gallery/data/hyperfine-startup.json +187 -0
  233. package/gallery/data/lcov.info +234 -0
  234. package/gallery/data/logs/media-sync.log +127 -0
  235. package/gallery/data/logs/thumbnailer.jsonl +17 -0
  236. package/gallery/data/logs/vitest-run.log +89 -0
  237. package/gallery/data/npm-audit.json +672 -0
  238. package/gallery/data/npm-outdated.json +179 -0
  239. package/gallery/data/oxlint.json +156 -0
  240. package/gallery/data/pip-outdated.json +68 -0
  241. package/gallery/data/pnpm-outdated.json +58 -0
  242. package/gallery/data/provenance/devicectl-install.json +35 -0
  243. package/gallery/data/provenance/install-timeout.log +4 -0
  244. package/gallery/data/provenance/iphone-install.json +72 -0
  245. package/gallery/data/provenance/iphone-no-receipt.json +57 -0
  246. package/gallery/data/provenance/live-home.jpg +0 -0
  247. package/gallery/data/provenance/live-probe.txt +10 -0
  248. package/gallery/data/provenance/xcodebuild-2811.log +19 -0
  249. package/gallery/data/provenance/xcodebuild-4712.log +19 -0
  250. package/gallery/data/pytest-junit.xml +64 -0
  251. package/gallery/data/python-org-trace.json +35 -0
  252. package/gallery/data/python-org.har +31 -0
  253. package/gallery/data/retry.ts +13 -0
  254. package/gallery/data/ruff.json +261 -0
  255. package/gallery/data/src/durations.py +63 -0
  256. package/gallery/data/src/json-logic.ts +156 -0
  257. package/gallery/data/src/mapMath.ts +135 -0
  258. package/gallery/data/src/semver/bump.go +73 -0
  259. package/gallery/data/traces/browser-error.json +5 -0
  260. package/gallery/data/traces/go/main.go +27 -0
  261. package/gallery/data/traces/go-panic.txt +16 -0
  262. package/gallery/data/traces/java-caused-by.txt +15 -0
  263. package/gallery/data/traces/node-aggregate.txt +30 -0
  264. package/gallery/data/traces/node-cause.txt +21 -0
  265. package/gallery/data/traces/py/deploy.json +5 -0
  266. package/gallery/data/traces/py/deploy.py +17 -0
  267. package/gallery/data/traces/py/settings.py +19 -0
  268. package/gallery/data/traces/python-chain.txt +30 -0
  269. package/gallery/data/traces/rust/main.rs +11 -0
  270. package/gallery/data/traces/rust-panic.txt +16 -0
  271. package/gallery/data/traces/shop/cart.mjs +19 -0
  272. package/gallery/data/traces/shop/pricing.mjs +12 -0
  273. package/gallery/data/traces/shop/status.mjs +9 -0
  274. package/gallery/data/traces/web/app.js +11 -0
  275. package/gallery/data/traces/web/index.html +1 -0
  276. package/gallery/data/traces/web/search.js +10 -0
  277. package/gallery/data/transcript/call-noisy.wav +0 -0
  278. package/gallery/data/transcript/call.json +362 -0
  279. package/gallery/data/transcript/call.wav +0 -0
  280. package/gallery/data/transcript/mlx-whisper-tiny.en-noisy.json +1 -0
  281. package/gallery/data/transcript/whisper-cpp-base.en-noisy.json +658 -0
  282. package/gallery/data/transcript/whisper-cpp-base.en.json +658 -0
  283. package/gallery/data/transcript/whisper-cpp-base.en.srt +16 -0
  284. package/gallery/data/transcript/whisper-cpp-tiny.en-noisy.json +619 -0
  285. package/gallery/data/tsc.txt +4 -0
  286. package/gallery/data/vitest-bench.json +119 -0
  287. package/gallery/data/vitest-junit.xml +133 -0
  288. package/gallery/data/vitest-report.json +612 -0
  289. package/gallery/images/bell.svg +10 -0
  290. package/gallery/images/bike.svg +47 -0
  291. package/gallery/images/footer-planet.webp +0 -0
  292. package/gallery/images/front-light.svg +13 -0
  293. package/gallery/images/golden-hour.webp +0 -0
  294. package/gallery/images/option-flowers.webp +0 -0
  295. package/gallery/images/option-hilltop.webp +0 -0
  296. package/gallery/images/option-meadow.webp +0 -0
  297. package/gallery/images/option-sky.webp +0 -0
  298. package/gallery/images/u-lock.svg +10 -0
  299. package/index.client.tsx +164 -0
  300. package/index.server.ts +216 -0
  301. package/package.json +50 -4
  302. package/paseo-plugin.json +14 -0
  303. package/server/data-source.ts +407 -0
  304. package/server/project.ts +981 -0
  305. package/server/web/address.ts +57 -0
  306. package/server/web/cache.ts +85 -0
  307. package/server/web/commons.ts +126 -0
  308. package/server/web/geocode.ts +40 -0
  309. package/server/web/image.ts +35 -0
  310. package/server/web/lookups.ts +248 -0
  311. package/server/web/net.ts +210 -0
  312. package/shared/core/acceptance.ts +744 -0
  313. package/shared/core/ast.ts +76 -0
  314. package/shared/core/benchmark.ts +743 -0
  315. package/shared/core/catalog.ts +733 -0
  316. package/shared/core/compare.ts +225 -0
  317. package/shared/core/coverage.ts +456 -0
  318. package/shared/core/data.ts +334 -0
  319. package/shared/core/decisions.ts +527 -0
  320. package/shared/core/dependencies.ts +604 -0
  321. package/shared/core/diagnostics.ts +24 -0
  322. package/shared/core/environments.ts +212 -0
  323. package/shared/core/evaluate.ts +737 -0
  324. package/shared/core/fields.ts +66 -0
  325. package/shared/core/findings.ts +1172 -0
  326. package/shared/core/form.ts +69 -0
  327. package/shared/core/index.ts +44 -0
  328. package/shared/core/junit.ts +252 -0
  329. package/shared/core/lexer.ts +275 -0
  330. package/shared/core/logs.ts +496 -0
  331. package/shared/core/markdown.ts +2374 -0
  332. package/shared/core/parser.ts +355 -0
  333. package/shared/core/perf-units.ts +228 -0
  334. package/shared/core/pipeline.ts +679 -0
  335. package/shared/core/project.ts +472 -0
  336. package/shared/core/provenance.ts +725 -0
  337. package/shared/core/stack-trace.ts +718 -0
  338. package/shared/core/streaming.ts +136 -0
  339. package/shared/core/test-results.ts +562 -0
  340. package/shared/core/thread-activity.ts +203 -0
  341. package/shared/core/tracking.ts +887 -0
  342. package/shared/core/transcript.ts +1364 -0
  343. package/shared/core/treemap.ts +446 -0
  344. package/shared/core/types.ts +70 -0
  345. package/shared/core/validate.ts +574 -0
  346. package/shared/core/waterfall.ts +536 -0
  347. package/shared/diagram/flowchart.ts +205 -0
  348. package/shared/diagram/layout.ts +440 -0
  349. package/shared/diagram/markdown.ts +135 -0
  350. package/shared/diagram/sequence.ts +122 -0
  351. package/shared/diagram/zoom.ts +114 -0
  352. package/shared/fallback.ts +120 -0
  353. package/shared/genui.test.ts +122 -0
  354. package/shared/instructions.ts +144 -0
  355. package/shared/logic/calendarMath.ts +130 -0
  356. package/shared/logic/chartMath.ts +289 -0
  357. package/shared/logic/choose-logic.ts +196 -0
  358. package/shared/logic/compare-logic.ts +270 -0
  359. package/shared/logic/content-model.ts +227 -0
  360. package/shared/logic/environments-logic.ts +53 -0
  361. package/shared/logic/explain-logic.ts +315 -0
  362. package/shared/logic/fieldLogic.ts +138 -0
  363. package/shared/logic/gallery-search.ts +71 -0
  364. package/shared/logic/icons.ts +322 -0
  365. package/shared/logic/json-logic.ts +153 -0
  366. package/shared/logic/mapMath.ts +133 -0
  367. package/shared/logic/project-logic.ts +39 -0
  368. package/shared/logic/references-model.ts +201 -0
  369. package/shared/logic/sketch.ts +165 -0
  370. package/shared/logic/supersession.ts +77 -0
  371. package/shared/logic/values.ts +133 -0
  372. package/shared/logic/widget-logic.ts +163 -0
  373. package/shared/rpc.ts +252 -0
  374. package/shared/segment.ts +81 -0
  375. package/shared/settings.ts +12 -0
@@ -0,0 +1,513 @@
1
+ import type { PluginTheme } from "@getpaseo/plugin";
2
+ import { Icon, Modal, ScrollView as SheetScrollView } from "@getpaseo/plugin/client/react-native";
3
+ import React, { useMemo, useState } from "react";
4
+ import { LayoutChangeEvent, Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
5
+ import { parseFlowchart, type Flowchart, type NodeShape } from "../../shared/diagram/flowchart";
6
+ import {
7
+ layoutFlowchart,
8
+ layoutSequence,
9
+ type FlowLayout,
10
+ type LaidOutEdge,
11
+ type LaidOutNode,
12
+ } from "../../shared/diagram/layout";
13
+ import { parseSequence, type SeqEvent, type SequenceDiagram } from "../../shared/diagram/sequence";
14
+ import { fitRowZoom, zoomIn, zoomOut, type Size } from "../../shared/diagram/zoom";
15
+ import { detailFor, parseDetails, type DiagramDetail } from "../../shared/logic/explain-logic";
16
+ import { isNode, text, type Props } from "../../shared/logic/values";
17
+ import { MarkdownText } from "../markdown";
18
+ import { RenderNode } from "../node-renderer";
19
+ import { useUiRuntime } from "../runtime";
20
+
21
+ const ARROW = 4;
22
+ const MIN_MEASURABLE = 120;
23
+
24
+ type ParsedDiagram =
25
+ | { kind: "flow"; layout: FlowLayout; skipped: Flowchart["skipped"] }
26
+ | { kind: "sequence"; diagram: SequenceDiagram; size: Size }
27
+ | { kind: "unsupported" };
28
+
29
+ type DrawableDiagram = Exclude<ParsedDiagram, { kind: "unsupported" }>;
30
+
31
+ function parseDiagram(source: string): ParsedDiagram {
32
+ const flow = parseFlowchart(source);
33
+ if (flow && flow.nodes.length > 0) {
34
+ return { kind: "flow", layout: layoutFlowchart(flow), skipped: flow.skipped };
35
+ }
36
+ const sequence = parseSequence(source);
37
+ if (sequence && sequence.participants.length > 0) {
38
+ const box = layoutSequence(sequence.participants, sequence.events.length);
39
+ return { kind: "sequence", diagram: sequence, size: { width: box.width, height: box.height } };
40
+ }
41
+ return { kind: "unsupported" };
42
+ }
43
+
44
+ function naturalSize(parsed: DrawableDiagram): Size {
45
+ return parsed.kind === "flow"
46
+ ? { width: parsed.layout.width, height: parsed.layout.height }
47
+ : parsed.size;
48
+ }
49
+
50
+ export function Diagram({ props }: { props: Props }) {
51
+ const { theme, compact } = useUiRuntime();
52
+ const source = text(props.mermaid);
53
+ const details = useMemo(() => parseDetails(props.details) ?? [], [props.details]);
54
+ const parsed = useMemo(() => parseDiagram(source), [source]);
55
+ const [selectedDetail, setSelectedDetail] = useState<DiagramDetail | null>(null);
56
+ const [showCode, setShowCode] = useState(false);
57
+ const [popped, setPopped] = useState(false);
58
+ const [available, setAvailable] = useState(0);
59
+ const [zoom, setZoom] = useState(1);
60
+
61
+ if (parsed.kind === "unsupported") {
62
+ return (
63
+ <View
64
+ style={{
65
+ borderRadius: 8,
66
+ borderWidth: StyleSheet.hairlineWidth,
67
+ borderColor: theme.colors.border,
68
+ backgroundColor: theme.colors.surface2,
69
+ padding: 10,
70
+ }}
71
+ >
72
+ <Text selectable style={{ color: theme.colors.foreground, fontFamily: "monospace", fontSize: 12 }}>
73
+ {source}
74
+ </Text>
75
+ </View>
76
+ );
77
+ }
78
+
79
+ const natural = naturalSize(parsed);
80
+ const baseScale = available > MIN_MEASURABLE ? Math.min(1.15, Math.max(0.55, available / natural.width)) : 1;
81
+ const fitZoom = fitRowZoom(baseScale, natural.width, available);
82
+ const scale = baseScale * zoom;
83
+ const box = {
84
+ width: Math.max(1, Math.floor(natural.width * scale)),
85
+ height: Math.max(1, Math.floor(natural.height * scale)),
86
+ };
87
+
88
+ const onLayout = (e: LayoutChangeEvent) => {
89
+ const w = Math.round(e.nativeEvent.layout.width);
90
+ if (w >= MIN_MEASURABLE && w !== available) setAvailable(w);
91
+ };
92
+
93
+ return (
94
+ <View style={{ gap: 8 }}>
95
+ <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "flex-end", gap: 6 }}>
96
+ <Pressable
97
+ accessibilityRole="button"
98
+ onPress={() => setZoom(zoomOut)}
99
+ style={{
100
+ paddingHorizontal: 8,
101
+ paddingVertical: 4,
102
+ borderRadius: 6,
103
+ backgroundColor: theme.colors.surface2,
104
+ }}
105
+ >
106
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>−</Text>
107
+ </Pressable>
108
+ <Pressable
109
+ accessibilityRole="button"
110
+ onPress={() => setZoom(fitZoom)}
111
+ style={{
112
+ paddingHorizontal: 8,
113
+ paddingVertical: 4,
114
+ borderRadius: 6,
115
+ backgroundColor: theme.colors.surface2,
116
+ }}
117
+ >
118
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>
119
+ {Math.round(zoom * 100)}%
120
+ </Text>
121
+ </Pressable>
122
+ <Pressable
123
+ accessibilityRole="button"
124
+ onPress={() => setZoom(zoomIn)}
125
+ style={{
126
+ paddingHorizontal: 8,
127
+ paddingVertical: 4,
128
+ borderRadius: 6,
129
+ backgroundColor: theme.colors.surface2,
130
+ }}
131
+ >
132
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>+</Text>
133
+ </Pressable>
134
+ <Pressable
135
+ accessibilityRole="button"
136
+ onPress={() => setShowCode((s) => !s)}
137
+ style={{
138
+ paddingHorizontal: 8,
139
+ paddingVertical: 4,
140
+ borderRadius: 6,
141
+ backgroundColor: theme.colors.surface2,
142
+ }}
143
+ >
144
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>
145
+ {showCode ? "Diagram" : "Mermaid"}
146
+ </Text>
147
+ </Pressable>
148
+ <Pressable
149
+ accessibilityRole="button"
150
+ onPress={() => setPopped(true)}
151
+ style={{
152
+ paddingHorizontal: 8,
153
+ paddingVertical: 4,
154
+ borderRadius: 6,
155
+ backgroundColor: theme.colors.surface2,
156
+ }}
157
+ >
158
+ <Icon name="Maximize2" size={12} color={theme.colors.foregroundMuted} />
159
+ </Pressable>
160
+ </View>
161
+
162
+ <View onLayout={onLayout} style={{ alignSelf: "stretch", height: 0 }} />
163
+
164
+ {showCode ? (
165
+ <View
166
+ style={{
167
+ borderRadius: 8,
168
+ borderWidth: StyleSheet.hairlineWidth,
169
+ borderColor: theme.colors.border,
170
+ backgroundColor: theme.colors.surface2,
171
+ padding: 10,
172
+ }}
173
+ >
174
+ <Text selectable style={{ color: theme.colors.foreground, fontFamily: "monospace", fontSize: 12 }}>
175
+ {source}
176
+ </Text>
177
+ </View>
178
+ ) : (
179
+ <ScrollView horizontal showsHorizontalScrollIndicator={!compact}>
180
+ <View style={box}>
181
+ <View
182
+ style={{
183
+ width: natural.width,
184
+ height: natural.height,
185
+ transform: [{ scale }],
186
+ transformOrigin: "top left",
187
+ }}
188
+ >
189
+ <Drawing
190
+ parsed={parsed}
191
+ theme={theme}
192
+ details={details}
193
+ selectedKey={selectedDetail?.key ?? null}
194
+ onSelectDetail={(detail) =>
195
+ setSelectedDetail((prev) => (prev?.key === detail.key ? null : detail))
196
+ }
197
+ />
198
+ </View>
199
+ </View>
200
+ </ScrollView>
201
+ )}
202
+
203
+ {selectedDetail !== null ? (
204
+ <View
205
+ style={{
206
+ borderRadius: 8,
207
+ borderWidth: StyleSheet.hairlineWidth,
208
+ borderColor: theme.colors.accent,
209
+ backgroundColor: theme.colors.surface2,
210
+ padding: 10,
211
+ gap: 6,
212
+ }}
213
+ >
214
+ <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
215
+ <Text style={{ color: theme.colors.accent, fontSize: 12, fontWeight: "600" }}>
216
+ {selectedDetail.key}
217
+ </Text>
218
+ <Pressable onPress={() => setSelectedDetail(null)}>
219
+ <Icon name="X" size={13} color={theme.colors.foregroundMuted} />
220
+ </Pressable>
221
+ </View>
222
+ {selectedDetail.parts.map((part, idx) =>
223
+ typeof part === "string" ? (
224
+ <MarkdownText key={idx} text={part} theme={theme} compact={compact} />
225
+ ) : isNode(part) ? (
226
+ <RenderNode key={idx} node={part} />
227
+ ) : null,
228
+ )}
229
+ </View>
230
+ ) : null}
231
+
232
+ {popped ? (
233
+ <Modal
234
+ title="Diagram"
235
+ icon={<Icon name="Workflow" size={16} color={theme.colors.foregroundMuted} />}
236
+ open={popped}
237
+ onOpenChange={setPopped}
238
+ >
239
+ <Modal.Content>
240
+ <SheetScrollView horizontal>
241
+ <Drawing
242
+ parsed={parsed}
243
+ theme={theme}
244
+ details={details}
245
+ selectedKey={selectedDetail?.key ?? null}
246
+ onSelectDetail={(detail) =>
247
+ setSelectedDetail((prev) => (prev?.key === detail.key ? null : detail))
248
+ }
249
+ />
250
+ </SheetScrollView>
251
+ </Modal.Content>
252
+ </Modal>
253
+ ) : null}
254
+ </View>
255
+ );
256
+ }
257
+
258
+ function Drawing({
259
+ parsed,
260
+ theme,
261
+ details,
262
+ selectedKey,
263
+ onSelectDetail,
264
+ }: {
265
+ parsed: DrawableDiagram;
266
+ theme: PluginTheme;
267
+ details: readonly DiagramDetail[];
268
+ selectedKey: string | null;
269
+ onSelectDetail(detail: DiagramDetail): void;
270
+ }) {
271
+ if (parsed.kind === "flow") {
272
+ return (
273
+ <View style={{ width: parsed.layout.width, height: parsed.layout.height, paddingBottom: 4 }}>
274
+ {parsed.layout.edges.map((edge, index) => (
275
+ <EdgeView key={index} edge={edge} theme={theme} />
276
+ ))}
277
+ {parsed.layout.nodes.map((node) => {
278
+ const detail = detailFor(details, node.id, node.label);
279
+ return (
280
+ <NodeView
281
+ key={node.id}
282
+ node={node}
283
+ theme={theme}
284
+ hasDetail={detail !== null}
285
+ selected={detail !== null && selectedKey === detail.key}
286
+ onPress={() => {
287
+ if (detail) onSelectDetail(detail);
288
+ }}
289
+ />
290
+ );
291
+ })}
292
+ </View>
293
+ );
294
+ }
295
+ return <SequenceView diagram={parsed.diagram} size={parsed.size} theme={theme} />;
296
+ }
297
+
298
+ function radiusFor(shape: NodeShape, node: LaidOutNode): number {
299
+ if (shape === "circle") return node.width / 2;
300
+ if (shape === "stadium") return node.height / 2;
301
+ if (shape === "round") return 12;
302
+ return 6;
303
+ }
304
+
305
+ function NodeView({
306
+ node,
307
+ theme,
308
+ hasDetail,
309
+ selected,
310
+ onPress,
311
+ }: {
312
+ node: LaidOutNode;
313
+ theme: PluginTheme;
314
+ hasDetail: boolean;
315
+ selected: boolean;
316
+ onPress(): void;
317
+ }) {
318
+ const borderColor = selected
319
+ ? theme.colors.accent
320
+ : hasDetail
321
+ ? theme.colors.accent
322
+ : theme.colors.border;
323
+
324
+ const content = (
325
+ <View
326
+ style={{
327
+ position: "absolute",
328
+ left: node.x,
329
+ top: node.y,
330
+ width: node.width,
331
+ height: node.height,
332
+ borderRadius: radiusFor(node.shape, node),
333
+ borderWidth: selected || hasDetail ? 1.5 : 1,
334
+ borderColor,
335
+ backgroundColor: theme.colors.surface2,
336
+ alignItems: "center",
337
+ justifyContent: "center",
338
+ paddingHorizontal: 8,
339
+ }}
340
+ >
341
+ <Text
342
+ numberOfLines={2}
343
+ style={{
344
+ color: theme.colors.foreground,
345
+ fontSize: 12,
346
+ fontWeight: hasDetail ? "600" : "400",
347
+ textAlign: "center",
348
+ }}
349
+ >
350
+ {node.label}
351
+ </Text>
352
+ </View>
353
+ );
354
+
355
+ return hasDetail ? (
356
+ <Pressable accessibilityRole="button" onPress={onPress}>
357
+ {content}
358
+ </Pressable>
359
+ ) : (
360
+ content
361
+ );
362
+ }
363
+
364
+ function EdgeView({ edge, theme }: { edge: LaidOutEdge; theme: PluginTheme }) {
365
+ const color = theme.colors.foregroundMuted;
366
+ return (
367
+ <>
368
+ {edge.segments.map((seg, i) => (
369
+ <View
370
+ key={i}
371
+ style={{
372
+ position: "absolute",
373
+ left: seg.x,
374
+ top: seg.y,
375
+ width: seg.width,
376
+ height: seg.height,
377
+ backgroundColor: color,
378
+ }}
379
+ />
380
+ ))}
381
+ {edge.arrowAt ? (
382
+ <View
383
+ style={{
384
+ position: "absolute",
385
+ left: edge.arrowAt.x - ARROW,
386
+ top: edge.arrowAt.y - ARROW,
387
+ width: ARROW * 2,
388
+ height: ARROW * 2,
389
+ borderRadius: ARROW,
390
+ backgroundColor: color,
391
+ }}
392
+ />
393
+ ) : null}
394
+ {edge.label && edge.labelAt ? (
395
+ <View
396
+ style={{
397
+ position: "absolute",
398
+ left: edge.labelAt.x - 40,
399
+ top: edge.labelAt.y - 10,
400
+ minWidth: 80,
401
+ alignItems: "center",
402
+ }}
403
+ >
404
+ <Text
405
+ style={{
406
+ color: theme.colors.foregroundMuted,
407
+ backgroundColor: theme.colors.surface1,
408
+ fontSize: 10.5,
409
+ paddingHorizontal: 4,
410
+ }}
411
+ >
412
+ {edge.label}
413
+ </Text>
414
+ </View>
415
+ ) : null}
416
+ </>
417
+ );
418
+ }
419
+
420
+ function SequenceView({
421
+ diagram,
422
+ size,
423
+ theme,
424
+ }: {
425
+ diagram: SequenceDiagram;
426
+ size: Size;
427
+ theme: PluginTheme;
428
+ }) {
429
+ const box = layoutSequence(diagram.participants, diagram.events.length);
430
+ const centerById = new Map(box.columns.map((col) => [col.id, col.centerX]));
431
+
432
+ return (
433
+ <View style={{ width: size.width, height: size.height }}>
434
+ {box.columns.map((col) => (
435
+ <React.Fragment key={col.id}>
436
+ <View
437
+ style={{
438
+ position: "absolute",
439
+ left: col.centerX - 1,
440
+ top: box.headerHeight,
441
+ width: 1.5,
442
+ height: size.height - box.headerHeight,
443
+ backgroundColor: theme.colors.border,
444
+ }}
445
+ />
446
+ <View
447
+ style={{
448
+ position: "absolute",
449
+ left: col.centerX - col.width / 2,
450
+ top: 4,
451
+ width: col.width,
452
+ height: box.headerHeight - 8,
453
+ borderRadius: 6,
454
+ borderWidth: 1,
455
+ borderColor: theme.colors.border,
456
+ backgroundColor: theme.colors.surface2,
457
+ alignItems: "center",
458
+ justifyContent: "center",
459
+ paddingHorizontal: 6,
460
+ }}
461
+ >
462
+ <Text numberOfLines={1} style={{ color: theme.colors.foreground, fontSize: 12, fontWeight: "600" }}>
463
+ {col.label}
464
+ </Text>
465
+ </View>
466
+ </React.Fragment>
467
+ ))}
468
+ {diagram.events.map((ev: SeqEvent, idx: number) => {
469
+ const row = box.rows[idx];
470
+ const y = (row?.y ?? box.headerHeight) + 20;
471
+ if (ev.kind === "note") {
472
+ const firstId = ev.over[0] ?? "";
473
+ const cx = centerById.get(firstId) ?? 60;
474
+ return (
475
+ <View
476
+ key={idx}
477
+ style={{
478
+ position: "absolute",
479
+ left: Math.max(0, cx - 60),
480
+ top: y - 12,
481
+ width: 120,
482
+ padding: 4,
483
+ borderRadius: 4,
484
+ backgroundColor: theme.colors.surface2,
485
+ borderWidth: StyleSheet.hairlineWidth,
486
+ borderColor: theme.colors.border,
487
+ }}
488
+ >
489
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11, textAlign: "center" }}>
490
+ {ev.text}
491
+ </Text>
492
+ </View>
493
+ );
494
+ }
495
+ const x1 = centerById.get(ev.from) ?? 0;
496
+ const x2 = centerById.get(ev.to) ?? 0;
497
+ const left = Math.min(x1, x2);
498
+ const width = Math.max(20, Math.abs(x2 - x1));
499
+ return (
500
+ <View key={idx} style={{ position: "absolute", left, top: y - 14, width }}>
501
+ <Text
502
+ numberOfLines={1}
503
+ style={{ color: theme.colors.foreground, fontSize: 11, textAlign: "center", marginBottom: 2 }}
504
+ >
505
+ {ev.text}
506
+ </Text>
507
+ <View style={{ height: 1.5, backgroundColor: theme.colors.accent }} />
508
+ </View>
509
+ );
510
+ })}
511
+ </View>
512
+ );
513
+ }