diff --git a/docs/channels.md b/docs/channels.md
index ad30fe0ebc..c8a4d68d07 100644
--- a/docs/channels.md
+++ b/docs/channels.md
@@ -1709,7 +1709,8 @@ Escape and Option+Arrow for the shell.
conversation is not among the rows, next starts from the top and previous from the
bottom. A conversation listed in two sections is stepped over once. The shortcuts
are unavailable, leaving the key alone, while the sidebar renders no rows (for
- example behind another page's own sidebar). Stepping activates the row's own
+ example behind another page's own sidebar) or focus is in a text field outside the
+ composer, which keeps Option+Arrow for editing. Stepping activates the row's own
button, so it takes the exact path of a click: the saved selection, navigation
intent, session staleness guard and Solo's immediate activation. The list scrolls
just far enough to show the row, and nothing above it moves.
diff --git a/src/bundled/channels/index.test.tsx b/src/bundled/channels/index.test.tsx
index 051314835a..2bd29be279 100644
--- a/src/bundled/channels/index.test.tsx
+++ b/src/bundled/channels/index.test.tsx
@@ -72,6 +72,20 @@ it("steps only while the sidebar renders rows, and not from a terminal", () => {
expect(next.when()).toBe(false);
});
+it("stays available in the message composer but not in other text fields", () => {
+ const next = registered().get("next-conversation");
+ sidebar("a", "*b", "c");
+ document.body.insertAdjacentHTML(
+ "beforeend",
+ `
+
`,
+ );
+ document.querySelector("textarea")?.focus();
+ expect(next.when()).toBe(false);
+ document.querySelector('[role="textbox"]')?.focus();
+ expect(next.when()).toBe(true);
+});
+
it("registers a plain Escape that waits for an open conversation to mark read", () => {
const shortcut = registered().get("mark-read");
expect(shortcut).toMatchObject({
diff --git a/src/bundled/channels/index.tsx b/src/bundled/channels/index.tsx
index ae54b251ed..449d20246e 100644
--- a/src/bundled/channels/index.tsx
+++ b/src/bundled/channels/index.tsx
@@ -12,6 +12,7 @@ import { isApplePlatform } from "../../features/shortcuts/format";
import type { ArchiveShortcut, ReadShortcut } from "./ChannelsPage";
import {
conversationRows,
+ inFieldOutsideComposer,
inTerminal,
stepConversation,
} from "./conversation-rows";
@@ -72,8 +73,9 @@ export const apply: PluginModule["apply"] = (ctx) => {
when: () => !!archive.current?.available(),
run: () => archive.current?.run(),
});
- // Slack's sidebar stepping. Terminals keep Option+Arrow, and stepping is
- // unavailable (the key is left alone) while the sidebar renders no rows.
+ // Slack's sidebar stepping. Terminals and text fields outside the composer
+ // keep Option+Arrow, and stepping is unavailable (the key is left alone)
+ // while the sidebar renders no rows.
const stepping = [
["previous-conversation", "Previous conversation", "ArrowUp", false, 30],
["next-conversation", "Next conversation", "ArrowDown", false, 31],
@@ -99,7 +101,10 @@ export const apply: PluginModule["apply"] = (ctx) => {
binding: { key, alt: true, shift: unreadOnly },
order,
allowInEditable: true,
- when: () => !inTerminal() && conversationRows().length > 0,
+ when: () =>
+ !inTerminal() &&
+ !inFieldOutsideComposer() &&
+ conversationRows().length > 0,
run: () =>
stepConversation(key === "ArrowUp" ? "previous" : "next", unreadOnly),
});