diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx
index fafc4e64f2..199ecc269b 100644
--- a/packages/tui/src/component/prompt/index.tsx
+++ b/packages/tui/src/component/prompt/index.tsx
@@ -640,6 +640,7 @@ export function Prompt(props: PromptProps) {
"prompt.submit",
"prompt.editor",
"prompt.editor_context.clear",
+ "prompt.images.view",
"prompt.stash",
"prompt.stash.pop",
"prompt.stash.list",
@@ -1510,7 +1511,7 @@ export function Prompt(props: PromptProps) {
- setFailed(true)}
- />
-
- {failed() ? "No preview" : (file.mention?.text ?? `Image ${index() + 1}`)}
-
+
+ No preview
+
+ }
+ >
+ setFailed(true)}
+ />
+
)
}}
diff --git a/packages/tui/src/config/v1/keybind.ts b/packages/tui/src/config/v1/keybind.ts
index 9787f9ad5d..b3ed88ced2 100644
--- a/packages/tui/src/config/v1/keybind.ts
+++ b/packages/tui/src/config/v1/keybind.ts
@@ -164,6 +164,7 @@ export const Definitions = {
prompt_submit: keybind("none", "Submit prompt"),
prompt_editor_context_clear: keybind("none", "Clear editor context"),
+ prompt_images_view: keybind("i", "View image attachments"),
prompt_skills: keybind("none", "Open skill selector"),
prompt_stash: keybind("none", "Stash prompt"),
prompt_stash_pop: keybind("none", "Pop stashed prompt"),
@@ -364,6 +365,7 @@ export const CommandMap = {
display_thinking: "session.toggle.thinking",
prompt_submit: "prompt.submit",
prompt_editor_context_clear: "prompt.editor_context.clear",
+ prompt_images_view: "prompt.images.view",
prompt_skills: "prompt.skills",
prompt_stash: "prompt.stash",
prompt_stash_pop: "prompt.stash.pop",
diff --git a/packages/tui/test/config.test.tsx b/packages/tui/test/config.test.tsx
index a9d5a007fe..20de66ce65 100644
--- a/packages/tui/test/config.test.tsx
+++ b/packages/tui/test/config.test.tsx
@@ -86,6 +86,14 @@ test("resolves a session move keybind", () => {
expect(config.keybinds.get("session.move")).toMatchObject([{ key: "ctrl+o" }])
})
+test("resolves the image viewer keybind", () => {
+ const defaults = resolve({}, { terminalSuspend: true })
+ const overridden = resolve({ keybinds: { prompt_images_view: "ctrl+shift+i" } }, { terminalSuspend: true })
+
+ expect(defaults.keybinds.get("prompt.images.view")).toMatchObject([{ key: "i" }])
+ expect(overridden.keybinds.get("prompt.images.view")).toMatchObject([{ key: "ctrl+shift+i" }])
+})
+
test("resolves message navigation defaults", () => {
const config = resolve({}, { terminalSuspend: true })
diff --git a/packages/tui/test/prompt/clipboard.test.ts b/packages/tui/test/prompt/clipboard.test.ts
index f7e772ffc2..41f315b366 100644
--- a/packages/tui/test/prompt/clipboard.test.ts
+++ b/packages/tui/test/prompt/clipboard.test.ts
@@ -318,6 +318,9 @@ test("renders at most three left-aligned cropped thumbnails", async () => {
expect(prompt.setup.renderer.root.findDescendantById("prompt-image-preview-3")).toBeUndefined()
const frame = await prompt.setup.waitForFrame((frame) => frame.includes("+1 more"))
expect(frame).toMatch(/^┃ █/m)
+ expect(frame.match(/\[Image 1\]/g)).toHaveLength(1)
+ expect(frame.match(/\[Image 2\]/g)).toHaveLength(1)
+ expect(frame.match(/\[Image 3\]/g)).toHaveLength(1)
await prompt.setup.mockMouse.click(49, 1, MouseButtons.LEFT)
await prompt.setup.waitForFrame((frame) => frame.includes("Image 4 of 4"))
@@ -331,13 +334,20 @@ test("renders at most three left-aligned cropped thumbnails", async () => {
}
})
-test("opens thumbnails by their exact mouse bounds and from the command palette", async () => {
+test("opens image attachments by keyboard, mouse, and command palette", async () => {
const prompt = await mountPrompt(readPngClipboard, true)
try {
await pasteImages(prompt, 2)
const thumbnail = prompt.setup.renderer.root.findDescendantById("prompt-image-preview-1")
if (!(thumbnail instanceof ImageRenderable)) throw new Error("Second image thumbnail did not render")
+
+ prompt.setup.mockInput.pressKey("x", { ctrl: true })
+ prompt.setup.mockInput.pressKey("i")
+ await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
+ prompt.setup.mockInput.pressCtrlC()
+ await prompt.setup.waitForFrame((frame) => !frame.includes("Image 1 of 2"))
+
await prompt.setup.mockMouse.click(14, 1, MouseButtons.LEFT)
await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
@@ -369,7 +379,8 @@ test("opens thumbnails by their exact mouse bounds and from the command palette"
prompt.setup.mockInput.pressKey("p", { ctrl: true })
await prompt.setup.waitForFrame((frame) => frame.includes("Commands"))
for (const key of "view image attachments") prompt.setup.mockInput.pressKey(key)
- await prompt.setup.waitForFrame((frame) => frame.includes("View image attachments"))
+ const palette = await prompt.setup.waitForFrame((frame) => frame.includes("View image attachments"))
+ expect(palette).toContain("ctrl+x i")
prompt.setup.mockInput.pressEnter()
await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
} finally {