@golba98/codexa 1.0.3 → 1.0.5
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/README.md +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +642 -303
- package/src/commands/handler.ts +7 -18
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +1 -1
- package/src/config/trustStore.ts +1 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/executableResolver.ts +5 -1
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +106 -0
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +64 -18
- package/src/core/providerLauncher/types.ts +12 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +10 -6
- package/src/core/providerRuntime/antigravity.ts +461 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -446
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +40 -15
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +76 -4
- package/src/core/providerRuntime/types.ts +20 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/{channel.ts → version/channel.ts} +1 -1
- package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +66 -98
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +1 -1
- package/src/test/runtimeTestUtils.ts +90 -0
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/chrome/AppShell.tsx +672 -0
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +159 -158
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +54 -38
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +5 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/layout.ts +298 -24
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +3 -3
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +224 -42
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/panels/ProviderPicker.tsx +737 -0
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +5 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +4 -4
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +4 -4
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +17 -23
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{logoVariants.ts → render/logoVariants.ts} +4 -8
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +23 -10
- package/src/ui/{textLayout.ts → render/textLayout.ts} +15 -4
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +1620 -1471
- package/src/ui/timeline/TranscriptShell.tsx +322 -0
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +206 -134
- package/src/core/codex.ts +0 -124
- package/src/ui/AppShell.tsx +0 -706
- package/src/ui/ProviderPicker.tsx +0 -321
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
|
@@ -4,17 +4,17 @@ import type {
|
|
|
4
4
|
RunProgressBlock,
|
|
5
5
|
RunResponseSegment,
|
|
6
6
|
RunToolActivity,
|
|
7
|
-
} from "
|
|
8
|
-
import * as renderDebug from "
|
|
9
|
-
import { getAssistantContent, getResponseSegmentText, getRunPlanText } from "
|
|
10
|
-
import { normalizeCommand, getFriendlyActionLabel } from "
|
|
11
|
-
import { formatTerminalAnswerInline } from "
|
|
12
|
-
import { RUN_OUTPUT_TRUNCATION_NOTICE } from "
|
|
13
|
-
import { sanitizeTerminalLines, sanitizeTerminalOutput } from "
|
|
14
|
-
import { clampVisualText, transcriptContentIndent } from "
|
|
15
|
-
import type { Segment } from "
|
|
16
|
-
import { classifyOutput, formatForBox, normalizeOutput, sanitizeOutput, sanitizeStreamChunk } from "
|
|
17
|
-
import { maybeRenderDiff, type DiffRenderLineType } from "
|
|
7
|
+
} from "../../session/types.js";
|
|
8
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
9
|
+
import { getAssistantContent, getResponseSegmentText, getRunPlanText } from "../../session/types.js";
|
|
10
|
+
import { normalizeCommand, getFriendlyActionLabel } from "../input/commandNormalize.js";
|
|
11
|
+
import { formatTerminalAnswerInline } from "../render/terminalAnswerFormat.js";
|
|
12
|
+
import { RUN_OUTPUT_TRUNCATION_NOTICE } from "../../session/chatLifecycle.js";
|
|
13
|
+
import { sanitizeTerminalLines, sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
14
|
+
import { clampVisualText, transcriptContentIndent } from "../layout.js";
|
|
15
|
+
import type { Segment } from "../render/Markdown.js";
|
|
16
|
+
import { classifyOutput, formatForBox, normalizeOutput, sanitizeOutput, sanitizeStreamChunk } from "../render/outputPipeline.js";
|
|
17
|
+
import { maybeRenderDiff, type DiffRenderLineType } from "../render/diffRenderer.js";
|
|
18
18
|
import {
|
|
19
19
|
formatProgressBlockBodyLines,
|
|
20
20
|
getProgressUpdateCount,
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
type VisibleProgressBlock,
|
|
23
23
|
} from "./progressEntries.js";
|
|
24
24
|
import { selectVisibleRunActivity } from "./runActivityView.js";
|
|
25
|
-
import { getTextUnits, getTextWidth, wrapPlainText, wrapCommandText, splitTextAtColumn } from "
|
|
25
|
+
import { getTextUnits, getTextWidth, wrapPlainText, wrapCommandText, splitTextAtColumn } from "../render/textLayout.js";
|
|
26
26
|
import type { RenderTimelineItem } from "./Timeline.js";
|
|
27
|
-
import { normalizePlanReviewMarkdown } from "
|
|
28
|
-
import {
|
|
27
|
+
import { normalizePlanReviewMarkdown } from "../../core/workspace/planStorage.js";
|
|
28
|
+
import { LOGO_COMPACT, LOGO_COMPACT_MIN_COLS, LOGO_LARGE_MIN_COLS, selectLogoVariant } from "../render/logoVariants.js";
|
|
29
29
|
|
|
30
30
|
// ─── Exported types ───────────────────────────────────────────────────────────
|
|
31
31
|
|
|
@@ -172,6 +172,37 @@ function padSpansToWidth(spans: TimelineRowSpan[], width: number): TimelineRowSp
|
|
|
172
172
|
return next;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Truncates a span list to at most `width` display columns, cutting the
|
|
177
|
+
* overflowing span on a grapheme/display-width boundary. Used as a safety net so
|
|
178
|
+
* a too-wide content row can never push a card border past its declared width.
|
|
179
|
+
*/
|
|
180
|
+
function clampSpansToWidth(spans: TimelineRowSpan[], width: number): TimelineRowSpan[] {
|
|
181
|
+
const safeWidth = Math.max(0, width);
|
|
182
|
+
const result: TimelineRowSpan[] = [];
|
|
183
|
+
let used = 0;
|
|
184
|
+
for (const span of spans) {
|
|
185
|
+
if (used >= safeWidth) break;
|
|
186
|
+
const spanWidth = getTextWidth(span.text);
|
|
187
|
+
if (used + spanWidth <= safeWidth) {
|
|
188
|
+
result.push({ ...span });
|
|
189
|
+
used += spanWidth;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const fitted = splitTextAtColumn(span.text, safeWidth - used).before;
|
|
193
|
+
if (fitted) {
|
|
194
|
+
result.push(cloneSpan(span, fitted));
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Clamp a row to `width` then pad it back out so it occupies exactly `width`. */
|
|
202
|
+
function fitSpansToWidth(spans: TimelineRowSpan[], width: number): TimelineRowSpan[] {
|
|
203
|
+
return padSpansToWidth(clampSpansToWidth(spans, width), width);
|
|
204
|
+
}
|
|
205
|
+
|
|
175
206
|
const ROW_CONTENT_CACHE_LIMIT = 2500;
|
|
176
207
|
const _rowContentCache = new Map<string, TimelineRow>();
|
|
177
208
|
|
|
@@ -432,14 +463,14 @@ function buildDashCardRows(params: {
|
|
|
432
463
|
return { ...span, tone: borderTone };
|
|
433
464
|
});
|
|
434
465
|
|
|
435
|
-
const rows: TimelineRow[] = [createRow(`${params.keyPrefix}-top`, topRow, width)];
|
|
466
|
+
const rows: TimelineRow[] = [createRow(`${params.keyPrefix}-top`, fitSpansToWidth(topRow, width), width)];
|
|
436
467
|
|
|
437
468
|
params.contentRows.forEach((row, index) => {
|
|
438
469
|
rows.push(createRow(
|
|
439
470
|
`${params.keyPrefix}-content-${index}`,
|
|
440
471
|
[
|
|
441
472
|
createSpan("│ ", borderTone),
|
|
442
|
-
...
|
|
473
|
+
...fitSpansToWidth(row, contentWidth),
|
|
443
474
|
createSpan(" │", borderTone),
|
|
444
475
|
],
|
|
445
476
|
width,
|
|
@@ -486,7 +517,7 @@ function buildPanelRows(params: {
|
|
|
486
517
|
`${params.keyPrefix}-content-${index}`,
|
|
487
518
|
[
|
|
488
519
|
createSpan("│ ", "borderActive"),
|
|
489
|
-
...
|
|
520
|
+
...fitSpansToWidth(row, contentWidth),
|
|
490
521
|
createSpan(" │", "borderActive"),
|
|
491
522
|
],
|
|
492
523
|
width,
|
|
@@ -1537,18 +1568,19 @@ export function buildStandaloneEventRows(item: Extract<RenderTimelineItem, { typ
|
|
|
1537
1568
|
return rows;
|
|
1538
1569
|
}
|
|
1539
1570
|
|
|
1540
|
-
export function buildIntroRows(item: Extract<RenderTimelineItem, { type: "intro" }>, width: number): TimelineRow[] {
|
|
1571
|
+
export function buildIntroRows(item: Extract<RenderTimelineItem, { type: "intro" }>, width: number): TimelineRow[] {
|
|
1541
1572
|
const rows: TimelineRow[] = [];
|
|
1542
1573
|
const { intro } = item;
|
|
1543
1574
|
const safeWidth = Math.max(10, width);
|
|
1544
1575
|
const startupHeaderMode = intro.startupHeaderMode
|
|
1545
|
-
?? (intro.layoutMode === "
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1576
|
+
?? (intro.layoutMode === "expanded" ? "large" : "compact");
|
|
1577
|
+
const workspaceName = getWorkspaceDisplayName(intro.workspaceLabel);
|
|
1578
|
+
if (startupHeaderMode === "tiny") {
|
|
1579
|
+
const messageRows = [
|
|
1580
|
+
`Codexa v${intro.version}`,
|
|
1581
|
+
workspaceName ? `Workspace: ${workspaceName}` : null,
|
|
1582
|
+
intro.providerLabel ? `Provider: ${intro.providerLabel}` : `Auth: ${intro.authLabel}`,
|
|
1583
|
+
].filter((line): line is string => Boolean(line));
|
|
1552
1584
|
messageRows.forEach((line, index) => {
|
|
1553
1585
|
rows.push(createRow(
|
|
1554
1586
|
`${item.key}-resize-${index}`,
|
|
@@ -1559,16 +1591,20 @@ export function buildIntroRows(item: Extract<RenderTimelineItem, { type: "intro"
|
|
|
1559
1591
|
return rows;
|
|
1560
1592
|
}
|
|
1561
1593
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1594
|
+
// Compact startup mode deliberately uses the one-line mark even when the
|
|
1595
|
+
// terminal is wide: its row budget is what made the full logo unsafe.
|
|
1596
|
+
const logoRows = startupHeaderMode === "large"
|
|
1597
|
+
? selectLogoVariant(safeWidth)
|
|
1598
|
+
: safeWidth >= LOGO_COMPACT_MIN_COLS ? LOGO_COMPACT : [];
|
|
1599
|
+
const effectiveLogoRows = logoRows.length > 0 ? logoRows : ["CODEXA"];
|
|
1600
|
+
if (startupHeaderMode === "large") {
|
|
1601
|
+
rows.push(createBlankRow(`${item.key}-top-gap`, safeWidth));
|
|
1602
|
+
}
|
|
1566
1603
|
const logoWidth = effectiveLogoRows.reduce((maxWidth, line) => Math.max(maxWidth, getTextWidth(line)), 0);
|
|
1567
|
-
const
|
|
1568
|
-
const metaLines = [
|
|
1604
|
+
const metaLines = [
|
|
1569
1605
|
`Codexa v${intro.version}`,
|
|
1570
|
-
`Auth: ${intro.authLabel}`,
|
|
1571
1606
|
workspaceName ? `Workspace: ${workspaceName}` : null,
|
|
1607
|
+
intro.providerLabel ? `Provider: ${intro.providerLabel}` : `Auth: ${intro.authLabel}`,
|
|
1572
1608
|
].filter((line): line is string => Boolean(line));
|
|
1573
1609
|
const gapWidth = 2;
|
|
1574
1610
|
const widestMetaLine = metaLines.reduce((maxWidth, line) => Math.max(maxWidth, getTextWidth(line)), 0);
|
|
@@ -1591,6 +1627,8 @@ export function buildIntroRows(item: Extract<RenderTimelineItem, { type: "intro"
|
|
|
1591
1627
|
if (effectiveLogoRows.length === 6) {
|
|
1592
1628
|
if (rowIndex === 2 || rowIndex === 3) logoTone = "logoSecondary";
|
|
1593
1629
|
else if (rowIndex === 4 || rowIndex === 5) logoTone = "logoShadow";
|
|
1630
|
+
} else if (effectiveLogoRows === LOGO_COMPACT) {
|
|
1631
|
+
logoTone = "accent";
|
|
1594
1632
|
}
|
|
1595
1633
|
// No bold on logo spans — bold on block/box-drawing chars causes spacing artifacts.
|
|
1596
1634
|
const spans = [
|
|
@@ -1614,6 +1652,8 @@ export function buildIntroRows(item: Extract<RenderTimelineItem, { type: "intro"
|
|
|
1614
1652
|
if (effectiveLogoRows.length === 6) {
|
|
1615
1653
|
if (index === 2 || index === 3) logoTone = "logoSecondary";
|
|
1616
1654
|
else if (index === 4 || index === 5) logoTone = "logoShadow";
|
|
1655
|
+
} else if (effectiveLogoRows === LOGO_COMPACT) {
|
|
1656
|
+
logoTone = "accent";
|
|
1617
1657
|
}
|
|
1618
1658
|
rows.push(createRow(
|
|
1619
1659
|
`${item.key}-logo-${index}`,
|
|
@@ -1694,7 +1734,7 @@ function applyTurnOpacity(rows: TimelineRow[], opacity: "active" | "recent" | "d
|
|
|
1694
1734
|
// ─── Stream event types ───────────────────────────────────────────────────────
|
|
1695
1735
|
|
|
1696
1736
|
export type StreamEvent =
|
|
1697
|
-
| { kind: "thinking"; streamSeq: number; block: RunProgressBlock
|
|
1737
|
+
| { kind: "thinking"; streamSeq: number; block: RunProgressBlock }
|
|
1698
1738
|
| { kind: "response"; streamSeq: number; segment: RunResponseSegment }
|
|
1699
1739
|
| { kind: "action"; streamSeq: number; tool: RunToolActivity }
|
|
1700
1740
|
| { kind: "actionSummary"; streamSeq: number; id: string; label: string; count: number }
|
|
@@ -1711,8 +1751,24 @@ function getCompactableActionLabel(event: StreamEvent): string | null {
|
|
|
1711
1751
|
return label === "Read file" || label === "List files" ? label : null;
|
|
1712
1752
|
}
|
|
1713
1753
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1754
|
+
/**
|
|
1755
|
+
* Collapse bursts of same-label completed action cards into a single summary
|
|
1756
|
+
* line. This is a *height-reducing* transform, so it must only run for a
|
|
1757
|
+
* FINISHED turn: applying it while the run is still live shrinks the turn's
|
|
1758
|
+
* total height mid-stream, and the bottom-anchored viewport then re-reveals
|
|
1759
|
+
* earlier (already-scrolled-off) content — the "old states come back" glitch.
|
|
1760
|
+
*
|
|
1761
|
+
* Callers pass `finalized = run.status !== "running"`. We deliberately key off
|
|
1762
|
+
* `run.status` rather than the render phase: `resolveTurnRunPhase` reports
|
|
1763
|
+
* "final" during the ANSWER_VISIBLE window while the run is still running, so a
|
|
1764
|
+
* phase-based gate would compact during that intermediate, still-active frame.
|
|
1765
|
+
*/
|
|
1766
|
+
export function compactActionBursts(
|
|
1767
|
+
events: StreamEvent[],
|
|
1768
|
+
verbose: boolean,
|
|
1769
|
+
finalized: boolean,
|
|
1770
|
+
): StreamEvent[] {
|
|
1771
|
+
if (verbose || !finalized) return events;
|
|
1716
1772
|
|
|
1717
1773
|
const compacted: StreamEvent[] = [];
|
|
1718
1774
|
for (let index = 0; index < events.length;) {
|
|
@@ -1774,13 +1830,11 @@ function buildCodexThinkingRows(params: {
|
|
|
1774
1830
|
keyPrefix: string;
|
|
1775
1831
|
width: number;
|
|
1776
1832
|
event: Extract<StreamEvent, { kind: "thinking" }>;
|
|
1777
|
-
isLive: boolean;
|
|
1778
1833
|
verbose: boolean;
|
|
1779
1834
|
}): TimelineRow[] {
|
|
1780
1835
|
renderDebug.traceRender("ThinkingBlock", params.event.block.status, {
|
|
1781
1836
|
keyPrefix: params.keyPrefix,
|
|
1782
1837
|
streamSeq: params.event.streamSeq,
|
|
1783
|
-
isLive: params.isLive,
|
|
1784
1838
|
textLength: params.event.block.text.length,
|
|
1785
1839
|
});
|
|
1786
1840
|
|
|
@@ -1794,8 +1848,6 @@ function buildCodexThinkingRows(params: {
|
|
|
1794
1848
|
textCacheToken(block.text),
|
|
1795
1849
|
params.width,
|
|
1796
1850
|
params.verbose,
|
|
1797
|
-
params.isLive,
|
|
1798
|
-
params.event.isActive,
|
|
1799
1851
|
]);
|
|
1800
1852
|
|
|
1801
1853
|
return getCachedStreamingBlockRows(cacheKey, () => {
|
|
@@ -1816,10 +1868,6 @@ function buildCodexThinkingRows(params: {
|
|
|
1816
1868
|
]);
|
|
1817
1869
|
}
|
|
1818
1870
|
|
|
1819
|
-
if (params.isLive && params.event.isActive) {
|
|
1820
|
-
contentRows.push([createSpan("▌", "accent")]);
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
1871
|
return buildCodexPlainRows(params.keyPrefix, params.width, contentRows);
|
|
1824
1872
|
});
|
|
1825
1873
|
}
|
|
@@ -1849,10 +1897,15 @@ function getActionDisplayDescriptor(params: {
|
|
|
1849
1897
|
isLive: boolean;
|
|
1850
1898
|
borderTone: TimelineTone;
|
|
1851
1899
|
}): ActionDisplayDescriptor {
|
|
1852
|
-
|
|
1900
|
+
// Strip ANSI/control sequences before measuring or wrapping: string-width only
|
|
1901
|
+
// collapses *complete* escape sequences, so leftover bytes would otherwise be
|
|
1902
|
+
// counted (and wrapped) character-by-character and corrupt the card width.
|
|
1903
|
+
const command = normalizeCommand(sanitizeTerminalOutput(params.tool.command));
|
|
1853
1904
|
const label = getFriendlyActionLabel(command);
|
|
1905
|
+
// Bare label (no leading gap) — the head-row builder right-aligns it and owns
|
|
1906
|
+
// the spacing, so the gap can never get baked into a width calculation.
|
|
1854
1907
|
const duration = params.tool.completedAt != null
|
|
1855
|
-
?
|
|
1908
|
+
? formatDuration(params.tool.completedAt - params.tool.startedAt)
|
|
1856
1909
|
: "";
|
|
1857
1910
|
const summary = params.verbose ? params.tool.summary ?? "" : "";
|
|
1858
1911
|
const showLiveCursor = params.isLive && params.tool.status === "running";
|
|
@@ -1879,7 +1932,7 @@ function getActionDisplayDescriptor(params: {
|
|
|
1879
1932
|
return descriptor;
|
|
1880
1933
|
}
|
|
1881
1934
|
|
|
1882
|
-
function buildPlainActionDebugRows(params: {
|
|
1935
|
+
function buildPlainActionDebugRows(params: {
|
|
1883
1936
|
keyPrefix: string;
|
|
1884
1937
|
width: number;
|
|
1885
1938
|
descriptor: ActionDisplayDescriptor;
|
|
@@ -1887,7 +1940,7 @@ function buildPlainActionDebugRows(params: {
|
|
|
1887
1940
|
const statusText = params.descriptor.label
|
|
1888
1941
|
? `${params.descriptor.label}: ${params.descriptor.command}`
|
|
1889
1942
|
: params.descriptor.command;
|
|
1890
|
-
const suffix = params.descriptor.duration ? params.descriptor.duration : "";
|
|
1943
|
+
const suffix = params.descriptor.duration ? ` ${params.descriptor.duration}` : "";
|
|
1891
1944
|
const text = clampVisualText(`${params.descriptor.icon} ${statusText}${suffix}`, Math.max(1, params.width - 1));
|
|
1892
1945
|
renderDebug.traceEvent("action", "plainActionRow", {
|
|
1893
1946
|
actionId: params.descriptor.id,
|
|
@@ -1903,10 +1956,56 @@ function buildPlainActionDebugRows(params: {
|
|
|
1903
1956
|
],
|
|
1904
1957
|
params.width,
|
|
1905
1958
|
),
|
|
1906
|
-
];
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1959
|
+
];
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function compactActionText(descriptor: ActionDisplayDescriptor): string {
|
|
1963
|
+
const command = descriptor.command.replace(/^([a-z][a-z0-9_]*):\s+/i, "$1 ");
|
|
1964
|
+
return descriptor.label && command === descriptor.command
|
|
1965
|
+
? `${descriptor.label} ${command}`
|
|
1966
|
+
: command;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function buildCompactActionRows(params: {
|
|
1970
|
+
keyPrefix: string;
|
|
1971
|
+
width: number;
|
|
1972
|
+
descriptor: ActionDisplayDescriptor;
|
|
1973
|
+
}): TimelineRow[] {
|
|
1974
|
+
const durationSuffix = params.descriptor.duration ? ` ${params.descriptor.duration}` : "";
|
|
1975
|
+
const liveSuffix = params.descriptor.showLiveCursor ? " ▌" : "";
|
|
1976
|
+
const availableWidth = Math.max(1, params.width - getTextWidth(params.descriptor.icon) - 1 - getTextWidth(durationSuffix) - getTextWidth(liveSuffix));
|
|
1977
|
+
const text = clampVisualText(compactActionText(params.descriptor), availableWidth);
|
|
1978
|
+
const rows: TimelineRow[] = [
|
|
1979
|
+
createRow(
|
|
1980
|
+
`${params.keyPrefix}-plain`,
|
|
1981
|
+
[
|
|
1982
|
+
createSpan(`${params.descriptor.icon} `, params.descriptor.iconTone),
|
|
1983
|
+
createSpan(text || " ", "text"),
|
|
1984
|
+
...(durationSuffix ? [createSpan(durationSuffix, "dim")] : []),
|
|
1985
|
+
...(liveSuffix ? [createSpan(liveSuffix, "accent")] : []),
|
|
1986
|
+
],
|
|
1987
|
+
params.width,
|
|
1988
|
+
),
|
|
1989
|
+
];
|
|
1990
|
+
|
|
1991
|
+
if (params.descriptor.verbose) {
|
|
1992
|
+
const detail = params.descriptor.showLiveCursor
|
|
1993
|
+
? "running"
|
|
1994
|
+
: params.descriptor.summary.trim() || "completed";
|
|
1995
|
+
rows.push(createRow(
|
|
1996
|
+
`${params.keyPrefix}-detail`,
|
|
1997
|
+
[
|
|
1998
|
+
createSpan(" "),
|
|
1999
|
+
createSpan(clampVisualText(detail, Math.max(1, params.width - 2)), "muted"),
|
|
2000
|
+
],
|
|
2001
|
+
params.width,
|
|
2002
|
+
));
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
return rows;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
export function buildActionEventRows(params: {
|
|
1910
2009
|
keyPrefix: string;
|
|
1911
2010
|
width: number;
|
|
1912
2011
|
event: Extract<StreamEvent, { kind: "action" }>;
|
|
@@ -1977,58 +2076,11 @@ export function buildActionEventRows(params: {
|
|
|
1977
2076
|
});
|
|
1978
2077
|
}
|
|
1979
2078
|
|
|
1980
|
-
const buildActionRows = () => {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
: descriptor.summary.trim()
|
|
1986
|
-
? descriptor.summary
|
|
1987
|
-
: " ";
|
|
1988
|
-
const contentRows: TimelineRowSpan[][] = [];
|
|
1989
|
-
|
|
1990
|
-
if (descriptor.label) {
|
|
1991
|
-
contentRows.push([
|
|
1992
|
-
createSpan(`${descriptor.icon} `, descriptor.iconTone),
|
|
1993
|
-
createSpan(descriptor.label, "text"),
|
|
1994
|
-
...(descriptor.duration ? [createSpan(descriptor.duration, "dim")] : []),
|
|
1995
|
-
]);
|
|
1996
|
-
wrapPlainText(descriptor.command, commandWidth).forEach((row) => {
|
|
1997
|
-
contentRows.push([
|
|
1998
|
-
createSpan(" "),
|
|
1999
|
-
createSpan(row || " ", "muted"),
|
|
2000
|
-
]);
|
|
2001
|
-
});
|
|
2002
|
-
} else {
|
|
2003
|
-
const headRows = wrapPlainText(descriptor.command, commandWidth);
|
|
2004
|
-
headRows.forEach((row, rowIndex) => {
|
|
2005
|
-
contentRows.push([
|
|
2006
|
-
createSpan(rowIndex === 0 ? `${descriptor.icon} ` : " ", rowIndex === 0 ? descriptor.iconTone : undefined),
|
|
2007
|
-
createSpan(row || " ", "text"),
|
|
2008
|
-
...(rowIndex === 0 && descriptor.duration ? [createSpan(descriptor.duration, "dim")] : []),
|
|
2009
|
-
]);
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
if (!descriptor.label) {
|
|
2014
|
-
contentRows.push([
|
|
2015
|
-
createSpan(" "),
|
|
2016
|
-
createSpan(" ", "muted"),
|
|
2017
|
-
]);
|
|
2018
|
-
}
|
|
2019
|
-
contentRows.push([
|
|
2020
|
-
createSpan(" "),
|
|
2021
|
-
createSpan(detailText, descriptor.showLiveCursor ? "accent" : "muted"),
|
|
2022
|
-
]);
|
|
2023
|
-
|
|
2024
|
-
return buildDashCardRows({
|
|
2025
|
-
keyPrefix: params.keyPrefix,
|
|
2026
|
-
width: params.width,
|
|
2027
|
-
title: "action",
|
|
2028
|
-
borderTone: descriptor.borderTone,
|
|
2029
|
-
contentRows: contentRows.length > 0 ? contentRows : [[createSpan(" ")]],
|
|
2030
|
-
});
|
|
2031
|
-
};
|
|
2079
|
+
const buildActionRows = () => buildCompactActionRows({
|
|
2080
|
+
keyPrefix: params.keyPrefix,
|
|
2081
|
+
width: params.width,
|
|
2082
|
+
descriptor,
|
|
2083
|
+
});
|
|
2032
2084
|
|
|
2033
2085
|
if (isCompleted) {
|
|
2034
2086
|
const rows = buildActionRows();
|
|
@@ -2178,13 +2230,11 @@ function buildApprovedPlanRows(params: {
|
|
|
2178
2230
|
|
|
2179
2231
|
function buildUnifiedStreamRows(item: Extract<RenderTimelineItem, { type: "turn" }>, width: number, options: { verbose?: boolean; workspaceRoot?: string | null }): TimelineRow[] {
|
|
2180
2232
|
const run = item.item.run!;
|
|
2181
|
-
const assistant = item.item.assistant;
|
|
2182
2233
|
const streaming = item.renderState.runPhase === "streaming";
|
|
2183
|
-
const dim = item.renderState.opacity !== "active";
|
|
2184
|
-
const borderTone = dim ? "borderSubtle" : streaming ? "borderActive" : "borderSubtle";
|
|
2185
2234
|
const actionBorderTone = item.renderState.opacity === "dim" ? "borderSubtle" : "borderActive";
|
|
2186
2235
|
const verbose = options.verbose ?? false;
|
|
2187
|
-
const
|
|
2236
|
+
const finalized = run.status !== "running";
|
|
2237
|
+
const events = compactActionBursts(collectStreamEvents(item), verbose, finalized);
|
|
2188
2238
|
|
|
2189
2239
|
const rows: TimelineRow[] = [];
|
|
2190
2240
|
|
|
@@ -2193,7 +2243,9 @@ function buildUnifiedStreamRows(item: Extract<RenderTimelineItem, { type: "turn"
|
|
|
2193
2243
|
const isLive = run.status === "running" && isLastEvent; // The cursor is on the last event
|
|
2194
2244
|
|
|
2195
2245
|
if (index > 0) {
|
|
2196
|
-
|
|
2246
|
+
// Key the gap by the stable creation-order streamSeq, not the array
|
|
2247
|
+
// index, so gaps don't remount/reorder when the event set changes.
|
|
2248
|
+
rows.push(createBlankRow(`${item.key}-stream-gap-${event.streamSeq}`, width));
|
|
2197
2249
|
}
|
|
2198
2250
|
|
|
2199
2251
|
if (event.kind === "thinking") {
|
|
@@ -2201,7 +2253,6 @@ function buildUnifiedStreamRows(item: Extract<RenderTimelineItem, { type: "turn"
|
|
|
2201
2253
|
keyPrefix: `${item.key}-codex-thinking-${event.streamSeq}`,
|
|
2202
2254
|
width,
|
|
2203
2255
|
event,
|
|
2204
|
-
isLive,
|
|
2205
2256
|
verbose,
|
|
2206
2257
|
}));
|
|
2207
2258
|
} else if (event.kind === "action") {
|
|
@@ -2242,7 +2293,7 @@ function buildUnifiedStreamRows(item: Extract<RenderTimelineItem, { type: "turn"
|
|
|
2242
2293
|
}
|
|
2243
2294
|
});
|
|
2244
2295
|
|
|
2245
|
-
if (!streaming &&
|
|
2296
|
+
if (!streaming && finalized) {
|
|
2246
2297
|
if (run.status === "canceled") {
|
|
2247
2298
|
rows.push(createBlankRow(`${item.key}-cancel-gap`, width));
|
|
2248
2299
|
rows.push(...buildCodexPlainRows(
|
|
@@ -2281,12 +2332,10 @@ function buildUnifiedStreamRows(item: Extract<RenderTimelineItem, { type: "turn"
|
|
|
2281
2332
|
return rows;
|
|
2282
2333
|
}
|
|
2283
2334
|
|
|
2284
|
-
function collectStreamEvents(
|
|
2285
|
-
item: Extract<RenderTimelineItem, { type: "turn" }>,
|
|
2286
|
-
streaming: boolean,
|
|
2287
|
-
): StreamEvent[] {
|
|
2335
|
+
function collectStreamEvents(item: Extract<RenderTimelineItem, { type: "turn" }>): StreamEvent[] {
|
|
2288
2336
|
const run = item.item.run!;
|
|
2289
2337
|
const assistant = item.item.assistant;
|
|
2338
|
+
const streaming = item.renderState.runPhase === "streaming";
|
|
2290
2339
|
const blocksById = new Map<string, RunProgressBlock>();
|
|
2291
2340
|
for (const entry of run.progressEntries ?? []) {
|
|
2292
2341
|
for (const block of entry.blocks) blocksById.set(block.id, block);
|
|
@@ -2298,14 +2347,23 @@ function collectStreamEvents(
|
|
|
2298
2347
|
const sortedItems = (run.streamItems ?? []).slice().sort((a, b) => a.streamSeq - b.streamSeq);
|
|
2299
2348
|
for (const it of sortedItems) {
|
|
2300
2349
|
if (it.kind === "thinking") {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2350
|
+
// Active-turn topology stability: while the run is live we never surface
|
|
2351
|
+
// reasoning blocks. A thinking block is assigned its streamSeq early (when
|
|
2352
|
+
// its reasoning first streams) but only *completes* later — revealing it
|
|
2353
|
+
// mid-stream slots it in at that early streamSeq, ABOVE answer/action
|
|
2354
|
+
// blocks that have already streamed at higher streamSeqs. That late
|
|
2355
|
+
// insert-above is what reorders the live turn. Defer all reasoning to
|
|
2356
|
+
// finalize, where the full streamSeq order (reasoning included) reflows
|
|
2357
|
+
// atomically. (Height grows when it appears — never shrinks mid-stream.)
|
|
2358
|
+
if (run.status !== "running") {
|
|
2359
|
+
const block = blocksById.get(it.refId);
|
|
2360
|
+
if (block && block.text.trim().length > 0) {
|
|
2361
|
+
events.push({
|
|
2362
|
+
kind: "thinking",
|
|
2363
|
+
streamSeq: it.streamSeq,
|
|
2364
|
+
block,
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2309
2367
|
}
|
|
2310
2368
|
} else if (it.kind === "action") {
|
|
2311
2369
|
const tool = toolsById.get(it.refId);
|
|
@@ -2336,13 +2394,15 @@ function collectStreamEvents(
|
|
|
2336
2394
|
if (!VISIBLE_THINKING_SOURCES.has(entry.source)) continue;
|
|
2337
2395
|
for (const block of entry.blocks) {
|
|
2338
2396
|
if (!block.text.trim()) continue;
|
|
2339
|
-
|
|
2397
|
+
// Same active-turn topology rule as the streamItems path: defer all
|
|
2398
|
+
// reasoning while the run is live so it cannot insert above already
|
|
2399
|
+
// streamed answer/action blocks. Reveal only once finalized.
|
|
2400
|
+
if (run.status === "running") continue;
|
|
2340
2401
|
legacySeq += 1;
|
|
2341
2402
|
events.push({
|
|
2342
2403
|
kind: "thinking",
|
|
2343
2404
|
streamSeq: legacySeq,
|
|
2344
2405
|
block,
|
|
2345
|
-
isActive: run.status === "running" && block.status === "active",
|
|
2346
2406
|
});
|
|
2347
2407
|
}
|
|
2348
2408
|
}
|
|
@@ -2477,8 +2537,10 @@ function buildStableIntroRows(item: Extract<RenderTimelineItem, { type: "intro"
|
|
|
2477
2537
|
innerWidth,
|
|
2478
2538
|
item.intro.version,
|
|
2479
2539
|
item.intro.layoutMode,
|
|
2540
|
+
item.intro.startupHeaderMode ?? "",
|
|
2480
2541
|
item.intro.authLabel,
|
|
2481
2542
|
textCacheToken(item.intro.workspaceLabel),
|
|
2543
|
+
item.intro.providerLabel ?? "",
|
|
2482
2544
|
]);
|
|
2483
2545
|
return getCachedFrozenRows(cacheKey, () => buildIntroRows(item, innerWidth));
|
|
2484
2546
|
}
|
|
@@ -2552,10 +2614,9 @@ function buildStableActiveTurnGroups(
|
|
|
2552
2614
|
}
|
|
2553
2615
|
|
|
2554
2616
|
const streaming = item.renderState.runPhase === "streaming";
|
|
2555
|
-
const dim = item.renderState.opacity !== "active";
|
|
2556
|
-
const borderTone = dim ? "borderSubtle" : streaming ? "borderActive" : "borderSubtle";
|
|
2557
2617
|
const actionBorderTone = item.renderState.opacity === "dim" ? "borderSubtle" : "borderActive";
|
|
2558
|
-
const
|
|
2618
|
+
const finalized = run.status !== "running";
|
|
2619
|
+
const events = compactActionBursts(collectStreamEvents(item), verbose, finalized);
|
|
2559
2620
|
let orderedRows = [...getCachedFrozenRows(rowCacheKey([
|
|
2560
2621
|
"stable-active-user",
|
|
2561
2622
|
item.key,
|
|
@@ -2571,7 +2632,9 @@ function buildStableActiveTurnGroups(
|
|
|
2571
2632
|
const isLastEvent = index === events.length - 1;
|
|
2572
2633
|
|
|
2573
2634
|
if (index > 0) {
|
|
2574
|
-
|
|
2635
|
+
// Stable creation-order key (streamSeq), not array index — matches the
|
|
2636
|
+
// native path and avoids index-based remount when events change.
|
|
2637
|
+
targetRows.push(createBlankRow(`${item.key}-stream-gap-${event.streamSeq}`, innerWidth));
|
|
2575
2638
|
}
|
|
2576
2639
|
|
|
2577
2640
|
if (event.kind === "thinking") {
|
|
@@ -2579,7 +2642,6 @@ function buildStableActiveTurnGroups(
|
|
|
2579
2642
|
keyPrefix: `${item.key}-codex-thinking-${event.streamSeq}`,
|
|
2580
2643
|
width: innerWidth,
|
|
2581
2644
|
event,
|
|
2582
|
-
isLive: liveEvent,
|
|
2583
2645
|
verbose,
|
|
2584
2646
|
});
|
|
2585
2647
|
targetRows.push(...(liveEvent ? build() : getCachedFrozenRows(rowCacheKey([
|
|
@@ -2708,7 +2770,6 @@ function buildNativeStreamEventRows(params: {
|
|
|
2708
2770
|
keyPrefix: `${item.key}-codex-thinking-${event.streamSeq}`,
|
|
2709
2771
|
width: innerWidth,
|
|
2710
2772
|
event,
|
|
2711
|
-
isLive: !params.forceStable && isNativeLiveStreamEvent(event, run),
|
|
2712
2773
|
verbose,
|
|
2713
2774
|
}));
|
|
2714
2775
|
} else if (event.kind === "action") {
|
|
@@ -2795,8 +2856,9 @@ function appendNativeTurnParts(
|
|
|
2795
2856
|
if (!run) return;
|
|
2796
2857
|
|
|
2797
2858
|
const events = compactActionBursts(
|
|
2798
|
-
collectStreamEvents(item
|
|
2859
|
+
collectStreamEvents(item),
|
|
2799
2860
|
verbose,
|
|
2861
|
+
run.status !== "running",
|
|
2800
2862
|
);
|
|
2801
2863
|
|
|
2802
2864
|
events.forEach((event, eventIndex) => {
|
|
@@ -2975,7 +3037,7 @@ export function buildTimelineSnapshot(
|
|
|
2975
3037
|
let builtRows: TimelineRow[];
|
|
2976
3038
|
|
|
2977
3039
|
if (item.type === "intro") {
|
|
2978
|
-
const cacheKey = `i:${item.key}:${innerWidth}:${item.intro.version}:${item.intro.layoutMode}:${item.intro.startupHeaderMode ?? ""}:${item.intro.authLabel}:${item.intro.workspaceLabel}:v${LOGO_LARGE_MIN_COLS}`;
|
|
3040
|
+
const cacheKey = `i:${item.key}:${innerWidth}:${item.intro.version}:${item.intro.layoutMode}:${item.intro.startupHeaderMode ?? ""}:${item.intro.authLabel}:${item.intro.workspaceLabel}:${item.intro.providerLabel ?? ""}:v${LOGO_LARGE_MIN_COLS}`;
|
|
2979
3041
|
const cached = _staticRowCache.get(cacheKey);
|
|
2980
3042
|
if (cached) {
|
|
2981
3043
|
renderDebug.traceEvent("timeline", "rowGeneration", {
|
|
@@ -2999,8 +3061,18 @@ export function buildTimelineSnapshot(
|
|
|
2999
3061
|
builtRows = r;
|
|
3000
3062
|
}
|
|
3001
3063
|
} else if (item.type === "event") {
|
|
3002
|
-
// Standalone events
|
|
3003
|
-
const cacheKey =
|
|
3064
|
+
// Standalone events are immutable for a given event payload, not just id.
|
|
3065
|
+
const cacheKey = rowCacheKey([
|
|
3066
|
+
"event",
|
|
3067
|
+
item.key,
|
|
3068
|
+
item.event.type,
|
|
3069
|
+
item.event.id,
|
|
3070
|
+
innerWidth,
|
|
3071
|
+
textCacheToken("title" in item.event ? item.event.title : item.event.command),
|
|
3072
|
+
textCacheToken("content" in item.event ? item.event.content : item.event.summary ?? ""),
|
|
3073
|
+
"status" in item.event ? item.event.status : "",
|
|
3074
|
+
"durationMs" in item.event ? item.event.durationMs : "",
|
|
3075
|
+
]);
|
|
3004
3076
|
const cached = _staticRowCache.get(cacheKey);
|
|
3005
3077
|
if (cached) {
|
|
3006
3078
|
renderDebug.traceEvent("timeline", "rowGeneration", {
|