diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 38f773cf037e..39c091e7bebf 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1562,6 +1562,7 @@ export default function ChatView(props: ChatViewProps) { }; }, [routeKind, routeThreadRef, routeThreadState]); const markThreadVisited = useUiStateStore((store) => store.markThreadVisited); + const markThreadUnread = useUiStateStore((store) => store.markThreadUnread); const settings = useEnvironmentSettings(environmentId); const setStickyComposerModelSelection = useComposerDraftStore( (store) => store.setStickyModelSelection, @@ -6656,6 +6657,14 @@ export default function ChatView(props: ChatViewProps) { return; } + if (command === "thread.markUnread") { + event.preventDefault(); + event.stopPropagation(); + if (!isServerThread || !activeThreadKey) return; + markThreadUnread(activeThreadKey, activeLatestTurn?.completedAt); + return; + } + if (command === "terminal.toggle") { event.preventDefault(); event.stopPropagation(); @@ -6820,6 +6829,8 @@ export default function ChatView(props: ChatViewProps) { activeThreadRef, activeThreadPinned, activeThreadSettled, + activeLatestTurn?.completedAt, + markThreadUnread, canInterruptRunningThread, activeThreadKey, terminalUiState.terminalOpen, diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts index ff3b30124a73..5a96f49279bc 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts @@ -235,12 +235,16 @@ describe("KeybindingsSettings.logic", () => { "chat.new", "rightPanel.toggleMaximized", "thread.stop", + "thread.markUnread", "script.setup-db.run", ]), ); expect(DEFAULT_RESOLVED_KEYBINDINGS.some((binding) => binding.command === "thread.stop")).toBe( false, ); + expect( + DEFAULT_RESOLVED_KEYBINDINGS.some((binding) => binding.command === "thread.markUnread"), + ).toBe(false); }); it("reports unknown when variables without rejecting parseable expressions", () => { diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index d335c68869bb..ef9b48ed4822 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -743,16 +743,19 @@ describe("cross-command precedence", () => { }); describe("resolveShortcutCommand", () => { - it("resolves a custom stop-thread shortcut", () => { - const keybindings = compile([{ shortcut: modShortcut("escape"), command: "thread.stop" }]); + it.each(["thread.stop", "thread.markUnread"] as const)( + "resolves a custom %s shortcut", + (command) => { + const keybindings = compile([{ shortcut: modShortcut("escape"), command }]); - assert.strictEqual( - resolveShortcutCommand(event({ key: "Escape", metaKey: true }), keybindings, { - platform: "MacIntel", - }), - "thread.stop", - ); - }); + assert.strictEqual( + resolveShortcutCommand(event({ key: "Escape", metaKey: true }), keybindings, { + platform: "MacIntel", + }), + command, + ); + }, + ); it("honors preview conditions for a stop-thread shortcut", () => { const keybindings = compile([ diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index acf6bd2aa1ac..3e8bb89ff7b2 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -28,6 +28,13 @@ to copy its URL and `mod+shift+k` to copy its number with a `#` prefix. Both shortcuts can be changed in Settings. Search for “Copy Link or Thread ID” or “Copy Number”. They copy the selected PR and leave terminal input alone. +## Mark a thread unread + +`thread.markUnread` marks the active thread unread after a completed turn. It has +no default shortcut; assign one in **Settings → Keybindings** on web and desktop. +Add `!terminalFocus` as its condition to leave terminal input alone. +The thread stays unread until you reopen it or a new turn completes. + ## iPad With a hardware keyboard, use `Cmd+1` through `Cmd+9` to open the first nine diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index 67574f0b0286..fc7a0e9f5f04 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -109,6 +109,10 @@ The linked pull request participates in automatic settlement. ## Find and reference work +Choose **Mark unread** from a thread's menu to revisit a completed response later. +On web and desktop, assign a shortcut to `thread.markUnread` in **Settings → Keybindings** +to mark the open thread unread. Reopening the thread marks it read again. + On web and desktop, open the command palette with `Cmd/Ctrl+K` to search threads across connected environments. Message search starts after two characters and includes your messages and final agent responses. diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 1256dcc77786..ab33d538e951 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -42,6 +42,7 @@ const THREAD_KEYBINDING_COMMANDS = [ "thread.copyReference", "thread.settle", "thread.pin", + "thread.markUnread", ...THREAD_JUMP_KEYBINDING_COMMANDS, ] as const; export type ThreadKeybindingCommand = (typeof THREAD_KEYBINDING_COMMANDS)[number];