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.
Files changed (182) hide show
  1. package/README.md +88 -0
  2. package/bin/codexa-local-harness-bridge.js +3 -0
  3. package/bin/codexa.js +28 -0
  4. package/bin/ubume-local-harness-bridge.js +325 -0
  5. package/bin/ubume.js +398 -0
  6. package/package.json +66 -0
  7. package/src/app.tsx +5759 -0
  8. package/src/commands/handler.ts +889 -0
  9. package/src/config/appVersion.ts +69 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.ts +196 -0
  12. package/src/config/layeredConfig.ts +853 -0
  13. package/src/config/legacyEnv.ts +16 -0
  14. package/src/config/persistence.ts +377 -0
  15. package/src/config/runtimeConfig.ts +558 -0
  16. package/src/config/settings.ts +405 -0
  17. package/src/config/toml-serialize.ts +109 -0
  18. package/src/config/trustStore.ts +84 -0
  19. package/src/config/updateCheckCache.ts +85 -0
  20. package/src/core/README.md +55 -0
  21. package/src/core/agent/loop.ts +464 -0
  22. package/src/core/agent/protocol.ts +345 -0
  23. package/src/core/agent/tools.ts +423 -0
  24. package/src/core/auth/codexAuth.ts +359 -0
  25. package/src/core/codex/codexExecArgs.ts +166 -0
  26. package/src/core/codex/codexLaunch.ts +163 -0
  27. package/src/core/codex/codexPrompt.ts +429 -0
  28. package/src/core/debug/inputDebug.ts +51 -0
  29. package/src/core/debug/localStreamDebug.ts +50 -0
  30. package/src/core/debug/modelStateDebug.ts +35 -0
  31. package/src/core/executables/antigravityExecutable.ts +48 -0
  32. package/src/core/executables/claudeExecutable.ts +63 -0
  33. package/src/core/executables/codexExecutable.ts +160 -0
  34. package/src/core/executables/executableResolver.ts +164 -0
  35. package/src/core/executables/geminiExecutable.ts +78 -0
  36. package/src/core/models/codexCapabilities.ts +97 -0
  37. package/src/core/models/codexModelCapabilities.ts +624 -0
  38. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  39. package/src/core/models/modelSpecs.ts +303 -0
  40. package/src/core/models/providerModelCache.ts +94 -0
  41. package/src/core/perf/profiler.ts +125 -0
  42. package/src/core/perf/renderDebug.ts +398 -0
  43. package/src/core/process/CommandRunner.ts +280 -0
  44. package/src/core/process/processValidation.ts +111 -0
  45. package/src/core/providerLauncher/launcher.ts +220 -0
  46. package/src/core/providerLauncher/registry.ts +354 -0
  47. package/src/core/providerLauncher/types.ts +95 -0
  48. package/src/core/providerLauncher/workspaceConfig.ts +487 -0
  49. package/src/core/providerRuntime/anthropic.ts +580 -0
  50. package/src/core/providerRuntime/antigravity.ts +500 -0
  51. package/src/core/providerRuntime/capabilityProfile.ts +383 -0
  52. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
  53. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  54. package/src/core/providerRuntime/codexaCupy.ts +97 -0
  55. package/src/core/providerRuntime/codexaNative.ts +425 -0
  56. package/src/core/providerRuntime/contextMetadata.ts +397 -0
  57. package/src/core/providerRuntime/gemini.ts +789 -0
  58. package/src/core/providerRuntime/lmstudio.ts +118 -0
  59. package/src/core/providerRuntime/local.ts +770 -0
  60. package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
  61. package/src/core/providerRuntime/localOutputBudget.ts +17 -0
  62. package/src/core/providerRuntime/mistralVibe.ts +667 -0
  63. package/src/core/providerRuntime/models.ts +175 -0
  64. package/src/core/providerRuntime/reasoning.ts +20 -0
  65. package/src/core/providerRuntime/registry.ts +286 -0
  66. package/src/core/providerRuntime/types.ts +142 -0
  67. package/src/core/providerRuntime/unsloth.ts +216 -0
  68. package/src/core/providers/codexJsonStream.ts +305 -0
  69. package/src/core/providers/codexSubprocess.ts +378 -0
  70. package/src/core/providers/codexTranscript.ts +695 -0
  71. package/src/core/providers/openaiNative.ts +13 -0
  72. package/src/core/providers/registry.ts +21 -0
  73. package/src/core/providers/types.ts +91 -0
  74. package/src/core/shared/attachments.ts +101 -0
  75. package/src/core/shared/cleanupFastFail.ts +67 -0
  76. package/src/core/shared/clipboard.ts +24 -0
  77. package/src/core/shared/clipboardImage.ts +111 -0
  78. package/src/core/shared/githubDiagnostics.ts +222 -0
  79. package/src/core/shared/hollowResponseFormat.ts +39 -0
  80. package/src/core/terminal/clearFrameBoundary.ts +852 -0
  81. package/src/core/terminal/frameLock.ts +110 -0
  82. package/src/core/terminal/inkRenderReset.ts +123 -0
  83. package/src/core/terminal/startupClear.ts +20 -0
  84. package/src/core/terminal/terminalCapabilities.ts +100 -0
  85. package/src/core/terminal/terminalControl.ts +169 -0
  86. package/src/core/terminal/terminalSanitize.ts +147 -0
  87. package/src/core/terminal/terminalTitle.ts +400 -0
  88. package/src/core/version/channel.ts +27 -0
  89. package/src/core/version/packageManager.ts +119 -0
  90. package/src/core/version/updateCheck.ts +203 -0
  91. package/src/core/workspace/appData.ts +107 -0
  92. package/src/core/workspace/conversationStore.ts +335 -0
  93. package/src/core/workspace/launchContext.ts +259 -0
  94. package/src/core/workspace/planStorage.ts +135 -0
  95. package/src/core/workspace/projectInstructions.ts +54 -0
  96. package/src/core/workspace/scratchDir.ts +64 -0
  97. package/src/core/workspace/workspaceActivity.ts +384 -0
  98. package/src/core/workspace/workspaceGuard.ts +377 -0
  99. package/src/core/workspace/workspaceRoot.ts +47 -0
  100. package/src/exec.ts +73 -0
  101. package/src/headless/execArgs.ts +296 -0
  102. package/src/headless/execRunner.ts +304 -0
  103. package/src/index.tsx +270 -0
  104. package/src/legacyEnvBootstrap.ts +5 -0
  105. package/src/session/appSession.ts +771 -0
  106. package/src/session/chatLifecycle.ts +994 -0
  107. package/src/session/conversation.ts +107 -0
  108. package/src/session/liveRenderScheduler.ts +214 -0
  109. package/src/session/persistedResponse.ts +93 -0
  110. package/src/session/planFlow.ts +159 -0
  111. package/src/session/planTranscript.ts +19 -0
  112. package/src/session/promptRunSchedule.ts +26 -0
  113. package/src/session/types.ts +234 -0
  114. package/src/test/runtimeTestUtils.ts +14 -0
  115. package/src/types/react-dom.d.ts +3 -0
  116. package/src/ui/chrome/ActivityBars.tsx +68 -0
  117. package/src/ui/chrome/ActivityIndicator.tsx +58 -0
  118. package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
  119. package/src/ui/chrome/AppShell.tsx +474 -0
  120. package/src/ui/chrome/BottomComposer.tsx +1135 -0
  121. package/src/ui/chrome/DashCard.tsx +82 -0
  122. package/src/ui/chrome/RunFooter.tsx +65 -0
  123. package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
  124. package/src/ui/chrome/Spinner.tsx +25 -0
  125. package/src/ui/chrome/TopHeader.tsx +439 -0
  126. package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
  127. package/src/ui/chrome/busyStatusAnimation.ts +11 -0
  128. package/src/ui/input/commandNormalize.ts +66 -0
  129. package/src/ui/input/focus.ts +73 -0
  130. package/src/ui/input/imageAttachments.ts +18 -0
  131. package/src/ui/input/inputBuffer.ts +203 -0
  132. package/src/ui/input/pastedContent.ts +75 -0
  133. package/src/ui/input/rawArrowKeys.ts +28 -0
  134. package/src/ui/input/slashCommands.ts +43 -0
  135. package/src/ui/input/useStdinRawModeLease.ts +23 -0
  136. package/src/ui/layout.ts +560 -0
  137. package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
  138. package/src/ui/panels/AuthPanel.tsx +149 -0
  139. package/src/ui/panels/BackendPicker.tsx +28 -0
  140. package/src/ui/panels/ModePicker.tsx +31 -0
  141. package/src/ui/panels/ModelPicker.tsx +31 -0
  142. package/src/ui/panels/ModelPickerScreen.tsx +761 -0
  143. package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
  144. package/src/ui/panels/Panel.tsx +51 -0
  145. package/src/ui/panels/PermissionsPanel.tsx +78 -0
  146. package/src/ui/panels/PlanActionPicker.tsx +187 -0
  147. package/src/ui/panels/ProviderPicker.tsx +753 -0
  148. package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
  149. package/src/ui/panels/ReasoningPicker.tsx +46 -0
  150. package/src/ui/panels/ResumePicker.tsx +90 -0
  151. package/src/ui/panels/SelectionPanel.tsx +138 -0
  152. package/src/ui/panels/SettingsPanel.tsx +156 -0
  153. package/src/ui/panels/TextEntryPanel.tsx +139 -0
  154. package/src/ui/panels/ThemePicker.tsx +32 -0
  155. package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
  156. package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
  157. package/src/ui/panels/responsivePickerViewport.ts +64 -0
  158. package/src/ui/render/Markdown.tsx +331 -0
  159. package/src/ui/render/diffRenderer.ts +116 -0
  160. package/src/ui/render/logoVariants.ts +113 -0
  161. package/src/ui/render/modeDisplay.ts +52 -0
  162. package/src/ui/render/outputPipeline.ts +64 -0
  163. package/src/ui/render/runtimeDisplay.ts +128 -0
  164. package/src/ui/render/terminalAnswerFormat.ts +128 -0
  165. package/src/ui/render/textLayout.ts +392 -0
  166. package/src/ui/theme.tsx +274 -0
  167. package/src/ui/themeFlow.ts +41 -0
  168. package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
  169. package/src/ui/timeline/AgentBlock.tsx +130 -0
  170. package/src/ui/timeline/StaticIntroItem.tsx +54 -0
  171. package/src/ui/timeline/ThinkingBlock.tsx +100 -0
  172. package/src/ui/timeline/Timeline.tsx +1410 -0
  173. package/src/ui/timeline/TranscriptShell.tsx +302 -0
  174. package/src/ui/timeline/TurnGroup.tsx +673 -0
  175. package/src/ui/timeline/layoutListWindow.ts +145 -0
  176. package/src/ui/timeline/liveViewportWindow.ts +68 -0
  177. package/src/ui/timeline/progressEntries.ts +156 -0
  178. package/src/ui/timeline/runActivityView.ts +37 -0
  179. package/src/ui/timeline/staticTranscriptCache.ts +174 -0
  180. package/src/ui/timeline/streamCoalesce.ts +53 -0
  181. package/src/ui/timeline/timelineMeasure.ts +3273 -0
  182. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,474 @@
1
+ import React, { memo, useEffect, useMemo, useRef } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { useTheme } from "../theme.js";
4
+ import type { RuntimeSummary } from "../../config/runtimeConfig.js";
5
+ import type { CodexAuthState } from "../../core/auth/codexAuth.js";
6
+ import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
7
+ import * as renderDebug from "../../core/perf/renderDebug.js";
8
+ import type { Screen, TimelineEvent, UIState } from "../../session/types.js";
9
+ import {
10
+ ActivePanelLayoutContext,
11
+ type ActivePanelLayout,
12
+ AppLayoutBudgetContext,
13
+ computeAppLayoutBudget,
14
+ getContentWidth,
15
+ getShellHeight,
16
+ getShellWidth,
17
+ isCrampedTerminal,
18
+ PanelAvailableRowsContext,
19
+ type Layout,
20
+ type LayoutMode,
21
+ type PanelLayout,
22
+ PanelLayoutContext,
23
+ MIN_TERMINAL_COLS,
24
+ MIN_TERMINAL_ROWS,
25
+ type TerminalViewport,
26
+ } from "../layout.js";
27
+ import { Timeline } from "../timeline/Timeline.js";
28
+ import { MemoizedTopHeader, measureTopHeaderRows, type UpdateAvailableInfo } from "./TopHeader.js";
29
+
30
+ const COMPACT_HEADER_TO_COMPOSER_GAP_ROWS = 1;
31
+ const MEDIUM_HEADER_TO_COMPOSER_GAP_ROWS = 1;
32
+ const TALL_HEADER_TO_COMPOSER_GAP_ROWS = 1;
33
+
34
+ // ─── Types & constants ────────────────────────────────────────────────────────
35
+
36
+ type AppShellLayout = TerminalViewport;
37
+ export interface AppShellProps {
38
+ layout: AppShellLayout;
39
+ screen: Screen;
40
+ authState: CodexAuthState;
41
+ workspaceLabel: string;
42
+ workspaceRoot?: string | null;
43
+ runtimeSummary?: RuntimeSummary | null;
44
+ staticEvents: TimelineEvent[];
45
+ activeEvents: TimelineEvent[];
46
+ uiState: UIState;
47
+ panel: React.ReactNode;
48
+ mainPanel?: React.ReactNode;
49
+ mainPanelMode?: "viewport" | "full-output";
50
+ composer: React.ReactNode;
51
+ composerRows: number;
52
+ panelHint?: React.ReactNode;
53
+ verboseMode?: boolean;
54
+ clearCount?: number;
55
+ headerConfig?: HeaderConfig;
56
+ updateAvailable?: UpdateAvailableInfo | null;
57
+ }
58
+
59
+ // ─── Helpers & subcomponents ─────────────────────────────────────────────────
60
+
61
+ export function isCrampedViewport(rows: number | undefined): boolean {
62
+ return (rows ?? 24) <= 24;
63
+ }
64
+
65
+ export function calculateNativeSpacerRows({
66
+ shellRows,
67
+ introRows,
68
+ composerRows,
69
+ staticRows,
70
+ liveRows,
71
+ }: {
72
+ shellRows: number;
73
+ introRows: number;
74
+ composerRows: number;
75
+ staticRows: number;
76
+ liveRows: number;
77
+ }): number {
78
+ const availableBodyRows = Math.max(0, shellRows - introRows - composerRows);
79
+ const visibleBodyContentRows = Math.max(0, staticRows) + Math.max(0, liveRows);
80
+ return Math.max(0, availableBodyRows - visibleBodyContentRows);
81
+ }
82
+
83
+ export function calculateColdStartSpacerRows({
84
+ shellRows,
85
+ headerRows,
86
+ composerRows,
87
+ layoutMode,
88
+ availableRows,
89
+ }: {
90
+ shellRows: number;
91
+ headerRows: number;
92
+ composerRows: number;
93
+ layoutMode: LayoutMode;
94
+ availableRows: number;
95
+ }): number {
96
+ if (availableRows <= 0) return 0;
97
+
98
+ const rowsAfterHeaderAndComposer = Math.max(0, shellRows - headerRows - composerRows);
99
+ const preferredRows = layoutMode === "compact" || shellRows <= 18
100
+ ? 1
101
+ : shellRows >= 36
102
+ ? TALL_HEADER_TO_COMPOSER_GAP_ROWS
103
+ : shellRows >= 28
104
+ ? MEDIUM_HEADER_TO_COMPOSER_GAP_ROWS
105
+ : COMPACT_HEADER_TO_COMPOSER_GAP_ROWS;
106
+
107
+ return Math.max(0, Math.min(availableRows, rowsAfterHeaderAndComposer, preferredRows));
108
+ }
109
+
110
+ export function calculateHeaderToContentGapRows(layout: Layout): number {
111
+ if (layout.mode === "compact" || layout.rows <= 18) return 0;
112
+ return 1;
113
+ }
114
+
115
+ function CrampedView({ layout }: { layout: Layout }) {
116
+ const theme = useTheme();
117
+ return (
118
+ <Box
119
+ flexDirection="column"
120
+ width="100%"
121
+ height={getShellHeight(layout.rows)}
122
+ alignItems="center"
123
+ justifyContent="center"
124
+ >
125
+ <Box borderStyle="round" borderColor={theme.error} paddingX={2} paddingY={1}>
126
+ <Text color={theme.error} bold>
127
+ Terminal too small — resize to at least {MIN_TERMINAL_COLS} x {MIN_TERMINAL_ROWS}
128
+ </Text>
129
+ </Box>
130
+ <Box marginTop={1}>
131
+ <Text dimColor>
132
+ Current: {layout.cols} x {layout.rows}
133
+ </Text>
134
+ </Box>
135
+ </Box>
136
+ );
137
+ }
138
+
139
+ function injectPanelLayout(
140
+ element: React.ReactNode,
141
+ availableRows: number,
142
+ activePanelLayout: ActivePanelLayout,
143
+ panelLayout: PanelLayout
144
+ ): React.ReactNode {
145
+ if (!React.isValidElement(element)) {
146
+ return element;
147
+ }
148
+ if (element.type === React.Fragment) {
149
+ const fragment = element as React.ReactElement<{ children?: React.ReactNode }>;
150
+ return React.cloneElement(
151
+ fragment,
152
+ fragment.props,
153
+ React.Children.map(fragment.props.children, (child) =>
154
+ injectPanelLayout(child, availableRows, activePanelLayout, panelLayout)
155
+ )
156
+ );
157
+ }
158
+ return React.cloneElement(
159
+ element as React.ReactElement<{
160
+ availableRows?: number;
161
+ activePanelLayout?: ActivePanelLayout;
162
+ panelLayout?: PanelLayout;
163
+ }>,
164
+ { availableRows, activePanelLayout, panelLayout }
165
+ );
166
+ }
167
+
168
+ // ─── Component ────────────────────────────────────────────────────────────────
169
+
170
+ function AppShellInner({
171
+ layout,
172
+ screen,
173
+ authState,
174
+ workspaceLabel,
175
+ workspaceRoot = null,
176
+ runtimeSummary = null,
177
+ staticEvents,
178
+ activeEvents,
179
+ uiState,
180
+ panel,
181
+ mainPanel,
182
+ mainPanelMode = "viewport",
183
+ composer,
184
+ composerRows,
185
+ panelHint,
186
+ verboseMode = false,
187
+ clearCount = 0,
188
+ headerConfig = HEADER_CONFIG_DEFAULTS,
189
+ updateAvailable = null,
190
+ }: AppShellProps) {
191
+ const theme = useTheme();
192
+ renderDebug.useRenderDebug("AppShell", {
193
+ cols: layout.cols,
194
+ rows: layout.rows,
195
+ mode: layout.mode,
196
+ layoutEpoch: layout.layoutEpoch,
197
+ screen,
198
+ authState,
199
+ workspaceLabel,
200
+ workspaceRoot,
201
+ runtimeSummary,
202
+ staticEvents,
203
+ activeEvents,
204
+ uiState,
205
+ composer,
206
+ composerRows,
207
+ verboseMode,
208
+ });
209
+ renderDebug.useLifecycleDebug("AppShell", {
210
+ screen,
211
+ cols: layout.cols,
212
+ rows: layout.rows,
213
+ mode: layout.mode,
214
+ });
215
+
216
+ if (layout.isCramped) {
217
+ return <CrampedView layout={layout} />;
218
+ }
219
+
220
+ const shellWidth = getShellWidth(layout.cols);
221
+ const shellHeight = getShellHeight(layout.rows);
222
+
223
+ const showComposer = true;
224
+ const showMainPanel = screen === "main" && mainPanel !== undefined && mainPanel !== null;
225
+ const showMainPanelFullOutput = showMainPanel && mainPanelMode === "full-output";
226
+ const showTimeline = screen === "main" && !showMainPanel;
227
+ const showPanelStage = screen !== "main";
228
+ const headerLayout = showPanelStage && layout.rows <= 24
229
+ ? { ...layout, cols: Math.min(layout.cols, 71), mode: "compact" as const }
230
+ : layout;
231
+ const headerRows = measureTopHeaderRows(headerLayout, headerConfig, !!updateAvailable);
232
+ const previousMeasurements = useRef<{
233
+ timelineRows: number;
234
+ composerRows: number;
235
+ shellHeight: number;
236
+ shellWidth: number;
237
+ } | null>(null);
238
+
239
+ const effectiveShowComposer = showComposer;
240
+ const panelHintRows = showPanelStage && panelHint ? 2 : 0;
241
+
242
+ // ─── App Layout Budget ────────────────────────────────────────────────────
243
+
244
+ const appLayoutBudget = computeAppLayoutBudget({
245
+ cols: layout.cols,
246
+ rows: layout.rows,
247
+ composerRows,
248
+ panelHintRows,
249
+ headerRows,
250
+ });
251
+
252
+ const headerToContentGapRows = appLayoutBudget.headerGapRows;
253
+ const effectiveComposerRows = appLayoutBudget.composerRows;
254
+ const bottomChromeRows = appLayoutBudget.bottomChromeBudget.totalRows;
255
+
256
+ const finalTimelineRows = appLayoutBudget.transcriptRows;
257
+ const finalShellHeight = shellHeight;
258
+ const finalShellWidth = shellWidth;
259
+
260
+ const { finalTimelineRows: resolvedTimelineRows } = useMemo(() => {
261
+ const prev = previousMeasurements.current;
262
+ const isValid = shellHeight > 0
263
+ && shellWidth > 0
264
+ && Number.isFinite(shellHeight)
265
+ && Number.isFinite(shellWidth)
266
+ && Number.isFinite(finalTimelineRows)
267
+ && finalTimelineRows >= 2;
268
+
269
+ if (!isValid && prev) {
270
+ return {
271
+ finalTimelineRows: prev.timelineRows,
272
+ };
273
+ }
274
+
275
+ return {
276
+ finalTimelineRows: Math.max(2, finalTimelineRows),
277
+ };
278
+ }, [shellHeight, shellWidth, finalTimelineRows]);
279
+
280
+ useEffect(() => {
281
+ previousMeasurements.current = {
282
+ timelineRows: resolvedTimelineRows,
283
+ composerRows: bottomChromeRows,
284
+ shellHeight: finalShellHeight,
285
+ shellWidth: finalShellWidth,
286
+ };
287
+ }, [resolvedTimelineRows, bottomChromeRows, finalShellHeight, finalShellWidth]);
288
+
289
+ const contentWidth = getContentWidth(layout.cols);
290
+ const panelStagePaddingY = appLayoutBudget.panelStagePaddingY;
291
+
292
+ const panelAvailableRows = appLayoutBudget.panelRows;
293
+
294
+ const activePanelLayout = useMemo<ActivePanelLayout>(() => {
295
+ const panelBoxHeight = Math.max(3, resolvedTimelineRows - 2 * panelStagePaddingY);
296
+ const showBorder = panelBoxHeight >= 7;
297
+ const borderRows = showBorder ? 2 : 0;
298
+ const borderCols = showBorder ? 4 : 0;
299
+ const panelTitleRows = showBorder ? 1 : 0;
300
+ const panelHeaderRows = panelBoxHeight >= 9 ? 1 : 0;
301
+ const panelChromeRows = borderRows + panelTitleRows + panelHeaderRows;
302
+
303
+ const availableRows = Math.max(1, panelBoxHeight - panelChromeRows);
304
+ const availableCols = Math.max(20, contentWidth - borderCols);
305
+
306
+ return {
307
+ width: contentWidth,
308
+ height: panelBoxHeight,
309
+ availableRows,
310
+ availableCols,
311
+ };
312
+ }, [resolvedTimelineRows, panelStagePaddingY, contentWidth]);
313
+
314
+ const panelLayout = useMemo<PanelLayout>(() => {
315
+ return {
316
+ mode: appLayoutBudget.mode,
317
+ availableRows: appLayoutBudget.activePanelRows,
318
+ availableCols: appLayoutBudget.activePanelCols,
319
+ };
320
+ }, [appLayoutBudget.mode, appLayoutBudget.activePanelRows, appLayoutBudget.activePanelCols]);
321
+
322
+ const mainContent = (
323
+ <AppLayoutBudgetContext.Provider value={appLayoutBudget}>
324
+ <Box flexDirection="column" width={contentWidth} height="100%">
325
+ <MemoizedTopHeader
326
+ authState={authState}
327
+ workspaceLabel={workspaceLabel}
328
+ layout={headerLayout}
329
+ runtimeSummary={runtimeSummary}
330
+ headerConfig={headerConfig}
331
+ updateAvailable={updateAvailable}
332
+ />
333
+
334
+ {headerToContentGapRows > 0 && (
335
+ <Box height={headerToContentGapRows} />
336
+ )}
337
+
338
+ <Box
339
+ flexDirection="column"
340
+ height={resolvedTimelineRows}
341
+ overflow="hidden"
342
+ display={showTimeline ? "flex" : "none"}
343
+ >
344
+ <Timeline
345
+ key={`timeline-${clearCount}`}
346
+ staticEvents={staticEvents}
347
+ activeEvents={activeEvents}
348
+ layout={layout}
349
+ uiState={uiState}
350
+ viewportRows={resolvedTimelineRows}
351
+ verboseMode={verboseMode}
352
+ authState={authState}
353
+ workspaceLabel={workspaceLabel}
354
+ workspaceRoot={workspaceRoot}
355
+ contentSized
356
+ showIntro={false}
357
+ />
358
+ </Box>
359
+
360
+ {showMainPanel && (
361
+ <Box flexDirection="column" height={resolvedTimelineRows} overflow="hidden" justifyContent="center">
362
+ {mainPanel}
363
+ </Box>
364
+ )}
365
+
366
+ {showPanelStage && (
367
+ <Box flexDirection="column" height={resolvedTimelineRows} overflow="hidden" paddingY={panelStagePaddingY}>
368
+ <PanelAvailableRowsContext.Provider value={panelAvailableRows}>
369
+ <ActivePanelLayoutContext.Provider value={activePanelLayout}>
370
+ <PanelLayoutContext.Provider value={panelLayout}>
371
+ {process.env.UBUME_DEBUG_LAYOUT === "1" && (
372
+ <Box>
373
+ <Text color="red">
374
+ DEBUG layout: rows={layout.rows} cols={layout.cols} mode={layout.mode} headerRows={headerRows} panelRows={panelAvailableRows} bottomChromeRows={appLayoutBudget.bottomChromeBudget.totalRows}
375
+ </Text>
376
+ </Box>
377
+ )}
378
+ {injectPanelLayout(panel, panelAvailableRows, activePanelLayout, panelLayout)}
379
+ </PanelLayoutContext.Provider>
380
+ </ActivePanelLayoutContext.Provider>
381
+ </PanelAvailableRowsContext.Provider>
382
+ </Box>
383
+ )}
384
+
385
+ {showPanelStage && panelHint}
386
+
387
+ {effectiveShowComposer && (
388
+ <Box flexDirection="column" flexShrink={0}>
389
+ {composer}
390
+ </Box>
391
+ )}
392
+ </Box>
393
+ </AppLayoutBudgetContext.Provider>
394
+ );
395
+
396
+ if (showMainPanelFullOutput) {
397
+ const fullOutputContent = (
398
+ <Box flexDirection="column" width={contentWidth}>
399
+ <MemoizedTopHeader
400
+ authState={authState}
401
+ workspaceLabel={workspaceLabel}
402
+ layout={layout}
403
+ runtimeSummary={runtimeSummary}
404
+ headerConfig={headerConfig}
405
+ updateAvailable={updateAvailable}
406
+ />
407
+
408
+ {headerToContentGapRows > 0 && (
409
+ <Box height={headerToContentGapRows} />
410
+ )}
411
+
412
+ {mainPanel}
413
+
414
+ {showComposer && (
415
+ <Box flexDirection="column" flexShrink={0}>
416
+ {composer}
417
+ </Box>
418
+ )}
419
+ </Box>
420
+ );
421
+
422
+ if (shellWidth > contentWidth) {
423
+ return (
424
+ <Box flexDirection="column" width="100%" alignItems="center">
425
+ {fullOutputContent}
426
+ </Box>
427
+ );
428
+ }
429
+ return fullOutputContent;
430
+ }
431
+
432
+ if (shellWidth > contentWidth) {
433
+ return (
434
+ <Box flexDirection="column" width="100%" height={finalShellHeight} alignItems="center">
435
+ {mainContent}
436
+ </Box>
437
+ );
438
+ }
439
+
440
+ return (
441
+ <Box flexDirection="column" width="100%" height={finalShellHeight}>
442
+ {mainContent}
443
+ </Box>
444
+ );
445
+ }
446
+
447
+ export const AppShell = memo(AppShellInner, (prev, next) => {
448
+ const panelPropsEqual = next.screen === "main"
449
+ ? prev.mainPanel === next.mainPanel
450
+ : (prev.panel === next.panel && prev.panelHint === next.panelHint);
451
+
452
+ return (
453
+ prev.layout.cols === next.layout.cols &&
454
+ prev.layout.rows === next.layout.rows &&
455
+ prev.layout.mode === next.layout.mode &&
456
+ prev.layout.layoutEpoch === next.layout.layoutEpoch &&
457
+ prev.screen === next.screen &&
458
+ prev.authState === next.authState &&
459
+ prev.workspaceLabel === next.workspaceLabel &&
460
+ prev.workspaceRoot === next.workspaceRoot &&
461
+ prev.runtimeSummary === next.runtimeSummary &&
462
+ prev.staticEvents === next.staticEvents &&
463
+ prev.activeEvents === next.activeEvents &&
464
+ prev.uiState === next.uiState &&
465
+ prev.composerRows === next.composerRows &&
466
+ prev.composer === next.composer &&
467
+ prev.mainPanel === next.mainPanel &&
468
+ prev.mainPanelMode === next.mainPanelMode &&
469
+ prev.verboseMode === next.verboseMode &&
470
+ prev.clearCount === next.clearCount &&
471
+ prev.updateAvailable === next.updateAvailable &&
472
+ panelPropsEqual
473
+ );
474
+ });