Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
78e93fd
fix(mobile): a caret move in one composer no longer moves another dra…
juliusmarminge Oct 26, 2026
d73fd12
test(web): attachment preview Word and large-text tests no longer fla…
juliusmarminge Oct 26, 2026
82dc892
feat(evals): shared evals package and an eval for T3 tool-use mistake…
t3dotgg Oct 26, 2026
0b0417a
refactor(web): Cmd+O presses the Open in editor button instead of a p…
juliusmarminge Oct 26, 2026
a7caea3
fix(mobile): queued messages are delivered by an app service, not a r…
juliusmarminge Oct 26, 2026
08bd2ec
test(web): PDF viewer behavior is covered by fast tests (#25699)
juliusmarminge Oct 27, 2026
0623d09
refactor(mobile): drop the unreachable pending-task editor from new-t…
juliusmarminge Oct 27, 2026
bd3287b
feat(web): thread state lives at the end of the timeline, not stacked…
t3dotgg Oct 27, 2026
5432d8e
refactor(web): drafts promote from the draft store, not from route ef…
juliusmarminge Oct 27, 2026
9bc0c33
refactor(mobile): settings use web's pages and groups (#25686)
juliusmarminge Oct 27, 2026
a2398c2
refactor(mobile): thread inspector mode is derived, not synced (#25682)
juliusmarminge Oct 27, 2026
68339a0
refactor(mobile): pull request list, filters and viewed files without…
juliusmarminge Oct 27, 2026
becf853
fix(web): PDF search finds phrases that wrap across lines (#25702)
juliusmarminge Oct 27, 2026
6b9cfd5
fix(server): remote task followers read only their own task
juliusmarminge Oct 22, 2026
09758cc
Merge remote-tracking branch 'upstream/main' into fork-sync/2026-10-2…
Yarden-zamir Oct 27, 2026
b0c085d
chore(sync): merge pingdotgg/t3code#16719 at 6b9cfd5ada
Yarden-zamir Oct 27, 2026
2c0af16
fix(web): fork composer prop keeps its ReactNode import after the ups…
Yarden-zamir Oct 27, 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
2 changes: 2 additions & 0 deletions .github/scripts/ci-changes.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const RUST = /^native\/(resource-monitor|kde-snap-shot|hyprland-snap-shot)\//;

// These tests read other packages without declaring dependencies.
const TEST_ONLY_DEPS = {
// Evals import the server's source to test its real model-facing text.
"@t3tools/evals": ["t3"],
"@t3tools/provider-pi": ["@t3tools/client-runtime"],
"@t3tools/scripts": [
"@t3tools/desktop",
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real

For authorized mobile verification, a missing or outdated native client is an install step, not a blocker. Run `vp run mobile:install <ios|android> [device-id]` on the device host before starting Metro; it targets the only booted simulator or connected Android device when the id is omitted. It installs the dev client for this checkout's native fingerprint from the shared cache in seconds and builds only when no machine has built it yet, so prefer it over `expo run:*`, `xcodebuild`, or `gradlew`, which rebuild from scratch. Its stdout is one JSON line; build output goes to the log it names. See `test-t3-mobile` for the full workflow.

## Evals

`evals/` checks how real models behave with T3 Code's model-facing text: agent instructions, MCP tool descriptions and schemas, and generated titles. Before you change that text, read [evals/README.md](evals/README.md) and run the evals it names. When you fix a model behavior bug, add a case that fails before the fix.

## Pull requests

- Never make a PR unless the developer explicitly asks you to do so.
Expand Down
15 changes: 10 additions & 5 deletions apps/mobile/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { installPermissionUpdateNotice } from "./features/connection/permissionUpdateNotice";
import { ForkAccessChangeNotices } from "./features/connection/ForkAccessChangeNotices";
import { installComposerAttachmentUploads } from "./state/composer-attachment-uploads";
import { installThreadOutboxDelivery } from "./state/threadOutboxDelivery";
import * as Linking from "expo-linking";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
Expand Down Expand Up @@ -95,11 +97,14 @@ const navigationRef = createNavigationContainerRef();

declare const module: { readonly hot?: FoundationHotModule } | undefined;

const disposePermissionUpdateNotice = installPermissionUpdateNotice();
disposeOnFoundationReplace(
typeof module === "undefined" ? undefined : module.hot,
disposePermissionUpdateNotice,
);
const appServiceDisposers = [
installPermissionUpdateNotice(),
installThreadOutboxDelivery(),
installComposerAttachmentUploads(),
];
disposeOnFoundationReplace(typeof module === "undefined" ? undefined : module.hot, () => {
for (const dispose of appServiceDisposers) dispose();
});

function SplashScreenCoordinator() {
const { isReady } = useAppearancePreferences();
Expand Down
60 changes: 31 additions & 29 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ import { SettingsEnvironmentDetailRouteScreen } from "./features/settings/Settin
import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";
import { SettingsFollowUpRouteScreen } from "./features/settings/SettingsFollowUpRouteScreen";
import {
SettingsEnvironmentAgentBehaviorRouteScreen,
SettingsEnvironmentMaintenanceRouteScreen,
SettingsEnvironmentNewThreadsRouteScreen,
SettingsThreadsRouteScreen,
SettingsWorktreesRouteScreen,
SettingsEnvironmentSourceControlRouteScreen,
} from "./features/settings/SettingsServerControlsRouteScreen";
import {
Expand All @@ -130,7 +130,7 @@ import { UsageRouteScreen } from "./features/usage/UsageRouteScreen";
import { SettingsAboutRouteScreen } from "./features/settings/SettingsAboutRouteScreen";
import { SettingsNotificationsRouteScreen } from "./features/settings/SettingsNotificationsRouteScreen";
import { SettingsRouteScreen } from "./features/settings/SettingsRouteScreen";
import { SettingsThreadsRouteScreen } from "./features/settings/SettingsThreadsRouteScreen";
import { SettingsLabsRouteScreen } from "./features/settings/ThreadSettingsSections";
import { SettingsEnvironmentFilterProvider } from "./features/settings/settings-environment-filter";
import { openSettings, settingsOpenIn } from "./features/settings/settings-navigation";
import { DEFAULT_SETTINGS_PAGE } from "./features/workspace/workspace-tab-navigation";
Expand All @@ -155,7 +155,7 @@ import { useNativeLayoutMetrics } from "./native/native-layout-metrics";
import { nativeHeaderScrollEdgeEffects } from "./native/StackHeader";
import { HORIZONTAL_FORM_SHEET_OPTIONS, formSheetDetents } from "./native/sheet-surface";
// Fork: runs once across the app and the Android assistant panel.
import { ThreadOutboxWorker } from "./state/thread-outbox-worker";
import { AppServicesHost } from "./state/app-services-host";
import { ReadAloudWorker } from "./features/read-aloud/ReadAloudWorker";
import {
selectWorkspaceDetail,
Expand Down Expand Up @@ -269,25 +269,36 @@ const SETTINGS_SCREENS = {
linking: "settings/environments/:environmentId",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Environment" },
}),
// Old links to New threads and Agent behavior open Threads, which absorbed them.
SettingsEnvironmentNewThreads: createNativeStackScreen({
screen: SettingsEnvironmentNewThreadsRouteScreen,
screen: SettingsThreadsRouteScreen,
linking: "settings/new-threads",
options: { ...SETTINGS_PAGE_OPTIONS, title: "New threads" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "Threads" },
}),
SettingsEnvironmentSourceControl: createNativeStackScreen({
screen: SettingsEnvironmentSourceControlRouteScreen,
linking: "settings/source-control",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Source control" },
}),
SettingsEnvironmentAgentBehavior: createNativeStackScreen({
screen: SettingsEnvironmentAgentBehaviorRouteScreen,
screen: SettingsThreadsRouteScreen,
linking: "settings/agent-behavior",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Agent behavior" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "Threads" },
}),
SettingsWorktrees: createNativeStackScreen({
screen: SettingsWorktreesRouteScreen,
linking: "settings/worktrees",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Worktrees" },
}),
SettingsLabs: createNativeStackScreen({
screen: SettingsLabsRouteScreen,
linking: "settings/labs",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Labs" },
}),
SettingsProviderAccounts: createNativeStackScreen({
screen: SettingsProviderAccountsRouteScreen,
linking: "settings/provider-accounts",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Provider accounts" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "Providers" },
}),
SettingsEnvironmentMaintenance: createNativeStackScreen({
screen: SettingsEnvironmentMaintenanceRouteScreen,
Expand All @@ -302,12 +313,12 @@ const SETTINGS_SCREENS = {
SettingsThreads: createNativeStackScreen({
screen: SettingsThreadsRouteScreen,
linking: "settings/thread-preferences",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Thread behavior" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "Threads" },
}),
SettingsAbout: createNativeStackScreen({
screen: SettingsAboutRouteScreen,
linking: "settings/about",
options: { ...SETTINGS_PAGE_OPTIONS, title: "About T3 Code" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "About" },
}),
SettingsEnvironmentNew: createNativeStackScreen({
screen: ConnectionsNewRouteScreen,
Expand All @@ -322,7 +333,7 @@ const SETTINGS_SCREENS = {
linking: "settings/archive",
options: {
...SETTINGS_PAGE_OPTIONS,
title: "Archived Threads",
title: "Archive",
},
}),
SettingsNavigation: createNativeStackScreen({
Expand Down Expand Up @@ -363,21 +374,21 @@ const SETTINGS_SCREENS = {
linking: "settings/project-grouping",
options: {
...SETTINGS_PAGE_OPTIONS,
title: "Organization",
title: "General",
},
}),
SettingsOrganization: createNativeStackScreen({
screen: SettingsProjectGroupingRouteScreen,
linking: "settings/organization",
options: {
...SETTINGS_PAGE_OPTIONS,
title: "Organization",
title: "General",
},
}),
SettingsProjectOverview: createNativeStackScreen({
screen: SettingsProjectOverviewRouteScreen,
linking: "settings/project",
options: { ...SETTINGS_PAGE_OPTIONS, title: "Project overview" },
options: { ...SETTINGS_PAGE_OPTIONS, title: "Projects" },
}),
SettingsKeyboard: createNativeStackScreen({
screen: SettingsKeyboardRouteScreen,
Expand All @@ -400,15 +411,15 @@ const SETTINGS_SCREENS = {
linking: "settings/follow-ups",
options: {
...SETTINGS_PAGE_OPTIONS,
title: "Follow-ups",
title: "Chat",
},
}),
SettingsScheduledTasks: createNativeStackScreen({
screen: SettingsScheduledTasksRouteScreen,
linking: "settings/scheduled-tasks",
options: {
...SETTINGS_PAGE_OPTIONS,
title: "Scheduled Tasks",
title: "Scheduled tasks",
headerTitleStyle: { fontSize: 16, fontWeight: "800" },
},
}),
Expand Down Expand Up @@ -528,16 +539,11 @@ function RootStackLayout(props: {
);
const draftRoute = props.state.routes.findLast((route) => route.name === "Thread");
const draftParams = (
draftRoute?.params as
| { draft?: { draftId?: string | string[]; pendingTaskId?: string | string[] } }
| undefined
draftRoute?.params as { draft?: { draftId?: string | string[] } } | undefined
)?.draft;
const routeDraftId = Array.isArray(draftParams?.draftId)
? draftParams.draftId[0]
: draftParams?.draftId;
const routePendingTaskId = Array.isArray(draftParams?.pendingTaskId)
? draftParams.pendingTaskId[0]
: draftParams?.pendingTaskId;
useAgentNotificationNavigation(workspaceLocation.pathname);
useConnectOnboardingNavigation();
useAppShortcuts(props.state);
Expand All @@ -559,13 +565,9 @@ function RootStackLayout(props: {
}, [navigation, pendingShare, props.state]);

return (
<NewTaskFlowProvider
active={isDraftThreadRoute(draftRoute)}
routeDraftId={routeDraftId}
routePendingTaskId={routePendingTaskId}
>
<NewTaskFlowProvider active={isDraftThreadRoute(draftRoute)} routeDraftId={routeDraftId}>
<HardwareKeyboardCommandProvider pathname={pathname}>
<ThreadOutboxWorker />
<AppServicesHost />
<ReadAloudWorker />
<ShowcaseCaptureCoordinator pathname={pathname} />
<SettingsEnvironmentFilterProvider open={settingsOpen}>
Expand Down
6 changes: 2 additions & 4 deletions apps/mobile/src/components/ui/ListChoiceRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ export function ListChoiceRow(props: {
onPress={props.onPress}
android_ripple={{ color: rippleStyle.backgroundColor }}
unstable_pressDelay={Platform.OS === "android" ? 50 : undefined}
className={cn(
props.selected ? "bg-subtle" : "bg-grouped-card",
props.separated && "border-t border-border-subtle",
)}
// Like UIKit and Material lists, only the checkmark or radio marks the selected row.
className={cn("bg-grouped-card", props.separated && "border-t border-border-subtle")}
>
<ListRowLayout
label={props.label}
Expand Down
14 changes: 5 additions & 9 deletions apps/mobile/src/features/assistant/AssistantPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {
flattenQueuedThreadMessages,
type QueuedThreadMessage,
} from "../../state/thread-outbox";
import { ThreadOutboxDrainWorker, useIsThreadOutboxHost } from "../../state/thread-outbox-worker";
import { useIsAppServicesHost } from "../../state/app-services-host";
import { useThreadOutboxMessages } from "../../state/use-thread-outbox";
import { useRemoteConnectionStatus } from "../../state/use-remote-environment-registry";
import { useVoicePreferences } from "../../state/voice-preferences";
Expand Down Expand Up @@ -158,16 +158,12 @@ export function AssistantPanelRoot() {
}

/**
* The app root runs the relay sign-in and the outbox sender when it is mounted. On a cold
* start only this panel is mounted, so it runs them until the app root mounts.
* The app root runs the relay sign-in when it is mounted. On a cold start only this panel is
* mounted, so it runs it until the app root mounts. The outbox is a module service.
*/
function AppServicesWhenAlone() {
if (!useIsThreadOutboxHost()) return null;
return (
<CloudAuthProvider>
<ThreadOutboxDrainWorker />
</CloudAuthProvider>
);
if (!useIsAppServicesHost()) return null;
return <CloudAuthProvider>{null}</CloudAuthProvider>;
}

type Phase =
Expand Down

This file was deleted.

Loading
Loading