patchrelay 0.36.18 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,259 @@
1
+ import { parseGitHubFailureContext, summarizeGitHubFailureContext } from "./github-failure-context.js";
2
+ import { derivePatchRelayWaitingReason } from "./waiting-reason.js";
3
+ import { deriveIssueStatusNote } from "./status-note.js";
4
+ import { isIssueSessionReadyForExecution } from "./issue-session.js";
5
+ function shouldSuppressStatusNote(params) {
6
+ if (!params.activeRunType && params.sessionState !== "running")
7
+ return false;
8
+ const note = params.statusNote?.trim().toLowerCase();
9
+ if (!note)
10
+ return true;
11
+ return note === "codex turn was interrupted"
12
+ || note.startsWith("zombie: never started")
13
+ || note === "stale thread after restart"
14
+ || note === "patchrelay received your mention. delegate the issue to patchrelay to start work.";
15
+ }
16
+ export function parseCiSnapshotSummary(snapshotJson) {
17
+ if (!snapshotJson)
18
+ return undefined;
19
+ try {
20
+ const snapshot = JSON.parse(snapshotJson);
21
+ const rawChecks = Array.isArray(snapshot.checks) ? snapshot.checks : [];
22
+ const checks = collapseEffectiveChecks(rawChecks);
23
+ if (checks.length === 0)
24
+ return undefined;
25
+ let passed = 0;
26
+ let failed = 0;
27
+ let pending = 0;
28
+ const failedNames = [];
29
+ for (const check of checks) {
30
+ if (check.status === "success")
31
+ passed++;
32
+ else if (check.status === "failure") {
33
+ failed++;
34
+ failedNames.push(check.name);
35
+ }
36
+ else
37
+ pending++;
38
+ }
39
+ return {
40
+ total: checks.length,
41
+ completed: passed + failed,
42
+ passed,
43
+ failed,
44
+ pending,
45
+ overall: snapshot.gateCheckStatus,
46
+ ...(failedNames.length > 0 ? { failedNames } : {}),
47
+ };
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ function collapseEffectiveChecks(checks) {
54
+ const effective = new Map();
55
+ for (const check of checks) {
56
+ const name = typeof check?.name === "string" ? check.name.trim() : "";
57
+ if (!name || effective.has(name))
58
+ continue;
59
+ effective.set(name, check);
60
+ }
61
+ return [...effective.values()];
62
+ }
63
+ export function parseStringArray(value) {
64
+ if (!value)
65
+ return [];
66
+ try {
67
+ const parsed = JSON.parse(value);
68
+ return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ }
74
+ export class TrackedIssueListQuery {
75
+ db;
76
+ constructor(db) {
77
+ this.db = db;
78
+ }
79
+ listTrackedIssues() {
80
+ const rows = this.db.connection
81
+ .prepare(`SELECT
82
+ s.project_id, s.linear_issue_id, s.issue_key, i.title,
83
+ i.current_linear_state, i.factory_state, s.session_state, s.waiting_reason, s.summary_text, s.updated_at,
84
+ i.pending_run_type,
85
+ i.pr_number, i.pr_head_sha, i.pr_review_state, i.pr_check_status, i.last_blocking_review_head_sha,
86
+ i.last_github_ci_snapshot_json,
87
+ i.last_github_failure_source,
88
+ i.last_github_failure_head_sha,
89
+ i.last_github_failure_check_name,
90
+ i.last_github_failure_context_json,
91
+ active_run.run_type AS active_run_type,
92
+ active_run.completion_check_thread_id AS active_completion_check_thread_id,
93
+ active_run.completion_check_outcome AS active_completion_check_outcome,
94
+ latest_run.run_type AS latest_run_type,
95
+ latest_run.status AS latest_run_status,
96
+ latest_run.summary_json AS latest_run_summary_json,
97
+ latest_run.report_json AS latest_run_report_json,
98
+ latest_run.completion_check_thread_id AS latest_run_completion_check_thread_id,
99
+ latest_run.completion_check_outcome AS latest_run_completion_check_outcome,
100
+ latest_run.completion_check_summary AS latest_run_completion_check_summary,
101
+ latest_run.completion_check_question AS latest_run_completion_check_question,
102
+ latest_run.completion_check_why AS latest_run_completion_check_why,
103
+ latest_run.completion_check_recommended_reply AS latest_run_completion_check_recommended_reply,
104
+ (
105
+ SELECT COUNT(*)
106
+ FROM issue_session_events e
107
+ WHERE e.project_id = s.project_id
108
+ AND e.linear_issue_id = s.linear_issue_id
109
+ AND e.processed_at IS NULL
110
+ ) AS pending_session_event_count,
111
+ (
112
+ SELECT COUNT(*)
113
+ FROM issue_dependencies d
114
+ LEFT JOIN issues blockers
115
+ ON blockers.project_id = d.project_id
116
+ AND blockers.linear_issue_id = d.blocker_linear_issue_id
117
+ WHERE d.project_id = s.project_id
118
+ AND d.linear_issue_id = s.linear_issue_id
119
+ AND (
120
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
121
+ AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
122
+ )
123
+ ) AS blocked_by_count,
124
+ (
125
+ SELECT json_group_array(COALESCE(blockers.issue_key, d.blocker_issue_key, d.blocker_linear_issue_id))
126
+ FROM issue_dependencies d
127
+ LEFT JOIN issues blockers
128
+ ON blockers.project_id = d.project_id
129
+ AND blockers.linear_issue_id = d.blocker_linear_issue_id
130
+ WHERE d.project_id = s.project_id
131
+ AND d.linear_issue_id = s.linear_issue_id
132
+ AND (
133
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
134
+ AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
135
+ )
136
+ ) AS blocked_by_keys_json
137
+ FROM issue_sessions s
138
+ LEFT JOIN issues i
139
+ ON i.project_id = s.project_id
140
+ AND i.linear_issue_id = s.linear_issue_id
141
+ LEFT JOIN runs active_run ON active_run.id = COALESCE(s.active_run_id, i.active_run_id)
142
+ LEFT JOIN runs latest_run ON latest_run.id = (
143
+ SELECT r.id FROM runs r
144
+ WHERE r.project_id = s.project_id AND r.linear_issue_id = s.linear_issue_id
145
+ ORDER BY r.id DESC LIMIT 1
146
+ )
147
+ ORDER BY s.updated_at DESC, s.issue_key ASC`)
148
+ .all();
149
+ return rows.map((row) => {
150
+ const failureContext = parseGitHubFailureContext(typeof row.last_github_failure_context_json === "string" ? row.last_github_failure_context_json : undefined);
151
+ const prChecksSummary = parseCiSnapshotSummary(typeof row.last_github_ci_snapshot_json === "string" ? row.last_github_ci_snapshot_json : undefined);
152
+ const blockedByKeys = parseStringArray(typeof row.blocked_by_keys_json === "string" ? row.blocked_by_keys_json : undefined);
153
+ const blockedByCount = Number(row.blocked_by_count ?? 0);
154
+ const hasPendingSessionEvents = Number(row.pending_session_event_count ?? 0) > 0;
155
+ const hasPendingWake = hasPendingSessionEvents
156
+ || this.db.issueSessions.peekIssueSessionWake(String(row.project_id), String(row.linear_issue_id)) !== undefined;
157
+ const readyForExecution = isIssueSessionReadyForExecution({
158
+ ...(typeof row.session_state === "string" ? { sessionState: String(row.session_state) } : {}),
159
+ factoryState: String(row.factory_state ?? "delegated"),
160
+ ...(row.active_run_type !== null ? { activeRunId: 1 } : {}),
161
+ blockedByCount,
162
+ hasPendingWake,
163
+ hasLegacyPendingRun: row.pending_run_type !== null && row.pending_run_type !== undefined,
164
+ ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
165
+ ...(row.pr_state !== null ? { prState: String(row.pr_state) } : {}),
166
+ ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
167
+ ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
168
+ ...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
169
+ });
170
+ const failureSummary = summarizeGitHubFailureContext(failureContext);
171
+ const sessionWaitingReason = typeof row.waiting_reason === "string" && row.waiting_reason.trim().length > 0
172
+ ? row.waiting_reason
173
+ : undefined;
174
+ const sessionSummary = typeof row.summary_text === "string" && row.summary_text.trim().length > 0
175
+ ? row.summary_text
176
+ : undefined;
177
+ const waitingReason = sessionWaitingReason ?? derivePatchRelayWaitingReason({
178
+ ...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
179
+ blockedByKeys,
180
+ factoryState: String(row.factory_state ?? "delegated"),
181
+ ...(row.pending_run_type !== null ? { pendingRunType: String(row.pending_run_type) } : {}),
182
+ ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
183
+ ...(row.pr_head_sha !== null ? { prHeadSha: String(row.pr_head_sha) } : {}),
184
+ ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
185
+ ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
186
+ ...(row.last_blocking_review_head_sha !== null ? { lastBlockingReviewHeadSha: String(row.last_blocking_review_head_sha) } : {}),
187
+ ...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
188
+ });
189
+ const latestRun = row.latest_run_type !== null && row.latest_run_status !== null
190
+ ? {
191
+ id: 0,
192
+ issueId: 0,
193
+ projectId: String(row.project_id),
194
+ linearIssueId: String(row.linear_issue_id),
195
+ runType: String(row.latest_run_type),
196
+ status: String(row.latest_run_status),
197
+ ...(typeof row.latest_run_summary_json === "string" ? { summaryJson: row.latest_run_summary_json } : {}),
198
+ ...(typeof row.latest_run_report_json === "string" ? { reportJson: row.latest_run_report_json } : {}),
199
+ ...(typeof row.latest_run_completion_check_thread_id === "string" ? { completionCheckThreadId: row.latest_run_completion_check_thread_id } : {}),
200
+ ...(typeof row.latest_run_completion_check_outcome === "string" ? { completionCheckOutcome: row.latest_run_completion_check_outcome } : {}),
201
+ ...(typeof row.latest_run_completion_check_summary === "string" ? { completionCheckSummary: row.latest_run_completion_check_summary } : {}),
202
+ ...(typeof row.latest_run_completion_check_question === "string" ? { completionCheckQuestion: row.latest_run_completion_check_question } : {}),
203
+ ...(typeof row.latest_run_completion_check_why === "string" ? { completionCheckWhy: row.latest_run_completion_check_why } : {}),
204
+ ...(typeof row.latest_run_completion_check_recommended_reply === "string" ? { completionCheckRecommendedReply: row.latest_run_completion_check_recommended_reply } : {}),
205
+ startedAt: String(row.updated_at),
206
+ }
207
+ : undefined;
208
+ const latestEvent = this.db.issueSessions.listIssueSessionEvents(String(row.project_id), String(row.linear_issue_id), { limit: 1 }).at(-1);
209
+ const statusNoteCandidate = deriveIssueStatusNote({
210
+ issue: { factoryState: String(row.factory_state ?? "delegated") },
211
+ sessionSummary,
212
+ latestRun: latestRun,
213
+ latestEvent,
214
+ failureSummary,
215
+ blockedByKeys,
216
+ waitingReason,
217
+ }) ?? waitingReason;
218
+ const statusNoteForReturn = shouldSuppressStatusNote({
219
+ activeRunType: row.active_run_type,
220
+ sessionState: row.session_state,
221
+ statusNote: statusNoteCandidate,
222
+ })
223
+ ? undefined
224
+ : statusNoteCandidate;
225
+ const completionCheckActive = typeof row.active_completion_check_thread_id === "string"
226
+ && row.active_completion_check_thread_id.length > 0
227
+ && row.active_completion_check_outcome === null
228
+ && row.active_run_type !== null;
229
+ return {
230
+ ...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
231
+ ...(row.title !== null ? { title: String(row.title) } : {}),
232
+ ...(statusNoteForReturn ? { statusNote: statusNoteForReturn } : {}),
233
+ projectId: String(row.project_id),
234
+ ...(row.session_state !== null ? { sessionState: String(row.session_state) } : {}),
235
+ factoryState: String(row.factory_state ?? "delegated"),
236
+ blockedByCount,
237
+ blockedByKeys,
238
+ readyForExecution,
239
+ ...(row.current_linear_state !== null ? { currentLinearState: String(row.current_linear_state) } : {}),
240
+ ...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
241
+ ...(row.pending_run_type !== null ? { pendingRunType: String(row.pending_run_type) } : {}),
242
+ ...(row.latest_run_type !== null ? { latestRunType: String(row.latest_run_type) } : {}),
243
+ ...(row.latest_run_status !== null ? { latestRunStatus: String(row.latest_run_status) } : {}),
244
+ ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
245
+ ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
246
+ ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
247
+ ...(prChecksSummary ? { prChecksSummary } : {}),
248
+ ...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
249
+ ...(row.last_github_failure_head_sha !== null ? { latestFailureHeadSha: String(row.last_github_failure_head_sha) } : {}),
250
+ ...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
251
+ ...(failureContext?.stepName ? { latestFailureStepName: failureContext.stepName } : {}),
252
+ ...(failureContext?.summary ? { latestFailureSummary: failureContext.summary } : {}),
253
+ ...(waitingReason ? { waitingReason } : {}),
254
+ ...(completionCheckActive ? { completionCheckActive } : {}),
255
+ updatedAt: String(row.updated_at),
256
+ };
257
+ });
258
+ }
259
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.36.18",
3
+ "version": "0.37.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,80 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- function cleanCommand(raw) {
4
- const bashMatch = raw.match(/^\/bin\/(?:ba)?sh\s+-\w*c\s+['"](.+?)['"]$/s);
5
- if (bashMatch?.[1])
6
- return bashMatch[1];
7
- const bashMatch2 = raw.match(/^\/bin\/(?:ba)?sh\s+-\w*c\s+"(.+?)"$/s);
8
- if (bashMatch2?.[1])
9
- return bashMatch2[1];
10
- return raw;
11
- }
12
- function summarizeFileChange(item) {
13
- const count = item.changes?.length ?? 0;
14
- return `updated ${count} file${count === 1 ? "" : "s"}`;
15
- }
16
- function summarizeToolCall(item) {
17
- return `used ${item.toolName ?? item.type}`;
18
- }
19
- function summarizeText(item) {
20
- return (item.text ?? "").replace(/\s+/g, " ").trim();
21
- }
22
- function itemPrefix(item) {
23
- if (item.type === "commandExecution")
24
- return "$ ";
25
- return "";
26
- }
27
- function formatItemDuration(ms) {
28
- if (ms === undefined || ms === null)
29
- return "";
30
- const seconds = Math.floor(ms / 1000);
31
- if (seconds < 1)
32
- return "";
33
- if (seconds < 60)
34
- return ` ${seconds}s`;
35
- const minutes = Math.floor(seconds / 60);
36
- return ` ${minutes}m`;
37
- }
38
- function itemText(item) {
39
- switch (item.type) {
40
- case "agentMessage":
41
- case "plan":
42
- case "reasoning":
43
- return summarizeText(item);
44
- case "commandExecution": {
45
- const cmd = cleanCommand(item.command ?? "?");
46
- const exit = item.exitCode !== undefined && item.exitCode !== null && item.exitCode !== 0
47
- ? ` exit ${item.exitCode}` : "";
48
- const dur = formatItemDuration(item.durationMs);
49
- return `${cmd}${exit}${dur}`;
50
- }
51
- case "fileChange":
52
- return summarizeFileChange(item);
53
- case "mcpToolCall":
54
- case "dynamicToolCall": {
55
- const dur = formatItemDuration(item.durationMs);
56
- return `${summarizeToolCall(item)}${dur}`;
57
- }
58
- case "userMessage":
59
- return `you: ${summarizeText(item)}`;
60
- default:
61
- return item.text ? summarizeText(item) : item.type;
62
- }
63
- }
64
- function itemColor(item) {
65
- if (item.status === "failed" || item.status === "declined")
66
- return "red";
67
- if (item.status === "inProgress")
68
- return "yellow";
69
- if (item.type === "userMessage")
70
- return "yellow";
71
- return undefined;
72
- }
73
- export function ItemLine({ item }) {
74
- const text = itemText(item);
75
- if (!text) {
76
- return _jsx(_Fragment, {});
77
- }
78
- const color = itemColor(item);
79
- return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Text, { wrap: "wrap", bold: item.type === "agentMessage", ...(color ? { color } : {}), children: [itemPrefix(item), text] }), item.output && item.status === "inProgress" && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: item.output.split("\n").filter(Boolean).at(-1) ?? "" }) }))] }));
80
- }
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
- import { Box, Static, Text, useStdout } from "ink";
4
- import { buildTimelineRows } from "./timeline-presentation.js";
5
- import { TimelineRow } from "./TimelineRow.js";
6
- const ACTIVE_TAIL = 8;
7
- export function Timeline({ entries, follow }) {
8
- const { stdout } = useStdout();
9
- const rows = stdout?.rows ?? 24;
10
- const maxActive = Math.max(ACTIVE_TAIL, rows - 12);
11
- const displayRows = useMemo(() => buildTimelineRows(entries), [entries]);
12
- // Always cap the rendered entries to prevent OOM/WASM crashes.
13
- // In follow mode: older entries go to Static (terminal scrollback).
14
- // Without follow: show last maxActive entries only.
15
- const splitIndex = Math.max(0, displayRows.length - maxActive);
16
- const finalized = follow ? displayRows.slice(0, splitIndex) : [];
17
- const active = displayRows.slice(splitIndex);
18
- if (displayRows.length === 0) {
19
- return _jsx(Text, { dimColor: true, children: "No timeline events yet." });
20
- }
21
- return (_jsxs(Box, { flexDirection: "column", children: [finalized.length > 0 && (_jsx(Static, { items: finalized, children: (entry) => _jsx(TimelineRow, { entry: entry }, entry.id) })), active.map((entry) => (_jsx(TimelineRow, { entry: entry }, entry.id)))] }));
22
- }
@@ -1,77 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import { ItemLine } from "./ItemLine.js";
4
- function formatDuration(startedAt, endedAt) {
5
- const ms = new Date(endedAt).getTime() - new Date(startedAt).getTime();
6
- const seconds = Math.floor(ms / 1000);
7
- if (seconds < 60)
8
- return `${seconds}s`;
9
- const minutes = Math.floor(seconds / 60);
10
- const s = seconds % 60;
11
- return `${minutes}m ${String(s).padStart(2, "0")}s`;
12
- }
13
- const CHECK_SYMBOLS = { passed: "\u2713", failed: "\u2717", pending: "\u25cf" };
14
- const CHECK_COLORS = { passed: "green", failed: "red", pending: "yellow" };
15
- const RUN_LABELS = {
16
- implementation: "implement",
17
- ci_repair: "ci fix",
18
- review_fix: "review fix",
19
- branch_upkeep: "branch upkeep",
20
- queue_repair: "merge fix",
21
- };
22
- function runDotColor(status) {
23
- if (status === "completed")
24
- return "green";
25
- if (status === "failed")
26
- return "red";
27
- if (status === "released")
28
- return "magenta";
29
- if (status === "running")
30
- return "yellow";
31
- return "white";
32
- }
33
- function detailColor(detail) {
34
- if (detail.tone === "command")
35
- return "white";
36
- if (detail.tone === "user")
37
- return "yellow";
38
- return undefined;
39
- }
40
- function detailPrefix(detail) {
41
- if (detail.tone === "command")
42
- return "$ ";
43
- return "";
44
- }
45
- function FeedRow({ entry }) {
46
- const label = entry.feed.status ?? entry.feed.feedKind;
47
- const repeatSuffix = entry.repeatCount && entry.repeatCount > 1 ? ` \u00d7${entry.repeatCount}` : "";
48
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", children: "\u25cf" }), _jsx(Text, { color: "cyan", children: ` ${label}` }), _jsx(Text, { dimColor: true, children: ` ${entry.feed.summary}${repeatSuffix}` })] }));
49
- }
50
- function RunRow({ entry, }) {
51
- const run = entry.run;
52
- const dotColor = runDotColor(run.status);
53
- const duration = run.endedAt ? formatDuration(run.startedAt, run.endedAt) : undefined;
54
- const showItems = entry.items.length > 0;
55
- const showDetails = !showItems && entry.details.length > 0;
56
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: "\u25cf" }), _jsx(Text, { bold: true, color: "yellow", children: ` ${RUN_LABELS[run.runType] ?? run.runType}` }), _jsx(Text, { bold: true, color: dotColor, children: ` ${run.status}` }), duration ? _jsx(Text, { dimColor: true, children: ` ${duration}` }) : null] }), showItems && entry.items.map((itemEntry, index) => (_jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: itemEntry.item }) }, `${entry.id}-item-${index}`))), showDetails && entry.details.map((detail, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { wrap: "wrap", ...(detailColor(detail) ? { color: detailColor(detail) } : { dimColor: true }), bold: detail.tone === "message", children: [detailPrefix(detail), detail.text] }) }, `${entry.id}-detail-${index}`)))] }));
57
- }
58
- function ItemRow({ entry, }) {
59
- return (_jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: entry.item }) }));
60
- }
61
- function CIChecksRow({ entry }) {
62
- const ci = entry.ciChecks;
63
- const dotColor = CHECK_COLORS[ci.overall] ?? "white";
64
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: dotColor, children: "\u25cf" }), _jsx(Text, { color: dotColor, bold: true, children: ` checks` }), _jsx(Text, { children: ` ` }), ci.checks.map((check, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { children: ` ` }) : null, _jsx(Text, { color: CHECK_COLORS[check.status] ?? "white", children: CHECK_SYMBOLS[check.status] ?? " " }), _jsx(Text, { dimColor: true, children: ` ${check.name}` })] }, `c-${i}`)))] }));
65
- }
66
- export function TimelineRow({ entry }) {
67
- switch (entry.kind) {
68
- case "feed":
69
- return _jsx(FeedRow, { entry: entry });
70
- case "run":
71
- return _jsx(RunRow, { entry: entry });
72
- case "item":
73
- return _jsx(ItemRow, { entry: entry });
74
- case "ci-checks":
75
- return _jsx(CIChecksRow, { entry: entry });
76
- }
77
- }