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 (
- 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/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 42f7e358..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[] = [
@@ -74,6 +75,15 @@ 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"],
+ desktopOnly: true,
+ },
{
id: "session-rename",
title: "Rename Sessions",