Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
629ddb0
🤖 feat: add immersive review foundation utilities
ibetitsmike Feb 17, 2026
024bf09
feat: add immersive review mode core UI
ibetitsmike Feb 17, 2026
63f1edb
🤖 tests: add immersive review utility unit tests
ibetitsmike Feb 17, 2026
96371ff
fix immersive review deep review findings
ibetitsmike Feb 17, 2026
3f60206
feat: update immersive review line-selection feedback flow
ibetitsmike Feb 17, 2026
3c5d1e3
fix: global immersive shortcut and inline line comment composer
ibetitsmike Feb 17, 2026
6b6abd7
feat: render immersive review as whole-file overlay and reverse hunk …
ibetitsmike Feb 17, 2026
abbe84b
Remove Add comment/Dislike header buttons from immersive review
ibetitsmike Feb 17, 2026
9e7d1ec
fix immersive review hunk targeting and scroll behavior
ibetitsmike Feb 17, 2026
3051bc4
🤖 feat: add immersive review notes sidebar
ibetitsmike Feb 17, 2026
06c091f
🤖 perf: optimize immersive review cursor navigation performance
ibetitsmike Feb 17, 2026
c634746
🤖 fix: address Codex immersive overlay and file nav feedback
ibetitsmike Feb 17, 2026
f91beeb
🤖 fix: align fallback lines and map notes to matching hunks
ibetitsmike Feb 17, 2026
86000c4
🤖 fix: keep fallback header rows out of line index map
ibetitsmike Feb 17, 2026
76ccc3a
🤖 fix: use all hunks when navigating from immersive notes
ibetitsmike Feb 17, 2026
f0b2d7e
🤖 fix: clear immersive state when sidebar is collapsed
ibetitsmike Feb 17, 2026
92b66ac
🤖 fix: preserve note navigation and clear stale external composer
ibetitsmike Feb 17, 2026
6f06e66
🤖 fix: handle immersive composer remap and aria isolation
ibetitsmike Feb 17, 2026
770a611
🤖 fix: harden immersive focus blocking and composer request lifecycle
ibetitsmike Feb 17, 2026
498dc6c
🤖 fix: reapply immersive inert state after placeholder mount
ibetitsmike Feb 17, 2026
c048b39
🤖 fix: rerun inert sync when workspace changes
ibetitsmike Feb 17, 2026
1996e52
🤖 fix: preserve shell layout while enforcing immersive pane inertness
ibetitsmike Feb 17, 2026
e99fadb
fix: update immersive review keybinds and navigation toast
ibetitsmike Feb 18, 2026
49ddc05
feat: add review tutorial for immersive toggle
ibetitsmike Feb 18, 2026
b24ccec
fix immersive review composer latency and note centering
ibetitsmike Feb 19, 2026
4b563b9
Fix immersive review submit, scroll jump, and loading UI
ibetitsmike Feb 20, 2026
d6cfe14
Fix Tooltip controlled/uncontrolled warning in DiffRenderer
ibetitsmike Feb 20, 2026
b01607d
feat: add keyboard focus mode for immersive review notes
ibetitsmike Feb 20, 2026
e2a8ce8
fix: resolve composer hunk from cursor position instead of stale sele…
ibetitsmike Feb 20, 2026
28c2b4c
perf: remove per-line diff tooltips in immersive review
ibetitsmike Feb 20, 2026
509ba96
🤖 fix: stabilize immersive file transitions
ibetitsmike Feb 20, 2026
4acb6f0
🤖 fix: gate immersive diff reveal and tighten row density
ibetitsmike Feb 20, 2026
9a6f47f
Fix immersive file reveal gating and diff row height consistency
ibetitsmike Feb 20, 2026
75dada6
fix: remove selectable row no-wrap guards causing line spacing regres…
ibetitsmike Feb 20, 2026
79699a2
fix: normalize highlighted diff rows to prevent extra blank spacing
ibetitsmike Feb 20, 2026
c8f2b82
fix: normalize immersive file lines to prevent extra row spacing
ibetitsmike Feb 20, 2026
37d45cd
fix: restore DiffRenderer baseline row layout and remove immersive li…
ibetitsmike Feb 20, 2026
452fc7f
fix: force comment overlay button absolute to prevent diff row height…
ibetitsmike Feb 20, 2026
26edaa9
Add immersive right sidebar review debug story
ibetitsmike Feb 20, 2026
763bd8b
tests: add mixed highlight/fallback immersive review story
ibetitsmike Feb 20, 2026
a8648fd
fix: use functional immersive toggle state update
ibetitsmike Feb 20, 2026
0ba4cee
fix: support immersive note edit hotkey and like icon
ibetitsmike Feb 20, 2026
b5c5a88
fmt: apply prettier for immersive review hotkey changes
ibetitsmike Feb 20, 2026
da314be
fix: sync immersive note editing escape and line selection
ibetitsmike Feb 20, 2026
4c57dd1
fix: ignore immersive Tab toggle in editable fields
ibetitsmike Feb 20, 2026
3edffed
🤖 fix: stabilize review composer sync and keybind semantics
ibetitsmike Feb 21, 2026
30e0b87
fix: prevent sticky external inline composer request
ibetitsmike Feb 21, 2026
240714e
fix inline composer external request/render cancel sync
ibetitsmike Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/browser/components/ChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ interface ChatPaneProps {
onToggleLeftSidebarCollapsed: () => void;
runtimeConfig?: RuntimeConfig;
onOpenTerminal: (options?: TerminalSessionCreateOptions) => void;
/** Hide + inactivate chat pane while immersive review overlay is active. */
immersiveHidden?: boolean;
}

type ReviewsState = ReturnType<typeof useReviews>;
Expand All @@ -144,11 +146,29 @@ export const ChatPane: React.FC<ChatPaneProps> = (props) => {
runtimeConfig,
onOpenTerminal,
workspaceState,
immersiveHidden = false,
} = props;
const { api } = useAPI();
const { workspaceMetadata } = useWorkspaceContext();
const chatAreaRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const chatPaneElement = chatAreaRef.current;
if (!chatPaneElement) {
return;
}

if (immersiveHidden) {
chatPaneElement.setAttribute("inert", "");
} else {
chatPaneElement.removeAttribute("inert");
}

return () => {
chatPaneElement.removeAttribute("inert");
};
}, [immersiveHidden, workspaceId]);

const storeRaw = useWorkspaceStoreRaw();
const aggregator = useWorkspaceAggregator(workspaceId);
const workspaceUsage = useWorkspaceUsage(workspaceId);
Expand Down Expand Up @@ -671,6 +691,7 @@ export const ChatPane: React.FC<ChatPaneProps> = (props) => {
<PerfRenderMarker id="chat-pane">
<div
ref={chatAreaRef}
aria-hidden={immersiveHidden || undefined}
className="flex min-w-96 flex-1 flex-col [@media(max-width:768px)]:max-h-full [@media(max-width:768px)]:w-full [@media(max-width:768px)]:min-w-0"
>
<PerfRenderMarker id="chat-pane.header">
Expand Down
90 changes: 89 additions & 1 deletion src/browser/components/RightSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import {
RIGHT_SIDEBAR_COLLAPSED_KEY,
RIGHT_SIDEBAR_TAB_KEY,
getReviewImmersiveKey,
getRightSidebarLayoutKey,
getTerminalTitlesKey,
} from "@/common/constants/storage";
Expand All @@ -20,7 +21,13 @@ import { ErrorBoundary } from "./ErrorBoundary";
import { StatsTab } from "./RightSidebar/StatsTab";
import { OutputTab } from "./OutputTab";

import { matchesKeybind, KEYBINDS, formatKeybind, isDialogOpen } from "@/browser/utils/ui/keybinds";
import {
matchesKeybind,
KEYBINDS,
formatKeybind,
isDialogOpen,
isEditableElement,
} from "@/browser/utils/ui/keybinds";
import { SidebarCollapseButton } from "./ui/SidebarCollapseButton";
import { cn } from "@/common/lib/utils";
import type { ReviewNoteData } from "@/common/types/review";
Expand Down Expand Up @@ -51,6 +58,7 @@ import {
removeTabEverywhere,
reorderTabInTabset,
selectTabByIndex,
selectOrAddTab,
selectTabInTabset,
setFocusedTabset,
updateSplitSizes,
Expand Down Expand Up @@ -103,6 +111,8 @@ interface SidebarContainerProps {
isResizing?: boolean;
/** Whether running in Electron desktop mode (hides border when collapsed) */
isDesktop?: boolean;
/** Hide + inactivate sidebar while immersive review overlay is active. */
immersiveHidden?: boolean;
children: React.ReactNode;
role: string;
"aria-label": string;
Expand All @@ -121,14 +131,35 @@ const SidebarContainer: React.FC<SidebarContainerProps> = ({
customWidth,
isResizing,
isDesktop,
immersiveHidden = false,
children,
role,
"aria-label": ariaLabel,
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const width = collapsed ? "20px" : customWidth ? `${customWidth}px` : "400px";

React.useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}

if (immersiveHidden) {
container.setAttribute("inert", "");
} else {
container.removeAttribute("inert");
}

return () => {
container.removeAttribute("inert");
};
}, [immersiveHidden]);

return (
<div
ref={containerRef}
aria-hidden={immersiveHidden || undefined}
className={cn(
"bg-sidebar border-l border-border-light flex flex-col overflow-hidden flex-shrink-0",
// Hide on mobile touch devices - too narrow for useful interaction
Expand Down Expand Up @@ -165,6 +196,8 @@ interface RightSidebarProps {
onReviewNote?: (data: ReviewNoteData) => void;
/** Workspace is still being created (git operations in progress) */
isCreating?: boolean;
/** Hide + inactivate sidebar while immersive review overlay is active. */
immersiveHidden?: boolean;
/** Ref callback to expose addTerminal function to parent */
addTerminalRef?: React.MutableRefObject<
((options?: TerminalSessionCreateOptions) => void) | null
Expand All @@ -189,6 +222,14 @@ const DragAwarePanelResizeHandle: React.FC<{
return <PanelResizeHandle className={className} />;
};

function hasMountedReviewPanel(node: RightSidebarLayoutNode): boolean {
if (node.type === "tabset") {
return node.activeTab === "review";
}

return node.children.some((child) => hasMountedReviewPanel(child));
}

type TabsetNode = Extract<RightSidebarLayoutNode, { type: "tabset" }>;

interface RightSidebarTabsetNodeProps {
Expand Down Expand Up @@ -594,6 +635,7 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
isResizing = false,
onReviewNote,
isCreating = false,
immersiveHidden = false,
addTerminalRef,
}) => {
// Trigger for focusing Review panel (preserves hunk selection)
Expand All @@ -611,6 +653,11 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
const [collapsed, setCollapsed] = usePersistedState<boolean>(RIGHT_SIDEBAR_COLLAPSED_KEY, false, {
listener: true,
});
const [isReviewImmersive, setIsReviewImmersive] = usePersistedState<boolean>(
getReviewImmersiveKey(workspaceId),
false,
{ listener: true }
);

// Stats tab feature flag
const { statsTabState } = useFeatureFlags();
Expand Down Expand Up @@ -671,6 +718,21 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
[layoutDraft, layoutRaw, initialActiveTab]
);

const hasReviewPanelMounted = React.useMemo(
() => !collapsed && hasMountedReviewPanel(layout.root),
[collapsed, layout.root]
);

// If immersive mode is active but no ReviewPanel is mounted (e.g., user switched tabs),
// clear the persisted immersive flag to avoid leaving a blank overlay mounted.
React.useEffect(() => {
if (!isReviewImmersive || hasReviewPanelMounted) {
return;
}

setIsReviewImmersive(false);
}, [hasReviewPanelMounted, isReviewImmersive, setIsReviewImmersive]);

// If the Stats tab feature is enabled, ensure it exists in the layout.
// If disabled, ensure it doesn't linger in persisted layouts.
React.useEffect(() => {
Expand Down Expand Up @@ -735,6 +797,11 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
[initialActiveTab, setLayoutRaw]
);

const selectOrOpenReviewTab = React.useCallback(() => {
setLayout((prev) => selectOrAddTab(prev, "review"));
_setFocusTrigger((prev) => prev + 1);
}, [setLayout]);

// Keyboard shortcuts for tab switching by position (Cmd/Ctrl+1-9)
// Auto-expands sidebar if collapsed
React.useEffect(() => {
Expand Down Expand Up @@ -783,6 +850,26 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
return () => window.removeEventListener("keydown", handleKeyDown);
}, [initialActiveTab, setAutoFocusTerminalSession, setCollapsed, setLayout, _setFocusTrigger]);

React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!matchesKeybind(e, KEYBINDS.TOGGLE_REVIEW_IMMERSIVE)) {
return;
}

if (isEditableElement(e.target)) {
return;
}

e.preventDefault();
setCollapsed(false);
selectOrOpenReviewTab();
setIsReviewImmersive((prev) => !prev);
};

window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isReviewImmersive, selectOrOpenReviewTab, setCollapsed, setIsReviewImmersive]);

const baseId = `right-sidebar-${workspaceId}`;

// Build map of tab → position for keybind tooltips
Expand Down Expand Up @@ -1218,6 +1305,7 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
collapsed={collapsed}
isResizing={isResizing}
isDesktop={isDesktopMode()}
immersiveHidden={immersiveHidden}
customWidth={width} // Unified width from AIView (applies to all tabs)
role="complementary"
aria-label="Workspace insights"
Expand Down
Loading
Loading