From 9bcdbd95eff4b0d62c6841835378c9d0c4cc6821 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 4 Aug 2026 19:01:07 -0700 Subject: [PATCH 1/2] fix(tui): stop gap={1} from inserting phantom space before caret Nest value and caret in a gapless Box on agent-modal provider/profile forms and codex-login profile name so the | caret sits flush after typed text. Validate trims spaces on save. Closes CL-5342. --- src/tui/components/agent-modal.test.ts | 23 ++++++++++++++++++ src/tui/components/agent-modal.tsx | 31 +++++++++++++++--------- src/tui/components/codex-login-modal.tsx | 11 ++++++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/tui/components/agent-modal.test.ts b/src/tui/components/agent-modal.test.ts index 759f6e62d..a25ed4058 100644 --- a/src/tui/components/agent-modal.test.ts +++ b/src/tui/components/agent-modal.test.ts @@ -89,4 +89,27 @@ describe("validateProviderForm", () => { expect(result.submission.keyless).toBeUndefined(); } }); + + test("trims leading and trailing spaces on text fields at save", () => { + const result = validateProviderForm( + form({ + name: " firepass ", + baseURL: " https://firepass.example/v1 ", + apiKey: " sk-key ", + models: " fp-large , fp-small ", + defaultModel: " fp-large ", + }), + undefined, + ); + expect(result).toEqual({ + ok: true, + submission: { + name: "firepass", + baseURL: "https://firepass.example/v1", + apiKey: "sk-key", + models: ["fp-large", "fp-small"], + defaultModel: "fp-large", + }, + }); + }); }); diff --git a/src/tui/components/agent-modal.tsx b/src/tui/components/agent-modal.tsx index cc7bcf3fd..8e5860810 100644 --- a/src/tui/components/agent-modal.tsx +++ b/src/tui/components/agent-modal.tsx @@ -961,6 +961,13 @@ export function AgentModal({ const isCursor = i === formIndex; const value = formValues[field]; const isKeyless = formValues.keyless === "yes"; + // gap only between label and value — never between value and caret, + // or the caret sits after a phantom space the user did not type. + const showCaret = + isCursor && + field !== "keyless" && + field !== "bifrostVirtualKey" && + !(field === "apiKey" && isKeyless); return ( @@ -977,16 +984,16 @@ export function AgentModal({ ) : field === "apiKey" && isKeyless ? ( (disabled — keyless provider) ) : ( - 0 ? color("text") : color("muted")}> - {value.length > 0 - ? maskInput(field, value) - : field === "apiKey" && editingProvider !== undefined - ? "leave blank to keep existing" - : FIELD_HINTS[field]} - - )} - {isCursor && field !== "keyless" && field !== "bifrostVirtualKey" && !(field === "apiKey" && isKeyless) && ( - | + + 0 ? color("text") : color("muted")}> + {value.length > 0 + ? maskInput(field, value) + : field === "apiKey" && editingProvider !== undefined + ? "leave blank to keep existing" + : FIELD_HINTS[field]} + + {showCaret && |} + )} ); @@ -1052,12 +1059,12 @@ export function AgentModal({ {isCursor ? " >" : ""} ) : ( - <> + 0 ? color("text") : color("muted")}> {profileFormValues[field].length > 0 ? profileFormValues[field] : PROFILE_FIELD_HINTS[field]} {isCursor && |} - + )} ); diff --git a/src/tui/components/codex-login-modal.tsx b/src/tui/components/codex-login-modal.tsx index f24ebe074..6f5985d1e 100644 --- a/src/tui/components/codex-login-modal.tsx +++ b/src/tui/components/codex-login-modal.tsx @@ -255,10 +255,13 @@ export function CodexLoginModal({ Profile name - 0 ? color("text") : color("muted")}> - {nameValue.length > 0 ? nameValue : "personal, work, ..."} - - | + {/* Keep value and caret in one Box so gap does not insert a phantom space. */} + + 0 ? color("text") : color("muted")}> + {nameValue.length > 0 ? nameValue : "personal, work, ..."} + + | + {nameError !== null && ( From a9f3e481057e52992c12920182e465e3bcb1ecda Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 4 Aug 2026 22:30:55 -0700 Subject: [PATCH 2/2] Nest operator-modal draft and caret so gap does not insert space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the agent-modal fix: parent gap={1} only separates › from the value+caret group, so the caret sits flush after typed text. --- src/tui/components/operator-modal.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tui/components/operator-modal.tsx b/src/tui/components/operator-modal.tsx index c134a89df..5eecef49d 100644 --- a/src/tui/components/operator-modal.tsx +++ b/src/tui/components/operator-modal.tsx @@ -277,10 +277,13 @@ export function OperatorModal({ {typing ? ( + {/* gap only between › and value — never between value and caret */} - {draft} - + + {draft} + + Enter to confirm · Esc to cancel