From e4038ba15dfecdb9daa602b30f55613c8f81ace6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:02:32 +0100 Subject: [PATCH 1/2] feat(web): show unsent drafts in the sidebar - Add a draft indicator to sidebar thread rows - Surface drafts containing text or attachments, excluding settings-only state --- apps/web/src/components/SidebarV2.tsx | 15 +++++++++++++++ apps/web/src/composerDraftStore.ts | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index 89419d428f6..df5fa3e41c8 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -29,6 +29,7 @@ import { GitBranchIcon, EllipsisIcon, MessageSquareIcon, + PencilIcon, PinIcon, PlusIcon, SearchIcon, @@ -83,6 +84,7 @@ import { type SidebarProjectGroupMember, type SidebarProjectSnapshot, } from "../sidebarProjectGrouping"; +import { useComposerThreadHasDraftContent } from "../composerDraftStore"; import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; import { useThreadSelectionStore } from "../threadSelectionStore"; import { useThreadActions } from "../hooks/useThreadActions"; @@ -469,6 +471,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { }); const terminalStatus = terminalStatusFromRunningIds(runningTerminalIds); const terminalProcessCount = runningTerminalIds.length; + const hasDraft = useComposerThreadHasDraftContent(threadRef); const gitCwd = thread.worktreePath ?? props.projectCwd; const gitStatus = useEnvironmentQuery( @@ -825,6 +828,16 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { ) : null; + const draftIcon = hasDraft ? ( + + + + ) : null; if (variant === "slim") { return ( @@ -865,6 +878,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { /> {title} + {draftIcon} {terminalStatusIcon} {isRegeneratingTitle ? ( @@ -1125,6 +1139,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { ) : ( )} + {draftIcon} {terminalStatusIcon} {prBadge} {diff ? ( diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 95dde6187c8..77d43152780 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -3468,6 +3468,29 @@ export function clearComposerDraftsEnvironment(environmentId: EnvironmentId): vo composerDebouncedStorage.flush(); } +/** + * Whether the thread has unsent composer content worth surfacing in thread + * lists. Content means prompt text or attachments — a draft entry that only + * carries settings (model selection, runtime mode) doesn't count. + */ +export function useComposerThreadHasDraftContent(threadRef: ComposerThreadTarget): boolean { + return useComposerDraftStore((state) => { + const draft = getComposerDraftState(state, threadRef); + if (!draft) { + return false; + } + return ( + draft.prompt.trim().length > 0 || + draft.images.length > 0 || + draft.persistedAttachments.length > 0 || + draft.terminalContexts.length > 0 || + draft.elementContexts.length > 0 || + draft.previewAnnotations.length > 0 || + draft.reviewComments.length > 0 + ); + }); +} + export function useComposerThreadDraft(threadRef: ComposerThreadTarget): ComposerThreadDraftState { return useComposerDraftStore((state) => { return getComposerDraftState(state, threadRef) ?? EMPTY_THREAD_DRAFT; From ba4ec6a52a55c8e693e9ac8c4a07231b16c8dd2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:58:49 +0100 Subject: [PATCH 2/2] fix(web): brighten the sidebar draft pencil on row hover The pencil sat at a flat secondary-label colour, so on a settled row it read brighter than the row's own title and stayed put while the rest of the row lifted. Match the PR badge's settled treatment: muted at rest, transitioning with the row on hover. The pencil has no state colour of its own, so it lifts to the same foreground the title does, and the active row opts out like the PR badge already does. --- apps/web/src/components/SidebarV2.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index df5fa3e41c8..2839a10bafd 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -833,7 +833,14 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { role="img" aria-label="Unsent draft" data-testid={`sidebar-v2-draft-${thread.id}`} - className="inline-flex shrink-0 items-center justify-center text-secondary-label" + className={cn( + // Matches the PR badge's settled treatment: muted at rest so the + // parked tail stays quiet, brightening with the row on hover. The + // pencil has no state colour of its own, so it lifts to the same + // foreground the title does. + "inline-flex shrink-0 items-center justify-center text-secondary-label", + !props.isActive && "transition-colors group-hover/v2-row:text-foreground", + )} >