@golba98/codexa 1.0.1 → 1.0.2

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 (232) hide show
  1. package/README.md +0 -0
  2. package/package.json +3 -2
  3. package/scripts/audit-codexa-capabilities.mjs +0 -0
  4. package/scripts/gen-build-info.mjs +33 -0
  5. package/scripts/smoke-terminal-bench.mjs +0 -0
  6. package/src/app.tsx +103 -2
  7. package/src/appRenderStability.test.ts +0 -0
  8. package/src/commands/handler.test.ts +12 -0
  9. package/src/commands/handler.ts +5 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.test.ts +31 -0
  12. package/src/config/launchArgs.ts +10 -0
  13. package/src/config/layeredConfig.test.ts +0 -0
  14. package/src/config/layeredConfig.ts +0 -0
  15. package/src/config/persistence.test.ts +6 -2
  16. package/src/config/persistence.ts +41 -0
  17. package/src/config/runtimeConfig.test.ts +0 -0
  18. package/src/config/runtimeConfig.ts +4 -0
  19. package/src/config/settings.test.ts +0 -0
  20. package/src/config/settings.ts +7 -6
  21. package/src/config/toml-serialize.ts +0 -0
  22. package/src/config/trustStore.test.ts +0 -0
  23. package/src/config/trustStore.ts +0 -0
  24. package/src/config/updateCheckCache.ts +45 -0
  25. package/src/core/attachments.test.ts +0 -0
  26. package/src/core/attachments.ts +0 -0
  27. package/src/core/auth/codexAuth.test.ts +0 -0
  28. package/src/core/auth/codexAuth.ts +0 -0
  29. package/src/core/cleanupFastFail.test.ts +0 -0
  30. package/src/core/cleanupFastFail.ts +0 -0
  31. package/src/core/clipboard.ts +0 -0
  32. package/src/core/codex.ts +0 -0
  33. package/src/core/codexExecArgs.test.ts +0 -0
  34. package/src/core/codexExecArgs.ts +0 -0
  35. package/src/core/codexLaunch.test.ts +0 -0
  36. package/src/core/codexLaunch.ts +0 -0
  37. package/src/core/codexPrompt.test.ts +0 -0
  38. package/src/core/codexPrompt.ts +0 -0
  39. package/src/core/executables/claudeExecutable.ts +0 -0
  40. package/src/core/executables/codexExecutable.test.ts +0 -0
  41. package/src/core/executables/codexExecutable.ts +0 -0
  42. package/src/core/executables/executableResolver.test.ts +0 -0
  43. package/src/core/executables/executableResolver.ts +0 -0
  44. package/src/core/executables/geminiExecutable.test.ts +0 -0
  45. package/src/core/executables/geminiExecutable.ts +0 -0
  46. package/src/core/executables/pathSanityScan.test.ts +0 -0
  47. package/src/core/githubDiagnostics.test.ts +0 -0
  48. package/src/core/githubDiagnostics.ts +0 -0
  49. package/src/core/hollowResponseFormat.test.ts +0 -0
  50. package/src/core/hollowResponseFormat.ts +0 -0
  51. package/src/core/inputDebug.ts +0 -0
  52. package/src/core/launchContext.test.ts +0 -0
  53. package/src/core/launchContext.ts +0 -0
  54. package/src/core/models/codexCapabilities.test.ts +0 -0
  55. package/src/core/models/codexCapabilities.ts +0 -0
  56. package/src/core/models/codexModelCapabilities.test.ts +0 -0
  57. package/src/core/models/codexModelCapabilities.ts +0 -0
  58. package/src/core/models/modelSpecs.test.ts +0 -0
  59. package/src/core/models/modelSpecs.ts +0 -0
  60. package/src/core/perf/profiler.ts +0 -0
  61. package/src/core/perf/renderDebug.test.ts +0 -0
  62. package/src/core/perf/renderDebug.ts +0 -0
  63. package/src/core/planStorage.test.ts +0 -0
  64. package/src/core/planStorage.ts +0 -0
  65. package/src/core/process/CommandRunner.test.ts +0 -0
  66. package/src/core/process/CommandRunner.ts +0 -0
  67. package/src/core/process/processValidation.ts +0 -0
  68. package/src/core/projectInstructions.test.ts +0 -0
  69. package/src/core/projectInstructions.ts +0 -0
  70. package/src/core/providerLauncher/launcher.test.ts +0 -0
  71. package/src/core/providerLauncher/launcher.ts +0 -0
  72. package/src/core/providerLauncher/registry.test.ts +0 -0
  73. package/src/core/providerLauncher/registry.ts +0 -0
  74. package/src/core/providerLauncher/types.ts +0 -0
  75. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -0
  76. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  77. package/src/core/providerRuntime/anthropic.test.ts +0 -0
  78. package/src/core/providerRuntime/anthropic.ts +0 -0
  79. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -0
  80. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  81. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  82. package/src/core/providerRuntime/contextMetadata.test.ts +0 -0
  83. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  84. package/src/core/providerRuntime/gemini.test.ts +0 -0
  85. package/src/core/providerRuntime/gemini.ts +0 -0
  86. package/src/core/providerRuntime/lmstudio.test.ts +0 -0
  87. package/src/core/providerRuntime/lmstudio.ts +0 -0
  88. package/src/core/providerRuntime/local.test.ts +0 -0
  89. package/src/core/providerRuntime/local.ts +0 -0
  90. package/src/core/providerRuntime/models.ts +0 -0
  91. package/src/core/providerRuntime/reasoning.ts +0 -0
  92. package/src/core/providerRuntime/registry.test.ts +0 -0
  93. package/src/core/providerRuntime/registry.ts +0 -0
  94. package/src/core/providerRuntime/types.ts +0 -0
  95. package/src/core/providers/codexJsonStream.test.ts +0 -0
  96. package/src/core/providers/codexJsonStream.ts +0 -0
  97. package/src/core/providers/codexSubprocess.test.ts +0 -0
  98. package/src/core/providers/codexSubprocess.ts +0 -0
  99. package/src/core/providers/codexTranscript.test.ts +0 -0
  100. package/src/core/providers/codexTranscript.ts +0 -0
  101. package/src/core/providers/openaiNative.ts +0 -0
  102. package/src/core/providers/registry.ts +0 -0
  103. package/src/core/providers/types.ts +0 -0
  104. package/src/core/terminal/startupClear.test.ts +55 -0
  105. package/src/core/terminal/startupClear.ts +20 -0
  106. package/src/core/terminal/terminalCapabilities.test.ts +0 -0
  107. package/src/core/terminal/terminalCapabilities.ts +0 -0
  108. package/src/core/terminal/terminalControl.test.ts +0 -0
  109. package/src/core/terminal/terminalControl.ts +0 -0
  110. package/src/core/terminal/terminalSanitize.test.ts +0 -0
  111. package/src/core/terminal/terminalSanitize.ts +0 -0
  112. package/src/core/terminal/terminalSelection.test.ts +0 -0
  113. package/src/core/terminal/terminalSelection.ts +0 -0
  114. package/src/core/terminal/terminalTitle.test.ts +0 -0
  115. package/src/core/terminal/terminalTitle.ts +0 -0
  116. package/src/core/updateCheck.test.ts +194 -0
  117. package/src/core/updateCheck.ts +172 -0
  118. package/src/core/workspaceActivity.test.ts +0 -0
  119. package/src/core/workspaceActivity.ts +0 -0
  120. package/src/core/workspaceGuard.test.ts +0 -0
  121. package/src/core/workspaceGuard.ts +0 -0
  122. package/src/core/workspaceRoot.test.ts +0 -0
  123. package/src/core/workspaceRoot.ts +0 -0
  124. package/src/exec.test.ts +0 -0
  125. package/src/exec.ts +0 -0
  126. package/src/headless/execArgs.test.ts +0 -0
  127. package/src/headless/execArgs.ts +1 -0
  128. package/src/headless/execRunner.test.ts +8 -6
  129. package/src/headless/execRunner.ts +0 -0
  130. package/src/index.test.tsx +5 -3
  131. package/src/index.tsx +13 -9
  132. package/src/session/appSession.test.ts +0 -0
  133. package/src/session/appSession.ts +0 -0
  134. package/src/session/chatLifecycle.test.ts +0 -0
  135. package/src/session/chatLifecycle.ts +0 -0
  136. package/src/session/liveRenderScheduler.test.ts +0 -0
  137. package/src/session/liveRenderScheduler.ts +0 -0
  138. package/src/session/planFlow.test.ts +0 -0
  139. package/src/session/planFlow.ts +0 -0
  140. package/src/session/planTranscript.test.ts +0 -0
  141. package/src/session/planTranscript.ts +0 -0
  142. package/src/session/promptRunSchedule.test.ts +0 -0
  143. package/src/session/promptRunSchedule.ts +0 -0
  144. package/src/session/types.ts +0 -0
  145. package/src/test/runtimeTestUtils.ts +0 -0
  146. package/src/types/react-dom.d.ts +0 -0
  147. package/src/ui/ActionRequiredBlock.tsx +0 -0
  148. package/src/ui/ActivityBars.tsx +0 -0
  149. package/src/ui/ActivityIndicator.test.tsx +0 -0
  150. package/src/ui/ActivityIndicator.tsx +0 -0
  151. package/src/ui/AgentBlock.test.ts +0 -0
  152. package/src/ui/AgentBlock.tsx +0 -0
  153. package/src/ui/AnimatedStatusText.test.ts +0 -0
  154. package/src/ui/AnimatedStatusText.tsx +0 -0
  155. package/src/ui/AppShell.test.tsx +60 -23
  156. package/src/ui/AppShell.tsx +28 -5
  157. package/src/ui/AttachmentImportPanel.test.tsx +0 -0
  158. package/src/ui/AttachmentImportPanel.tsx +0 -0
  159. package/src/ui/AuthPanel.tsx +0 -0
  160. package/src/ui/BackendPicker.tsx +0 -0
  161. package/src/ui/BottomComposer.test.ts +0 -0
  162. package/src/ui/BottomComposer.tsx +0 -0
  163. package/src/ui/CodexLogo.tsx +0 -0
  164. package/src/ui/DashCard.tsx +0 -0
  165. package/src/ui/Markdown.test.ts +0 -0
  166. package/src/ui/Markdown.tsx +0 -0
  167. package/src/ui/ModePicker.tsx +0 -0
  168. package/src/ui/ModelPicker.tsx +0 -0
  169. package/src/ui/ModelPickerProviderScope.test.tsx +0 -0
  170. package/src/ui/ModelPickerScreen.test.tsx +0 -0
  171. package/src/ui/ModelPickerScreen.tsx +0 -0
  172. package/src/ui/ModelPickerState.test.tsx +0 -0
  173. package/src/ui/ModelReasoningPicker.test.tsx +0 -0
  174. package/src/ui/ModelReasoningPicker.tsx +0 -0
  175. package/src/ui/Panel.tsx +0 -0
  176. package/src/ui/PermissionsPanel.tsx +0 -0
  177. package/src/ui/PlanActionPicker.tsx +0 -0
  178. package/src/ui/PlanReviewPanel.test.tsx +0 -0
  179. package/src/ui/PlanReviewPanel.tsx +0 -0
  180. package/src/ui/PromptCardBorder.test.tsx +0 -0
  181. package/src/ui/ProviderPicker.test.tsx +0 -0
  182. package/src/ui/ProviderPicker.tsx +0 -0
  183. package/src/ui/ProviderShortcut.test.tsx +0 -0
  184. package/src/ui/ReasoningPicker.tsx +0 -0
  185. package/src/ui/RunFooter.tsx +0 -0
  186. package/src/ui/SelectionPanel.tsx +0 -0
  187. package/src/ui/SettingsPanel.test.tsx +0 -0
  188. package/src/ui/SettingsPanel.tsx +0 -0
  189. package/src/ui/Spinner.tsx +0 -0
  190. package/src/ui/StaticIntroItem.tsx +0 -0
  191. package/src/ui/StaticTranscriptItem.tsx +0 -0
  192. package/src/ui/TextEntryPanel.tsx +0 -0
  193. package/src/ui/ThemePicker.tsx +0 -0
  194. package/src/ui/ThinkingBlock.tsx +0 -0
  195. package/src/ui/Timeline.test.ts +0 -0
  196. package/src/ui/Timeline.tsx +0 -0
  197. package/src/ui/TimelineNavigation.test.tsx +0 -0
  198. package/src/ui/TopHeader.test.tsx +53 -38
  199. package/src/ui/TopHeader.tsx +51 -15
  200. package/src/ui/TurnGroup.test.tsx +0 -0
  201. package/src/ui/TurnGroup.tsx +0 -0
  202. package/src/ui/busyStatusAnimation.test.ts +0 -0
  203. package/src/ui/busyStatusAnimation.ts +0 -0
  204. package/src/ui/commandNormalize.test.ts +0 -0
  205. package/src/ui/commandNormalize.ts +0 -0
  206. package/src/ui/diffRenderer.test.ts +0 -0
  207. package/src/ui/diffRenderer.ts +0 -0
  208. package/src/ui/focus.ts +0 -0
  209. package/src/ui/focusFlow.test.tsx +0 -0
  210. package/src/ui/inputBuffer.test.ts +0 -0
  211. package/src/ui/inputBuffer.ts +0 -0
  212. package/src/ui/layout.test.ts +3 -2
  213. package/src/ui/layout.ts +0 -0
  214. package/src/ui/modeDisplay.test.ts +0 -0
  215. package/src/ui/modeDisplay.ts +0 -0
  216. package/src/ui/outputPipeline.ts +0 -0
  217. package/src/ui/progressEntries.ts +0 -0
  218. package/src/ui/runActivityView.test.ts +0 -0
  219. package/src/ui/runActivityView.ts +0 -0
  220. package/src/ui/runLifecycleView.test.tsx +0 -0
  221. package/src/ui/slashCommands.ts +0 -0
  222. package/src/ui/statusRenderIsolation.test.tsx +0 -0
  223. package/src/ui/terminalAnswerFormat.test.ts +0 -0
  224. package/src/ui/terminalAnswerFormat.ts +0 -0
  225. package/src/ui/textLayout.test.ts +0 -0
  226. package/src/ui/textLayout.ts +0 -0
  227. package/src/ui/theme.tsx +0 -0
  228. package/src/ui/themeFlow.test.ts +0 -0
  229. package/src/ui/themeFlow.ts +0 -0
  230. package/src/ui/timelineMeasure.ts +0 -0
  231. package/src/ui/timelineMeasureCache.test.ts +0 -0
  232. package/src/ui/useThrottledValue.ts +0 -0
@@ -365,8 +365,9 @@ test("resize repaint does not erase terminal scrollback buffer", async () => {
365
365
  const harness = createSupportedHarness();
366
366
  startApp(harness.deps);
367
367
 
368
- // Startup clears only the visible viewport; native terminal scrollback must survive.
369
- assert.doesNotMatch(harness.stdout.writes, /\x1b\[3J/, "startup must not erase scrollback");
368
+ // Startup intentionally clears scrollback (\x1b[3J) so the app opens into a
369
+ // clean screen. Verify it IS written exactly once during startup.
370
+ assert.match(harness.stdout.writes, /\x1b\[3J/, "startup must erase scrollback for clean open");
370
371
 
371
372
  // Capture offset so we can inspect only post-startup writes.
372
373
  const startupEnd = harness.stdout.writes.length;
@@ -407,7 +408,8 @@ test("startup writes safe fallback title before repaint and workspace title befo
407
408
  const harness = createSupportedHarness();
408
409
  startApp(harness.deps);
409
410
 
410
- const repaintIndex = harness.stdout.writes.indexOf("\x1b[2J\x1b[H");
411
+ // Startup now writes a full transcript clear: viewport + scrollback + cursor home.
412
+ const repaintIndex = harness.stdout.writes.indexOf("\x1b[2J\x1b[3J\x1b[H");
411
413
  const firstTitleIndex = harness.stdout.writes.indexOf("\x1b]0;");
412
414
  const postRepaintTitleIndex = harness.stdout.writes.indexOf("\x1b]0;", repaintIndex + 1);
413
415
  const bracketedPasteIndex = harness.stdout.writes.indexOf("\x1b[?2004h");
package/src/index.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  traceTerminalClear,
20
20
  writeTerminalControl,
21
21
  } from "./core/terminal/terminalControl.js";
22
+ import { performStartupClear } from "./core/terminal/startupClear.js";
22
23
 
23
24
  type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
24
25
  const KITTY_KEYBOARD_OPTIONS: RenderOptions["kittyKeyboard"] = {
@@ -174,15 +175,18 @@ export function startApp({
174
175
  durationMs: 3500,
175
176
  });
176
177
 
177
- // Clear the screen and move cursor to home before rendering so no stale
178
- // content from a previous process (e.g. bun --watch restart) ghosts above
179
- // the new frame. We stay in the normal screen buffer (no \x1b[?1049h) to
180
- // preserve terminal scrollback and allow mouse text selection.
181
- // NOTE: Mouse reporting (\x1b[?1000h / \x1b[?1006h) is NOT enabled here.
182
- // It is managed exclusively by the React app (app.tsx) and defaults to OFF
183
- // so native terminal drag-selection and copy work without any special steps.
184
- traceTerminalClear("src/index.tsx:startup", { mode: "hard" });
185
- writeStdout(TERMINAL_SEQUENCES.hardRepaint, "src/index.tsx:startup");
178
+ // Clear the screen (viewport + scrollback) and move cursor home before Ink
179
+ // renders the first frame. This removes any previous terminal content so
180
+ // the app opens into a clean screen. We stay in the normal screen buffer
181
+ // (no \x1b[?1049h) to preserve mouse text selection after exit.
182
+ // Skipped when --no-clear or CODEXA_NO_CLEAR=1 is set.
183
+ // NOTE: Mouse reporting is NOT enabled here managed solely by app.tsx.
184
+ traceTerminalClear("src/index.tsx:startup", { mode: "transcript" });
185
+ performStartupClear({
186
+ write: (c) => writeStdout(c, "src/index.tsx:startup"),
187
+ noClear: launchArgs.noClear,
188
+ env,
189
+ });
186
190
  const startupWorkspaceRoot = resolveWorkspaceRoot();
187
191
  const startupSettings = loadSettings();
188
192
  const startupTitle =
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -4,8 +4,9 @@ import React from "react";
4
4
  import { PassThrough } from "node:stream";
5
5
  import { render, Text } from "ink";
6
6
  import type { Screen, TimelineEvent, UIState } from "../session/types.js";
7
- import { buildRuntimeSummary } from "../config/runtimeConfig.js";
8
- import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../config/settings.js";
7
+ import { buildRuntimeSummary } from "../config/runtimeConfig.js";
8
+ import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../config/settings.js";
9
+ import type { UpdateCheckResult } from "../core/updateCheck.js";
9
10
  import { TEST_RUNTIME } from "../test/runtimeTestUtils.js";
10
11
  import { BottomComposer, measureBottomComposerRows } from "./BottomComposer.js";
11
12
  import { AppShell, calculateColdStartSpacerRows, calculateHeaderToContentGapRows, calculateNativeSpacerRows } from "./AppShell.js";
@@ -77,6 +78,13 @@ const EVENTS: TimelineEvent[] = [
77
78
  },
78
79
  ];
79
80
 
81
+ const STARTUP_HEADER_CONFIG: HeaderConfig = {
82
+ ...HEADER_CONFIG_DEFAULTS,
83
+ showModel: false,
84
+ showReasoning: false,
85
+ showContext: true,
86
+ };
87
+
80
88
  function stripAnsi(value: string): string {
81
89
  return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
82
90
  }
@@ -89,15 +97,16 @@ function sleep(ms = 50): Promise<void> {
89
97
  return new Promise((resolve) => setTimeout(resolve, ms));
90
98
  }
91
99
 
92
- function renderShell(
93
- layoutCols: number,
94
- layoutRows: number,
95
- uiState: UIState,
96
- screen: "main" | "theme-picker" | "model-picker" = "main",
97
- panel: React.ReactNode = null,
98
- mainPanel: React.ReactNode = null,
99
- mainPanelMode: "viewport" | "full-output" = "viewport",
100
- ): Promise<string> {
100
+ function renderShell(
101
+ layoutCols: number,
102
+ layoutRows: number,
103
+ uiState: UIState,
104
+ screen: "main" | "theme-picker" | "model-picker" = "main",
105
+ panel: React.ReactNode = null,
106
+ mainPanel: React.ReactNode = null,
107
+ mainPanelMode: "viewport" | "full-output" = "viewport",
108
+ updateCheckResult: UpdateCheckResult | null = null,
109
+ ): Promise<string> {
101
110
  const stdin = new TestInput();
102
111
  const stdout = new TestOutput();
103
112
  stdout.columns = layoutCols;
@@ -163,9 +172,11 @@ function renderShell(
163
172
  onQuit={() => {}}
164
173
  />
165
174
  }
166
- composerRows={composerRows}
167
- />
168
- </ThemeProvider>,
175
+ composerRows={composerRows}
176
+ headerConfig={STARTUP_HEADER_CONFIG}
177
+ updateCheckResult={updateCheckResult}
178
+ />
179
+ </ThemeProvider>,
169
180
  {
170
181
  stdin: stdin as unknown as NodeJS.ReadStream,
171
182
  stdout: stdout as unknown as NodeJS.WriteStream,
@@ -181,7 +192,28 @@ function renderShell(
181
192
  await sleep(20);
182
193
  return stripAnsi(output);
183
194
  });
184
- }
195
+ }
196
+
197
+ test("renders npm update notice when a newer Codexa version is available", async () => {
198
+ const output = await renderShell(
199
+ 120,
200
+ 40,
201
+ { kind: "IDLE" },
202
+ "main",
203
+ null,
204
+ null,
205
+ "viewport",
206
+ {
207
+ status: "update-available",
208
+ currentVersion: "1.0.1",
209
+ latestVersion: "1.0.2",
210
+ checkedAt: Date.now(),
211
+ },
212
+ );
213
+
214
+ assert.match(stripAnsi(output), /Update available: Codexa 1\.0\.2 is available\. You are using 1\.0\.1\./);
215
+ assert.match(stripAnsi(output), /Run: npm install -g @golba98\/codexa@latest/);
216
+ });
185
217
 
186
218
  function renderStartupShell(
187
219
  layoutCols: number,
@@ -256,6 +288,7 @@ function renderStartupShell(
256
288
  />
257
289
  }
258
290
  composerRows={composerRows}
291
+ headerConfig={STARTUP_HEADER_CONFIG}
259
292
  />
260
293
  </ThemeProvider>,
261
294
  {
@@ -283,13 +316,17 @@ test("startup uses the large logo only when the viewport height can contain it",
283
316
  assert.match(output, /\n╭[─]+╮\n│ ❯/);
284
317
  });
285
318
 
286
- test("startup uses live compact header at normal shorter terminal height", async () => {
319
+ test("startup uses stacked ASCII header at normal shorter terminal width", async () => {
287
320
  const output = await renderStartupShell(100, 24);
288
321
 
322
+ assert.match(output, /██████/);
289
323
  assert.match(output, /Codexa v/);
290
324
  assert.match(output, /C:\\Development\\1-JavaScript\\13-Custom CLI/);
325
+ assert.match(output, /Provider: Codexa Core/);
326
+ assert.match(output, /Context: Unknown/);
327
+ assert.doesNotMatch(output, /Model: gpt-5\.4/);
328
+ assert.doesNotMatch(output, /Reasoning:/);
291
329
  assert.match(output, /\n╭[─]+╮\n│ ❯/);
292
- assert.doesNotMatch(output, /██████/);
293
330
  });
294
331
 
295
332
  test("startup micro mode keeps the live header and composer visible", async () => {
@@ -551,22 +588,22 @@ test("cold-start header gap adapts to terminal height", () => {
551
588
  layoutMode: "micro",
552
589
  availableRows: 9,
553
590
  }), 1);
554
- // full 30-row medium: measureTopHeaderRows=8 (1+6+1), headerToContentGap=1, shellHeight=29
591
+ // full 30-row medium: measureTopHeaderRows=7 (1+6+0), headerToContentGap=1, shellHeight=29
555
592
  assert.equal(calculateColdStartSpacerRows({
556
593
  shellRows: 29,
557
594
  headerRows: 8,
558
595
  composerRows: 7,
559
596
  layoutMode: "full",
560
597
  availableRows: 11,
561
- }), 4);
562
- // full 40-row tall: measureTopHeaderRows=9 (1+6+2), headerToContentGap=1, shellHeight=39
598
+ }), 1);
599
+ // full 40-row tall: measureTopHeaderRows=7 (1+6+0), headerToContentGap=1, shellHeight=39
563
600
  assert.equal(calculateColdStartSpacerRows({
564
601
  shellRows: 39,
565
602
  headerRows: 9,
566
603
  composerRows: 7,
567
604
  layoutMode: "full",
568
605
  availableRows: 20,
569
- }), 6);
606
+ }), 1);
570
607
  });
571
608
 
572
609
  test("cold-start header gap is capped by available rows", () => {
@@ -576,7 +613,7 @@ test("cold-start header gap is capped by available rows", () => {
576
613
  composerRows: 7,
577
614
  layoutMode: "full",
578
615
  availableRows: 2,
579
- }), 2);
616
+ }), 1);
580
617
  });
581
618
 
582
619
  test("header-to-content gap is reserved outside the hero", () => {
@@ -1156,7 +1193,7 @@ test("project instructions render with breathing room below the live header", as
1156
1193
  const projectRow = rows.findIndex((row) => row.includes("Project instructions"));
1157
1194
 
1158
1195
  assert.ok(lastLogoRow >= 0, "logo should render");
1159
- assert.ok(projectRow > lastLogoRow + 2, "project instructions should not touch the logo block");
1196
+ assert.ok(projectRow > lastLogoRow + 1, "project instructions should not touch the logo block");
1160
1197
  assertHeaderBefore(raw, "Project instructions");
1161
1198
  assert.equal(countLogoInOutput(raw), 1, "project instruction notice must not add a transcript banner");
1162
1199
  });
@@ -1,5 +1,7 @@
1
1
  import React, { memo, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Box, Text, useStdin } from "ink";
3
+ import { useTheme } from "./theme.js";
4
+ import type { UpdateCheckResult } from "../core/updateCheck.js";
3
5
  import type { RuntimeSummary } from "../config/runtimeConfig.js";
4
6
  import type { CodexAuthState } from "../core/auth/codexAuth.js";
5
7
  import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../config/settings.js";
@@ -25,9 +27,9 @@ import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type Timeline
25
27
  import type { TerminalSelectionProfile } from "../core/terminal/terminalSelection.js";
26
28
  import { MemoizedTopHeader, measureTopHeaderRows } from "./TopHeader.js";
27
29
 
28
- const COMPACT_HEADER_TO_COMPOSER_GAP_ROWS = 2;
29
- const MEDIUM_HEADER_TO_COMPOSER_GAP_ROWS = 4;
30
- const TALL_HEADER_TO_COMPOSER_GAP_ROWS = 6;
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;
31
33
 
32
34
  // ─── Types & constants ────────────────────────────────────────────────────────
33
35
 
@@ -57,6 +59,7 @@ export interface AppShellProps {
57
59
  selectionProfile?: TerminalSelectionProfile;
58
60
  clearCount?: number;
59
61
  headerConfig?: HeaderConfig;
62
+ updateCheckResult?: UpdateCheckResult | null;
60
63
  }
61
64
 
62
65
  // ─── Helpers & subcomponents ─────────────────────────────────────────────────
@@ -161,7 +164,9 @@ function AppShellInner({
161
164
  selectionProfile,
162
165
  clearCount = 0,
163
166
  headerConfig = HEADER_CONFIG_DEFAULTS,
167
+ updateCheckResult = null,
164
168
  }: AppShellProps) {
169
+ const theme = useTheme();
165
170
  renderDebug.useRenderDebug("AppShell", {
166
171
  cols: layout.cols,
167
172
  rows: layout.rows,
@@ -188,7 +193,7 @@ function AppShellInner({
188
193
 
189
194
  const shellWidth = getShellWidth(layout.cols);
190
195
  const shellHeight = getShellHeight(layout.rows);
191
- const headerRows = measureTopHeaderRows(layout);
196
+ const headerRows = measureTopHeaderRows(layout, headerConfig);
192
197
  const headerToContentGapRows = calculateHeaderToContentGapRows(layout);
193
198
  const showComposer = screen === "main";
194
199
  const showMainPanel = screen === "main" && mainPanel !== undefined && mainPanel !== null;
@@ -500,6 +505,17 @@ function AppShellInner({
500
505
  ? React.cloneElement(composer as React.ReactElement<{ selectionProfile?: TerminalSelectionProfile }>, { selectionProfile })
501
506
  : composer;
502
507
 
508
+ const updateNotice = updateCheckResult?.status === "update-available" ? (
509
+ <Box paddingX={1} flexDirection="column">
510
+ <Text color={theme.WARNING}>
511
+ {`Update available: Codexa ${updateCheckResult.latestVersion ?? "unknown"} is available. You are using ${updateCheckResult.currentVersion}.`}
512
+ </Text>
513
+ <Text color={theme.WARNING}>
514
+ {"Run: npm install -g @golba98/codexa@latest"}
515
+ </Text>
516
+ </Box>
517
+ ) : null;
518
+
503
519
  if (showMainPanelFullOutput) {
504
520
  return (
505
521
  <Box flexDirection="column" width="100%">
@@ -512,6 +528,8 @@ function AppShellInner({
512
528
  headerConfig={headerConfig}
513
529
  />
514
530
 
531
+ {updateNotice}
532
+
515
533
  {headerToContentGapRows > 0 && (
516
534
  <Box height={headerToContentGapRows} />
517
535
  )}
@@ -542,6 +560,8 @@ function AppShellInner({
542
560
  headerConfig={headerConfig}
543
561
  />
544
562
 
563
+ {updateNotice}
564
+
545
565
  {headerToContentGapRows > 0 && (
546
566
  <Box height={headerToContentGapRows} />
547
567
  )}
@@ -596,6 +616,8 @@ function AppShellInner({
596
616
  headerConfig={headerConfig}
597
617
  />
598
618
 
619
+ {updateNotice}
620
+
599
621
  {headerToContentGapRows > 0 && (
600
622
  <Box height={headerToContentGapRows} />
601
623
  )}
@@ -692,7 +714,8 @@ export const AppShell = memo(AppShellInner, (prev, next) => {
692
714
  prev.verboseMode === next.verboseMode &&
693
715
  prev.mouseCapture === next.mouseCapture &&
694
716
  prev.onMouseActivity === next.onMouseActivity &&
695
- prev.clearCount === next.clearCount &&
717
+ prev.clearCount === next.clearCount &&
718
+ prev.updateCheckResult === next.updateCheckResult &&
696
719
  panelPropsEqual
697
720
  );
698
721
  });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/ui/Panel.tsx CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes