@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.
- package/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ticket's card in the panel: its number and title, its stage pill, the
|
|
3
|
+
* step dots, its agent's model and Clef's line, the fix and review counters, its tokens and tests, and its
|
|
4
|
+
* details when opened. With an entry in the run's live file (#628), the
|
|
5
|
+
* step text gives way to what its agent is doing now, and its tokens count
|
|
6
|
+
* up with the turn, followed by the latest action's age; a busy card's
|
|
7
|
+
* line ends in its heartbeat (#631), each running helper gets a line under
|
|
8
|
+
* it (#636), and a check waiting there for a slot (#632) reads as a wait,
|
|
9
|
+
* with the amber bar. In a run with no sign of life (#635), a busy or
|
|
10
|
+
* waiting card's step text says so, in place of any wait, its heartbeat is
|
|
11
|
+
* a flat red line, and its timers stop at the last sign of life. Each line stays on one line (`one-line.tsx`): the
|
|
12
|
+
* number, pill, dots, counters and tokens keep their size, the text beside
|
|
13
|
+
* them is cut, and a tap on it shows it whole in a bubble.
|
|
14
|
+
*/
|
|
15
|
+
import { Icon } from '@getpaseo/plugin/client/react-native'
|
|
16
|
+
import { Pressable, Text, View } from 'react-native'
|
|
17
|
+
|
|
18
|
+
import { stageColor, tokensText, useNow } from './board-look'
|
|
19
|
+
import { Counter, StepLine } from './board-panel-parts'
|
|
20
|
+
import type { Look } from './board-panel-styles'
|
|
21
|
+
import { noLife } from './engine-life'
|
|
22
|
+
import { Heartbeat } from './heartbeat'
|
|
23
|
+
import { HelperLines } from './helper-lines'
|
|
24
|
+
import { liveAgeText, liveText } from './live-line'
|
|
25
|
+
import { ModelLine } from './model-line'
|
|
26
|
+
import { BubbleScope, CutLine, useBubbles } from './one-line'
|
|
27
|
+
import { StepDots } from './step-dots'
|
|
28
|
+
import { TicketDetail } from './ticket-detail'
|
|
29
|
+
import { slotWait, ticketWait } from './ticket-wait'
|
|
30
|
+
import { WaitLine } from './wait-line'
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
planUsedText,
|
|
34
|
+
type BoardState,
|
|
35
|
+
type TicketCard,
|
|
36
|
+
type TicketStage,
|
|
37
|
+
} from '../shared/board-state'
|
|
38
|
+
|
|
39
|
+
/** The stages whose card has no heartbeat (#631), live entry or not. */
|
|
40
|
+
const QUIET: ReadonlySet<TicketStage> = new Set([
|
|
41
|
+
'done',
|
|
42
|
+
'skipped',
|
|
43
|
+
'stuck',
|
|
44
|
+
'blocked',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
export const TicketCardView = ({
|
|
48
|
+
ticket,
|
|
49
|
+
state,
|
|
50
|
+
open,
|
|
51
|
+
onToggle,
|
|
52
|
+
theme,
|
|
53
|
+
styles,
|
|
54
|
+
}: {
|
|
55
|
+
ticket: TicketCard
|
|
56
|
+
state: BoardState
|
|
57
|
+
open: boolean
|
|
58
|
+
onToggle: () => void
|
|
59
|
+
} & Look) => {
|
|
60
|
+
const bubbles = useBubbles()
|
|
61
|
+
const now = useNow()
|
|
62
|
+
const held = ticketWait({ state, ticket }) ?? slotWait({ ticket })
|
|
63
|
+
// A busy or waiting card of a run with no sign of life says so, and
|
|
64
|
+
// its timers stop.
|
|
65
|
+
const busy =
|
|
66
|
+
!QUIET.has(ticket.stage) &&
|
|
67
|
+
(ticket.live !== null || ticket.current_step !== null || held !== null)
|
|
68
|
+
const lifeless = busy ? noLife({ state, now }) : null
|
|
69
|
+
const clock = lifeless?.at ?? now
|
|
70
|
+
// A ticket waiting on purpose between steps, or its check waiting for
|
|
71
|
+
// a slot (#632), reads "waiting", in amber, while the run is alive.
|
|
72
|
+
const wait = lifeless ? null : held
|
|
73
|
+
// What its agent is doing now, from the live file, in place of the step text.
|
|
74
|
+
const doing = liveText({ live: ticket.live, now: clock })
|
|
75
|
+
const color = wait
|
|
76
|
+
? theme.colors.statusWarning
|
|
77
|
+
: stageColor({ stage: ticket.stage, theme })
|
|
78
|
+
const failing =
|
|
79
|
+
ticket.tests &&
|
|
80
|
+
ticket.tests.failing > 0 &&
|
|
81
|
+
ticket.stage !== 'done' &&
|
|
82
|
+
ticket.stage !== 'skipped'
|
|
83
|
+
? ticket.tests
|
|
84
|
+
: null
|
|
85
|
+
const blockers =
|
|
86
|
+
ticket.findings && ticket.stage !== 'done' ? ticket.findings.blocker : 0
|
|
87
|
+
return (
|
|
88
|
+
<Pressable
|
|
89
|
+
accessibilityRole="button"
|
|
90
|
+
accessibilityLabel={`Ticket ${ticket.number}, ${ticket.stage}. ${open ? 'Hide' : 'Show'} details`}
|
|
91
|
+
onPress={() => {
|
|
92
|
+
// A tap beside an open bubble only closes it.
|
|
93
|
+
if (bubbles.open) bubbles.show(null)
|
|
94
|
+
else onToggle()
|
|
95
|
+
}}
|
|
96
|
+
style={({ pressed }) => [
|
|
97
|
+
styles.card,
|
|
98
|
+
pressed ? styles.cardPressed : null,
|
|
99
|
+
]}
|
|
100
|
+
>
|
|
101
|
+
<BubbleScope bubbles={bubbles} styles={styles}>
|
|
102
|
+
<View style={styles.cardTop}>
|
|
103
|
+
<Text style={styles.number} numberOfLines={1}>
|
|
104
|
+
#{ticket.number}
|
|
105
|
+
</Text>
|
|
106
|
+
<CutLine
|
|
107
|
+
text={ticket.title}
|
|
108
|
+
style={styles.cardTitle}
|
|
109
|
+
styles={styles}
|
|
110
|
+
/>
|
|
111
|
+
{ticket.refactor ? (
|
|
112
|
+
<Text style={styles.tag} numberOfLines={1}>
|
|
113
|
+
refactor
|
|
114
|
+
</Text>
|
|
115
|
+
) : null}
|
|
116
|
+
<View style={styles.spacer} />
|
|
117
|
+
<Text
|
|
118
|
+
style={[styles.pill, { color, borderColor: color }]}
|
|
119
|
+
numberOfLines={1}
|
|
120
|
+
>
|
|
121
|
+
{wait ? 'waiting' : ticket.activity}
|
|
122
|
+
</Text>
|
|
123
|
+
<Icon
|
|
124
|
+
name={open ? 'ChevronDown' : 'ChevronRight'}
|
|
125
|
+
size={14}
|
|
126
|
+
color={theme.colors.foregroundMuted}
|
|
127
|
+
/>
|
|
128
|
+
</View>
|
|
129
|
+
<View style={styles.line}>
|
|
130
|
+
<StepDots ticket={ticket} theme={theme} styles={styles} />
|
|
131
|
+
{lifeless ? (
|
|
132
|
+
<CutLine
|
|
133
|
+
text={lifeless.text}
|
|
134
|
+
style={[
|
|
135
|
+
styles.text,
|
|
136
|
+
{ color: theme.colors.statusDanger },
|
|
137
|
+
]}
|
|
138
|
+
styles={styles}
|
|
139
|
+
/>
|
|
140
|
+
) : wait ? (
|
|
141
|
+
<WaitLine wait={wait} theme={theme} styles={styles} />
|
|
142
|
+
) : doing || ticket.role ? (
|
|
143
|
+
<CutLine
|
|
144
|
+
text={doing ?? ticket.role ?? ''}
|
|
145
|
+
style={styles.text}
|
|
146
|
+
styles={styles}
|
|
147
|
+
/>
|
|
148
|
+
) : null}
|
|
149
|
+
{lifeless ||
|
|
150
|
+
(!wait &&
|
|
151
|
+
ticket.live !== null &&
|
|
152
|
+
!QUIET.has(ticket.stage)) ? (
|
|
153
|
+
<>
|
|
154
|
+
<View style={styles.spacer} />
|
|
155
|
+
<Heartbeat
|
|
156
|
+
live={ticket.live}
|
|
157
|
+
gone={lifeless !== null}
|
|
158
|
+
theme={theme}
|
|
159
|
+
styles={styles}
|
|
160
|
+
/>
|
|
161
|
+
</>
|
|
162
|
+
) : null}
|
|
163
|
+
</View>
|
|
164
|
+
<HelperLines
|
|
165
|
+
live={ticket.live}
|
|
166
|
+
now={clock}
|
|
167
|
+
theme={theme}
|
|
168
|
+
styles={styles}
|
|
169
|
+
/>
|
|
170
|
+
{ticket.current_step && doing === null && wait === null ? (
|
|
171
|
+
<StepLine
|
|
172
|
+
step={ticket.current_step}
|
|
173
|
+
prefix=""
|
|
174
|
+
at={lifeless?.at ?? null}
|
|
175
|
+
styles={styles}
|
|
176
|
+
/>
|
|
177
|
+
) : null}
|
|
178
|
+
{ticket.model !== null && ticket.model.role === ticket.role ? (
|
|
179
|
+
<ModelLine
|
|
180
|
+
model={ticket.model}
|
|
181
|
+
bar={state.confidence_bar}
|
|
182
|
+
theme={theme}
|
|
183
|
+
styles={styles}
|
|
184
|
+
/>
|
|
185
|
+
) : null}
|
|
186
|
+
<View style={styles.line}>
|
|
187
|
+
<Counter
|
|
188
|
+
label="fix"
|
|
189
|
+
value={ticket.fix_round}
|
|
190
|
+
theme={theme}
|
|
191
|
+
styles={styles}
|
|
192
|
+
/>
|
|
193
|
+
<Counter
|
|
194
|
+
label="review"
|
|
195
|
+
value={ticket.review_round}
|
|
196
|
+
theme={theme}
|
|
197
|
+
styles={styles}
|
|
198
|
+
/>
|
|
199
|
+
<Text style={[styles.mono, styles.fixed]} numberOfLines={1}>
|
|
200
|
+
{`${tokensText({
|
|
201
|
+
tokens: ticket.tokens + (ticket.live?.tokens ?? 0),
|
|
202
|
+
})} tokens${liveAgeText({ live: ticket.live, now: clock })}`}
|
|
203
|
+
</Text>
|
|
204
|
+
{ticket.plan_used.length > 0 ? (
|
|
205
|
+
<CutLine
|
|
206
|
+
text={`plan: ${planUsedText({ used: ticket.plan_used, sign: '+' })}`}
|
|
207
|
+
style={styles.mono}
|
|
208
|
+
styles={styles}
|
|
209
|
+
/>
|
|
210
|
+
) : null}
|
|
211
|
+
{failing ? (
|
|
212
|
+
<CutLine
|
|
213
|
+
text={`${failing.failing} of ${failing.total} tests failing`}
|
|
214
|
+
style={[
|
|
215
|
+
styles.muted,
|
|
216
|
+
{ color: theme.colors.statusDanger },
|
|
217
|
+
]}
|
|
218
|
+
styles={styles}
|
|
219
|
+
/>
|
|
220
|
+
) : null}
|
|
221
|
+
{blockers > 0 ? (
|
|
222
|
+
<CutLine
|
|
223
|
+
text={`${blockers} blocker from the ticket review`}
|
|
224
|
+
style={[
|
|
225
|
+
styles.muted,
|
|
226
|
+
{ color: theme.colors.statusWarning },
|
|
227
|
+
]}
|
|
228
|
+
styles={styles}
|
|
229
|
+
/>
|
|
230
|
+
) : null}
|
|
231
|
+
</View>
|
|
232
|
+
</BubbleScope>
|
|
233
|
+
{open ? (
|
|
234
|
+
<TicketDetail ticket={ticket} state={state} styles={styles} />
|
|
235
|
+
) : null}
|
|
236
|
+
</Pressable>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a ticket card shows when it is open: its steps, what it waits on, its
|
|
3
|
+
* last tests, findings, review fix rounds, tokens, and what was tried.
|
|
4
|
+
*/
|
|
5
|
+
import { Text, View } from 'react-native'
|
|
6
|
+
|
|
7
|
+
import { tokensText } from './board-look'
|
|
8
|
+
import type { Styles } from './board-panel-styles'
|
|
9
|
+
import { dotKinds } from './step-dots'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
LOOP_CAP,
|
|
13
|
+
STEP_NAMES,
|
|
14
|
+
type BoardState,
|
|
15
|
+
type TicketCard,
|
|
16
|
+
} from '../shared/board-state'
|
|
17
|
+
|
|
18
|
+
export const TicketDetail = ({
|
|
19
|
+
ticket,
|
|
20
|
+
state,
|
|
21
|
+
styles,
|
|
22
|
+
}: {
|
|
23
|
+
ticket: TicketCard
|
|
24
|
+
state: BoardState
|
|
25
|
+
styles: Styles
|
|
26
|
+
}) => {
|
|
27
|
+
const kinds = dotKinds({ ticket })
|
|
28
|
+
const waits = ticket.blockers.map((number) => {
|
|
29
|
+
const blocker = state.tickets.find((card) => card.number === number)
|
|
30
|
+
return `#${number} ${blocker ? blocker.stage : 'closed'}`
|
|
31
|
+
})
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.detail}>
|
|
34
|
+
<Text style={styles.muted}>
|
|
35
|
+
steps:{' '}
|
|
36
|
+
{STEP_NAMES.map(
|
|
37
|
+
(name, index) =>
|
|
38
|
+
`${name} ${kinds[index] === 'skip' ? '(not needed)' : (kinds[index] ?? '')}`
|
|
39
|
+
).join(' → ')}
|
|
40
|
+
</Text>
|
|
41
|
+
<Text style={styles.muted}>
|
|
42
|
+
waits on: {waits.length > 0 ? waits.join(', ') : 'nothing'}
|
|
43
|
+
</Text>
|
|
44
|
+
{ticket.tests ? (
|
|
45
|
+
<Text style={styles.muted}>
|
|
46
|
+
last tests:{' '}
|
|
47
|
+
{ticket.tests.failing > 0
|
|
48
|
+
? `${ticket.tests.failing} of ${ticket.tests.total} failing`
|
|
49
|
+
: `${ticket.tests.total} passing`}
|
|
50
|
+
</Text>
|
|
51
|
+
) : null}
|
|
52
|
+
{ticket.findings ? (
|
|
53
|
+
<Text style={styles.muted}>
|
|
54
|
+
findings: {ticket.findings.blocker} blocker ·{' '}
|
|
55
|
+
{ticket.findings.should_fix} should-fix ·{' '}
|
|
56
|
+
{ticket.findings.nit} nit
|
|
57
|
+
</Text>
|
|
58
|
+
) : null}
|
|
59
|
+
{ticket.review_fix_round > 0 ? (
|
|
60
|
+
<Text style={styles.muted}>
|
|
61
|
+
review fix rounds: {ticket.review_fix_round}/{LOOP_CAP}
|
|
62
|
+
</Text>
|
|
63
|
+
) : null}
|
|
64
|
+
{Object.keys(ticket.agent_tokens).length > 0 ? (
|
|
65
|
+
<Text style={styles.muted}>
|
|
66
|
+
tokens:{' '}
|
|
67
|
+
{Object.entries(ticket.agent_tokens)
|
|
68
|
+
.map(
|
|
69
|
+
([role, tokens]) =>
|
|
70
|
+
`${role} ${tokensText({ tokens })}`
|
|
71
|
+
)
|
|
72
|
+
.join(' · ')}
|
|
73
|
+
</Text>
|
|
74
|
+
) : null}
|
|
75
|
+
{ticket.tried.length > 0 ? (
|
|
76
|
+
<>
|
|
77
|
+
<Text style={styles.label}>Tried</Text>
|
|
78
|
+
{ticket.tried.map((line, index) => (
|
|
79
|
+
<Text key={`${index}-${line}`} style={styles.muted}>
|
|
80
|
+
{index + 1}. {line}
|
|
81
|
+
</Text>
|
|
82
|
+
))}
|
|
83
|
+
</>
|
|
84
|
+
) : null}
|
|
85
|
+
</View>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tickets as a stack of stages (Blocked, Building, Reviewing, Testing,
|
|
3
|
+
* Fixing, Joining, Done, Skipped), each a fold of ticket cards, then the
|
|
4
|
+
* tickets left out for a person.
|
|
5
|
+
*/
|
|
6
|
+
import type { Dispatch, SetStateAction } from 'react'
|
|
7
|
+
|
|
8
|
+
import { Text, View } from 'react-native'
|
|
9
|
+
|
|
10
|
+
import { stageColor } from './board-look'
|
|
11
|
+
import { Section, StageHeader } from './board-panel-parts'
|
|
12
|
+
import {
|
|
13
|
+
FOR_A_PERSON,
|
|
14
|
+
TICKET_STAGES,
|
|
15
|
+
toggled,
|
|
16
|
+
type Key,
|
|
17
|
+
} from './board-panel-stages'
|
|
18
|
+
import type { Look } from './board-panel-styles'
|
|
19
|
+
import { TicketCardView } from './ticket-card-view'
|
|
20
|
+
|
|
21
|
+
import type { BoardState, TicketCard } from '../shared/board-state'
|
|
22
|
+
|
|
23
|
+
export const TicketStack = ({
|
|
24
|
+
state,
|
|
25
|
+
folded,
|
|
26
|
+
setFolded,
|
|
27
|
+
openCards,
|
|
28
|
+
setOpenCards,
|
|
29
|
+
theme,
|
|
30
|
+
styles,
|
|
31
|
+
}: {
|
|
32
|
+
state: BoardState
|
|
33
|
+
folded: ReadonlySet<Key>
|
|
34
|
+
setFolded: Dispatch<SetStateAction<ReadonlySet<Key>>>
|
|
35
|
+
openCards: ReadonlySet<Key>
|
|
36
|
+
setOpenCards: Dispatch<SetStateAction<ReadonlySet<Key>>>
|
|
37
|
+
} & Look) => {
|
|
38
|
+
const look = { theme, styles }
|
|
39
|
+
const card = (ticket: TicketCard) => {
|
|
40
|
+
const key = `ticket-${ticket.number}`
|
|
41
|
+
return (
|
|
42
|
+
<TicketCardView
|
|
43
|
+
key={key}
|
|
44
|
+
ticket={ticket}
|
|
45
|
+
state={state}
|
|
46
|
+
open={openCards.has(key)}
|
|
47
|
+
onToggle={() => setOpenCards((set) => toggled({ set, key }))}
|
|
48
|
+
{...look}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
{TICKET_STAGES.map((entry) => {
|
|
56
|
+
const tickets = state.tickets.filter(
|
|
57
|
+
(ticket) => ticket.stage === entry.stage
|
|
58
|
+
)
|
|
59
|
+
const isFolded = folded.has(entry.stage)
|
|
60
|
+
return (
|
|
61
|
+
<Section key={entry.stage} styles={styles}>
|
|
62
|
+
<StageHeader
|
|
63
|
+
title={entry.title}
|
|
64
|
+
hint={entry.hint}
|
|
65
|
+
icon={entry.icon}
|
|
66
|
+
count={tickets.length}
|
|
67
|
+
color={stageColor({ stage: entry.stage, theme })}
|
|
68
|
+
folded={isFolded}
|
|
69
|
+
onPress={() =>
|
|
70
|
+
setFolded((set) =>
|
|
71
|
+
toggled({ set, key: entry.stage })
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
{...look}
|
|
75
|
+
/>
|
|
76
|
+
{tickets.length > 0 && !isFolded ? (
|
|
77
|
+
<View style={styles.stageBody}>
|
|
78
|
+
{tickets.map(card)}
|
|
79
|
+
</View>
|
|
80
|
+
) : null}
|
|
81
|
+
</Section>
|
|
82
|
+
)
|
|
83
|
+
})}
|
|
84
|
+
|
|
85
|
+
{state.for_a_person.length > 0 ? (
|
|
86
|
+
<Section styles={styles}>
|
|
87
|
+
<StageHeader
|
|
88
|
+
title="For a person"
|
|
89
|
+
hint="left out of this run: a person does these, or they wait on one"
|
|
90
|
+
icon="User"
|
|
91
|
+
count={state.for_a_person.length}
|
|
92
|
+
color={theme.colors.foregroundMuted}
|
|
93
|
+
folded={folded.has(FOR_A_PERSON)}
|
|
94
|
+
onPress={() =>
|
|
95
|
+
setFolded((set) =>
|
|
96
|
+
toggled({ set, key: FOR_A_PERSON })
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
{...look}
|
|
100
|
+
/>
|
|
101
|
+
{folded.has(FOR_A_PERSON) ? null : (
|
|
102
|
+
<View style={styles.stageBody}>
|
|
103
|
+
{state.for_a_person.map((ticket) => (
|
|
104
|
+
<Text
|
|
105
|
+
key={ticket.number}
|
|
106
|
+
style={styles.muted}
|
|
107
|
+
numberOfLines={2}
|
|
108
|
+
>
|
|
109
|
+
#{ticket.number} {ticket.title} ·{' '}
|
|
110
|
+
{ticket.why}
|
|
111
|
+
</Text>
|
|
112
|
+
))}
|
|
113
|
+
</View>
|
|
114
|
+
)}
|
|
115
|
+
</Section>
|
|
116
|
+
) : null}
|
|
117
|
+
</>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a waiting card waits for (#558, #559): a ticket the engine holds on
|
|
3
|
+
* purpose between steps. The engine's `ticket_waiting` names the reason; an
|
|
4
|
+
* old journal without it gets the same reasons from the board's own rules,
|
|
5
|
+
* or "waiting for the engine's next step" when none fits, never a guess.
|
|
6
|
+
* A check waiting for a slot inside its step (#632) comes from the live file.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
BASELINE_STEP_TEXT,
|
|
10
|
+
type BoardState,
|
|
11
|
+
type TicketCard,
|
|
12
|
+
type TicketStage,
|
|
13
|
+
type WaitFor,
|
|
14
|
+
} from '../shared/board-state'
|
|
15
|
+
|
|
16
|
+
/** A wait as the card shows it: its text, and since when (if known). */
|
|
17
|
+
export type ShownWait = { text: string; since: string | null }
|
|
18
|
+
|
|
19
|
+
/** The cards that never wait: done, stuck, skipped, or blocked. */
|
|
20
|
+
const NEVER_WAITS = new Set<TicketStage>([
|
|
21
|
+
'done',
|
|
22
|
+
'stuck',
|
|
23
|
+
'skipped',
|
|
24
|
+
'blocked',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
/** What no rule explains. */
|
|
28
|
+
export const ENGINE_NEXT_STEP = "waiting for the engine's next step"
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A wait's reason in words.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* waitText({ waiting_for: 'join', on_ticket: 331 }) // 'waiting to join after #331'
|
|
35
|
+
*/
|
|
36
|
+
export const waitText = ({
|
|
37
|
+
waiting_for,
|
|
38
|
+
on_ticket,
|
|
39
|
+
}: {
|
|
40
|
+
waiting_for: WaitFor
|
|
41
|
+
on_ticket: number | null
|
|
42
|
+
}): string => {
|
|
43
|
+
const other = on_ticket === null ? null : `#${on_ticket}`
|
|
44
|
+
switch (waiting_for) {
|
|
45
|
+
case 'baseline':
|
|
46
|
+
return other === null
|
|
47
|
+
? 'waiting for the shared baseline tests'
|
|
48
|
+
: `waiting for ${other}'s baseline tests`
|
|
49
|
+
case 'join':
|
|
50
|
+
return other === null
|
|
51
|
+
? 'waiting to join'
|
|
52
|
+
: `waiting to join after ${other}`
|
|
53
|
+
case 'start':
|
|
54
|
+
return other === null
|
|
55
|
+
? 'waiting to start'
|
|
56
|
+
: `starts once ${other} has joined`
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* What a ticket's check waits for inside its step (#632), from the live
|
|
62
|
+
* file, or `null` when it waits for no slot.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* slotWait({ ticket }) // { text: 'waiting for a test slot (2 of 2 in use)', since: '2026-...' }
|
|
66
|
+
*/
|
|
67
|
+
export const slotWait = ({
|
|
68
|
+
ticket,
|
|
69
|
+
}: {
|
|
70
|
+
ticket: TicketCard
|
|
71
|
+
}): ShownWait | null => {
|
|
72
|
+
const wait = ticket.slot_wait
|
|
73
|
+
if (wait === null) return null
|
|
74
|
+
const slot = wait.for === 'prepare_slot' ? 'a prepare slot' : 'a test slot'
|
|
75
|
+
return {
|
|
76
|
+
text: `waiting for ${slot} (${wait.in_use} of ${wait.slots} in use)`,
|
|
77
|
+
since: wait.since,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** An approved ticket in the join queue, not yet joining. */
|
|
82
|
+
const approvedWaiting = (card: TicketCard): boolean =>
|
|
83
|
+
card.stage === 'joining' &&
|
|
84
|
+
card.activity === 'approved' &&
|
|
85
|
+
card.current_step === null
|
|
86
|
+
|
|
87
|
+
/** The ticket joining the run branch now, other than `number`. */
|
|
88
|
+
const joinerBesides = ({
|
|
89
|
+
state,
|
|
90
|
+
number,
|
|
91
|
+
}: {
|
|
92
|
+
state: BoardState
|
|
93
|
+
number: number
|
|
94
|
+
}): number | null =>
|
|
95
|
+
state.tickets.find(
|
|
96
|
+
(card) =>
|
|
97
|
+
card.number !== number &&
|
|
98
|
+
card.stage === 'joining' &&
|
|
99
|
+
!approvedWaiting(card)
|
|
100
|
+
)?.number ?? null
|
|
101
|
+
|
|
102
|
+
/** The ticket running the baseline tests now, other than `number`. */
|
|
103
|
+
const baselineRunner = ({
|
|
104
|
+
state,
|
|
105
|
+
number,
|
|
106
|
+
}: {
|
|
107
|
+
state: BoardState
|
|
108
|
+
number: number
|
|
109
|
+
}): number | null =>
|
|
110
|
+
state.tickets.find(
|
|
111
|
+
(card) =>
|
|
112
|
+
card.number !== number &&
|
|
113
|
+
card.current_step?.text === BASELINE_STEP_TEXT
|
|
114
|
+
)?.number ?? null
|
|
115
|
+
|
|
116
|
+
/** #558's rules, for a started ticket between steps of an old journal. */
|
|
117
|
+
const guessedReason = ({
|
|
118
|
+
state,
|
|
119
|
+
ticket,
|
|
120
|
+
}: {
|
|
121
|
+
state: BoardState
|
|
122
|
+
ticket: TicketCard
|
|
123
|
+
}): string => {
|
|
124
|
+
const { number } = ticket
|
|
125
|
+
if (ticket.stage === 'building' && ticket.baseline_tests === null) {
|
|
126
|
+
const runner = baselineRunner({ state, number })
|
|
127
|
+
if (runner !== null) {
|
|
128
|
+
return waitText({ waiting_for: 'baseline', on_ticket: runner })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (approvedWaiting(ticket)) {
|
|
132
|
+
const joiner = joinerBesides({ state, number })
|
|
133
|
+
if (joiner !== null) {
|
|
134
|
+
return waitText({ waiting_for: 'join', on_ticket: joiner })
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return ENGINE_NEXT_STEP
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* What `ticket` waits for between steps, or `null` when it isn't waiting:
|
|
142
|
+
* it is in a step, it never waits (done, stuck, skipped, blocked), or the
|
|
143
|
+
* engine isn't running the run. A ticket not started yet waits only to
|
|
144
|
+
* start, behind a ticket in the join queue.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ticketWait({ state, ticket })
|
|
148
|
+
* // { text: "waiting for #553's baseline tests", since: '2026-...' }
|
|
149
|
+
*/
|
|
150
|
+
export const ticketWait = ({
|
|
151
|
+
state,
|
|
152
|
+
ticket,
|
|
153
|
+
}: {
|
|
154
|
+
state: BoardState
|
|
155
|
+
ticket: TicketCard
|
|
156
|
+
}): ShownWait | null => {
|
|
157
|
+
if (NEVER_WAITS.has(ticket.stage) || ticket.current_step !== null) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
if (state.run.engine_ended !== null || state.run.stopped !== null) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
const { wait } = ticket
|
|
164
|
+
if (wait?.for) {
|
|
165
|
+
return {
|
|
166
|
+
text: waitText({
|
|
167
|
+
waiting_for: wait.for,
|
|
168
|
+
on_ticket: wait.on_ticket,
|
|
169
|
+
}),
|
|
170
|
+
since: wait.since,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!ticket.started) {
|
|
174
|
+
const ahead =
|
|
175
|
+
joinerBesides({ state, number: ticket.number }) ??
|
|
176
|
+
state.tickets.find(approvedWaiting)?.number ??
|
|
177
|
+
null
|
|
178
|
+
return ahead === null
|
|
179
|
+
? null
|
|
180
|
+
: {
|
|
181
|
+
text: waitText({ waiting_for: 'start', on_ticket: ahead }),
|
|
182
|
+
since: null,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// A journal with no step records never says the ticket is between steps.
|
|
186
|
+
if (wait === null) return null
|
|
187
|
+
return { text: guessedReason({ state, ticket }), since: wait.since }
|
|
188
|
+
}
|