Skip to content

Commit bea8f72

Browse files
committed
Speak dialog choices in plain English, not renderable names
Accepting a settings or approval dialog echoed its internal overlay kind and label straight to the transcript: "overlay chose (settings): compaction summarize ‹ drop ›". The word "overlay" named a renderable no operator knows, the "chose (kind): label" shape read as a state transition, and a cycled field's own selection markers survived into the line. Build the echo from the field's id and the value that actually won, worded as a sentence a person would say, and use the overlay's kind as its meta column instead of the literal word "overlay". The same path serves settings, permissions, and operator dialogs. Also stop labelling the goal/task/agents visibility toggles with the internal "chrome" zone name in their transcript text and meta.
1 parent f0dc610 commit bea8f72

3 files changed

Lines changed: 50 additions & 9 deletions

File tree

src/tui-opentui/command-surfaces.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,27 @@ describe("settings surface", () => {
166166
})
167167
})
168168

169+
test("choosing a cycled row writes a plain-English transcript line, not the internal echo", async () => {
170+
await withShell(async (shell) => {
171+
const { deps } = settingsDeps()
172+
openCommandSurface(shell, "settings", deps)
173+
await Promise.resolve()
174+
await Promise.resolve()
175+
176+
cycleOverlaySelection(shell, 1)
177+
await Promise.resolve()
178+
await Promise.resolve()
179+
acceptOverlaySelection(shell)
180+
181+
const row = shell.streamLog.at(-1)
182+
expect(row?.text).toBe("Set compaction to drop.")
183+
expect(row?.meta).not.toBe("overlay")
184+
expect(row?.text).not.toContain("‹")
185+
expect(row?.text).not.toContain("›")
186+
expect(row?.text).not.toContain("overlay")
187+
})
188+
})
189+
169190
test("session mode scope switch honours a local write", async () => {
170191
await withShell(async (shell) => {
171192
const { deps, calls } = settingsDeps()

src/tui-opentui/overlays.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ describe("model / provider picker", () => {
223223

224224
await h.renderOnce()
225225
frame = h.captureCharFrame()
226-
expect(frame).toContain("chose (model_picker)")
226+
expect(frame).toContain("model picker")
227+
expect(frame).toMatch(/Chose /)
227228
} finally {
228229
shell.dispose()
229230
}

src/tui-opentui/shell.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,7 +3347,7 @@ export function handleOverlayAnswerKey(
33473347
appendStreamRow(shell, {
33483348
role: "system",
33493349
text: `answered: ${text}`,
3350-
meta: "overlay",
3350+
meta: overlayKindWord(shell.overlayKind ?? "operator"),
33513351
})
33523352
// Deliberate submit, not a dismiss — closeInsetOverlay must not also fire
33533353
// the Esc/cancel path.
@@ -3735,14 +3735,33 @@ export function acceptOverlaySelection(shell: AppShell): void {
37353735
if (bag?.overlayEchoChoice !== false) {
37363736
appendStreamRow(shell, {
37373737
role: "system",
3738-
text: `chose (${kind}): ${label}`,
3739-
meta: "overlay",
3738+
text: overlayChoiceText(label, id),
3739+
meta: overlayKindWord(kind),
37403740
})
37413741
}
37423742
closeInsetOverlay(shell)
37433743
dispatchOverlayAccept(shell, selection, perOpen)
37443744
}
37453745

3746+
/**
3747+
* Plain-English echo of an accepted choice. A cycled settings field's label
3748+
* carries every option with `‹ ›` around the active one (list-painting detail,
3749+
* not something an operator asked for) — name the field by its id and report
3750+
* only the value that won. A plain list item has no such markers, so it is
3751+
* quoted as-is.
3752+
*/
3753+
function overlayChoiceText(label: string, id: string | undefined): string {
3754+
const active = label.match(/\s*(.+?)\s*/)
3755+
if (active === null) return `Chose ${label.trim()}.`
3756+
const field = id === undefined ? "setting" : id.replace(/[-_]/g, " ")
3757+
return `Set ${field} to ${active[1]}.`
3758+
}
3759+
3760+
/** Internal overlay kinds read as words in the transcript, not identifiers. */
3761+
function overlayKindWord(kind: PrimaryOverlayKind): string {
3762+
return kind.replace(/_/g, " ")
3763+
}
3764+
37463765
/**
37473766
* Dispatch a selected palette item after the palette has closed.
37483767
* - residual → `runPaletteAction` (overlays / chrome)
@@ -3843,8 +3862,8 @@ export function runPaletteAction(
38433862
})
38443863
appendStreamRow(shell, {
38453864
role: "system",
3846-
text: on ? "goal chrome off" : "goal chrome on",
3847-
meta: "chrome",
3865+
text: on ? "goal banner off" : "goal banner on",
3866+
meta: "goal",
38483867
})
38493868
return
38503869
}
@@ -3856,8 +3875,8 @@ export function runPaletteAction(
38563875
})
38573876
appendStreamRow(shell, {
38583877
role: "system",
3859-
text: on ? "task chrome off" : "task chrome on",
3860-
meta: "chrome",
3878+
text: on ? "task banner off" : "task banner on",
3879+
meta: "task",
38613880
})
38623881
return
38633882
}
@@ -3870,7 +3889,7 @@ export function runPaletteAction(
38703889
appendStreamRow(shell, {
38713890
role: "system",
38723891
text: on ? "agents strip off" : "agents strip on",
3873-
meta: "chrome",
3892+
meta: "agents",
38743893
})
38753894
return
38763895
}

0 commit comments

Comments
 (0)