Skip to content

Commit 8457e47

Browse files
Merge remote-tracking branch 'origin/main' into fix/issue-32637
2 parents 6f9adc6 + 871b009 commit 8457e47

154 files changed

Lines changed: 5368 additions & 732 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/mobile/global.css‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
/* ─── Theme tokens ──────────────────────────────────────────────────── */
66
@layer theme {
77
:root {
8+
/* Native text takes one family name, not Tailwind's font stack. */
9+
@variant ios {
10+
--font-mono: "ui-monospace";
11+
}
812
@variant android {
913
--font-mono: "monospace";
1014
}

‎apps/mobile/src/components/ui/useBarInset.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ export function useBarOverlap(): number {
3838
}
3939

4040
/**
41-
* The content's top safe area under the AppBar: a translucent bar's height, zero under a solid
42-
* one or on Android, where the bar sits in flow. For scroll content insets, which must not
43-
* double-count a solid bar.
41+
* The content's top safe area under the AppBar: the height of the iOS bar its column lays content
42+
* under, glass or solid, and zero on Android, where the bar sits in flow. For manual scroll
43+
* content insets.
4444
*/
4545
export function useContentTopInset(): number {
4646
const insets = useSafeAreaInsets();

‎apps/mobile/src/features/layout/workspace/workspace-list-insets.ts‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
22

33
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../../native/native-glass";
44

5-
/** A workspace list scrolls under its bar where the bar is glass; under a solid bar it starts below. */
6-
export const WORKSPACE_LIST_SCROLL_INSETS = {
7-
automaticallyAdjustsScrollIndicatorInsets: NATIVE_LIQUID_GLASS_SUPPORTED,
8-
contentInsetAdjustmentBehavior: NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never",
9-
} as const;
10-
115
/** How far an empty workspace list keeps its message below a glass bar and its search. */
126
export function useWorkspaceListEmptyTopInset(): number {
137
const insets = useSafeAreaInsets();

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,6 @@ function ThreadDetailBody(
14961496
keyboardOffset={composerKeyboardOffset}
14971497
sendingWithKeyboard={sendingWithKeyboard}
14981498
fitShortFeedToViewport={questionInFlow && !isKeyboardVisible}
1499-
contentTopInset={0}
15001499
contentBottomInset={
15011500
questionInFlow
15021501
? 0

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ import { getReviewSectionIdForCheckpoint } from "../review/reviewModel";
170170
import { setReviewSelectedSectionId } from "../review/reviewState";
171171
import { Menu } from "../../components/ui/Menu";
172172
import { useAndroidControlSizing } from "../../components/ui/useAndroidControlSizing";
173-
import { useBarInset } from "../../components/ui/useBarInset";
173+
import { useBarInset, useContentTopInset } from "../../components/ui/useBarInset";
174174
import { copyPromptLink } from "../prompt-links/prompt-link-actions";
175175
import { parseReviewCommentMessageSegments } from "../review/reviewCommentSelection";
176176
import {
@@ -236,8 +236,9 @@ import { readEnvironmentScope, usePreparedConnection } from "../../state/session
236236
import { projectEnvironment } from "../../state/projects";
237237
import {
238238
needsWorkspaceBasenameLookup,
239-
pickWorkspaceBasenameMatch,
239+
pickWorkspaceLinkMatch,
240240
WORKSPACE_BASENAME_LOOKUP_LIMIT,
241+
workspaceLinkLookupQuery,
241242
} from "@t3tools/client-runtime/workspace-basename-lookup";
242243
import {
243244
resolveThreadLink,
@@ -332,7 +333,6 @@ export interface ThreadFeedProps {
332333
readonly keyboardOffset: number;
333334
readonly sendingWithKeyboard?: boolean;
334335
readonly fitShortFeedToViewport?: boolean;
335-
readonly contentTopInset?: number;
336336
readonly contentBottomInset?: number;
337337
readonly historyControls?: ThreadFeedHistoryControls;
338338
readonly contentMaxWidth?: number;
@@ -1914,7 +1914,10 @@ function renderFeedEntry(
19141914
const bubbleBodyVisible = foldRowId === null || foldExpanded;
19151915
const intentBadge = props.userMessageGrouping.continuesSteer
19161916
? null
1917-
: resolveUserMessageIntentBadge(message.inputIntent);
1917+
: resolveUserMessageIntentBadge(
1918+
message.inputIntent,
1919+
message.projectedItem?.item.status === "pending",
1920+
);
19181921
const referenceIds = new Set(
19191922
collectComposerContextReferences(message.text).map((reference) => reference.contextId),
19201923
);
@@ -2772,10 +2775,12 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
27722775
const markdownContentWidth = Math.max(0, contentWidth - ASSISTANT_ROW_HORIZONTAL_PADDING * 2);
27732776
const reviewCommentBubbleWidth = Math.min(Math.max(280, contentWidth * 0.85), contentWidth);
27742777
const barInset = useBarInset();
2775-
const topContentInset = props.contentTopInset ?? barInset;
2778+
const columnTopInset = useContentTopInset();
27762779
const bottomContentInset = props.contentBottomInset ?? 18;
27772780
const usesNativeAutomaticInsets =
27782781
props.usesAutomaticContentInsets === true && Platform.OS === "ios";
2782+
// Without the native contentInset, a list-header spacer clears the bar the column lays content under.
2783+
const topContentInset = usesNativeAutomaticInsets ? 0 : columnTopInset;
27792784
const anchorTopInset = usesNativeAutomaticInsets ? barInset : topContentInset;
27802785

27812786
const theme = useUniwindTheme();
@@ -2893,17 +2898,18 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
28932898
props.workspaceRoot &&
28942899
relativePath &&
28952900
!/[\\/]$/.test(presentation.path) &&
2896-
needsWorkspaceBasenameLookup(relativePath) &&
2901+
workspaceLinkLookupQuery(presentation.path, props.workspaceRoot) !== null &&
28972902
readEnvironmentScope(props.environmentId, AuthFilesystemReadScope)
28982903
) {
2904+
const workspaceRoot = props.workspaceRoot;
28992905
const originalPath = resolveWorkspaceFilePath(props.workspaceRoot, relativePath);
29002906
void executeAtomQuery(
29012907
appAtomRegistry,
29022908
projectEnvironment.searchEntries({
29032909
environmentId: props.environmentId,
29042910
input: {
2905-
cwd: props.workspaceRoot,
2906-
query: relativePath,
2911+
cwd: workspaceRoot,
2912+
query: workspaceLinkLookupQuery(relativePath, workspaceRoot) ?? relativePath,
29072913
kind: "file",
29082914
limit: WORKSPACE_BASENAME_LOOKUP_LIMIT,
29092915
},
@@ -2913,7 +2919,7 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
29132919
(result) => {
29142920
const match =
29152921
result._tag === "Success"
2916-
? pickWorkspaceBasenameMatch(relativePath, result.value.entries)
2922+
? pickWorkspaceLinkMatch(relativePath, workspaceRoot, result.value.entries)
29172923
: null;
29182924
if (!match || match === relativePath) {
29192925
openAt(relativePath);
@@ -2930,7 +2936,10 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
29302936
).then(
29312937
(metadata) =>
29322938
openAt(
2933-
metadata._tag === "Success" && metadata.value?.kind !== "directory"
2939+
metadata._tag === "Success" &&
2940+
(needsWorkspaceBasenameLookup(relativePath)
2941+
? metadata.value?.kind !== "directory"
2942+
: metadata.value === null)
29342943
? match
29352944
: relativePath,
29362945
),
@@ -3200,7 +3209,9 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
32003209
if (state) onIsAtEndChange?.(state.isAtEnd);
32013210
};
32023211
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
3203-
reportHeaderMaterialVisibility(event.nativeEvent.contentOffset.y + anchorTopInset > 6);
3212+
reportHeaderMaterialVisibility(
3213+
event.nativeEvent.contentOffset.y + (usesNativeAutomaticInsets ? barInset : 0) > 6,
3214+
);
32043215
handleMetricsChange();
32053216
};
32063217
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to the list's end state to report it to the parent

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import { usePendingTaskListActions } from "../home/usePendingTaskListActions";
4848
import { useThreadListActions } from "../home/useThreadListActions";
4949
import { useFabScroll } from "../home/FabScrollContext";
5050
import { WorkspaceListHeader } from "../layout/workspace/WorkspaceListHeader";
51-
import { WORKSPACE_LIST_SCROLL_INSETS } from "../layout/workspace/workspace-list-insets";
5251
import { WorkspaceSidebarColumn } from "../layout/workspace/WorkspaceSidebarColumn";
5352
import {
5453
ThreadListV2PendingRow,
@@ -592,7 +591,9 @@ function ThreadNavigationSidebarPane(props: ThreadNavigationSidebarProps) {
592591
itemsAreEqual={sidebarItemsAreEqual}
593592
keyExtractor={(item) => item.key}
594593
renderItem={renderListItem}
595-
{...WORKSPACE_LIST_SCROLL_INSETS}
594+
automaticallyAdjustsScrollIndicatorInsets
595+
// The column lays the list under its bar, glass or solid; UIKit insets it past both.
596+
contentInsetAdjustmentBehavior="automatic"
596597
contentContainerStyle={[
597598
styles.threadListContent,
598599
{

‎apps/mobile/src/features/threads/subagent-card-presentation.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe("subagent card", () => {
2222
expect(subagentCardElapsed([unfinished], DateTime.toEpochMillis(later))).toBeNull();
2323
expect(
2424
subagentCardElapsed([{ ...unfinished, status: "running" }], DateTime.toEpochMillis(later)),
25-
).toBe("2m");
25+
).toBe("2m 0s");
2626
expect(
2727
subagentCardElapsed([{ ...unfinished, status: "idle" }], DateTime.toEpochMillis(later)),
2828
).toBeNull();

‎apps/mobile/src/features/threads/thread-command-output.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ export function ThreadCommandOutput(props: {
5353
}
5454
return (
5555
<View className="pt-1.5">
56-
{data?.output.truncated ? (
57-
<Text className="pb-0.5 text-3xs text-foreground-subtle">Earlier output not shown</Text>
58-
) : null}
5956
<ScrollView
6057
ref={scrollRef}
6158
nestedScrollEnabled
@@ -73,6 +70,9 @@ export function ThreadCommandOutput(props: {
7370
if (followRef.current) scrollRef.current?.scrollToEnd({ animated: false });
7471
}}
7572
>
73+
{data?.output.truncated ? (
74+
<Text className="pb-0.5 text-3xs text-foreground-subtle">Earlier output not shown</Text>
75+
) : null}
7676
<Text selectable className="font-mono text-2xs leading-normal text-foreground-muted">
7777
{spans.map((span, index) =>
7878
span.style === null ? (

‎apps/mobile/src/features/threads/thread-work-log.tsx‎

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
useEffect,
2626
useId,
2727
useLayoutEffect,
28+
useMemo,
2829
useRef,
2930
useState,
3031
type ComponentProps,
@@ -36,6 +37,7 @@ import {
3637
Pressable,
3738
ScrollView,
3839
StyleSheet,
40+
Text as RNText,
3941
useAnimatedValue,
4042
View,
4143
} from "react-native";
@@ -54,6 +56,11 @@ import { useAtomCommand } from "../../state/use-atom-command";
5456
import { toolActivityFaviconUrl } from "@t3tools/shared/favicon";
5557
import { isJavaScriptReplTool } from "@t3tools/shared/toolActivity";
5658
import { useMarkdownCodeHighlight } from "./markdownCodeHighlightState";
59+
import { highlightCommand, type HighlightedLines } from "../../lib/highlights";
60+
import {
61+
commandHighlightLanguage,
62+
withVisibleControlCharacters,
63+
} from "@t3tools/client-runtime/work-log/command-label";
5764

5865
import { AppText as Text } from "../../components/ui/AppText";
5966
import { T3Wordmark } from "../../components/T3Wordmark";
@@ -807,6 +814,31 @@ function ToolCodeText({
807814
readonly theme: "light" | "dark";
808815
}) {
809816
const lines = useMarkdownCodeHighlight({ code, language, theme, enabled: true });
817+
return <HighlightedCodeText code={code} lines={lines} />;
818+
}
819+
820+
function CommandCodeText({
821+
command,
822+
theme,
823+
}: {
824+
readonly command: string;
825+
readonly theme: "light" | "dark";
826+
}) {
827+
const code = withVisibleControlCharacters(command.trim());
828+
const lines = useMemo(
829+
() => highlightCommand(code, commandHighlightLanguage(code), theme),
830+
[code, theme],
831+
);
832+
return <HighlightedCodeText code={code} lines={lines} />;
833+
}
834+
835+
function HighlightedCodeText({
836+
code,
837+
lines,
838+
}: {
839+
readonly code: string;
840+
readonly lines: HighlightedLines | null;
841+
}) {
810842
let offset = 0;
811843
const highlighted = lines?.flatMap((line, lineIndex) => {
812844
const separator = lineIndex > 0 ? "\n" : "";
@@ -816,10 +848,11 @@ function ToolCodeText({
816848
...line.map((token) => {
817849
const start = offset;
818850
offset += token.content.length;
851+
// Plain RN Text inherits the mono font; AppText would reset it to sans.
819852
return (
820-
<Text key={start} style={token.color ? { color: token.color } : undefined}>
853+
<RNText key={start} style={token.color ? { color: token.color } : undefined}>
821854
{token.content}
822-
</Text>
855+
</RNText>
823856
);
824857
}),
825858
];
@@ -1179,7 +1212,13 @@ const ThreadWorkLogRow = function ThreadWorkLogRow(
11791212
props.renderReasoning(reasoning.text)
11801213
) : call ? (
11811214
[
1182-
call.command,
1215+
call.command ? (
1216+
<CommandCodeText
1217+
key="command"
1218+
command={call.command}
1219+
theme={props.themeAppearance}
1220+
/>
1221+
) : null,
11831222
...(call.args ?? []).map(([key, value]) => {
11841223
const language =
11851224
key === "html"

‎apps/mobile/src/features/threads/userMessageIntentBadge.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ export interface UserMessageIntentBadgePresentation {
77

88
export function resolveUserMessageIntentBadge(
99
intent: OrchestrationV2UserMessageInputIntent | undefined,
10+
unread = false,
1011
): UserMessageIntentBadgePresentation | null {
12+
if (unread && (intent === "steer" || intent === "promoted_queued_to_steer")) {
13+
return {
14+
label: "Steer · waiting to be read",
15+
accessibilityLabel:
16+
"Steer waiting for the agent to read it after the running command finishes",
17+
};
18+
}
1119
switch (intent) {
1220
case "steer":
1321
return {

0 commit comments

Comments
 (0)