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,641 @@
1
+ import { ExternalLink } from "@getpaseo/plugin/client/ui";
2
+ import React, { useEffect, useMemo, useState } from "react";
3
+ import { Pressable, StyleSheet, Text, View } from "react-native";
4
+ import { acceptanceVerdict, checkLabel, readAcceptance, verdictLine } from "../../shared/core/acceptance";
5
+ import { readCompare } from "../../shared/core/compare";
6
+ import {
7
+ changeTotals,
8
+ checkCounts,
9
+ parseChangesTarget,
10
+ parseCommitLimit,
11
+ parsePathFilter,
12
+ parseThreadIds,
13
+ scopeLabel,
14
+ threadStatusLabel,
15
+ type ChangesResult,
16
+ type CommitsResult,
17
+ type EnvironmentsResult,
18
+ type PullRequestResult,
19
+ type ThreadsResult,
20
+ } from "../../shared/core/project";
21
+ import { chainSummary, mismatchText, readProvenance } from "../../shared/core/provenance";
22
+ import { analyse, countsText as werCountsText, formatWer, readTracks } from "../../shared/core/transcript";
23
+ import { optionalText, type Props } from "../../shared/logic/values";
24
+ import { useProjectRpc } from "../loaders";
25
+ import { ErrorLine } from "../node-renderer";
26
+ import { useUiRuntime } from "../runtime";
27
+ import { LoadedImageView } from "./content";
28
+ import { Diff } from "./data";
29
+
30
+ export function Changes({ props }: { props: Props }) {
31
+ const { theme, agentId, blockKey } = useUiRuntime();
32
+ const projectRpc = useProjectRpc();
33
+ const rawTarget = optionalText(props.target);
34
+ const parsedPaths = parsePathFilter(props.paths);
35
+ const [state, setState] = useState<ChangesResult | null>(null);
36
+ const [expandedFile, setExpandedFile] = useState<string | null>(null);
37
+
38
+ useEffect(() => {
39
+ const parsedTarget = parseChangesTarget(rawTarget);
40
+ if (!parsedTarget.ok || !parsedPaths.ok) return;
41
+ let live = true;
42
+ void projectRpc
43
+ .changes({
44
+ agentId: agentId ?? "gallery",
45
+ target: rawTarget,
46
+ paths: parsedPaths.value ? [...parsedPaths.value] : null,
47
+ blockKey,
48
+ refresh: false,
49
+ })
50
+ .then((res) => {
51
+ if (live) setState(res);
52
+ });
53
+ return () => {
54
+ live = false;
55
+ };
56
+ }, [agentId, blockKey, parsedPaths, projectRpc, rawTarget]);
57
+
58
+ if (!parsedPaths.ok) return <ErrorLine message={parsedPaths.error} />;
59
+ if (state === null) {
60
+ return (
61
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
62
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>Reading changes…</Text>
63
+ </View>
64
+ );
65
+ }
66
+ if (!state.ok) return <ErrorLine message={state.message} />;
67
+
68
+ const totals = changeTotals(state.files);
69
+ const patchByPath = new Map(state.patches.map((p) => [p.path, p.patch]));
70
+
71
+ return (
72
+ <View
73
+ style={{
74
+ borderRadius: 8,
75
+ borderWidth: StyleSheet.hairlineWidth,
76
+ borderColor: theme.colors.border,
77
+ backgroundColor: theme.colors.surface2,
78
+ overflow: "hidden",
79
+ }}
80
+ >
81
+ <View
82
+ style={{
83
+ flexDirection: "row",
84
+ justifyContent: "space-between",
85
+ paddingHorizontal: 10,
86
+ paddingVertical: 8,
87
+ borderBottomWidth: StyleSheet.hairlineWidth,
88
+ borderBottomColor: theme.colors.border,
89
+ }}
90
+ >
91
+ <Text style={{ color: theme.colors.foreground, fontSize: 12.5, fontWeight: "600" }}>
92
+ {scopeLabel(state.scope)}
93
+ </Text>
94
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12, fontFamily: "monospace" }}>
95
+ {totals.files} files ·{" "}
96
+ <Text style={{ color: theme.colors.statusSuccess }}>+{totals.additions}</Text>{" "}
97
+ <Text style={{ color: theme.colors.statusDanger }}>−{totals.deletions}</Text>
98
+ </Text>
99
+ </View>
100
+ {state.files.map((file, idx) => {
101
+ const patch = patchByPath.get(file.path);
102
+ const isOpen = expandedFile === file.path;
103
+ return (
104
+ <View
105
+ key={file.path}
106
+ style={{
107
+ borderTopWidth: idx === 0 ? 0 : StyleSheet.hairlineWidth,
108
+ borderTopColor: theme.colors.border,
109
+ }}
110
+ >
111
+ <Pressable
112
+ onPress={() => setExpandedFile(isOpen ? null : file.path)}
113
+ style={{
114
+ flexDirection: "row",
115
+ justifyContent: "space-between",
116
+ paddingHorizontal: 10,
117
+ paddingVertical: 7,
118
+ }}
119
+ >
120
+ <Text style={{ color: theme.colors.foreground, fontFamily: "monospace", fontSize: 12, flex: 1 }}>
121
+ {file.path}
122
+ </Text>
123
+ <Text style={{ color: theme.colors.foregroundMuted, fontFamily: "monospace", fontSize: 12 }}>
124
+ <Text style={{ color: theme.colors.statusSuccess }}>+{file.additions}</Text>{" "}
125
+ <Text style={{ color: theme.colors.statusDanger }}>−{file.deletions}</Text>
126
+ </Text>
127
+ </Pressable>
128
+ {isOpen && patch ? (
129
+ <View style={{ paddingHorizontal: 10, paddingBottom: 8 }}>
130
+ <Diff props={{ patch }} />
131
+ </View>
132
+ ) : null}
133
+ </View>
134
+ );
135
+ })}
136
+ </View>
137
+ );
138
+ }
139
+
140
+ export function Commits({ props }: { props: Props }) {
141
+ const { theme, agentId, blockKey } = useUiRuntime();
142
+ const projectRpc = useProjectRpc();
143
+ const limitParsed = parseCommitLimit(props.limit);
144
+ const [state, setState] = useState<CommitsResult | null>(null);
145
+
146
+ useEffect(() => {
147
+ if (!limitParsed.ok) return;
148
+ let live = true;
149
+ void projectRpc
150
+ .commits({ agentId: agentId ?? "gallery", blockKey, refresh: false })
151
+ .then((res) => {
152
+ if (live) setState(res);
153
+ });
154
+ return () => {
155
+ live = false;
156
+ };
157
+ }, [agentId, blockKey, limitParsed.ok, projectRpc]);
158
+
159
+ if (!limitParsed.ok) return <ErrorLine message={limitParsed.error} />;
160
+ if (state === null) {
161
+ return (
162
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
163
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>Reading commits…</Text>
164
+ </View>
165
+ );
166
+ }
167
+ if (!state.ok) return <ErrorLine message={state.message} />;
168
+
169
+ const commits = limitParsed.value ? state.commits.slice(0, limitParsed.value) : state.commits;
170
+
171
+ return (
172
+ <View
173
+ style={{
174
+ borderRadius: 8,
175
+ borderWidth: StyleSheet.hairlineWidth,
176
+ borderColor: theme.colors.border,
177
+ backgroundColor: theme.colors.surface2,
178
+ overflow: "hidden",
179
+ }}
180
+ >
181
+ <View
182
+ style={{
183
+ flexDirection: "row",
184
+ justifyContent: "space-between",
185
+ paddingHorizontal: 10,
186
+ paddingVertical: 8,
187
+ borderBottomWidth: StyleSheet.hairlineWidth,
188
+ borderBottomColor: theme.colors.border,
189
+ }}
190
+ >
191
+ <Text style={{ color: theme.colors.foreground, fontSize: 12.5, fontWeight: "600" }}>
192
+ {state.branch ?? "HEAD"} vs {state.base}
193
+ </Text>
194
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>
195
+ {commits.length} commit{commits.length === 1 ? "" : "s"}
196
+ </Text>
197
+ </View>
198
+ {commits.map((c, idx) => (
199
+ <View
200
+ key={c.sha}
201
+ style={{
202
+ flexDirection: "row",
203
+ justifyContent: "space-between",
204
+ alignItems: "center",
205
+ gap: 8,
206
+ paddingHorizontal: 10,
207
+ paddingVertical: 7,
208
+ borderTopWidth: idx === 0 ? 0 : StyleSheet.hairlineWidth,
209
+ borderTopColor: theme.colors.border,
210
+ }}
211
+ >
212
+ <Text style={{ color: theme.colors.accent, fontFamily: "monospace", fontSize: 12 }}>
213
+ {c.shortSha}
214
+ </Text>
215
+ <Text numberOfLines={1} style={{ color: theme.colors.foreground, fontSize: 12.5, flex: 1 }}>
216
+ {c.subject}
217
+ </Text>
218
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>{c.author}</Text>
219
+ </View>
220
+ ))}
221
+ </View>
222
+ );
223
+ }
224
+
225
+ export function PullRequest() {
226
+ const { theme, agentId } = useUiRuntime();
227
+ const projectRpc = useProjectRpc();
228
+ const [state, setState] = useState<PullRequestResult | null>(null);
229
+
230
+ useEffect(() => {
231
+ let live = true;
232
+ void projectRpc.pullRequest({ agentId: agentId ?? "gallery" }).then((res) => {
233
+ if (live) setState(res);
234
+ });
235
+ return () => {
236
+ live = false;
237
+ };
238
+ }, [agentId, projectRpc]);
239
+
240
+ if (state === null) {
241
+ return (
242
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
243
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>Reading pull request…</Text>
244
+ </View>
245
+ );
246
+ }
247
+ if (!state.ok) return <ErrorLine message={state.message} />;
248
+ if (!state.pullRequest) {
249
+ return (
250
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
251
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>
252
+ No open pull request for {state.branch ?? "this branch"}
253
+ </Text>
254
+ </View>
255
+ );
256
+ }
257
+
258
+ const pr = state.pullRequest;
259
+ return (
260
+ <View
261
+ style={{
262
+ borderRadius: 8,
263
+ borderWidth: StyleSheet.hairlineWidth,
264
+ borderColor: theme.colors.border,
265
+ backgroundColor: theme.colors.surface2,
266
+ padding: 12,
267
+ gap: 6,
268
+ }}
269
+ >
270
+ <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
271
+ <ExternalLink href={pr.url}>
272
+ <Text style={{ color: theme.colors.accent, fontSize: 13.5, fontWeight: "600" }}>
273
+ #{pr.number} {pr.title}
274
+ </Text>
275
+ </ExternalLink>
276
+ <Text style={{ color: theme.colors.statusSuccess, fontSize: 11.5, fontWeight: "600" }}>
277
+ {pr.state}
278
+ </Text>
279
+ </View>
280
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>
281
+ {pr.headRef} → {pr.baseRef} · Checks: {checkCounts(pr.checks)} · Review: {pr.review.state}
282
+ </Text>
283
+ </View>
284
+ );
285
+ }
286
+
287
+ export function Threads({ props }: { props: Props }) {
288
+ const { theme, agentId } = useUiRuntime();
289
+ const projectRpc = useProjectRpc();
290
+ const parsedIds = parseThreadIds(props.ids);
291
+ const [state, setState] = useState<ThreadsResult | null>(null);
292
+
293
+ useEffect(() => {
294
+ if (!parsedIds.ok) return;
295
+ let live = true;
296
+ void projectRpc
297
+ .threads({
298
+ agentId: agentId ?? "gallery",
299
+ ids: parsedIds.value ? [...parsedIds.value] : null,
300
+ })
301
+ .then((res) => {
302
+ if (live) setState(res);
303
+ });
304
+ return () => {
305
+ live = false;
306
+ };
307
+ }, [agentId, parsedIds, projectRpc]);
308
+
309
+ if (!parsedIds.ok) return <ErrorLine message={parsedIds.error} />;
310
+ if (state === null) {
311
+ return (
312
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
313
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>Reading threads…</Text>
314
+ </View>
315
+ );
316
+ }
317
+ if (!state.ok) return <ErrorLine message={state.message} />;
318
+
319
+ return (
320
+ <View style={{ gap: 6 }}>
321
+ {state.threads.map((t) =>
322
+ t.found ? (
323
+ <View
324
+ key={t.id}
325
+ style={{
326
+ borderRadius: 8,
327
+ borderWidth: StyleSheet.hairlineWidth,
328
+ borderColor: theme.colors.border,
329
+ backgroundColor: theme.colors.surface2,
330
+ padding: 10,
331
+ gap: 4,
332
+ }}
333
+ >
334
+ <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
335
+ <Text style={{ color: theme.colors.foreground, fontSize: 13, fontWeight: "600", flex: 1 }}>
336
+ {t.title}
337
+ </Text>
338
+ <Text style={{ color: theme.colors.accent, fontSize: 11.5 }}>
339
+ {threadStatusLabel(t)}
340
+ </Text>
341
+ </View>
342
+ {t.lastMessage ? (
343
+ <Text numberOfLines={2} style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>
344
+ {t.lastMessage.text}
345
+ </Text>
346
+ ) : null}
347
+ </View>
348
+ ) : (
349
+ <ErrorLine key={t.id} message={`Thread ${t.id} not found`} />
350
+ ),
351
+ )}
352
+ </View>
353
+ );
354
+ }
355
+
356
+ export function Environments() {
357
+ const { theme, agentId } = useUiRuntime();
358
+ const projectRpc = useProjectRpc();
359
+ const [state, setState] = useState<EnvironmentsResult | null>(null);
360
+
361
+ useEffect(() => {
362
+ let live = true;
363
+ void projectRpc.environments({ agentId: agentId ?? "gallery" }).then((res) => {
364
+ if (live) setState(res);
365
+ });
366
+ return () => {
367
+ live = false;
368
+ };
369
+ }, [agentId, projectRpc]);
370
+
371
+ if (state === null) {
372
+ return (
373
+ <View style={{ padding: 10, borderRadius: 8, backgroundColor: theme.colors.surface2 }}>
374
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>Reading environments…</Text>
375
+ </View>
376
+ );
377
+ }
378
+ if (!state.ok) return <ErrorLine message={state.message} />;
379
+
380
+ return (
381
+ <View style={{ gap: 6 }}>
382
+ {state.environments.map((env) => (
383
+ <View
384
+ key={env.id}
385
+ style={{
386
+ borderRadius: 8,
387
+ borderWidth: StyleSheet.hairlineWidth,
388
+ borderColor: env.current ? theme.colors.accent : theme.colors.border,
389
+ backgroundColor: theme.colors.surface2,
390
+ padding: 10,
391
+ gap: 4,
392
+ }}
393
+ >
394
+ <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
395
+ <Text style={{ color: theme.colors.foreground, fontSize: 13, fontWeight: "600" }}>
396
+ {env.name ?? env.id} {env.branch ? `(${env.branch})` : ""}
397
+ </Text>
398
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>
399
+ {env.kind} · {env.threadCounts.live} active
400
+ </Text>
401
+ </View>
402
+ {env.path ? (
403
+ <Text style={{ color: theme.colors.foregroundMuted, fontFamily: "monospace", fontSize: 11.5 }}>
404
+ {env.path}
405
+ </Text>
406
+ ) : null}
407
+ </View>
408
+ ))}
409
+ </View>
410
+ );
411
+ }
412
+
413
+ export function Acceptance({ props }: { props: Props }) {
414
+ const { theme } = useUiRuntime();
415
+ const parsed = useMemo(
416
+ () => readAcceptance(props.criteria, props.columns),
417
+ [props.criteria, props.columns],
418
+ );
419
+ if (!parsed.ok) return <ErrorLine message={parsed.error} />;
420
+ const { report } = parsed;
421
+ const verdict = acceptanceVerdict(report);
422
+
423
+ return (
424
+ <View style={{ gap: 8 }}>
425
+ <View
426
+ style={{
427
+ padding: 10,
428
+ borderRadius: 8,
429
+ backgroundColor: theme.colors.surface2,
430
+ borderWidth: StyleSheet.hairlineWidth,
431
+ borderColor: theme.colors.border,
432
+ }}
433
+ >
434
+ <Text style={{ color: theme.colors.foreground, fontSize: 13, fontWeight: "600" }}>
435
+ {verdictLine(verdict)}
436
+ </Text>
437
+ </View>
438
+ <View
439
+ style={{
440
+ borderRadius: 8,
441
+ borderWidth: StyleSheet.hairlineWidth,
442
+ borderColor: theme.colors.border,
443
+ overflow: "hidden",
444
+ }}
445
+ >
446
+ {report.criteria.map((crit, idx) => (
447
+ <View
448
+ key={idx}
449
+ style={{
450
+ paddingHorizontal: 10,
451
+ paddingVertical: 8,
452
+ borderTopWidth: idx === 0 ? 0 : StyleSheet.hairlineWidth,
453
+ borderTopColor: theme.colors.border,
454
+ gap: 4,
455
+ }}
456
+ >
457
+ <Text style={{ color: theme.colors.foreground, fontSize: 12.5, fontWeight: "600" }}>
458
+ {crit.name}
459
+ </Text>
460
+ <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
461
+ {(crit.cells ?? [crit.own]).map((chk, cIdx) => {
462
+ const color =
463
+ chk.status === "passed"
464
+ ? theme.colors.statusSuccess
465
+ : chk.status === "failed"
466
+ ? theme.colors.statusDanger
467
+ : chk.status === "claimed" || chk.status === "blocked"
468
+ ? theme.colors.statusWarning
469
+ : theme.colors.foregroundMuted;
470
+ return (
471
+ <Text key={cIdx} style={{ color, fontSize: 11.5 }}>
472
+ {report.columns[cIdx] ? `${report.columns[cIdx]}: ` : ""}
473
+ {checkLabel(chk)}
474
+ </Text>
475
+ );
476
+ })}
477
+ </View>
478
+ </View>
479
+ ))}
480
+ </View>
481
+ </View>
482
+ );
483
+ }
484
+
485
+ export function Compare({ props }: { props: Props }) {
486
+ const { theme, compact } = useUiRuntime();
487
+ const parsed = useMemo(() => readCompare(props.images), [props.images]);
488
+ if (!parsed.ok) return <ErrorLine message={parsed.error} />;
489
+ const { images } = parsed;
490
+
491
+ return (
492
+ <View style={{ flexDirection: compact ? "column" : "row", gap: 10 }}>
493
+ {images.map((img, idx) => (
494
+ <View
495
+ key={idx}
496
+ style={{
497
+ flex: 1,
498
+ borderRadius: 8,
499
+ borderWidth: StyleSheet.hairlineWidth,
500
+ borderColor: theme.colors.border,
501
+ backgroundColor: theme.colors.surface2,
502
+ padding: 8,
503
+ gap: 4,
504
+ }}
505
+ >
506
+ <Text style={{ color: theme.colors.foreground, fontSize: 12, fontWeight: "600" }}>
507
+ {img.label}
508
+ </Text>
509
+ <LoadedImageView src={img.src} alt={img.label} height={180} />
510
+ </View>
511
+ ))}
512
+ </View>
513
+ );
514
+ }
515
+
516
+ export function Transcript({ props }: { props: Props }) {
517
+ const { theme, loadAudio, playAudio } = useUiRuntime();
518
+ const parsed = useMemo(() => readTracks(props.tracks), [props.tracks]);
519
+ const audioPath = optionalText(props.audio);
520
+ if (!parsed.ok) return <ErrorLine message={parsed.error} />;
521
+ const analysis = useMemo(() => analyse(parsed.tracks), [parsed.tracks]);
522
+
523
+ return (
524
+ <View style={{ gap: 8 }}>
525
+ {audioPath && loadAudio && playAudio ? (
526
+ <Pressable
527
+ onPress={() => {
528
+ void loadAudio(audioPath).then((res) => {
529
+ if (res.ok) void playAudio(res.url);
530
+ });
531
+ }}
532
+ style={{
533
+ alignSelf: "flex-start",
534
+ paddingHorizontal: 10,
535
+ paddingVertical: 5,
536
+ borderRadius: 6,
537
+ backgroundColor: theme.colors.accent,
538
+ }}
539
+ >
540
+ <Text style={{ color: theme.colors.accentForeground, fontSize: 12, fontWeight: "600" }}>
541
+ ▶ Play recording
542
+ </Text>
543
+ </Pressable>
544
+ ) : null}
545
+ {parsed.tracks.map((track, idx) => {
546
+ const comp = idx > 0 && analysis ? analysis.comparisons[idx - 1] : null;
547
+ return (
548
+ <View
549
+ key={idx}
550
+ style={{
551
+ borderRadius: 8,
552
+ borderWidth: StyleSheet.hairlineWidth,
553
+ borderColor: theme.colors.border,
554
+ backgroundColor: theme.colors.surface2,
555
+ padding: 10,
556
+ gap: 4,
557
+ }}
558
+ >
559
+ <View style={{ flexDirection: "row", justifyContent: "space-between" }}>
560
+ <Text style={{ color: theme.colors.foreground, fontSize: 12.5, fontWeight: "600" }}>
561
+ {track.name}
562
+ </Text>
563
+ {comp ? (
564
+ <Text style={{ color: theme.colors.accent, fontSize: 11.5 }}>
565
+ {comp.wer !== null ? `WER ${formatWer(comp.wer)} · ` : ""}
566
+ {werCountsText(comp.counts)}
567
+ </Text>
568
+ ) : (
569
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>Reference</Text>
570
+ )}
571
+ </View>
572
+ <Text selectable style={{ color: theme.colors.foreground, fontSize: 12.5, lineHeight: 19 }}>
573
+ {track.words.map((w) => w.text).join(" ")}
574
+ </Text>
575
+ </View>
576
+ );
577
+ })}
578
+ </View>
579
+ );
580
+ }
581
+
582
+ export function Provenance({ props }: { props: Props }) {
583
+ const { theme } = useUiRuntime();
584
+ const parsed = useMemo(
585
+ () => readProvenance(props.stages, props.expected),
586
+ [props.stages, props.expected],
587
+ );
588
+ if (!parsed.ok) return <ErrorLine message={parsed.error} />;
589
+ const { provenance } = parsed;
590
+
591
+ return (
592
+ <View style={{ gap: 8 }}>
593
+ <Text style={{ color: theme.colors.foreground, fontSize: 12.5, fontWeight: "600" }}>
594
+ {chainSummary(provenance)}
595
+ </Text>
596
+ {provenance.mismatches.map((m, idx) => (
597
+ <View
598
+ key={idx}
599
+ style={{
600
+ padding: 8,
601
+ borderRadius: 6,
602
+ backgroundColor: theme.colors.surface2,
603
+ borderWidth: StyleSheet.hairlineWidth,
604
+ borderColor: theme.colors.statusDanger,
605
+ }}
606
+ >
607
+ <Text style={{ color: theme.colors.statusDanger, fontSize: 12 }}>
608
+ {mismatchText(provenance.stages, m)}
609
+ </Text>
610
+ </View>
611
+ ))}
612
+ <View style={{ gap: 6 }}>
613
+ {provenance.stages.map((stage, idx) => (
614
+ <View
615
+ key={idx}
616
+ style={{
617
+ padding: 10,
618
+ borderRadius: 8,
619
+ borderWidth: StyleSheet.hairlineWidth,
620
+ borderColor: theme.colors.border,
621
+ backgroundColor: theme.colors.surface2,
622
+ gap: 4,
623
+ }}
624
+ >
625
+ <View style={{ flexDirection: "row", justifyContent: "space-between" }}>
626
+ <Text style={{ color: theme.colors.foreground, fontSize: 13, fontWeight: "600" }}>
627
+ {idx + 1}. {stage.name}
628
+ </Text>
629
+ <Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5 }}>{stage.status}</Text>
630
+ </View>
631
+ {stage.facts.length > 0 ? (
632
+ <Text selectable style={{ color: theme.colors.foregroundMuted, fontSize: 11.5, fontFamily: "monospace" }}>
633
+ {stage.facts.map((f) => `${f.key}: ${f.value}`).join(" · ")}
634
+ </Text>
635
+ ) : null}
636
+ </View>
637
+ ))}
638
+ </View>
639
+ </View>
640
+ );
641
+ }