@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21

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 (321) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +7 -2
  3. package/board/client/board-look.ts +31 -1
  4. package/board/client/board-panel-parts.tsx +150 -0
  5. package/board/client/board-panel-stages.ts +99 -0
  6. package/board/client/board-panel-styles.ts +223 -0
  7. package/board/client/board-panel.tsx +77 -1275
  8. package/board/client/engine-life.ts +48 -0
  9. package/board/client/final-review-stack.tsx +144 -0
  10. package/board/client/heartbeat.tsx +61 -0
  11. package/board/client/helper-lines.tsx +69 -0
  12. package/board/client/live-feed.tsx +86 -0
  13. package/board/client/live-line.ts +73 -0
  14. package/board/client/model-line.tsx +178 -0
  15. package/board/client/needs-you-section.tsx +133 -0
  16. package/board/client/one-line.tsx +193 -0
  17. package/board/client/repo-wait-banner.tsx +69 -0
  18. package/board/client/run-footer.tsx +68 -0
  19. package/board/client/run-header.tsx +189 -0
  20. package/board/client/run-picker.tsx +98 -0
  21. package/board/client/status-bar.tsx +131 -0
  22. package/board/client/step-dots.tsx +127 -0
  23. package/board/client/stuck-actions.tsx +269 -0
  24. package/board/client/ticket-card-view.tsx +238 -0
  25. package/board/client/ticket-detail.tsx +87 -0
  26. package/board/client/ticket-stack.tsx +119 -0
  27. package/board/client/ticket-wait.ts +188 -0
  28. package/board/client/timeline-rows.tsx +41 -14
  29. package/board/client/usage-line.tsx +51 -0
  30. package/board/client/wait-line.tsx +39 -0
  31. package/board/index.client.tsx +1 -1
  32. package/board/index.server.ts +7 -1
  33. package/board/package.json +1 -1
  34. package/board/server/board-panel-state-helpers.ts +67 -0
  35. package/board/server/board-server-check.ts +294 -0
  36. package/board/server/board-server-engine.ts +129 -0
  37. package/board/server/board-server-events.ts +134 -0
  38. package/board/server/board-server-read.ts +190 -0
  39. package/board/server/board-server-resume.ts +164 -0
  40. package/board/server/board-server-start.ts +174 -0
  41. package/board/server/board-server-store.ts +281 -0
  42. package/board/server/board-server-types.ts +114 -0
  43. package/board/server/board-server-util.ts +4 -0
  44. package/board/server/board-server.ts +52 -943
  45. package/board/server/board-vocabulary-engine.ts +144 -0
  46. package/board/server/board-vocabulary-review.ts +115 -0
  47. package/board/server/board-vocabulary-run.ts +145 -0
  48. package/board/server/board-vocabulary-shapes.ts +153 -0
  49. package/board/server/board-vocabulary.ts +20 -375
  50. package/board/server/current-step-panel-helpers.ts +108 -0
  51. package/board/server/describe-record.ts +403 -0
  52. package/board/server/engine-launch.ts +20 -3
  53. package/board/server/engine-restart-helpers.ts +105 -0
  54. package/board/server/engine-watch.ts +45 -5
  55. package/board/server/journal-rebuild-helpers.ts +169 -0
  56. package/board/server/live-file.ts +130 -0
  57. package/board/server/luca-version.ts +1 -1
  58. package/board/server/make-rows.ts +14 -629
  59. package/board/server/record-texts.ts +350 -0
  60. package/board/server/reduce-board-agent-start.ts +224 -0
  61. package/board/server/reduce-board-agents.ts +160 -0
  62. package/board/server/reduce-board-counters.ts +138 -0
  63. package/board/server/reduce-board-create.ts +148 -0
  64. package/board/server/reduce-board-final-review.ts +174 -0
  65. package/board/server/reduce-board-needs-you.ts +283 -0
  66. package/board/server/reduce-board-reasons.ts +59 -0
  67. package/board/server/reduce-board-run.ts +255 -0
  68. package/board/server/reduce-board-settle.ts +88 -0
  69. package/board/server/reduce-board-steps.ts +226 -0
  70. package/board/server/reduce-board-text.ts +139 -0
  71. package/board/server/reduce-board-ticket-checks.ts +168 -0
  72. package/board/server/reduce-board-ticket-join.ts +144 -0
  73. package/board/server/reduce-board-ticket-start.ts +132 -0
  74. package/board/server/reduce-board-update.ts +117 -0
  75. package/board/server/reduce-board-usage.ts +247 -0
  76. package/board/server/reduce-board.ts +100 -1917
  77. package/board/server/reply-post.ts +407 -0
  78. package/board/server/run-registry.ts +8 -4
  79. package/board/server/ticket-stages-panel-helpers.ts +296 -0
  80. package/board/shared/board-rows.ts +9 -0
  81. package/board/shared/board-rpc.ts +42 -0
  82. package/board/shared/board-state-counts.ts +77 -0
  83. package/board/shared/board-state-live.ts +99 -0
  84. package/board/shared/board-state-review.ts +59 -0
  85. package/board/shared/board-state-run.ts +236 -0
  86. package/board/shared/board-state-ticket.ts +257 -0
  87. package/board/shared/board-state-usage.ts +107 -0
  88. package/board/shared/board-state.ts +97 -567
  89. package/board/shared/reply-actions.ts +207 -0
  90. package/engine/agents/agent-launcher.ts +27 -4
  91. package/engine/agents/claude-launcher-helpers.ts +233 -0
  92. package/engine/agents/claude-launcher.ts +28 -669
  93. package/engine/agents/claude-messages.ts +212 -0
  94. package/engine/agents/claude-options.ts +43 -8
  95. package/engine/agents/common-instructions.ts +138 -0
  96. package/engine/agents/input-channel.ts +36 -0
  97. package/engine/agents/launch-paths.ts +29 -0
  98. package/engine/agents/launch-session.ts +227 -0
  99. package/engine/agents/learner-instructions.ts +31 -0
  100. package/engine/agents/open-session.ts +210 -0
  101. package/engine/agents/prompt-sections.ts +95 -0
  102. package/engine/agents/rejoin-prompts.ts +135 -0
  103. package/engine/agents/reviewer-instructions.ts +99 -0
  104. package/engine/agents/role-instructions.ts +31 -244
  105. package/engine/agents/role-prompts.ts +39 -109
  106. package/engine/agents/role-results.ts +106 -7
  107. package/engine/agents/scripted-launcher.ts +21 -6
  108. package/engine/agents/session-pool.ts +124 -0
  109. package/engine/agents/turn-progress.ts +283 -0
  110. package/engine/agents/writer-instructions.ts +151 -0
  111. package/engine/cli/computer-adapters-real.ts +15 -3
  112. package/engine/cli/computer-adapters.ts +2 -0
  113. package/engine/cli/computer-checks.ts +87 -4
  114. package/engine/cli/config-reload-text.ts +39 -0
  115. package/engine/cli/decision-model-setup.ts +70 -0
  116. package/engine/cli/doctor-command.ts +1 -0
  117. package/engine/cli/doctor.ts +21 -3
  118. package/engine/cli/going-runs.ts +192 -36
  119. package/engine/cli/init-command.ts +1 -0
  120. package/engine/cli/init.ts +23 -1
  121. package/engine/cli/live-runs.ts +73 -0
  122. package/engine/cli/luca-run.ts +42 -7
  123. package/engine/cli/luca-skills.ts +183 -0
  124. package/engine/cli/luca.ts +8 -1
  125. package/engine/cli/models-command.ts +119 -0
  126. package/engine/cli/read-run.ts +81 -0
  127. package/engine/cli/run-args.ts +33 -6
  128. package/engine/cli/run-demo.ts +143 -0
  129. package/engine/cli/run-end.ts +184 -0
  130. package/engine/cli/run-modes-helpers.ts +81 -0
  131. package/engine/cli/run-modes.ts +62 -439
  132. package/engine/cli/setup.ts +5 -48
  133. package/engine/cli/unfinished-runs.ts +122 -0
  134. package/engine/cli/upgrade-command.ts +212 -30
  135. package/engine/cli/upgrade-finish.ts +149 -0
  136. package/engine/cli/upgrade.ts +112 -71
  137. package/engine/config/config-fields.ts +127 -0
  138. package/engine/config/config-reload.ts +250 -0
  139. package/engine/config/engine-config-schema.ts +235 -0
  140. package/engine/config/engine-config.ts +72 -142
  141. package/engine/config/test-commands.ts +102 -0
  142. package/engine/core/agent-guards-helpers.ts +84 -0
  143. package/engine/core/agent-turn.ts +395 -0
  144. package/engine/core/already-done.ts +44 -1
  145. package/engine/core/build-action.ts +214 -0
  146. package/engine/core/build-context.ts +135 -0
  147. package/engine/core/build-launch.ts +259 -0
  148. package/engine/core/commit-in.ts +282 -0
  149. package/engine/core/decide-build-early.ts +108 -0
  150. package/engine/core/decide-build-ending.ts +145 -0
  151. package/engine/core/decide-build-helpers.ts +87 -0
  152. package/engine/core/decide-build-tickets.ts +207 -0
  153. package/engine/core/decide-build-view.ts +103 -0
  154. package/engine/core/decide-build.ts +101 -1368
  155. package/engine/core/decide-final-fix.ts +203 -0
  156. package/engine/core/decide-final-review.ts +55 -310
  157. package/engine/core/decide-red-green.ts +398 -0
  158. package/engine/core/decide-rejoin.ts +259 -0
  159. package/engine/core/decide-repo-wait.ts +129 -0
  160. package/engine/core/decide-run-budget.ts +86 -12
  161. package/engine/core/decide-stuck-helpers.ts +127 -0
  162. package/engine/core/decide-stuck.ts +60 -8
  163. package/engine/core/decide-ticket-review.ts +95 -0
  164. package/engine/core/decide-ticket.ts +267 -0
  165. package/engine/core/decide-waits.ts +47 -0
  166. package/engine/core/decide.ts +74 -23
  167. package/engine/core/engine-chores.ts +163 -0
  168. package/engine/core/environment-rerun.ts +83 -0
  169. package/engine/core/evidence-text.ts +84 -0
  170. package/engine/core/execute-action.ts +303 -0
  171. package/engine/core/execute-already-done-check.ts +133 -0
  172. package/engine/core/execute-build.ts +68 -944
  173. package/engine/core/execute-changeset.ts +5 -7
  174. package/engine/core/execute-final-review.ts +125 -13
  175. package/engine/core/execute-gates.ts +182 -0
  176. package/engine/core/execute-intake.ts +130 -0
  177. package/engine/core/execute-join.ts +276 -0
  178. package/engine/core/execute-memory.ts +12 -0
  179. package/engine/core/execute-pull-request.ts +84 -0
  180. package/engine/core/execute-red-check.ts +219 -0
  181. package/engine/core/execute-repo-wait.ts +129 -0
  182. package/engine/core/execute-stuck.ts +25 -10
  183. package/engine/core/execute-waits.ts +158 -0
  184. package/engine/core/execute-with-jev.ts +118 -0
  185. package/engine/core/execute.ts +124 -831
  186. package/engine/core/final-failure-cap.ts +38 -0
  187. package/engine/core/final-review-action.ts +125 -0
  188. package/engine/core/final-review-text.ts +63 -17
  189. package/engine/core/final-stuck-text.ts +86 -0
  190. package/engine/core/fix-loop-text.ts +49 -2
  191. package/engine/core/left-out.ts +120 -0
  192. package/engine/core/live-file.ts +372 -0
  193. package/engine/core/loop-caps.ts +25 -2
  194. package/engine/core/not-in-run.ts +7 -3
  195. package/engine/core/pull-request-text.ts +255 -6
  196. package/engine/core/repo-wait-text.ts +81 -0
  197. package/engine/core/review-text.ts +55 -1
  198. package/engine/core/run-crash-recovery-helpers.ts +16 -0
  199. package/engine/core/run-session-close-helpers.ts +96 -0
  200. package/engine/core/run-start.ts +148 -0
  201. package/engine/core/run-stuck-text.ts +86 -0
  202. package/engine/core/schedule-rules.ts +193 -0
  203. package/engine/core/stuck-detail-text.ts +89 -0
  204. package/engine/core/stuck-format.ts +22 -0
  205. package/engine/core/stuck-text.ts +29 -332
  206. package/engine/core/ticket-stuck-text.ts +192 -0
  207. package/engine/core/worktree-changes.ts +106 -0
  208. package/engine/gates/already-done-check.ts +120 -0
  209. package/engine/gates/feature-map.ts +96 -0
  210. package/engine/gates/gate-runner.ts +108 -108
  211. package/engine/gates/gate-schemas.ts +61 -2
  212. package/engine/gates/leftover-scan.ts +154 -31
  213. package/engine/gates/new-tests.ts +45 -0
  214. package/engine/gates/own-files-check.ts +98 -0
  215. package/engine/gates/prepare-check.ts +60 -0
  216. package/engine/gates/prepare-slots.ts +99 -0
  217. package/engine/gates/red-check.ts +184 -16
  218. package/engine/gates/shell-check.ts +64 -0
  219. package/engine/gates/slot-waits.ts +55 -0
  220. package/engine/gates/slow-test-runner.ts +59 -0
  221. package/engine/gates/test-checks.ts +161 -0
  222. package/engine/gates/test-runner.ts +164 -5
  223. package/engine/gates/test-slots.ts +48 -0
  224. package/engine/git/git-adapter.ts +55 -396
  225. package/engine/git/git-commits.ts +132 -0
  226. package/engine/git/git-join.ts +139 -0
  227. package/engine/git/git-read.ts +123 -0
  228. package/engine/git/git-run.ts +32 -0
  229. package/engine/git/git-worktrees.ts +143 -0
  230. package/engine/git/outside-links.ts +55 -0
  231. package/engine/guards/after-turn-check.ts +31 -5
  232. package/engine/guards/guard-hook.ts +60 -12
  233. package/engine/guards/guard-roles.ts +68 -0
  234. package/engine/guards/helper-rules.ts +92 -0
  235. package/engine/guards/permission-rules.ts +161 -0
  236. package/engine/guards/role-rules-helpers.ts +48 -0
  237. package/engine/guards/role-rules.ts +34 -606
  238. package/engine/guards/shell-rules.ts +344 -0
  239. package/engine/guards/worktree-git.ts +208 -0
  240. package/engine/guards/worktree-snapshot.ts +412 -0
  241. package/engine/guards/worktree-state.ts +28 -825
  242. package/engine/guards/worktree-undo.ts +260 -0
  243. package/engine/guards/write-rules.ts +149 -0
  244. package/engine/index.ts +30 -9
  245. package/engine/intake/criteria-checks.ts +114 -0
  246. package/engine/intake/intake-checks.ts +164 -30
  247. package/engine/intake/intake-schemas.ts +37 -2
  248. package/engine/jev/clef-client.ts +259 -0
  249. package/engine/jev/decision-model-credentials.ts +212 -0
  250. package/engine/jev/jev-jobs.ts +144 -60
  251. package/engine/jev/jev-schemas.ts +86 -5
  252. package/engine/jev/jev-shadow-helpers.ts +218 -0
  253. package/engine/jev/jev-shadow.ts +109 -64
  254. package/engine/jev/model-pick.ts +275 -0
  255. package/engine/journal/agent-entries.ts +167 -0
  256. package/engine/journal/agent-message-entries.ts +72 -0
  257. package/engine/journal/agent-turn-replay.ts +203 -0
  258. package/engine/journal/already-done-entries.ts +55 -0
  259. package/engine/journal/commit-entries.ts +56 -0
  260. package/engine/journal/cross-ticket-replay.ts +222 -0
  261. package/engine/journal/decision-model-entries.ts +86 -0
  262. package/engine/journal/entry-fields.ts +10 -0
  263. package/engine/journal/final-review-entries.ts +128 -0
  264. package/engine/journal/final-review-replay.ts +324 -0
  265. package/engine/journal/final-review-round.ts +320 -0
  266. package/engine/journal/final-review-state.ts +180 -0
  267. package/engine/journal/gate-entries.ts +81 -0
  268. package/engine/journal/intake-entries.ts +87 -0
  269. package/engine/journal/join-entries.ts +126 -0
  270. package/engine/journal/journal-entry-schema.ts +218 -0
  271. package/engine/journal/journal-kinds.ts +97 -0
  272. package/engine/journal/journal-record-schema.ts +218 -0
  273. package/engine/journal/journal-record.ts +43 -1400
  274. package/engine/journal/journal.ts +6 -1
  275. package/engine/journal/limit-entries.ts +90 -0
  276. package/engine/journal/memory-entries.ts +124 -0
  277. package/engine/journal/memory-replay.ts +157 -0
  278. package/engine/journal/plan-replay.ts +139 -0
  279. package/engine/journal/pull-request-entries.ts +41 -0
  280. package/engine/journal/replay.ts +157 -2144
  281. package/engine/journal/reply-replay.ts +144 -0
  282. package/engine/journal/repo-wait-entries.ts +53 -0
  283. package/engine/journal/run-entries.ts +172 -0
  284. package/engine/journal/run-state.ts +264 -0
  285. package/engine/journal/shared-failure-replay.ts +133 -0
  286. package/engine/journal/stuck-entries.ts +193 -0
  287. package/engine/journal/ticket-progress.ts +392 -0
  288. package/engine/journal/ticket-replay.ts +411 -0
  289. package/engine/journal/ticket-result-replay.ts +364 -0
  290. package/engine/journal/ticket-retry-replay.ts +142 -0
  291. package/engine/journal/worktree-entries.ts +122 -0
  292. package/engine/models/model-score.ts +512 -0
  293. package/engine/shell/run-command.ts +187 -18
  294. package/engine/testing/agent-turn-fixtures.ts +287 -0
  295. package/engine/testing/already-done-fixtures.ts +97 -0
  296. package/engine/testing/build-fixtures.ts +68 -973
  297. package/engine/testing/checkouts-fixtures.ts +48 -0
  298. package/engine/testing/final-review-fixtures.ts +62 -1
  299. package/engine/testing/final-review-steps.ts +143 -0
  300. package/engine/testing/limit-fixtures.ts +85 -0
  301. package/engine/testing/live-file-fixtures.ts +57 -0
  302. package/engine/testing/practice-repo.ts +10 -4
  303. package/engine/testing/practice-run.ts +1 -1
  304. package/engine/testing/reply-fixtures.ts +88 -0
  305. package/engine/testing/run-budget-fixtures.ts +165 -0
  306. package/engine/testing/run-fixtures.ts +219 -0
  307. package/engine/testing/sdk-play.ts +177 -0
  308. package/engine/testing/setup-repo.ts +194 -0
  309. package/engine/testing/step-record-fixtures.ts +55 -0
  310. package/engine/testing/ticket-step-fixtures.ts +272 -0
  311. package/engine/testing/wait-scenarios.ts +186 -0
  312. package/engine/tracker/gh-retry.ts +217 -0
  313. package/engine/tracker/github-tracker.ts +310 -90
  314. package/engine/tracker/in-memory-tracker.ts +8 -4
  315. package/engine/tracker/tracker.ts +131 -6
  316. package/package.json +2 -1
  317. package/skills/luca-retro/SKILL.md +74 -0
  318. package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
  319. package/skills/luca-unstick/SKILL.md +153 -0
  320. package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
  321. package/engine/jev/jev-client.ts +0 -173
@@ -1,1040 +1,50 @@
1
- import { useMemo, useState, type ReactNode } from 'react'
1
+ import { useMemo, useState } from 'react'
2
2
 
3
- import type { PluginTheme } from '@getpaseo/plugin'
4
3
  import {
5
4
  useRpc,
6
5
  useWorkspace,
7
6
  type PluginWorkspacePanelProps,
8
7
  } from '@getpaseo/plugin/client'
9
- import {
10
- Icon,
11
- ScrollView,
12
- copyText,
13
- useToast,
14
- } from '@getpaseo/plugin/client/react-native'
15
- import { ExternalLink } from '@getpaseo/plugin/client/ui'
8
+ import { ScrollView } from '@getpaseo/plugin/client/react-native'
16
9
  import { useQuery } from '@tanstack/react-query'
17
- import { Pressable, StyleSheet, Text, View } from 'react-native'
18
-
19
- import {
20
- DIMMED,
21
- MONO,
22
- lensColor,
23
- limitWaitText,
24
- runTitle,
25
- stageColor,
26
- statusColor,
27
- statusText,
28
- tokensText,
29
- percentText,
30
- usageColor,
31
- usageLineWaitText,
32
- useNow,
33
- } from './board-look'
10
+ import { Text, View } from 'react-native'
11
+
12
+ import { FOR_A_PERSON, TICKET_STAGES, type Key } from './board-panel-stages'
13
+ import { makeStyles } from './board-panel-styles'
14
+ import { FinalReviewStack } from './final-review-stack'
15
+ import { LiveFeed } from './live-feed'
16
+ import { NeedsYouSection } from './needs-you-section'
17
+ import { BubbleProvider, SharedBubbles } from './one-line'
18
+ import { RunFooter } from './run-footer'
19
+ import { RunHeader } from './run-header'
20
+ import { StatusBar } from './status-bar'
21
+ import { TicketStack } from './ticket-stack'
34
22
 
35
23
  import { boardReadRpc } from '../shared/board-rpc'
36
24
  import {
37
- ALL_STEPS_DONE,
38
25
  EMPTY_BOARD_TEXT,
39
- LOOP_CAP,
40
26
  POLL_MS,
41
- REFACTOR_SKIPS_STEPS,
42
- STEP_NAMES,
43
- currentStepText,
44
- foldRuns,
45
- planUsedText,
46
27
  showsFinalReview,
47
- stoppedText,
48
- type BoardState,
49
- type CurrentStep,
50
- type LensCard,
51
- type LensState,
52
- type NeedsYou,
53
- type RunSummary,
54
- type TicketCard,
55
- type TicketStage,
56
28
  } from '../shared/board-state'
57
29
 
58
30
  /**
59
31
  * The side panel: one run of this workspace, top to bottom. Plan usage, a
60
32
  * limit-wait banner, "Needs you" pinned on top, the tickets as a stack of
61
- * stages (Blocked → Building → Reviewing → Done → Skipped) with step dots,
62
- * then the final review's 5 lenses as a second stack. It polls `board.read`.
33
+ * stages (Blocked → Building → Reviewing → Testing → Fixing → Joining → Done
34
+ * → Skipped) with step dots, then the final review's 5 lenses as a second
35
+ * stack, then the live feed, folded, and the status bar pinned below them
36
+ * all. It polls `board.read`.
37
+ *
38
+ * The sections are in their own modules: `run-header.tsx` (the top, with
39
+ * `run-picker.tsx` and `usage-line.tsx`), `needs-you-section.tsx`,
40
+ * `ticket-stack.tsx` (with `ticket-card-view.tsx`, `ticket-detail.tsx` and
41
+ * `step-dots.tsx`), `final-review-stack.tsx`, `live-feed.tsx`,
42
+ * `run-footer.tsx` and `status-bar.tsx`. They share
43
+ * `board-panel-styles.ts`, `board-panel-stages.ts` and `board-panel-parts.tsx`,
44
+ * and `one-line.tsx` keeps one bubble open at a time across the panel.
45
+ * This module keeps what a person has opened, folded and picked.
63
46
  */
64
47
 
65
- type StackStage = Exclude<TicketStage, 'stuck'>
66
-
67
- const TICKET_STAGES: {
68
- stage: StackStage
69
- title: string
70
- hint: string
71
- icon: string
72
- folded: boolean
73
- }[] = [
74
- {
75
- stage: 'blocked',
76
- title: 'Blocked',
77
- hint: "waits on a ticket that isn't done",
78
- icon: 'Lock',
79
- folded: false,
80
- },
81
- {
82
- stage: 'building',
83
- title: 'Building',
84
- hint: 'tests, red check, code, checks',
85
- icon: 'Hammer',
86
- folded: false,
87
- },
88
- {
89
- stage: 'reviewing',
90
- title: 'Reviewing',
91
- hint: 'a fresh ticket review and its fix rounds',
92
- icon: 'ScanSearch',
93
- folded: false,
94
- },
95
- {
96
- stage: 'done',
97
- title: 'Done',
98
- hint: 'joined the run branch',
99
- icon: 'CircleCheck',
100
- folded: true,
101
- },
102
- {
103
- stage: 'skipped',
104
- title: 'Skipped',
105
- hint: 'left out of this run, stays open',
106
- icon: 'SkipForward',
107
- folded: true,
108
- },
109
- ]
110
-
111
- const LENS_STAGES: { stage: LensState; title: string; icon: string }[] = [
112
- { stage: 'waiting', title: 'Waiting', icon: 'Clock' },
113
- { stage: 'reviewing', title: 'Reviewing', icon: 'ScanSearch' },
114
- { stage: 'fixing', title: 'Fixing', icon: 'Wrench' },
115
- { stage: 'clean', title: 'Clean', icon: 'CircleCheck' },
116
- ]
117
-
118
- /** A card or stage key. */
119
- type Key = string
120
-
121
- type DotKind = 'done' | 'current' | 'todo' | 'skip' | 'stopped'
122
-
123
- const makeStyles = ({
124
- theme,
125
- compact,
126
- }: {
127
- theme: PluginTheme
128
- compact: boolean
129
- }) => {
130
- const { colors } = theme
131
- return StyleSheet.create({
132
- screen: { flex: 1, backgroundColor: colors.surface0 },
133
- content: { padding: compact ? 10 : 16, gap: 8 },
134
- row: {
135
- flexDirection: 'row',
136
- alignItems: 'center',
137
- flexWrap: 'wrap',
138
- gap: compact ? 6 : 10,
139
- },
140
- title: { color: colors.foreground, fontSize: 16, fontWeight: '700' },
141
- spacer: { flex: 1 },
142
- text: { color: colors.foreground, fontSize: 13 },
143
- muted: { color: colors.foregroundMuted, fontSize: 12 },
144
- mono: { color: colors.foregroundMuted, fontSize: 12, fontFamily: MONO },
145
- small: { color: colors.foregroundMuted, fontSize: 11 },
146
- pill: {
147
- borderWidth: 1,
148
- borderRadius: 999,
149
- paddingHorizontal: 7,
150
- paddingVertical: 1,
151
- fontSize: 11,
152
- fontWeight: '700',
153
- },
154
- chip: {
155
- borderWidth: 1,
156
- borderColor: colors.border,
157
- borderRadius: 6,
158
- paddingHorizontal: 8,
159
- paddingVertical: 3,
160
- },
161
- chipOn: {
162
- borderColor: colors.accent,
163
- backgroundColor: colors.surface2,
164
- },
165
- banner: {
166
- flexDirection: 'row',
167
- alignItems: 'center',
168
- gap: 6,
169
- borderWidth: 1,
170
- borderRadius: 6,
171
- paddingHorizontal: 8,
172
- paddingVertical: 5,
173
- },
174
- bannerText: { fontSize: 12, flexShrink: 1 },
175
- block: { gap: 6 },
176
- stageHeader: {
177
- flexDirection: 'row',
178
- alignItems: 'center',
179
- gap: 6,
180
- paddingVertical: 4,
181
- paddingHorizontal: 6,
182
- borderRadius: 6,
183
- },
184
- stageHeaderThin: { paddingVertical: 1, opacity: DIMMED },
185
- stageTitle: { fontSize: 12, fontWeight: '700', letterSpacing: 1 },
186
- stageCount: {
187
- color: colors.foregroundMuted,
188
- fontSize: 12,
189
- fontFamily: MONO,
190
- },
191
- stageHint: {
192
- color: colors.foregroundMuted,
193
- fontSize: 11,
194
- flexShrink: 1,
195
- },
196
- stageBody: { gap: 6, paddingLeft: compact ? 0 : 8 },
197
- card: {
198
- backgroundColor: colors.surface1,
199
- borderColor: colors.border,
200
- borderWidth: 1,
201
- borderRadius: 8,
202
- paddingHorizontal: compact ? 8 : 10,
203
- paddingVertical: compact ? 7 : 8,
204
- gap: 5,
205
- },
206
- cardStuck: { borderColor: colors.statusDanger, borderLeftWidth: 4 },
207
- cardPressed: { backgroundColor: colors.surface2 },
208
- cardTop: { flexDirection: 'row', alignItems: 'center', gap: 6 },
209
- number: {
210
- color: colors.foregroundMuted,
211
- fontSize: 12,
212
- fontWeight: '700',
213
- fontFamily: MONO,
214
- },
215
- cardTitle: {
216
- color: colors.foreground,
217
- fontSize: 13,
218
- fontWeight: '600',
219
- flexShrink: 1,
220
- },
221
- tag: {
222
- color: colors.foregroundMuted,
223
- borderColor: colors.border,
224
- borderWidth: 1,
225
- borderRadius: 4,
226
- paddingHorizontal: 4,
227
- fontSize: 10,
228
- },
229
- dots: { flexDirection: 'row', alignItems: 'center' },
230
- dotCell: { flexDirection: 'row', alignItems: 'center' },
231
- link: { width: compact ? 7 : 10, height: 2 },
232
- dot: { width: 8, height: 8, borderRadius: 4, borderWidth: 1.5 },
233
- dotCurrent: { width: 12, height: 12, borderRadius: 6, borderWidth: 2 },
234
- dotSkip: { borderStyle: 'dashed' },
235
- stepLabel: { fontSize: 12, fontWeight: '600', marginLeft: 6 },
236
- counter: {
237
- borderWidth: 1,
238
- borderRadius: 5,
239
- paddingHorizontal: 5,
240
- fontSize: 11,
241
- fontFamily: MONO,
242
- },
243
- detail: {
244
- gap: 3,
245
- borderTopColor: colors.border,
246
- borderTopWidth: 1,
247
- paddingTop: 5,
248
- },
249
- label: {
250
- color: colors.foregroundMuted,
251
- fontSize: 12,
252
- fontWeight: '700',
253
- },
254
- replyRow: {
255
- flexDirection: 'row',
256
- flexWrap: 'wrap',
257
- alignItems: 'center',
258
- gap: 5,
259
- },
260
- reply: {
261
- color: colors.foreground,
262
- backgroundColor: colors.surface2,
263
- borderRadius: 4,
264
- paddingHorizontal: 6,
265
- paddingVertical: 2,
266
- fontSize: 12,
267
- fontFamily: MONO,
268
- },
269
- rule: { height: 1, backgroundColor: colors.border, marginVertical: 4 },
270
- reviewTitle: {
271
- color: colors.foreground,
272
- fontSize: 14,
273
- fontWeight: '700',
274
- },
275
- prLink: { color: colors.accent, fontSize: 12 },
276
- })
277
- }
278
-
279
- type MakeStyles = typeof makeStyles
280
-
281
- type Styles = ReturnType<MakeStyles>
282
-
283
- type Look = { theme: PluginTheme; styles: Styles }
284
-
285
- const toggled = ({
286
- set,
287
- key,
288
- }: {
289
- set: ReadonlySet<Key>
290
- key: string
291
- }): Set<Key> => {
292
- const next = new Set(set)
293
- if (next.has(key)) next.delete(key)
294
- else next.add(key)
295
- return next
296
- }
297
-
298
- const dotKinds = ({ ticket }: { ticket: TicketCard }): DotKind[] =>
299
- STEP_NAMES.map((_, index) => {
300
- if (ticket.refactor && index < REFACTOR_SKIPS_STEPS) return 'skip'
301
- if (index < ticket.step) return 'done'
302
- if (index === ticket.step) {
303
- return ticket.stage === 'skipped' || ticket.stage === 'stuck'
304
- ? 'stopped'
305
- : 'current'
306
- }
307
- return 'todo'
308
- })
309
-
310
- const stepLabel = ({ ticket }: { ticket: TicketCard }): string => {
311
- if (ticket.step >= ALL_STEPS_DONE) return 'all steps done'
312
- const name = STEP_NAMES[ticket.step]
313
- if (!name) return ticket.activity
314
- if (ticket.stage === 'stuck') return `stuck at ${name}`
315
- if (ticket.stage === 'skipped') return `stopped at ${name}`
316
- return name
317
- }
318
-
319
- const StepDots = ({ ticket, theme, styles }: { ticket: TicketCard } & Look) => {
320
- const kinds = dotKinds({ ticket })
321
- const { colors } = theme
322
- const highlight =
323
- ticket.stage === 'stuck' ? colors.statusDanger : colors.accent
324
- const labelColor =
325
- ticket.step >= ALL_STEPS_DONE
326
- ? colors.statusSuccess
327
- : kinds.includes('current')
328
- ? highlight
329
- : colors.foregroundMuted
330
- const described = STEP_NAMES.map(
331
- (name, index) => `${name} ${kinds[index] ?? ''}`
332
- ).join(', ')
333
- return (
334
- <View
335
- style={styles.dots}
336
- accessible
337
- accessibilityLabel={`Steps: ${described}`}
338
- >
339
- {kinds.map((kind, index) => {
340
- const reached = kind === 'done' || kind === 'current'
341
- const look =
342
- kind === 'done'
343
- ? [
344
- styles.dot,
345
- {
346
- backgroundColor: colors.statusSuccess,
347
- borderColor: colors.statusSuccess,
348
- },
349
- ]
350
- : kind === 'current'
351
- ? [
352
- styles.dotCurrent,
353
- {
354
- backgroundColor: highlight,
355
- borderColor: highlight,
356
- },
357
- ]
358
- : kind === 'stopped'
359
- ? [
360
- styles.dotCurrent,
361
- {
362
- backgroundColor: colors.surface2,
363
- borderColor: highlight,
364
- },
365
- ]
366
- : kind === 'skip'
367
- ? [
368
- styles.dot,
369
- styles.dotSkip,
370
- { borderColor: colors.foregroundMuted },
371
- ]
372
- : [
373
- styles.dot,
374
- { borderColor: colors.foregroundMuted },
375
- ]
376
- return (
377
- <View key={STEP_NAMES[index]} style={styles.dotCell}>
378
- {index > 0 ? (
379
- <View
380
- style={[
381
- styles.link,
382
- {
383
- backgroundColor: reached
384
- ? colors.statusSuccess
385
- : colors.border,
386
- },
387
- ]}
388
- />
389
- ) : null}
390
- <View style={look} />
391
- </View>
392
- )
393
- })}
394
- <Text style={[styles.stepLabel, { color: labelColor }]}>
395
- {stepLabel({ ticket })}
396
- </Text>
397
- </View>
398
- )
399
- }
400
-
401
- const Counter = ({
402
- label,
403
- value,
404
- theme,
405
- styles,
406
- }: { label: string; value: number } & Look) => {
407
- const color =
408
- value >= LOOP_CAP
409
- ? theme.colors.statusDanger
410
- : value > 0
411
- ? theme.colors.statusWarning
412
- : theme.colors.foregroundMuted
413
- return (
414
- <Text style={[styles.counter, { color, borderColor: color }]}>
415
- {label} {value}/{LOOP_CAP}
416
- </Text>
417
- )
418
- }
419
-
420
- /** A running step with its time, counting up by itself. */
421
- const StepLine = ({
422
- step,
423
- prefix,
424
- styles,
425
- }: {
426
- step: CurrentStep
427
- prefix: string
428
- styles: Styles
429
- }) => {
430
- const now = useNow()
431
- return (
432
- <Text style={styles.muted} numberOfLines={1}>
433
- {prefix}
434
- {currentStepText({ step, now })}
435
- </Text>
436
- )
437
- }
438
-
439
- /** The exact replies; tap one to copy it. */
440
- const Replies = ({
441
- replies,
442
- spec_number,
443
- styles,
444
- }: {
445
- replies: string[]
446
- spec_number: number | null
447
- styles: Styles
448
- }) => {
449
- const toast = useToast()
450
- const copy = async ({ reply }: { reply: string }) => {
451
- try {
452
- await copyText(reply)
453
- toast.show(`Copied "${reply}"`, { variant: 'success' })
454
- } catch {
455
- toast.error('Could not copy. Select the text and use Copy.')
456
- }
457
- }
458
- return (
459
- <View style={styles.replyRow}>
460
- <Text style={styles.label}>
461
- Reply on{' '}
462
- {spec_number === null
463
- ? 'the spec issue'
464
- : `spec issue #${spec_number}`}{' '}
465
- with
466
- </Text>
467
- {replies.map((reply) => (
468
- <Pressable
469
- key={reply}
470
- accessibilityRole="button"
471
- accessibilityLabel={`Copy the reply ${reply}`}
472
- onPress={() => void copy({ reply })}
473
- >
474
- <Text style={styles.reply} selectable>
475
- {reply}
476
- </Text>
477
- </Pressable>
478
- ))}
479
- </View>
480
- )
481
- }
482
-
483
- const NeedsYouCard = ({
484
- item,
485
- spec_number,
486
- theme,
487
- styles,
488
- }: { item: NeedsYou; spec_number: number | null } & Look) => (
489
- <View style={[styles.card, styles.cardStuck]}>
490
- <View style={styles.cardTop}>
491
- <Icon
492
- name="OctagonAlert"
493
- size={14}
494
- color={theme.colors.statusDanger}
495
- />
496
- <Text
497
- style={[styles.cardTitle, { color: theme.colors.statusDanger }]}
498
- >
499
- {item.subject}
500
- </Text>
501
- </View>
502
- <Text style={styles.text}>{item.reason}</Text>
503
- {item.detail ? (
504
- <Text style={styles.muted} numberOfLines={8} selectable>
505
- {item.detail}
506
- </Text>
507
- ) : null}
508
- {item.tried.length > 0 ? (
509
- <>
510
- <Text style={styles.label}>Tried</Text>
511
- {item.tried.map((line, index) => (
512
- <Text key={`${index}-${line}`} style={styles.muted}>
513
- {index + 1}. {line}
514
- </Text>
515
- ))}
516
- </>
517
- ) : null}
518
- <Replies
519
- replies={item.replies}
520
- spec_number={spec_number}
521
- styles={styles}
522
- />
523
- </View>
524
- )
525
-
526
- const StageHeader = ({
527
- title,
528
- hint,
529
- icon,
530
- count,
531
- color,
532
- folded,
533
- onPress,
534
- theme,
535
- styles,
536
- }: {
537
- title: string
538
- hint: string
539
- icon: string
540
- count: number
541
- color: string
542
- folded: boolean | null
543
- onPress: (() => void) | null
544
- } & Look) => {
545
- const empty = count === 0
546
- const tint = empty ? theme.colors.foregroundMuted : color
547
- const content = (
548
- <>
549
- <Icon name={icon} size={13} color={tint} />
550
- <Text style={[styles.stageTitle, { color: tint }]}>
551
- {title.toUpperCase()}
552
- </Text>
553
- <Text style={styles.stageCount}>{count}</Text>
554
- {empty || hint === '' ? null : (
555
- <Text style={styles.stageHint} numberOfLines={1}>
556
- {hint}
557
- </Text>
558
- )}
559
- <View style={styles.spacer} />
560
- {folded === null || empty ? null : (
561
- <Icon
562
- name={folded ? 'ChevronRight' : 'ChevronDown'}
563
- size={13}
564
- color={theme.colors.foregroundMuted}
565
- />
566
- )}
567
- </>
568
- )
569
- const style = [styles.stageHeader, empty ? styles.stageHeaderThin : null]
570
- if (!onPress || empty) return <View style={style}>{content}</View>
571
- return (
572
- <Pressable
573
- accessibilityRole="button"
574
- accessibilityLabel={`${title}, ${count}. ${folded ? 'Open' : 'Fold'} this stage`}
575
- onPress={onPress}
576
- style={style}
577
- >
578
- {content}
579
- </Pressable>
580
- )
581
- }
582
-
583
- const TicketDetail = ({
584
- ticket,
585
- state,
586
- styles,
587
- }: {
588
- ticket: TicketCard
589
- state: BoardState
590
- styles: Styles
591
- }) => {
592
- const kinds = dotKinds({ ticket })
593
- const waits = ticket.blockers.map((number) => {
594
- const blocker = state.tickets.find((card) => card.number === number)
595
- return `#${number} ${blocker ? blocker.stage : 'closed'}`
596
- })
597
- return (
598
- <View style={styles.detail}>
599
- <Text style={styles.muted}>
600
- steps:{' '}
601
- {STEP_NAMES.map(
602
- (name, index) =>
603
- `${name} ${kinds[index] === 'skip' ? '(not needed)' : (kinds[index] ?? '')}`
604
- ).join(' → ')}
605
- </Text>
606
- <Text style={styles.muted}>
607
- waits on: {waits.length > 0 ? waits.join(', ') : 'nothing'}
608
- </Text>
609
- {ticket.tests ? (
610
- <Text style={styles.muted}>
611
- last tests:{' '}
612
- {ticket.tests.failing > 0
613
- ? `${ticket.tests.failing} of ${ticket.tests.total} failing`
614
- : `${ticket.tests.total} passing`}
615
- </Text>
616
- ) : null}
617
- {ticket.findings ? (
618
- <Text style={styles.muted}>
619
- findings: {ticket.findings.blocker} blocker ·{' '}
620
- {ticket.findings.should_fix} should-fix ·{' '}
621
- {ticket.findings.nit} nit
622
- </Text>
623
- ) : null}
624
- {ticket.review_fix_round > 0 ? (
625
- <Text style={styles.muted}>
626
- review fix rounds: {ticket.review_fix_round}/{LOOP_CAP}
627
- </Text>
628
- ) : null}
629
- {Object.keys(ticket.agent_tokens).length > 0 ? (
630
- <Text style={styles.muted}>
631
- tokens:{' '}
632
- {Object.entries(ticket.agent_tokens)
633
- .map(
634
- ([role, tokens]) =>
635
- `${role} ${tokensText({ tokens })}`
636
- )
637
- .join(' · ')}
638
- </Text>
639
- ) : null}
640
- {ticket.tried.length > 0 ? (
641
- <>
642
- <Text style={styles.label}>Tried</Text>
643
- {ticket.tried.map((line, index) => (
644
- <Text key={`${index}-${line}`} style={styles.muted}>
645
- {index + 1}. {line}
646
- </Text>
647
- ))}
648
- </>
649
- ) : null}
650
- </View>
651
- )
652
- }
653
-
654
- const TicketCardView = ({
655
- ticket,
656
- state,
657
- open,
658
- onToggle,
659
- theme,
660
- styles,
661
- }: {
662
- ticket: TicketCard
663
- state: BoardState
664
- open: boolean
665
- onToggle: () => void
666
- } & Look) => {
667
- const color = stageColor({ stage: ticket.stage, theme })
668
- const failing =
669
- ticket.tests &&
670
- ticket.tests.failing > 0 &&
671
- ticket.stage !== 'done' &&
672
- ticket.stage !== 'skipped'
673
- ? ticket.tests
674
- : null
675
- const blockers =
676
- ticket.findings && ticket.stage !== 'done' ? ticket.findings.blocker : 0
677
- return (
678
- <Pressable
679
- accessibilityRole="button"
680
- accessibilityLabel={`Ticket ${ticket.number}, ${ticket.stage}. ${open ? 'Hide' : 'Show'} details`}
681
- onPress={onToggle}
682
- style={({ pressed }) => [
683
- styles.card,
684
- pressed ? styles.cardPressed : null,
685
- ]}
686
- >
687
- <View style={styles.cardTop}>
688
- <Text style={styles.number}>#{ticket.number}</Text>
689
- <Text style={styles.cardTitle} numberOfLines={1}>
690
- {ticket.title}
691
- </Text>
692
- {ticket.refactor ? (
693
- <Text style={styles.tag}>refactor</Text>
694
- ) : null}
695
- <View style={styles.spacer} />
696
- <Text style={[styles.pill, { color, borderColor: color }]}>
697
- {ticket.activity}
698
- </Text>
699
- <Icon
700
- name={open ? 'ChevronDown' : 'ChevronRight'}
701
- size={14}
702
- color={theme.colors.foregroundMuted}
703
- />
704
- </View>
705
- <View style={styles.row}>
706
- <StepDots ticket={ticket} theme={theme} styles={styles} />
707
- <Text style={styles.text} numberOfLines={1}>
708
- {ticket.role ?? ''}
709
- </Text>
710
- </View>
711
- {ticket.current_step ? (
712
- <StepLine
713
- step={ticket.current_step}
714
- prefix=""
715
- styles={styles}
716
- />
717
- ) : null}
718
- <View style={styles.row}>
719
- <Counter
720
- label="fix"
721
- value={ticket.fix_round}
722
- theme={theme}
723
- styles={styles}
724
- />
725
- <Counter
726
- label="review"
727
- value={ticket.review_round}
728
- theme={theme}
729
- styles={styles}
730
- />
731
- <Text style={styles.mono}>
732
- {tokensText({ tokens: ticket.tokens })} tokens
733
- </Text>
734
- {ticket.plan_used.length > 0 ? (
735
- <Text style={styles.mono}>
736
- plan:{' '}
737
- {planUsedText({ used: ticket.plan_used, sign: '+' })}
738
- </Text>
739
- ) : null}
740
- {failing ? (
741
- <Text
742
- style={[
743
- styles.muted,
744
- { color: theme.colors.statusDanger },
745
- ]}
746
- >
747
- {failing.failing} of {failing.total} tests failing
748
- </Text>
749
- ) : null}
750
- {blockers > 0 ? (
751
- <Text
752
- style={[
753
- styles.muted,
754
- { color: theme.colors.statusWarning },
755
- ]}
756
- >
757
- {blockers} blocker from the ticket review
758
- </Text>
759
- ) : null}
760
- </View>
761
- {open ? (
762
- <TicketDetail ticket={ticket} state={state} styles={styles} />
763
- ) : null}
764
- </Pressable>
765
- )
766
- }
767
-
768
- const findingsLine = ({ lens }: { lens: LensCard }): string | null => {
769
- const parts = [
770
- lens.findings.blocker > 0 ? `${lens.findings.blocker} blocker` : null,
771
- lens.findings.should_fix > 0
772
- ? `${lens.findings.should_fix} should-fix`
773
- : null,
774
- lens.findings.nit > 0 ? `${lens.findings.nit} nit` : null,
775
- ].filter((part) => part !== null)
776
- return parts.length > 0 ? parts.join(', ') : null
777
- }
778
-
779
- const LensCardView = ({ lens, theme, styles }: { lens: LensCard } & Look) => {
780
- const color = lensColor({ state: lens.state, theme })
781
- const findings = findingsLine({ lens })
782
- return (
783
- <View style={styles.card}>
784
- <View style={styles.cardTop}>
785
- <Text style={styles.cardTitle}>{lens.name}</Text>
786
- <View style={styles.spacer} />
787
- <Text style={[styles.pill, { color, borderColor: color }]}>
788
- {lens.state}
789
- </Text>
790
- </View>
791
- {findings ? (
792
- <Text
793
- style={[
794
- styles.muted,
795
- {
796
- color:
797
- lens.findings.blocker > 0
798
- ? theme.colors.statusDanger
799
- : theme.colors.statusWarning,
800
- },
801
- ]}
802
- >
803
- {findings}
804
- </Text>
805
- ) : null}
806
- </View>
807
- )
808
- }
809
-
810
- const reviewLine = ({ state }: { state: BoardState }): string => {
811
- const review = state.final_review
812
- const open = state.tickets.filter(
813
- (ticket) => ticket.stage !== 'done' && ticket.stage !== 'skipped'
814
- ).length
815
- switch (review.state) {
816
- case 'waiting':
817
- return review.active
818
- ? 'Every ticket is done or skipped. It starts next.'
819
- : `Starts when every ticket is done or skipped (${open} still open).`
820
- case 'reviewing':
821
- return `Round ${review.round}: a fresh reviewer per lens reads the whole run branch.`
822
- case 'fixing':
823
- return `Fix round ${review.fix_round}/${LOOP_CAP}: the implementer fixes what the lenses found.`
824
- case 'stuck':
825
- return 'Stuck: it needs your reply (see Needs you).'
826
- case 'passed':
827
- return 'Passed.'
828
- }
829
- }
830
-
831
- const FinalReviewStack = ({
832
- state,
833
- theme,
834
- styles,
835
- }: { state: BoardState } & Look) => {
836
- const review = state.final_review
837
- return (
838
- <View
839
- style={[styles.block, review.active ? null : { opacity: DIMMED }]}
840
- >
841
- <View style={styles.row}>
842
- <Icon
843
- name="GitPullRequest"
844
- size={15}
845
- color={theme.colors.foreground}
846
- />
847
- <Text style={styles.reviewTitle}>Final review · 5 lenses</Text>
848
- <View style={styles.spacer} />
849
- <Counter
850
- label="round"
851
- value={review.round}
852
- theme={theme}
853
- styles={styles}
854
- />
855
- <Counter
856
- label="fix"
857
- value={review.fix_round}
858
- theme={theme}
859
- styles={styles}
860
- />
861
- </View>
862
- <Text style={styles.muted}>{reviewLine({ state })}</Text>
863
- {LENS_STAGES.map((entry) => {
864
- const lenses = review.lenses.filter(
865
- (lens) => lens.state === entry.stage
866
- )
867
- return (
868
- <View key={entry.stage} style={styles.block}>
869
- <StageHeader
870
- title={entry.title}
871
- hint=""
872
- icon={entry.icon}
873
- count={lenses.length}
874
- color={lensColor({ state: entry.stage, theme })}
875
- folded={null}
876
- onPress={null}
877
- theme={theme}
878
- styles={styles}
879
- />
880
- {lenses.length > 0 ? (
881
- <View style={styles.stageBody}>
882
- {lenses.map((lens) => (
883
- <LensCardView
884
- key={lens.name}
885
- lens={lens}
886
- theme={theme}
887
- styles={styles}
888
- />
889
- ))}
890
- </View>
891
- ) : null}
892
- </View>
893
- )
894
- })}
895
- </View>
896
- )
897
- }
898
-
899
- const Banner = ({
900
- icon,
901
- color,
902
- children,
903
- styles,
904
- }: {
905
- icon: string
906
- color: string
907
- children: ReactNode
908
- styles: Styles
909
- }) => (
910
- <View style={[styles.banner, { borderColor: color }]}>
911
- <Icon name={icon} size={13} color={color} />
912
- <Text style={[styles.bannerText, { color }]}>{children}</Text>
913
- </View>
914
- )
915
-
916
- const UsageLine = ({ state, theme, styles }: { state: BoardState } & Look) => {
917
- const { usage, usage_label } = state
918
- return (
919
- <View style={styles.row}>
920
- <Icon name="Gauge" size={13} color={theme.colors.foregroundMuted} />
921
- {usage ? (
922
- <Text style={styles.mono}>
923
- {usage_label}: 5h{' '}
924
- <Text
925
- style={{
926
- color: usageColor({
927
- level: usage.five_hour_level,
928
- theme,
929
- }),
930
- }}
931
- >
932
- {percentText({ percent: usage.five_hour_percent })}
933
- </Text>{' '}
934
- · week{' '}
935
- <Text
936
- style={{
937
- color: usageColor({
938
- level: usage.weekly_level,
939
- theme,
940
- }),
941
- }}
942
- >
943
- {percentText({ percent: usage.weekly_percent })}
944
- </Text>
945
- </Text>
946
- ) : (
947
- <Text style={styles.mono}>{usage_label}: no reading yet</Text>
948
- )}
949
- </View>
950
- )
951
- }
952
-
953
- /** A tab per recent run; the older ones stay folded behind one chip. */
954
- const RunPicker = ({
955
- runs,
956
- selected,
957
- onPick,
958
- theme,
959
- styles,
960
- }: {
961
- runs: RunSummary[]
962
- selected: string
963
- onPick: (run_id: string) => void
964
- } & Look) => {
965
- const [showOlder, setShowOlder] = useState(false)
966
- const { shown, folded } = foldRuns({ runs, selected })
967
- return (
968
- <View style={styles.row}>
969
- {(showOlder ? [...shown, ...folded] : shown).map((run) => (
970
- <RunChip
971
- key={run.run_id}
972
- run={run}
973
- on={run.run_id === selected}
974
- onPick={onPick}
975
- theme={theme}
976
- styles={styles}
977
- />
978
- ))}
979
- {folded.length > 0 ? (
980
- <Pressable
981
- accessibilityRole="button"
982
- accessibilityLabel={
983
- showOlder
984
- ? 'Fold the older runs'
985
- : `Show ${folded.length} older runs`
986
- }
987
- onPress={() => setShowOlder((open) => !open)}
988
- style={styles.chip}
989
- >
990
- <Text style={styles.small}>
991
- {showOlder ? 'fold older' : `${folded.length} older`}
992
- </Text>
993
- </Pressable>
994
- ) : null}
995
- </View>
996
- )
997
- }
998
-
999
- const RunChip = ({
1000
- run,
1001
- on,
1002
- onPick,
1003
- theme,
1004
- styles,
1005
- }: {
1006
- run: RunSummary
1007
- on: boolean
1008
- onPick: (run_id: string) => void
1009
- } & Look) => (
1010
- <Pressable
1011
- accessibilityRole="button"
1012
- accessibilityLabel={`Show run ${run.run_id}`}
1013
- onPress={() => onPick(run.run_id)}
1014
- style={[styles.chip, on ? styles.chipOn : null]}
1015
- >
1016
- <Text style={styles.small}>
1017
- {run.spec_number === null ? 'demo' : `#${run.spec_number}`} ·{' '}
1018
- <Text
1019
- style={{
1020
- color: statusColor({ status: run.status, theme }),
1021
- }}
1022
- >
1023
- {statusText({ status: run.status })}
1024
- </Text>
1025
- {run.needs_you > 0 ? ` · ${run.needs_you} need you` : ''}
1026
- </Text>
1027
- </Pressable>
1028
- )
1029
-
1030
- const Section = ({
1031
- children,
1032
- styles,
1033
- }: {
1034
- children: ReactNode
1035
- styles: Styles
1036
- }) => <View style={styles.block}>{children}</View>
1037
-
1038
48
  /** The workspace panel: the newest (or picked) run of this workspace. */
1039
49
  export const BoardPanel = ({
1040
50
  theme,
@@ -1063,12 +73,14 @@ export const BoardPanel = ({
1063
73
  )
1064
74
  const [folded, setFolded] = useState<ReadonlySet<Key>>(
1065
75
  () =>
1066
- new Set(
1067
- TICKET_STAGES.filter((entry) => entry.folded).map(
76
+ new Set<Key>([
77
+ ...TICKET_STAGES.filter((entry) => entry.folded).map(
1068
78
  (entry) => entry.stage
1069
- )
1070
- )
79
+ ),
80
+ FOR_A_PERSON,
81
+ ])
1071
82
  )
83
+ const [feedFolded, setFeedFolded] = useState(true)
1072
84
  const look = { theme, styles }
1073
85
  const state = board.data?.selected ?? null
1074
86
 
@@ -1090,266 +102,56 @@ export const BoardPanel = ({
1090
102
  )
1091
103
  }
1092
104
 
1093
- const { run } = state
1094
105
  const runs = board.data?.runs ?? []
1095
- const card = (ticket: TicketCard) => {
1096
- const key = `ticket-${ticket.number}`
1097
- return (
1098
- <TicketCardView
1099
- key={key}
1100
- ticket={ticket}
1101
- state={state}
1102
- open={openCards.has(key)}
1103
- onToggle={() => setOpenCards((set) => toggled({ set, key }))}
1104
- {...look}
1105
- />
1106
- )
1107
- }
106
+ const posted = board.data?.posted ?? []
107
+ const chat_agent_id = board.data?.chat_agent_id ?? null
1108
108
 
1109
109
  return (
1110
- <ScrollView
1111
- style={styles.screen}
1112
- contentContainerStyle={styles.content}
1113
- >
1114
- <View style={styles.row}>
1115
- <Text style={styles.title}>Luca board</Text>
1116
- <View style={styles.spacer} />
1117
- <Text
1118
- style={[
1119
- styles.pill,
1120
- {
1121
- color: statusColor({ status: run.status, theme }),
1122
- borderColor: statusColor({
1123
- status: run.status,
1124
- theme,
1125
- }),
1126
- },
1127
- ]}
1128
- >
1129
- {statusText({ status: run.status })}
1130
- </Text>
1131
- </View>
1132
- {runs.length > 1 ? (
1133
- <RunPicker
1134
- runs={runs}
1135
- selected={run.run_id}
1136
- onPick={setPicked}
1137
- {...look}
1138
- />
1139
- ) : null}
1140
- <Text style={styles.text} numberOfLines={layout.compact ? 3 : 2}>
1141
- {runTitle(run)}
1142
- {run.branch ? (
1143
- <Text style={styles.muted}> · {run.branch}</Text>
1144
- ) : null}
1145
- </Text>
1146
- {run.pr_url ? (
1147
- <ExternalLink href={run.pr_url}>
1148
- <Text style={styles.prLink}>
1149
- Pull request{run.pr_number ? ` #${run.pr_number}` : ''}:{' '}
1150
- {run.pr_url}
1151
- </Text>
1152
- </ExternalLink>
1153
- ) : null}
1154
- <UsageLine state={state} {...look} />
1155
- {state.current_step ? (
1156
- <StepLine
1157
- step={state.current_step}
1158
- prefix="Now: "
1159
- styles={styles}
1160
- />
1161
- ) : null}
1162
- {state.run_tokens > 0 ? (
1163
- <Text style={styles.mono}>
1164
- This run&apos;s tokens:{' '}
1165
- {tokensText({ tokens: state.run_tokens })} of its run budget{' '}
1166
- {tokensText({ tokens: state.run_budget_tokens })}
1167
- </Text>
1168
- ) : null}
1169
- {state.run_plan_used.length > 0 ? (
1170
- <Text style={styles.mono}>
1171
- This run used:{' '}
1172
- {planUsedText({ used: state.run_plan_used, sign: '' })}
1173
- </Text>
1174
- ) : null}
1175
- {state.limit_wait ? (
1176
- <Banner
1177
- icon="Hourglass"
1178
- color={theme.colors.statusWarning}
1179
- styles={styles}
1180
- >
1181
- {limitWaitText({
1182
- window: state.limit_wait.window,
1183
- resets_at: state.limit_wait.resets_at,
1184
- now: Date.now(),
1185
- })}
1186
- </Banner>
1187
- ) : null}
1188
- {state.usage_line_wait ? (
1189
- <Banner
1190
- icon="Hourglass"
1191
- color={theme.colors.statusWarning}
1192
- styles={styles}
1193
- >
1194
- {usageLineWaitText({
1195
- ...state.usage_line_wait,
1196
- now: Date.now(),
1197
- })}
1198
- </Banner>
1199
- ) : null}
1200
- {run.version_note ? (
1201
- <Banner
1202
- icon="Info"
1203
- color={theme.colors.statusWarning}
1204
- styles={styles}
1205
- >
1206
- {run.version_note}
1207
- </Banner>
1208
- ) : null}
1209
- {run.stopped ? (
1210
- <Banner
1211
- icon="OctagonX"
1212
- color={theme.colors.statusDanger}
1213
- styles={styles}
1214
- >
1215
- {stoppedText(run.stopped)}
1216
- </Banner>
1217
- ) : null}
1218
- {run.engine_ended && !run.engine_ended.ok ? (
1219
- <Banner
1220
- icon="OctagonX"
1221
- color={theme.colors.statusDanger}
1222
- styles={styles}
110
+ <BubbleProvider>
111
+ <View style={styles.screen}>
112
+ <ScrollView
113
+ style={styles.scroll}
114
+ contentContainerStyle={styles.fill}
1223
115
  >
1224
- The engine stopped: {run.engine_ended.message}
1225
- </Banner>
1226
- ) : null}
1227
- {run.refusal.length > 0 ? (
1228
- <Banner
1229
- icon="Ban"
1230
- color={theme.colors.statusDanger}
1231
- styles={styles}
1232
- >
1233
- Intake refused the run. {run.refusal.join(' ')}
1234
- </Banner>
1235
- ) : null}
1236
- <Text style={styles.small}>
1237
- Step dots: tests → red check → code → checks → review. Tap a
1238
- card for details.
1239
- </Text>
1240
-
1241
- <Section styles={styles}>
1242
- <StageHeader
1243
- title="Needs you"
1244
- hint="stuck work waiting for your reply on the spec issue"
1245
- icon="OctagonAlert"
1246
- count={state.needs_you.length}
1247
- color={theme.colors.statusDanger}
1248
- folded={null}
1249
- onPress={null}
1250
- {...look}
1251
- />
1252
- {state.needs_you.length > 0 ? (
1253
- <View style={styles.stageBody}>
1254
- {state.needs_you.map((item) => (
1255
- <NeedsYouCard
1256
- key={item.key}
1257
- item={item}
1258
- spec_number={run.spec_number}
1259
- {...look}
1260
- />
1261
- ))}
1262
- </View>
1263
- ) : null}
1264
- </Section>
1265
-
1266
- {TICKET_STAGES.map((entry) => {
1267
- const tickets = state.tickets.filter(
1268
- (ticket) => ticket.stage === entry.stage
1269
- )
1270
- const isFolded = folded.has(entry.stage)
1271
- return (
1272
- <Section key={entry.stage} styles={styles}>
1273
- <StageHeader
1274
- title={entry.title}
1275
- hint={entry.hint}
1276
- icon={entry.icon}
1277
- count={tickets.length}
1278
- color={stageColor({ stage: entry.stage, theme })}
1279
- folded={isFolded}
1280
- onPress={() =>
1281
- setFolded((set) =>
1282
- toggled({ set, key: entry.stage })
1283
- )
1284
- }
116
+ <SharedBubbles style={[styles.content, styles.fill]}>
117
+ <RunHeader
118
+ state={state}
119
+ runs={runs}
120
+ setPicked={setPicked}
121
+ layout={layout}
122
+ {...look}
123
+ />
124
+ <NeedsYouSection
125
+ state={state}
126
+ posted={posted}
127
+ chat_agent_id={chat_agent_id}
1285
128
  {...look}
1286
129
  />
1287
- {tickets.length > 0 && !isFolded ? (
1288
- <View style={styles.stageBody}>
1289
- {tickets.map(card)}
1290
- </View>
130
+ <TicketStack
131
+ state={state}
132
+ folded={folded}
133
+ setFolded={setFolded}
134
+ openCards={openCards}
135
+ setOpenCards={setOpenCards}
136
+ {...look}
137
+ />
138
+ {showsFinalReview({ state }) ? (
139
+ <>
140
+ <View style={styles.rule} />
141
+ <FinalReviewStack state={state} {...look} />
142
+ </>
1291
143
  ) : null}
1292
- </Section>
1293
- )
1294
- })}
1295
-
1296
- {showsFinalReview({ state }) ? (
1297
- <>
1298
- <View style={styles.rule} />
1299
- <FinalReviewStack state={state} {...look} />
1300
- </>
1301
- ) : null}
1302
-
1303
- <View style={styles.rule} />
1304
- <Text style={styles.small}>
1305
- {run.run_id} · {state.event_count} events · engine{' '}
1306
- {run.engine_ended
1307
- ? run.engine_ended.ok
1308
- ? 'finished'
1309
- : 'stopped'
1310
- : 'running'}
1311
- </Text>
1312
- {state.latest ? (
1313
- <Text style={styles.small} numberOfLines={2}>
1314
- latest: {state.latest}
1315
- </Text>
1316
- ) : null}
1317
- {state.jev.asked > 0 ? (
1318
- <Text style={styles.small}>
1319
- Jev, in shadow mode: {state.jev.asked} asked,{' '}
1320
- {state.jev.answered} answered, {state.jev.failed} without an
1321
- answer. The engine doesn't act on them.
1322
- </Text>
1323
- ) : null}
1324
- {state.memory.searches > 0 || state.memory.learner !== 'waiting' ? (
1325
- <Text style={styles.small}>
1326
- Memory: {state.memory.searches} searches (
1327
- {state.memory.search_errors} vault errors),{' '}
1328
- {state.memory.shown} shown. Learner: {state.memory.learner}
1329
- {state.memory.added +
1330
- state.memory.updated +
1331
- state.memory.refused +
1332
- state.memory.failed >
1333
- 0
1334
- ? `; ${state.memory.added} added, ${state.memory.updated} updated, ${state.memory.refused} refused, ${state.memory.failed} failed`
1335
- : ''}
1336
- .
1337
- </Text>
1338
- ) : null}
1339
- {state.messages.sent + state.messages.refused > 0 ? (
1340
- <Text style={styles.small}>
1341
- Agent messages: {state.messages.sent} sent,{' '}
1342
- {state.messages.delivered} handed over,{' '}
1343
- {state.messages.refused} refused.
1344
- </Text>
1345
- ) : null}
1346
- {state.shared_git_changed > 0 ? (
1347
- <Text style={styles.small}>
1348
- Outside changes to the shared .git:{' '}
1349
- {state.shared_git_changed}. Other processes made them, not
1350
- the agents.
1351
- </Text>
1352
- ) : null}
1353
- </ScrollView>
144
+ <LiveFeed
145
+ state={state}
146
+ folded={feedFolded}
147
+ onToggle={() => setFeedFolded((was) => !was)}
148
+ {...look}
149
+ />
150
+ <RunFooter state={state} {...look} />
151
+ </SharedBubbles>
152
+ </ScrollView>
153
+ <StatusBar state={state} {...look} />
154
+ </View>
155
+ </BubbleProvider>
1354
156
  )
1355
157
  }