Skip to content

Commit ebb0157

Browse files
authored
Merge branch 'main' into codex/live-dictation-prototype
2 parents 549ab2b + c25c3e0 commit ebb0157

27 files changed

Lines changed: 2382 additions & 816 deletions

‎apps/mobile/src/Stack.tsx‎

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,27 @@ import { SettingsClientStorageRouteScreen } from "./features/settings/SettingsCl
5959
import { SettingsDiagnosticsRouteScreen } from "./features/diagnostics/SettingsDiagnosticsRouteScreen";
6060
import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteScreen";
6161
import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";
62+
import {
63+
SettingsEnvironmentAgentBehaviorRouteScreen,
64+
SettingsEnvironmentMaintenanceRouteScreen,
65+
SettingsEnvironmentNewThreadsRouteScreen,
66+
SettingsEnvironmentSourceControlRouteScreen,
67+
} from "./features/settings/SettingsServerControlsRouteScreen";
6268
import { SettingsKeyboardRouteScreen } from "./features/settings/SettingsKeyboardRouteScreen";
6369
import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen";
6470
import {
6571
SettingsOpenSourceLicenseRouteScreen,
6672
SettingsOpenSourceLicensesRouteScreen,
6773
} from "./features/settings/SettingsOpenSourceLicensesRouteScreen";
6874
import { SettingsProjectGroupingRouteScreen } from "./features/settings/SettingsProjectGroupingRouteScreen";
75+
import { SettingsProjectOverviewRouteScreen } from "./features/settings/SettingsProjectOverviewRouteScreen";
6976
import { UsageLimitAccountScreen } from "./features/usage/UsageLimitsPooled";
7077
import { UsageRouteScreen } from "./features/usage/UsageRouteScreen";
78+
import { SettingsAboutRouteScreen } from "./features/settings/SettingsAboutRouteScreen";
79+
import { SettingsNotificationsRouteScreen } from "./features/settings/SettingsNotificationsRouteScreen";
7180
import { SettingsRouteScreen } from "./features/settings/SettingsRouteScreen";
81+
import { SettingsThreadsRouteScreen } from "./features/settings/SettingsThreadsRouteScreen";
82+
import { SettingsEnvironmentFilterProvider } from "./features/settings/settings-environment-filter";
7283
import { ShowcaseCaptureCoordinator } from "./features/showcase/ShowcaseCaptureCoordinator";
7384
import {
7485
SettingsLegalDocumentCloseHeaderButton,
@@ -168,6 +179,41 @@ const SettingsContentStack = createNativeStackNavigator({
168179
title: "Environments",
169180
},
170181
}),
182+
SettingsEnvironmentNewThreads: createNativeStackScreen({
183+
screen: SettingsEnvironmentNewThreadsRouteScreen,
184+
linking: "new-threads",
185+
options: { title: "New threads" },
186+
}),
187+
SettingsEnvironmentSourceControl: createNativeStackScreen({
188+
screen: SettingsEnvironmentSourceControlRouteScreen,
189+
linking: "source-control",
190+
options: { title: "Source control" },
191+
}),
192+
SettingsEnvironmentAgentBehavior: createNativeStackScreen({
193+
screen: SettingsEnvironmentAgentBehaviorRouteScreen,
194+
linking: "agent-behavior",
195+
options: { title: "Agent behavior" },
196+
}),
197+
SettingsEnvironmentMaintenance: createNativeStackScreen({
198+
screen: SettingsEnvironmentMaintenanceRouteScreen,
199+
linking: "maintenance",
200+
options: { title: "Maintenance" },
201+
}),
202+
SettingsNotifications: createNativeStackScreen({
203+
screen: SettingsNotificationsRouteScreen,
204+
linking: "notifications",
205+
options: { title: "Notifications" },
206+
}),
207+
SettingsThreads: createNativeStackScreen({
208+
screen: SettingsThreadsRouteScreen,
209+
linking: "thread-preferences",
210+
options: { title: "Thread behavior" },
211+
}),
212+
SettingsAbout: createNativeStackScreen({
213+
screen: SettingsAboutRouteScreen,
214+
linking: "about",
215+
options: { title: "About T3 Code" },
216+
}),
171217
SettingsEnvironmentNew: createNativeStackScreen({
172218
screen: ConnectionsNewRouteScreen,
173219
linking: "environment-new",
@@ -193,9 +239,21 @@ const SettingsContentStack = createNativeStackNavigator({
193239
screen: SettingsProjectGroupingRouteScreen,
194240
linking: "project-grouping",
195241
options: {
196-
title: "Project Grouping",
242+
title: "Organization",
197243
},
198244
}),
245+
SettingsOrganization: createNativeStackScreen({
246+
screen: SettingsProjectGroupingRouteScreen,
247+
linking: "organization",
248+
options: {
249+
title: "Organization",
250+
},
251+
}),
252+
SettingsProjectOverview: createNativeStackScreen({
253+
screen: SettingsProjectOverviewRouteScreen,
254+
linking: "project",
255+
options: { title: "Project overview" },
256+
}),
199257
SettingsKeyboard: createNativeStackScreen({
200258
screen: SettingsKeyboardRouteScreen,
201259
linking: "keyboard",
@@ -257,6 +315,9 @@ const SettingsSheetStack = createNativeStackNavigator({
257315
SettingsContent: createNativeStackScreen({
258316
screen: SettingsContentStack,
259317
linking: "",
318+
layout: ({ children }) => (
319+
<SettingsEnvironmentFilterProvider>{children}</SettingsEnvironmentFilterProvider>
320+
),
260321
}),
261322
SettingsAuth: createNativeStackScreen({
262323
screen: SettingsAuthRouteScreen,
@@ -632,7 +693,7 @@ export const RootStack = createNativeStackNavigator({
632693
? { presentation: "card" as const }
633694
: {
634695
...FORM_SHEET_PRESENTATION_OPTIONS,
635-
sheetAllowedDetents: [0.7, 0.92],
696+
sheetAllowedDetents: [0.92],
636697
sheetGrabberVisible: true,
637698
}),
638699
},
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
2+
import Constants from "expo-constants";
3+
import { useCallback, useEffect, useRef, useState } from "react";
4+
import { Alert, Pressable, View } from "react-native";
5+
import { useSafeAreaInsets } from "react-native-safe-area-context";
6+
7+
import { AppText as Text } from "../../components/AppText";
8+
import { SymbolView } from "../../components/AppSymbol";
9+
import {
10+
type AppUpdateCheckState,
11+
isAppUpdateCheckAvailable,
12+
registerHiddenUpdateTap,
13+
runAppUpdateCheck,
14+
} from "../updates/app-updates";
15+
import { SettingsRow } from "./components/SettingsRow";
16+
import { SettingsSection } from "./components/SettingsSection";
17+
import { SettingsScreen } from "./components/SettingsScreen";
18+
19+
export function SettingsAboutRouteScreen() {
20+
const insets = useSafeAreaInsets();
21+
22+
return (
23+
<SettingsScreen title="About T3 Code">
24+
<ScrollView
25+
contentInsetAdjustmentBehavior="automatic"
26+
showsVerticalScrollIndicator={false}
27+
className="flex-1"
28+
contentContainerClassName="gap-6 px-5 pt-4"
29+
contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }}
30+
>
31+
<AppSettingsSection />
32+
</ScrollView>
33+
</SettingsScreen>
34+
);
35+
}
36+
37+
function AppSettingsSection() {
38+
const [updateState, setUpdateState] = useState<AppUpdateCheckState>("idle");
39+
const updateInFlight = useRef(false);
40+
const hiddenUpdateTapCount = useRef(0);
41+
42+
const version = Constants.expoConfig?.version ?? "0.0.0";
43+
// Fall back to "production" to match resolveAppVariant in app.config.ts, so a
44+
// missing variant never mislabels a production build as development.
45+
const variant = (Constants.expoConfig?.extra?.appVariant as string | undefined) ?? "production";
46+
const variantLabel = variant === "production" ? "" : capitalize(variant);
47+
const versionLabel = variantLabel ? `${version} · ${variantLabel}` : version;
48+
const updateCheckAvailable = isAppUpdateCheckAvailable();
49+
const busy =
50+
updateState === "checking" || updateState === "downloading" || updateState === "restarting";
51+
52+
// "Up to date" is a transient acknowledgement, not a state worth persisting —
53+
// return the version row to its normal, deliberately quiet state.
54+
useEffect(() => {
55+
if (updateState !== "current") return;
56+
const timer = setTimeout(() => setUpdateState("idle"), 3000);
57+
return () => clearTimeout(timer);
58+
}, [updateState]);
59+
60+
const checkForUpdate = useCallback(async () => {
61+
// `disabled={busy}` only takes effect on the next render, so two taps in the
62+
// same frame would both get through. The ref closes that window.
63+
if (updateInFlight.current) return;
64+
updateInFlight.current = true;
65+
try {
66+
// The user asked for this restart by tapping the version row, so it may
67+
// apply immediately instead of prompting.
68+
await runAppUpdateCheck({
69+
applyMode: "immediate",
70+
onFailure: (message) => Alert.alert("Update failed", message),
71+
onStateChange: setUpdateState,
72+
});
73+
} finally {
74+
updateInFlight.current = false;
75+
}
76+
}, []);
77+
78+
const handleVersionPress = useCallback(() => {
79+
if (!updateCheckAvailable || updateInFlight.current) return;
80+
const tap = registerHiddenUpdateTap(hiddenUpdateTapCount.current);
81+
hiddenUpdateTapCount.current = tap.nextCount;
82+
if (tap.shouldCheck) {
83+
void checkForUpdate();
84+
}
85+
}, [checkForUpdate, updateCheckAvailable]);
86+
87+
const statusLabel =
88+
updateState === "checking"
89+
? "Checking…"
90+
: updateState === "downloading"
91+
? "Downloading…"
92+
: // "ready" appears only when this check joined an in-flight background-mode
93+
// check; that download installs at the next backgrounding.
94+
updateState === "ready"
95+
? "Update ready"
96+
: updateState === "restarting"
97+
? "Restarting…"
98+
: updateState === "current"
99+
? "Up to date"
100+
: null;
101+
102+
const versionRow = (
103+
<View className="flex-row items-center gap-4 p-4">
104+
<SymbolView
105+
name="info.circle"
106+
size={22}
107+
tintColorClassName="accent-icon"
108+
type="monochrome"
109+
weight="regular"
110+
/>
111+
<Text className="flex-1 text-lg text-foreground">Version</Text>
112+
<View className="items-end">
113+
<Text className="text-lg text-foreground-muted">{versionLabel}</Text>
114+
{statusLabel ? (
115+
<Text className="text-xs text-foreground-muted/70">{statusLabel}</Text>
116+
) : null}
117+
</View>
118+
</View>
119+
);
120+
121+
return (
122+
<SettingsSection title="App">
123+
<SettingsRow icon="internaldrive" label="Client Storage" target="SettingsClientStorage" />
124+
<SettingsRow icon="stethoscope" label="Diagnostics" target="SettingsDiagnostics" />
125+
<SettingsRow
126+
icon="doc.on.doc"
127+
label="Open source licenses"
128+
target="SettingsOpenSourceLicenses"
129+
/>
130+
<SettingsRow icon="doc.text" label="Legal" fullScreenTarget="SettingsLegal" />
131+
{updateCheckAvailable ? (
132+
<Pressable
133+
accessibilityLabel={`Version ${versionLabel}`}
134+
accessibilityRole="text"
135+
disabled={busy}
136+
onPress={handleVersionPress}
137+
>
138+
{versionRow}
139+
</Pressable>
140+
) : (
141+
versionRow
142+
)}
143+
</SettingsSection>
144+
);
145+
}
146+
147+
function capitalize(value: string): string {
148+
return value.length > 0 ? value.charAt(0).toUpperCase() + value.slice(1) : value;
149+
}

0 commit comments

Comments
 (0)