From fb202a31b16a7528cc2303c47d7d8354edd1da77 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Fri, 10 Jul 2026 08:04:03 -0600 Subject: [PATCH 1/2] Add split tab tips --- .../components/app/center-pane-tab-bar.tsx | 90 +++++++++++-------- .../components/app/docs-sections/agents.tsx | 9 +- apps/web/src/lib/tips/tips.ts | 8 ++ 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/apps/web/src/components/app/center-pane-tab-bar.tsx b/apps/web/src/components/app/center-pane-tab-bar.tsx index 467a6c4e..e8ba9393 100644 --- a/apps/web/src/components/app/center-pane-tab-bar.tsx +++ b/apps/web/src/components/app/center-pane-tab-bar.tsx @@ -1,6 +1,7 @@ import { memo, useCallback } from "react"; import type { DiffStats } from "@/components/app/types"; +import { TipSpot } from "@/components/tips/tip-spot"; import { type CenterTab, type SplitPaneState } from "@/lib/store"; import { cn } from "@/lib/utils"; @@ -54,46 +55,57 @@ export const CenterPaneTabBar = memo(function CenterPaneTabBar({ return (
- {visibleTabs.map((tab) => ( - - ))} + {tab.id === "changes" && hasChanges ? ( + + +{diffStats.added} + + {"−"} + {diffStats.deleted} + + + ) : null} + + ); + + if (tab.id !== "changes" || activeTab === tab.id || isMobile || isSplit) + return button; + + return ( + + {button} + + ); + })}
); }); diff --git a/apps/web/src/components/app/docs-sections/agents.tsx b/apps/web/src/components/app/docs-sections/agents.tsx index 840e4f2d..ead9006c 100644 --- a/apps/web/src/components/app/docs-sections/agents.tsx +++ b/apps/web/src/components/app/docs-sections/agents.tsx @@ -166,7 +166,7 @@ export function AgentsContent() {
-

Changes tab

+

Changes tab

The Changes tab next to Terminal in the center pane shows a diff of the agent's uncommitted work against @@ -184,6 +184,13 @@ export function AgentsContent() { edits. These settings persist across sessions. On mobile, the diff always renders in unified mode.

+

+ On desktop, drag the Terminal or{" "} + Changes tab onto the left or right side of the center + pane to split the workspace. Split panes let you keep the terminal and + diff visible together, resize them with the center handle, and return + to a single pane with the unsplit control. +

Select one or more lines in a diff, then click the comment icon to leave a note for the agent. The comment is injected into the agent's diff --git a/apps/web/src/lib/tips/tips.ts b/apps/web/src/lib/tips/tips.ts index 42f7e358..969f855b 100644 --- a/apps/web/src/lib/tips/tips.ts +++ b/apps/web/src/lib/tips/tips.ts @@ -74,6 +74,14 @@ export const tips: Tip[] = [ since: "0.23.9", surfaces: ["ambient"], }, + { + id: "split-tabs", + title: "Split Tabs", + body: "Drag the Changes tab to the left or right side of the center pane to compare the diff beside the live terminal. Use the split handle to resize or unsplit.", + docsSection: "agents#split-tabs", + since: "0.27.4", + surfaces: ["inline", "ambient"], + }, { id: "session-rename", title: "Rename Sessions", From 9578a0a01f042420f7d0070188e4cde5c0a7e177 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Fri, 10 Jul 2026 08:12:16 -0600 Subject: [PATCH 2/2] Hide desktop-only tips on mobile --- .../src/components/tips/ambient-tip-bar.tsx | 35 +++++++++++++++++-- apps/web/src/lib/tips/tips.ts | 2 ++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/tips/ambient-tip-bar.tsx b/apps/web/src/components/tips/ambient-tip-bar.tsx index 0d067262..071eac30 100644 --- a/apps/web/src/components/tips/ambient-tip-bar.tsx +++ b/apps/web/src/components/tips/ambient-tip-bar.tsx @@ -12,6 +12,16 @@ const IDLE_DELAY_MS = 2.5 * 60 * 1000; // 2.5 minutes const SHOW_CHANCE = 0.4; const AUTO_HIDE_MS = 30_000; const ALL_SEEN_MS = 5_000; +const DESKTOP_MEDIA_QUERY = "(min-width: 768px)"; + +function isTipEligibleForViewport(tip: Tip, isDesktop: boolean): boolean { + return !tip.desktopOnly || isDesktop; +} + +function getIsDesktop(): boolean { + if (typeof window === "undefined") return true; + return window.matchMedia(DESKTOP_MEDIA_QUERY).matches; +} export function AmbientTipBar() { const navigate = useNavigate(); @@ -21,6 +31,7 @@ export function AmbientTipBar() { const setEnabled = useSetAtom(tipsEnabledAtom); const [visibleTip, setVisibleTip] = useState(null); + const [isDesktop, setIsDesktop] = useState(getIsDesktop); const [allSeenMessage, setAllSeenMessage] = useState(false); const shownThisSessionRef = useRef(new Set()); const hoveredRef = useRef(false); @@ -32,12 +43,13 @@ export function AmbientTipBar() { const eligible = tips.filter( (t) => t.surfaces.includes("ambient") && + isTipEligibleForViewport(t, isDesktop) && !dismissed.includes(t.id) && !shownThisSessionRef.current.has(t.id) ); if (eligible.length === 0) return null; return eligible[Math.floor(Math.random() * eligible.length)]!; - }, [dismissed]); + }, [dismissed, isDesktop]); const startAutoHide = useCallback(() => { clearTimeout(autoHideTimerRef.current); @@ -68,6 +80,20 @@ export function AmbientTipBar() { const lastResetRef = useRef(0); + useEffect(() => { + const media = window.matchMedia(DESKTOP_MEDIA_QUERY); + const onChange = () => setIsDesktop(media.matches); + onChange(); + media.addEventListener("change", onChange); + return () => media.removeEventListener("change", onChange); + }, []); + + useEffect(() => { + if (visibleTip?.desktopOnly && !isDesktop) { + setVisibleTip(null); + } + }, [isDesktop, visibleTip]); + useEffect(() => { if (!enabled) { setVisibleTip(null); @@ -126,7 +152,10 @@ export function AmbientTipBar() { const handleRequestTip = useCallback(() => { if (visibleTip || allSeenMessage) return; const eligible = tips.filter( - (t) => t.surfaces.includes("ambient") && !dismissed.includes(t.id) + (t) => + t.surfaces.includes("ambient") && + isTipEligibleForViewport(t, isDesktop) && + !dismissed.includes(t.id) ); if (eligible.length === 0) { setAllSeenMessage(true); @@ -141,7 +170,7 @@ export function AmbientTipBar() { shownThisSessionRef.current.add(tip.id); setVisibleTip(tip); startAutoHide(); - }, [visibleTip, allSeenMessage, dismissed, startAutoHide]); + }, [visibleTip, allSeenMessage, dismissed, isDesktop, startAutoHide]); useEffect(() => { return () => clearTimeout(allSeenTimerRef.current); diff --git a/apps/web/src/lib/tips/tips.ts b/apps/web/src/lib/tips/tips.ts index 969f855b..314a71e1 100644 --- a/apps/web/src/lib/tips/tips.ts +++ b/apps/web/src/lib/tips/tips.ts @@ -7,6 +7,7 @@ export type Tip = { docsSection?: string; since: string; surfaces: Surface[]; + desktopOnly?: boolean; }; export const tips: Tip[] = [ @@ -81,6 +82,7 @@ export const tips: Tip[] = [ docsSection: "agents#split-tabs", since: "0.27.4", surfaces: ["inline", "ambient"], + desktopOnly: true, }, { id: "session-rename",