Skip to content

Commit 89b9125

Browse files
fix: reconcile main's round-9 features after the rebase
Re-applies the deltas that mid-stack blob reverts discarded, and merges main's round-9 work into the v2-owned surfaces: - settings: main's Integrations page (#7082) coexists with the branch's Scheduled Tasks page in the path union, section labels, icons, and search catalog - contracts: main's preview appearance/zoom/viewport settings imports restored beside the branch's modelSelection home for ModelSelection - mobile: main's built-in themes (#6619) re-applied to the v2 thread screens and work log (useThemeColor over hand-rolled color-scheme ternaries) - MessagesTimeline: main's #7157 cleanup adopted (toolCallExpandedBody class name unexported, implementation-detail test dropped) - ChangedFilesTree: main's styled tooltip (#7209) carrying the v2 runId - pullRequestDetail tests: branch's row-action coverage renamed onto main's buildAddSelectionToAgentHandoff (#6597) - lint: migrated the six branch-owned native title tooltips that main's new no-native-title-tooltip rule (#7209) flags to styled Tooltips (GitActionsControl, QueuedRunsControl, TimelineSystemDivider, MessagesTimeline intent badge and MCP tool logo) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 095923f commit 89b9125

10 files changed

Lines changed: 127 additions & 59 deletions

‎apps/mobile/src/features/threads/NewTaskDraftScreen.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import {
7070
type DraftComposerFileAttachment,
7171
} from "../../lib/composerImages";
7272
import { useScaledTextRole } from "../settings/appearance/useScaledTextRole";
73+
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
7374
import {
7475
clearComposerDraftContent,
7576
flushComposerDrafts,

‎apps/mobile/src/features/threads/ThreadComposer.tsx‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
Platform,
2424
Pressable,
2525
StyleSheet,
26-
useColorScheme,
2726
View,
2827
type ViewStyle,
2928
} from "react-native";
@@ -36,6 +35,7 @@ import Animated, {
3635
LinearTransition,
3736
} from "react-native-reanimated";
3837
import { useThemeColor } from "../../lib/useThemeColor";
38+
import { themeColorWithAlpha } from "../../lib/mobileTheme";
3939
import { armAgentAwarenessLiveActivityForLocalWork } from "../agent-awareness/remoteRegistration";
4040
import { scopedThreadKey } from "../../lib/scopedEntities";
4141

@@ -61,6 +61,7 @@ import type {
6161
} from "../../lib/composerImages";
6262
import { buildModelOptions, groupByProvider } from "../../lib/modelOptions";
6363
import { useScaledTextRole } from "../settings/appearance/useScaledTextRole";
64+
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
6465
import type { RemoteClientConnectionState } from "../../lib/connection";
6566
import {
6667
insertRankedSearchResult,
@@ -151,11 +152,14 @@ export function ComposerSurface(props: {
151152
/** Existing thread composers morph between pill and card layouts. */
152153
readonly animateLayout?: boolean;
153154
}) {
155+
const cardColor = useThemeColor("--color-card-translucent");
156+
const borderColor = useThemeColor("--color-border");
157+
const shadowColor = useThemeColor("--color-primary-shadow");
154158
// Drop shadow lives on a wrapper: `overflow: "hidden"` on the surface itself
155159
// (needed to clip content to the pill shape) would clip the shadow on iOS.
156160
const shadowStyle: ViewStyle = {
157161
borderRadius: props.style.borderRadius,
158-
shadowColor: "#000000",
162+
shadowColor,
159163
shadowOpacity: props.isDarkMode ? 0.35 : 0.12,
160164
shadowRadius: 14,
161165
shadowOffset: { width: 0, height: 6 },
@@ -170,9 +174,9 @@ export function ComposerSurface(props: {
170174
<GlassSurface
171175
chrome="none"
172176
fallbackStyle={{
173-
backgroundColor: props.isDarkMode ? "rgba(44,44,46,0.96)" : "rgba(255,255,255,0.96)",
177+
backgroundColor: cardColor,
174178
borderWidth: 1,
175-
borderColor: props.isDarkMode ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.06)",
179+
borderColor,
176180
}}
177181
glassEffectStyle="regular"
178182
// The composer is a passive material containing interactive controls.
@@ -242,6 +246,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill(
242246
readonly status: ComposerStatusPillState;
243247
}) {
244248
const isReconnecting = props.status.kind !== "unavailable";
249+
const indicatorColor = useThemeColor("--color-icon-muted");
245250

246251
return (
247252
<Animated.View
@@ -253,10 +258,10 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill(
253258
<Pressable
254259
accessibilityRole="button"
255260
onPress={props.onPress}
256-
className="max-w-full flex-row items-center gap-2 rounded-full bg-white/90 px-3 py-2 shadow-sm active:opacity-70 dark:bg-neutral-900/90"
261+
className="max-w-full flex-row items-center gap-2 rounded-full bg-card px-3 py-2 shadow-sm active:opacity-70"
257262
>
258263
{isReconnecting ? (
259-
<ActivityIndicator size="small" color="#8e8e93" />
264+
<ActivityIndicator size="small" color={indicatorColor} />
260265
) : (
261266
<View className="h-2 w-2 rounded-full bg-red-500" />
262267
)}
@@ -273,7 +278,8 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill(
273278

274279
export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) {
275280
const navigation = useNavigation();
276-
const isDarkMode = useColorScheme() === "dark";
281+
const { themeAppearance } = useAppearancePreferences();
282+
const isDarkMode = themeAppearance === "dark";
277283
const foregroundColor = useThemeColor("--color-foreground");
278284
const bodyText = useScaledTextRole("body");
279285
const fallbackInputRef = useRef<ComposerEditorHandle>(null);
@@ -341,8 +347,11 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
341347
environmentLabel: props.environmentLabel,
342348
threadSyncPhase: props.threadSyncPhase,
343349
});
344-
const toolbarFadeOpaque = isDarkMode ? "rgba(0,0,0,0.95)" : "rgba(255,255,255,0.95)";
345-
const toolbarFadeTransparent = isDarkMode ? "rgba(0,0,0,0)" : "rgba(255,255,255,0)";
350+
const toolbarSurface = String(useThemeColor("--color-card"));
351+
const backdropSurface = String(useThemeColor("--color-screen"));
352+
const toolbarFadeOpaque = themeColorWithAlpha(toolbarSurface, 0.95);
353+
const toolbarFadeTransparent = themeColorWithAlpha(toolbarSurface, 0);
354+
const backdropGradient = `linear-gradient(to bottom, ${themeColorWithAlpha(backdropSurface, 0)} 0%, ${themeColorWithAlpha(backdropSurface, 0.6)} 55%, ${themeColorWithAlpha(backdropSurface, 0.9)} 100%)`;
346355
const selectedProviderStatus = useMemo(() => {
347356
if (!props.serverConfig) return null;
348357
return (
@@ -649,9 +658,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
649658
style={[
650659
StyleSheet.absoluteFill,
651660
{
652-
experimental_backgroundImage: isDarkMode
653-
? "linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 55%, rgba(0,0,0,0.9) 100%)"
654-
: "linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 55%, rgba(255,255,255,0.9) 100%)",
661+
experimental_backgroundImage: backdropGradient,
655662
},
656663
]}
657664
/>

‎apps/mobile/src/features/threads/ThreadDetailScreen.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
AppState,
3333
Keyboard,
3434
Platform,
35-
useColorScheme,
3635
useWindowDimensions,
3736
View,
3837
type GestureResponderEvent,
@@ -53,6 +52,7 @@ import Animated, {
5352
import { useSafeAreaInsets } from "react-native-safe-area-context";
5453

5554
import { ControlPill } from "../../components/ControlPill";
55+
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
5656
import type { ComposerEditorHandle } from "../../components/ComposerEditor";
5757
import type { StatusTone } from "../../components/StatusPill";
5858
import type { DraftComposerAttachment } from "../../lib/composerImages";
@@ -592,7 +592,8 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
592592
}, [freeze, scrollMessageToEnd]);
593593

594594
const showScrollToEndButton = contentPresentationKind === "ready" && !endFollowEnabled;
595-
const isDarkMode = useColorScheme() === "dark";
595+
const { themeAppearance } = useAppearancePreferences();
596+
const isDarkMode = themeAppearance === "dark";
596597

597598
const handleFeedTouchStart = useCallback((event: GestureResponderEvent) => {
598599
feedTouchStartRef.current = {

‎apps/web/src/components/GitActionsControl.tsx‎

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,18 @@ function GitActionProgressButtonContent({
450450
)}
451451
>
452452
<div className="min-h-0 overflow-hidden">
453-
<p
454-
className="truncate pt-0.5 text-left text-[11px] font-normal text-muted-foreground"
455-
title={progress.output ?? undefined}
456-
>
457-
{progress.output}
458-
</p>
453+
<Tooltip>
454+
<TooltipTrigger
455+
render={
456+
<p className="truncate pt-0.5 text-left text-[11px] font-normal text-muted-foreground" />
457+
}
458+
>
459+
{progress.output}
460+
</TooltipTrigger>
461+
<TooltipPopup side="bottom" className="max-w-96 break-words">
462+
{progress.output}
463+
</TooltipPopup>
464+
</Tooltip>
459465
</div>
460466
</div>
461467
</div>
@@ -480,12 +486,18 @@ function GitActionSuccessButtonContent({ success }: { success: InlineGitActionSu
480486
)}
481487
>
482488
<div className="min-h-0 overflow-hidden">
483-
<p
484-
className="truncate pt-0.5 text-left text-[11px] font-normal text-muted-foreground"
485-
title={success.description ?? undefined}
486-
>
487-
{success.description}
488-
</p>
489+
<Tooltip>
490+
<TooltipTrigger
491+
render={
492+
<p className="truncate pt-0.5 text-left text-[11px] font-normal text-muted-foreground" />
493+
}
494+
>
495+
{success.description}
496+
</TooltipTrigger>
497+
<TooltipPopup side="bottom" className="max-w-96 break-words">
498+
{success.description}
499+
</TooltipPopup>
500+
</Tooltip>
489501
</div>
490502
</div>
491503
</div>

‎apps/web/src/components/chat/MessagesTimeline.test.tsx‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ function matchMedia() {
127127
}
128128

129129
let MessagesTimeline: typeof import("./MessagesTimeline").MessagesTimeline;
130-
let toolCallExpandedBodyClassName: typeof import("./MessagesTimeline").toolCallExpandedBodyClassName;
131130

132131
beforeAll(async () => {
133132
const classList = {
@@ -161,7 +160,7 @@ beforeAll(async () => {
161160
},
162161
});
163162

164-
({ MessagesTimeline, toolCallExpandedBodyClassName } = await import("./MessagesTimeline"));
163+
({ MessagesTimeline } = await import("./MessagesTimeline"));
165164
}, 30_000);
166165

167166
const ACTIVE_THREAD_ENVIRONMENT_ID = EnvironmentId.make("environment-local");
@@ -232,11 +231,6 @@ function buildAssistantTimelineEntry(text: string) {
232231
}
233232

234233
describe("MessagesTimeline", () => {
235-
it("sizes expanded tool details with the configured code font size", () => {
236-
expect(toolCallExpandedBodyClassName).toContain("var(--font-size-code");
237-
expect(toolCallExpandedBodyClassName).not.toContain("text-[11px]");
238-
});
239-
240234
it("preserves arbitrary XML-like tags and comparisons in rendered user messages", async () => {
241235
const { MessagesTimeline } = await import("./MessagesTimeline");
242236
const markup = renderToStaticMarkup(
@@ -712,7 +706,7 @@ describe("MessagesTimeline", () => {
712706
/>,
713707
);
714708

715-
expect(markup).toContain("Steered the active turn");
709+
expect(markup).toContain("data-base-ui-tooltip-trigger");
716710
expect(markup).toContain("lucide-redo-2");
717711
expect(markup).toContain('data-user-message-intent="steer"');
718712
expect(markup).toContain("items-center justify-end gap-1");

‎apps/web/src/components/chat/MessagesTimeline.tsx‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,20 +1354,26 @@ function UserMessageIntentMarker({
13541354
};
13551355
const IntentIcon = presentation.icon;
13561356
return (
1357-
<div
1358-
className="me-1 flex items-center justify-end gap-1 text-xs leading-none text-muted-foreground"
1359-
data-user-message-intent={intent}
1360-
title={
1361-
intent === "queued_turn"
1357+
<Tooltip>
1358+
<TooltipTrigger
1359+
render={
1360+
<div
1361+
className="me-1 flex items-center justify-end gap-1 text-xs leading-none text-muted-foreground"
1362+
data-user-message-intent={intent}
1363+
/>
1364+
}
1365+
>
1366+
{IntentIcon ? <IntentIcon aria-hidden="true" className="size-3" /> : null}
1367+
{presentation.label}
1368+
</TooltipTrigger>
1369+
<TooltipPopup side="top">
1370+
{intent === "queued_turn"
13621371
? "Queued behind the active turn"
13631372
: intent === "promoted_queued_to_steer"
13641373
? "Originally queued, then promoted to steer the active turn"
1365-
: "Steered the active turn"
1366-
}
1367-
>
1368-
{IntentIcon ? <IntentIcon aria-hidden="true" className="size-3" /> : null}
1369-
{presentation.label}
1370-
</div>
1374+
: "Steered the active turn"}
1375+
</TooltipPopup>
1376+
</Tooltip>
13711377
);
13721378
}
13731379

@@ -2627,7 +2633,7 @@ function T3CodeToolLogo({ className }: { className?: string }) {
26272633
"flex size-4 shrink-0 items-center justify-center overflow-hidden rounded-[4px] bg-background ring-1 ring-border/65",
26282634
className,
26292635
)}
2630-
title="T3 Code MCP tool"
2636+
aria-label="T3 Code MCP tool"
26312637
>
26322638
<img alt="" aria-hidden="true" className="size-4 object-cover" src={logoUrl} />
26332639
</span>
@@ -2740,7 +2746,7 @@ function buildToolCallExpandedBody(
27402746
return blocks.length > 0 ? blocks.join("\n\n") : null;
27412747
}
27422748

2743-
export const toolCallExpandedBodyClassName =
2749+
const toolCallExpandedBodyClassName =
27442750
"max-h-64 cursor-text overflow-auto whitespace-pre-wrap break-words font-mono text-secondary-label text-[length:var(--font-size-code,0.6875rem)] leading-relaxed select-text";
27452751

27462752
function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName {

‎apps/web/src/components/chat/QueuedRunsControl.tsx‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useThreadProjection } from "../../state/entities";
1818
import { useAtomCommand } from "../../state/use-atom-command";
1919
import type { ChatMessage } from "../../types";
2020
import { Button } from "../ui/button";
21+
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
2122

2223
export function QueuedRunsControl(props: {
2324
readonly environmentId: EnvironmentId;
@@ -201,9 +202,14 @@ export function QueuedRunsControl(props: {
201202
</>
202203
) : (
203204
<>
204-
<span className="min-w-0 flex-1 truncate text-xs" title={item.text}>
205-
{item.text}
206-
</span>
205+
<Tooltip>
206+
<TooltipTrigger render={<span className="min-w-0 flex-1 truncate text-xs" />}>
207+
{item.text}
208+
</TooltipTrigger>
209+
<TooltipPopup side="top" className="max-w-96 break-words">
210+
{item.text}
211+
</TooltipPopup>
212+
</Tooltip>
207213
<Button
208214
size="icon-xs"
209215
variant="ghost"

‎apps/web/src/components/chat/TimelineSystemDivider.tsx‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
22
import type { LucideIcon } from "lucide-react";
33

44
import { cn } from "~/lib/utils";
5+
import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip";
56

67
export function TimelineSystemDivider(props: {
78
readonly label: string;
@@ -35,15 +36,21 @@ export function TimelineSystemDivider(props: {
3536
>
3637
<span aria-hidden="true" className="h-px flex-1 bg-border/70" />
3738
{props.onAction ? (
38-
<button
39-
type="button"
40-
aria-label={props.actionLabel}
41-
onClick={props.onAction}
42-
className="flex min-w-0 items-center gap-1.5 rounded-full border border-border/70 bg-background px-2.5 py-1 transition-colors hover:bg-muted"
43-
title={props.actionLabel}
44-
>
45-
{content}
46-
</button>
39+
<Tooltip>
40+
<TooltipTrigger
41+
render={
42+
<button
43+
type="button"
44+
aria-label={props.actionLabel}
45+
onClick={props.onAction}
46+
className="flex min-w-0 items-center gap-1.5 rounded-full border border-border/70 bg-background px-2.5 py-1 transition-colors hover:bg-muted"
47+
/>
48+
}
49+
>
50+
{content}
51+
</TooltipTrigger>
52+
<TooltipPopup side="top">{props.actionLabel}</TooltipPopup>
53+
</Tooltip>
4754
) : (
4855
<span className="flex min-w-0 items-center gap-1.5 rounded-full px-2 py-1">{content}</span>
4956
)}

‎apps/web/src/components/settings/SettingsSidebarNav.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {
1414
ArchiveIcon,
1515
ArrowLeftIcon,
16+
BlocksIcon,
1617
BotIcon,
1718
CalendarClockIcon,
1819
FlaskConicalIcon,
@@ -73,6 +74,7 @@ const SETTINGS_SECTION_ICONS: Readonly<
7374
"/settings/appearance": PaletteIcon,
7475
"/settings/keybindings": KeyboardIcon,
7576
"/settings/providers": BotIcon,
77+
"/settings/integrations": BlocksIcon,
7678
"/settings/scheduled-tasks": CalendarClockIcon,
7779
"/settings/source-control": GitBranchIcon,
7880
"/settings/connections": Link2Icon,

0 commit comments

Comments
 (0)