) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleDescriptionSave(descriptionDraft);
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ handleDescriptionCancel();
+ }
+ },
+ [descriptionDraft, handleDescriptionSave, handleDescriptionCancel]
+ );
+
+ const handleDescriptionBlur = useCallback(() => {
+ handleDescriptionSave(descriptionDraft);
+ }, [descriptionDraft, handleDescriptionSave]);
+
+ // Sync draft with tab.description when it changes externally
+ useEffect(() => {
+ if (!isEditingDescription) {
+ setDescriptionDraft(tab.description ?? '');
+ }
+ }, [tab.description, isEditingDescription]);
+
+ // Keep ref in sync with latest draft value to avoid stale closures in cleanup
+ useEffect(() => {
+ descriptionDraftRef.current = descriptionDraft;
+ }, [descriptionDraft]);
+
+ // Save description draft when overlay closes while editing
+ useEffect(() => {
+ if (!overlayOpen && isEditingDescription) {
+ const draft = descriptionDraftRef.current.trim();
+ if (draft !== (tab.description ?? '')) {
+ onUpdateTabDescription?.(tabId, draft);
+ }
+ setIsEditingDescription(false);
+ setDescriptionDraft(draft || (tab.description ?? ''));
+ }
+ }, [overlayOpen, isEditingDescription, tab.description, onUpdateTabDescription, tabId]);
+
// Handlers for drag events using stable tabId
const handleTabSelect = useCallback(() => {
onSelect(tabId);
@@ -681,6 +756,66 @@ const Tab = memo(function Tab({
)}
+ {/* Description section - only render when feature is enabled */}
+ {onUpdateTabDescription && (
+
+ {isEditingDescription ? (
+
+ )}
+
{/* Actions */}
{tab.agentSessionId && (
@@ -1512,6 +1647,7 @@ function TabBarInner({
onNewTab,
onRequestRename,
onTabReorder,
+ onUpdateTabDescription,
onTabStar,
onTabMarkUnread,
onMergeWith,
@@ -1975,6 +2111,7 @@ function TabBarInner({
? handleTabPublishGist
: undefined
}
+ onUpdateTabDescription={onUpdateTabDescription}
onMoveToFirst={
!isFirstTab && onUnifiedTabReorder ? handleMoveToFirst : undefined
}
@@ -2092,6 +2229,7 @@ function TabBarInner({
? handleTabPublishGist
: undefined
}
+ onUpdateTabDescription={onUpdateTabDescription}
onMoveToFirst={!isFirstTab && onTabReorder ? handleMoveToFirst : undefined}
onMoveToLast={!isLastTab && onTabReorder ? handleMoveToLast : undefined}
isFirstTab={isFirstTab}
diff --git a/src/renderer/hooks/props/useMainPanelProps.ts b/src/renderer/hooks/props/useMainPanelProps.ts
index dd4bd232b..3b34fd798 100644
--- a/src/renderer/hooks/props/useMainPanelProps.ts
+++ b/src/renderer/hooks/props/useMainPanelProps.ts
@@ -170,6 +170,7 @@ export interface UseMainPanelPropsDeps {
agentSessionId: string,
updates: { name?: string | null; starred?: boolean }
) => void;
+ handleUpdateTabDescription?: (tabId: string, description: string) => void;
handleTabStar: (tabId: string, starred: boolean) => void;
handleTabMarkUnread: (tabId: string) => void;
handleToggleTabReadOnlyMode: () => void;
@@ -351,6 +352,7 @@ export function useMainPanelProps(deps: UseMainPanelPropsDeps) {
onTabReorder: deps.handleTabReorder,
onUnifiedTabReorder: deps.handleUnifiedTabReorder,
onUpdateTabByClaudeSessionId: deps.handleUpdateTabByClaudeSessionId,
+ onUpdateTabDescription: deps.handleUpdateTabDescription,
onTabStar: deps.handleTabStar,
onTabMarkUnread: deps.handleTabMarkUnread,
onToggleTabReadOnlyMode: deps.handleToggleTabReadOnlyMode,
@@ -553,6 +555,7 @@ export function useMainPanelProps(deps: UseMainPanelPropsDeps) {
deps.handleTabReorder,
deps.handleUnifiedTabReorder,
deps.handleUpdateTabByClaudeSessionId,
+ deps.handleUpdateTabDescription,
deps.handleTabStar,
deps.handleTabMarkUnread,
deps.handleToggleTabReadOnlyMode,
diff --git a/src/renderer/hooks/tabs/useTabHandlers.ts b/src/renderer/hooks/tabs/useTabHandlers.ts
index dfb59d4f7..5d6edcd4f 100644
--- a/src/renderer/hooks/tabs/useTabHandlers.ts
+++ b/src/renderer/hooks/tabs/useTabHandlers.ts
@@ -75,6 +75,7 @@ export interface TabHandlersReturn {
agentSessionId: string,
updates: { name?: string | null; starred?: boolean }
) => void;
+ handleUpdateTabDescription: (tabId: string, description: string) => void;
handleTabStar: (tabId: string, starred: boolean) => void;
handleTabMarkUnread: (tabId: string) => void;
handleToggleTabReadOnlyMode: () => void;
@@ -966,6 +967,22 @@ export function useTabHandlers(): TabHandlersReturn {
// Tab Properties
// ========================================================================
+ const handleUpdateTabDescription = useCallback((tabId: string, description: string) => {
+ const trimmed = description.trim();
+ const { setSessions, activeSessionId } = useSessionStore.getState();
+ setSessions((prev: Session[]) =>
+ prev.map((s) => {
+ if (s.id !== activeSessionId) return s;
+ return {
+ ...s,
+ aiTabs: s.aiTabs.map((tab) =>
+ tab.id === tabId ? { ...tab, description: trimmed || undefined } : tab
+ ),
+ };
+ })
+ );
+ }, []);
+
const handleRequestTabRename = useCallback((tabId: string) => {
const { sessions, activeSessionId, setSessions } = useSessionStore.getState();
const session = sessions.find((s) => s.id === activeSessionId);
@@ -1393,6 +1410,7 @@ export function useTabHandlers(): TabHandlersReturn {
handleCloseTabsRight,
handleCloseCurrentTab,
handleRequestTabRename,
+ handleUpdateTabDescription,
handleUpdateTabByClaudeSessionId,
handleTabStar,
handleTabMarkUnread,
diff --git a/src/renderer/stores/settingsStore.ts b/src/renderer/stores/settingsStore.ts
index c10e6071e..d7c52f586 100644
--- a/src/renderer/stores/settingsStore.ts
+++ b/src/renderer/stores/settingsStore.ts
@@ -109,6 +109,7 @@ export const DEFAULT_ONBOARDING_STATS: OnboardingStats = {
export const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = {
directorNotes: false,
+ tabDescription: false,
};
export const DEFAULT_DIRECTOR_NOTES_SETTINGS: DirectorNotesSettings = {
diff --git a/src/renderer/types/index.ts b/src/renderer/types/index.ts
index 74fdb2b14..8a2a58b09 100644
--- a/src/renderer/types/index.ts
+++ b/src/renderer/types/index.ts
@@ -425,6 +425,8 @@ export interface AITab {
autoSendOnActivate?: boolean; // When true, automatically send inputValue when tab becomes active
wizardState?: SessionWizardState; // Per-tab inline wizard state for /wizard command
isGeneratingName?: boolean; // True while automatic tab naming is in progress
+ /** Optional user-defined description for tab context */
+ description?: string;
}
// A single "thinking item" — one busy tab within a session.
@@ -905,6 +907,7 @@ export interface LeaderboardSubmitResponse {
// Each key is a feature ID, value indicates whether it's enabled
export interface EncoreFeatureFlags {
directorNotes: boolean;
+ tabDescription: boolean;
}
// Director's Notes settings for synopsis generation