ubume 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import React, { memo, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { Box, Static, Text } from "ink";
|
|
3
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
4
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
5
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
6
|
+
import type { TimelineEvent, UIState } from "../../session/types.js";
|
|
7
|
+
import {
|
|
8
|
+
buildActiveRenderItems,
|
|
9
|
+
buildIntroRenderItem,
|
|
10
|
+
buildStaticRenderItems,
|
|
11
|
+
buildTimelineItems,
|
|
12
|
+
TimelineRowView,
|
|
13
|
+
} from "./Timeline.js";
|
|
14
|
+
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "./timelineMeasure.js";
|
|
15
|
+
import { LIVE_WINDOW_SAFETY_ROWS, windowLiveRows } from "./liveViewportWindow.js";
|
|
16
|
+
import { buildStaticTranscript, createStaticTranscriptCache, type StaticTranscriptCache } from "./staticTranscriptCache.js";
|
|
17
|
+
import { getShellHeight, getShellWidth, resolveStartupHeaderMode, type TerminalViewport } from "../layout.js";
|
|
18
|
+
import { LOGO_COMPACT, LOGO_COMPACT_MIN_COLS, LOGO_LARGE, LOGO_MEDIUM, selectLogoVariant } from "../render/logoVariants.js";
|
|
19
|
+
import { useTheme } from "../theme.js";
|
|
20
|
+
|
|
21
|
+
export interface TranscriptShellProps {
|
|
22
|
+
layout: TerminalViewport;
|
|
23
|
+
authState: CodexAuthState;
|
|
24
|
+
workspaceLabel: string;
|
|
25
|
+
workspaceRoot?: string | null;
|
|
26
|
+
runtimeSummary?: RuntimeSummary | null;
|
|
27
|
+
staticEvents: TimelineEvent[];
|
|
28
|
+
activeEvents: TimelineEvent[];
|
|
29
|
+
uiState: UIState;
|
|
30
|
+
composer: React.ReactNode;
|
|
31
|
+
composerRows?: number;
|
|
32
|
+
notice?: string | null;
|
|
33
|
+
verboseMode?: boolean;
|
|
34
|
+
clearCount?: number;
|
|
35
|
+
repaintGeneration?: number;
|
|
36
|
+
visible?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isTranscriptEvent(event: TimelineEvent): boolean {
|
|
40
|
+
return event.type === "user" || event.type === "assistant" || event.type === "run" || event.type === "shell";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isHomeScreenState({
|
|
44
|
+
staticEvents,
|
|
45
|
+
activeEvents,
|
|
46
|
+
uiState,
|
|
47
|
+
}: {
|
|
48
|
+
staticEvents: TimelineEvent[];
|
|
49
|
+
activeEvents: TimelineEvent[];
|
|
50
|
+
uiState: UIState;
|
|
51
|
+
}): boolean {
|
|
52
|
+
return uiState.kind === "IDLE"
|
|
53
|
+
&& !staticEvents.some(isTranscriptEvent)
|
|
54
|
+
&& !activeEvents.some(isTranscriptEvent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getLogoVariantName(rows: readonly string[]): "large" | "medium" | "compact" | "wordmark" | "none" {
|
|
58
|
+
if (rows.length === 0) return process.env["UBUME_NO_ASCII_LOGO"] === "1" ? "none" : "wordmark";
|
|
59
|
+
if (rows === LOGO_LARGE || rows.join("\n") === LOGO_LARGE.join("\n")) return "large";
|
|
60
|
+
if (rows === LOGO_MEDIUM || rows.join("\n") === LOGO_MEDIUM.join("\n")) return "medium";
|
|
61
|
+
if (rows === LOGO_COMPACT || rows.join("\n") === LOGO_COMPACT.join("\n")) return "compact";
|
|
62
|
+
return "wordmark";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getLogoHiddenReason({
|
|
66
|
+
startupHeaderMode,
|
|
67
|
+
logoVariant,
|
|
68
|
+
width,
|
|
69
|
+
}: {
|
|
70
|
+
startupHeaderMode: ReturnType<typeof resolveStartupHeaderMode>;
|
|
71
|
+
logoVariant: ReturnType<typeof getLogoVariantName>;
|
|
72
|
+
width: number;
|
|
73
|
+
}): string | null {
|
|
74
|
+
if (startupHeaderMode === "tiny") return "terminal-too-small";
|
|
75
|
+
if (process.env["UBUME_NO_ASCII_LOGO"] === "1") return "UBUME_NO_ASCII_LOGO";
|
|
76
|
+
if (logoVariant === "wordmark") return `no-ascii-variant-fits-width-${width}`;
|
|
77
|
+
if (logoVariant === "none") return `no-logo-variant-fits-width-${width}`;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function TranscriptShellInner({
|
|
82
|
+
layout,
|
|
83
|
+
authState,
|
|
84
|
+
workspaceLabel,
|
|
85
|
+
workspaceRoot = null,
|
|
86
|
+
runtimeSummary = null,
|
|
87
|
+
staticEvents,
|
|
88
|
+
activeEvents,
|
|
89
|
+
uiState,
|
|
90
|
+
composer,
|
|
91
|
+
composerRows,
|
|
92
|
+
notice = null,
|
|
93
|
+
verboseMode = false,
|
|
94
|
+
clearCount = 0,
|
|
95
|
+
visible = true,
|
|
96
|
+
}: TranscriptShellProps) {
|
|
97
|
+
const theme = useTheme();
|
|
98
|
+
const startupHeaderMode = useMemo(
|
|
99
|
+
() => resolveStartupHeaderMode({
|
|
100
|
+
cols: layout.cols,
|
|
101
|
+
rows: layout.rows,
|
|
102
|
+
introRows: 8,
|
|
103
|
+
composerRows: composerRows ?? 5,
|
|
104
|
+
}),
|
|
105
|
+
[composerRows, layout.cols, layout.rows],
|
|
106
|
+
);
|
|
107
|
+
const homeScreenActive = visible && isHomeScreenState({ staticEvents, activeEvents, uiState });
|
|
108
|
+
const shellWidth = getShellWidth(layout.cols);
|
|
109
|
+
const introInnerWidth = Math.max(10, shellWidth - 2);
|
|
110
|
+
const selectedLogoRows = startupHeaderMode === "tiny"
|
|
111
|
+
? []
|
|
112
|
+
: startupHeaderMode === "large"
|
|
113
|
+
? selectLogoVariant(introInnerWidth)
|
|
114
|
+
: introInnerWidth >= LOGO_COMPACT_MIN_COLS ? LOGO_COMPACT : [];
|
|
115
|
+
const selectedLogoVariant = getLogoVariantName(selectedLogoRows);
|
|
116
|
+
const logoHiddenReason = getLogoHiddenReason({
|
|
117
|
+
startupHeaderMode,
|
|
118
|
+
logoVariant: selectedLogoVariant,
|
|
119
|
+
width: introInnerWidth,
|
|
120
|
+
});
|
|
121
|
+
const startupTraceKeyRef = useRef<string | null>(null);
|
|
122
|
+
// Stable snapshot of the transcript inputs; hold the last visible one while
|
|
123
|
+
// an overlay hides the shell. Identity only changes when the reducer
|
|
124
|
+
// replaces one of these arrays, never on a keystroke or a stream flush.
|
|
125
|
+
const liveTranscript = useMemo(
|
|
126
|
+
() => ({ staticEvents, activeEvents, uiState }),
|
|
127
|
+
[staticEvents, activeEvents, uiState],
|
|
128
|
+
);
|
|
129
|
+
const visibleTranscriptRef = useRef(liveTranscript);
|
|
130
|
+
if (visible) {
|
|
131
|
+
visibleTranscriptRef.current = liveTranscript;
|
|
132
|
+
}
|
|
133
|
+
const renderedTranscript = visibleTranscriptRef.current;
|
|
134
|
+
const renderedStaticEvents = renderedTranscript.staticEvents;
|
|
135
|
+
const renderedActiveEvents = renderedTranscript.activeEvents;
|
|
136
|
+
const renderedUiState = renderedTranscript.uiState;
|
|
137
|
+
const conversationViewportRows = Math.max(
|
|
138
|
+
2,
|
|
139
|
+
getShellHeight(layout.rows) - (composerRows ?? 0) - (notice ? 1 : 0),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const staticTimelineItems = useMemo(() => buildTimelineItems(renderedStaticEvents), [renderedStaticEvents]);
|
|
143
|
+
const activeTimelineItems = useMemo(() => buildTimelineItems(renderedActiveEvents), [renderedActiveEvents]);
|
|
144
|
+
const staticTurnIds = useMemo(
|
|
145
|
+
() => staticTimelineItems.flatMap((item) => item.type === "turn" ? [item.turnId] : []),
|
|
146
|
+
[staticTimelineItems],
|
|
147
|
+
);
|
|
148
|
+
const activeTurnIds = useMemo(
|
|
149
|
+
() => activeTimelineItems.flatMap((item) => item.type === "turn" ? [item.turnId] : []),
|
|
150
|
+
[activeTimelineItems],
|
|
151
|
+
);
|
|
152
|
+
const allTurnIds = useMemo(() => [...staticTurnIds, ...activeTurnIds], [staticTurnIds, activeTurnIds]);
|
|
153
|
+
const activeTurnId = activeTurnIds[0] ?? null;
|
|
154
|
+
|
|
155
|
+
// Static half: intro + finalized turns, built incrementally. The cache ref
|
|
156
|
+
// lives in this keyed instance, so /clear and width repaints (which remount
|
|
157
|
+
// the shell) start from an empty cache.
|
|
158
|
+
const staticCacheRef = useRef<StaticTranscriptCache | null>(null);
|
|
159
|
+
const staticTranscript = useMemo(() => {
|
|
160
|
+
const cache = staticCacheRef.current ?? (staticCacheRef.current = createStaticTranscriptCache());
|
|
161
|
+
return buildStaticTranscript(
|
|
162
|
+
cache,
|
|
163
|
+
[
|
|
164
|
+
buildIntroRenderItem({
|
|
165
|
+
authState,
|
|
166
|
+
workspaceLabel,
|
|
167
|
+
layout,
|
|
168
|
+
providerLabel: runtimeSummary?.providerLabel ?? null,
|
|
169
|
+
startupHeaderMode,
|
|
170
|
+
}),
|
|
171
|
+
...buildStaticRenderItems(staticTimelineItems, allTurnIds, activeTurnId, null, null),
|
|
172
|
+
],
|
|
173
|
+
{ totalWidth: shellWidth, verboseMode, workspaceRoot },
|
|
174
|
+
);
|
|
175
|
+
}, [activeTurnId, allTurnIds, authState, layout, runtimeSummary?.providerLabel, shellWidth, startupHeaderMode, staticTimelineItems, verboseMode, workspaceLabel, workspaceRoot]);
|
|
176
|
+
|
|
177
|
+
// Live half: only the running turn, rebuilt per streaming flush.
|
|
178
|
+
const activeTranscript = useMemo(
|
|
179
|
+
() => buildNativeTranscriptParts(
|
|
180
|
+
buildActiveRenderItems(activeTimelineItems, allTurnIds, renderedUiState),
|
|
181
|
+
{
|
|
182
|
+
totalWidth: shellWidth,
|
|
183
|
+
verboseMode,
|
|
184
|
+
debugLabel: "transcript-shell-native",
|
|
185
|
+
workspaceRoot,
|
|
186
|
+
},
|
|
187
|
+
),
|
|
188
|
+
[activeTimelineItems, allTurnIds, renderedUiState, shellWidth, verboseMode, workspaceRoot],
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const nativeTranscript = useMemo(() => ({
|
|
192
|
+
staticItems: [...staticTranscript.staticItems, ...activeTranscript.staticItems],
|
|
193
|
+
liveRows: [...staticTranscript.liveRows, ...activeTranscript.liveRows],
|
|
194
|
+
}), [activeTranscript, staticTranscript]);
|
|
195
|
+
const committedRows = useMemo(
|
|
196
|
+
() => nativeTranscript.staticItems.reduce((total, item) => total + item.rows.length, 0),
|
|
197
|
+
[nativeTranscript.staticItems],
|
|
198
|
+
);
|
|
199
|
+
// The whole running turn is measured live (see appendNativeTurnParts), but
|
|
200
|
+
// only its tail is rendered: Ink clears the terminal *and scrollback* on any
|
|
201
|
+
// frame whose live output exceeds the viewport, which yanks a user who
|
|
202
|
+
// scrolled up back to the bottom on every streaming tick. The full turn is
|
|
203
|
+
// committed to <Static> at finalize, so nothing is lost from scrollback.
|
|
204
|
+
const liveWindowRows = Math.max(1, conversationViewportRows - LIVE_WINDOW_SAFETY_ROWS);
|
|
205
|
+
const visibleLiveRows = useMemo(
|
|
206
|
+
() => windowLiveRows(nativeTranscript.liveRows, liveWindowRows),
|
|
207
|
+
[liveWindowRows, nativeTranscript.liveRows],
|
|
208
|
+
);
|
|
209
|
+
const liveRowsHidden = nativeTranscript.liveRows.length - visibleLiveRows.length;
|
|
210
|
+
const spacerRows = Math.max(0, conversationViewportRows - committedRows - visibleLiveRows.length);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const nextKey = [
|
|
214
|
+
layout.cols,
|
|
215
|
+
layout.rows,
|
|
216
|
+
layout.mode,
|
|
217
|
+
startupHeaderMode,
|
|
218
|
+
homeScreenActive ? "home" : "transcript",
|
|
219
|
+
staticEvents.length,
|
|
220
|
+
activeEvents.length,
|
|
221
|
+
uiState.kind,
|
|
222
|
+
selectedLogoVariant,
|
|
223
|
+
clearCount,
|
|
224
|
+
].join("|");
|
|
225
|
+
if (startupTraceKeyRef.current === nextKey) return;
|
|
226
|
+
startupTraceKeyRef.current = nextKey;
|
|
227
|
+
renderDebug.traceEvent("startup", "homeRender", {
|
|
228
|
+
cols: layout.cols,
|
|
229
|
+
rows: layout.rows,
|
|
230
|
+
layoutMode: layout.mode,
|
|
231
|
+
activeRoot: "TranscriptShell",
|
|
232
|
+
messageCount: [...staticEvents, ...activeEvents].filter(isTranscriptEvent).length,
|
|
233
|
+
staticEventsLength: staticEvents.length,
|
|
234
|
+
activeEventsLength: activeEvents.length,
|
|
235
|
+
uiStateKind: uiState.kind,
|
|
236
|
+
selectedLayoutMode: startupHeaderMode,
|
|
237
|
+
selectedLogoVariant,
|
|
238
|
+
logoBranchSelected: selectedLogoVariant !== "none" && selectedLogoVariant !== "wordmark",
|
|
239
|
+
logoHiddenReason,
|
|
240
|
+
composerCount: visible ? 1 : 0,
|
|
241
|
+
footerCount: visible ? 1 : 0,
|
|
242
|
+
homeScreenRendererUsed: homeScreenActive,
|
|
243
|
+
staticItemCount: staticEvents.length,
|
|
244
|
+
liveRowCount: activeEvents.length,
|
|
245
|
+
liveRowsHidden,
|
|
246
|
+
clearCount,
|
|
247
|
+
});
|
|
248
|
+
}, [
|
|
249
|
+
activeEvents,
|
|
250
|
+
clearCount,
|
|
251
|
+
homeScreenActive,
|
|
252
|
+
layout.cols,
|
|
253
|
+
layout.mode,
|
|
254
|
+
layout.rows,
|
|
255
|
+
liveRowsHidden,
|
|
256
|
+
logoHiddenReason,
|
|
257
|
+
selectedLogoVariant,
|
|
258
|
+
startupHeaderMode,
|
|
259
|
+
staticEvents,
|
|
260
|
+
uiState.kind,
|
|
261
|
+
visible,
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<Box flexDirection="column" width="100%" display={visible ? "flex" : "none"}>
|
|
266
|
+
<Static key={`native-static-${clearCount}`} items={nativeTranscript.staticItems}>
|
|
267
|
+
{(item: NativeTranscriptRowItem) => <NativeRowsItem key={item.key} rows={item.rows} />}
|
|
268
|
+
</Static>
|
|
269
|
+
{spacerRows > 0 && <Box height={spacerRows} />}
|
|
270
|
+
<NativeRowsItem rows={visibleLiveRows} />
|
|
271
|
+
|
|
272
|
+
{visible && notice && (
|
|
273
|
+
<Box width="100%" paddingX={1}>
|
|
274
|
+
<Text color={theme.success} wrap="truncate">{notice}</Text>
|
|
275
|
+
</Box>
|
|
276
|
+
)}
|
|
277
|
+
{visible && composer}
|
|
278
|
+
</Box>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const TranscriptShell = memo(function TranscriptShell(props: TranscriptShellProps) {
|
|
283
|
+
// repaintGeneration must fold into the outer remount key (not just <Static>'s
|
|
284
|
+
// own key) — Ink only reliably re-flushes already-printed <Static> content on
|
|
285
|
+
// a genuine fresh mount of the whole subtree, confirmed empirically: keying
|
|
286
|
+
// away only the inner <Static> node did not trigger Ink's isStaticDirty/
|
|
287
|
+
// onImmediateRender escape hatch the same way a full remount does.
|
|
288
|
+
return (
|
|
289
|
+
<TranscriptShellInner
|
|
290
|
+
key={`clear-${props.clearCount ?? 0}-repaint-${props.repaintGeneration ?? 0}`}
|
|
291
|
+
{...props}
|
|
292
|
+
/>
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function NativeRowsItem({ rows }: { rows: TimelineRow[] }) {
|
|
297
|
+
return (
|
|
298
|
+
<Box flexDirection="column">
|
|
299
|
+
{rows.map((row) => <TimelineRowView key={row.key} row={row} />)}
|
|
300
|
+
</Box>
|
|
301
|
+
);
|
|
302
|
+
}
|