authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-09 04:49:16-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-09 13:49:16+02:00
log843cf176eae729838b6a2cc8c68cbdadda667d95
tree5d358473a2820411824beb0507b28b045d1c0282
parentca1e08b5a0463963672725d9edd5443b68da30ad
signature Signed by PGP key B5690EEEBB952194

fix(mobile): embed fonts and render project favicons reliably (#3823)

Co-authored-by: codex <codex@users.noreply.github.com>

64 files changed, 444 insertions(+), 837 deletions(-)

apps/mobile/app.config.ts+34-1
......@@ -63,6 +63,16 @@ function resolveAppVariant(value: string | undefined): AppVariant {
6363
6464const variant = VARIANT_CONFIG[APP_VARIANT];
6565
66const dmSansFonts = {
67 regular: "@expo-google-fonts/dm-sans/400Regular/DMSans_400Regular.ttf",
68 medium: "@expo-google-fonts/dm-sans/500Medium/DMSans_500Medium.ttf",
69 bold: "@expo-google-fonts/dm-sans/700Bold/DMSans_700Bold.ttf",
70} as const;
71
72// These aliases match the fonts' PostScript names on iOS. Register the same
73// names on Android so React Native and the native composer use one set of
74// family names without waiting for runtime font loading.
75
6676const config: ExpoConfig = {
6777 name: variant.appName,
6878 slug: "t3-code",
......@@ -121,7 +131,30 @@ const config: ExpoConfig = {
121131 favicon: "./assets/favicon.png",
122132 },
123133 plugins: [
124 "expo-font",
134 [
135 "expo-font",
136 {
137 ios: {
138 fonts: [dmSansFonts.regular, dmSansFonts.medium, dmSansFonts.bold],
139 },
140 android: {
141 fonts: [
142 {
143 fontFamily: "DMSans-Regular",
144 fontDefinitions: [{ path: dmSansFonts.regular, weight: 400 }],
145 },
146 {
147 fontFamily: "DMSans-Medium",
148 fontDefinitions: [{ path: dmSansFonts.medium, weight: 500 }],
149 },
150 {
151 fontFamily: "DMSans-Bold",
152 fontDefinitions: [{ path: dmSansFonts.bold, weight: 700 }],
153 },
154 ],
155 },
156 },
157 ],
125158 "expo-secure-store",
126159 "expo-sqlite",
127160 ["@clerk/expo", { theme: "./clerk-theme.json" }],
apps/mobile/global.css+9-6
......@@ -51,6 +51,7 @@
5151 /* Inputs */
5252 --color-input: #ffffff;
5353 --color-input-border: rgba(0, 0, 0, 0.1);
54 --color-sidebar-search: rgba(118, 118, 128, 0.12);
5455 --color-placeholder: #a3a3a3;
5556
5657 /* Icons */
......@@ -144,6 +145,7 @@
144145 /* Inputs */
145146 --color-input: #141414;
146147 --color-input-border: rgba(255, 255, 255, 0.08);
148 --color-sidebar-search: rgba(118, 118, 128, 0.24);
147149 --color-placeholder: #8e8e93;
148150
149151 /* Icons */
......@@ -194,9 +196,10 @@
194196
195197/* ─── Typography ────────────────────────────────────────────────────── */
196198@theme {
197 --font-sans: "DMSans_400Regular";
198 --font-medium: "DMSans_500Medium";
199 --font-bold: "DMSans_700Bold";
199 /* Keep these native family names aligned with app.config.ts. */
200 --font-sans: "DMSans-Regular";
201 --font-medium: "DMSans-Medium";
202 --font-bold: "DMSans-Bold";
200203
201204 /* Keep this scale aligned with src/lib/typography.ts for native style props. */
202205 --text-3xs: 11px;
......@@ -221,14 +224,14 @@
221224
222225/* ─── Custom utilities ──────────────────────────────────────────────── */
223226@utility font-t3-medium {
224 font-family: "DMSans_500Medium";
227 font-family: var(--font-medium);
225228}
226229
227230@utility font-t3-bold {
228 font-family: "DMSans_700Bold";
231 font-family: var(--font-bold);
229232}
230233
231234@utility font-t3-extrabold {
232 font-family: "DMSans_700Bold";
235 font-family: var(--font-bold);
233236 font-weight: 800;
234237}
apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift+2-2
......@@ -300,7 +300,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
300300 skillText: "#a21caf",
301301 fileTint: "#737373"
302302 )
303 private var fontFamily = "DMSans_400Regular"
303 private var fontFamily = "DMSans-Regular"
304304 private var fontSize: CGFloat = 14
305305 private var lineHeight: CGFloat = 20
306306 private var contentInsetVertical: CGFloat = 0
......@@ -608,7 +608,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
608608 iconImage: UIImage?,
609609 style: ComposerChipStyle
610610 ) -> UIImage {
611 let font = UIFont(name: "DMSans_500Medium", size: max(12, fontSize - 2))
611 let font = UIFont(name: "DMSans-Medium", size: max(12, fontSize - 2))
612612 ?? UIFont.systemFont(ofSize: max(12, fontSize - 2), weight: .medium)
613613 let fallbackIcon = UIImage(
614614 systemName: iconName,
apps/mobile/package.json+2-1
......@@ -6,7 +6,7 @@
66 "scripts": {
77 "dev": "expo start --clear",
88 "dev:client": "APP_VARIANT=development expo start --dev-client --scheme t3code-dev --clear",
9 "dev:client:preview": "eas env:exec preview 'EXPO_NO_DOTENV=1 APP_VARIANT=preview expo start --dev-client --scheme t3code-preview --clear'",
9 "dev:client:preview": "eas env:exec preview 'EXPO_NO_DOTENV=1 APP_VARIANT=preview expo start --dev-client --scheme t3code-preview --clear --lan'",
1010 "start": "expo start",
1111 "start:dev": "APP_VARIANT=development expo start",
1212 "start:preview": "APP_VARIANT=preview expo start",
......@@ -80,6 +80,7 @@
8080 "expo-font": "~56.0.7",
8181 "expo-glass-effect": "~56.0.4",
8282 "expo-haptics": "~56.0.3",
83 "expo-image": "~56.0.11",
8384 "expo-image-picker": "~56.0.18",
8485 "expo-linking": "~56.0.14",
8586 "expo-network": "~56.0.5",
apps/mobile/src/App.tsx+3-14
......@@ -1,11 +1,6 @@
1import {
2 DMSans_400Regular,
3 DMSans_500Medium,
4 DMSans_700Bold,
5 useFonts,
6} from "@expo-google-fonts/dm-sans";
71import * as Linking from "expo-linking";
82import * as SplashScreen from "expo-splash-screen";
3import { useEffect } from "react";
94import { StatusBar, useColorScheme } from "react-native";
105import { GestureHandlerRootView } from "react-native-gesture-handler";
116import { KeyboardProvider } from "react-native-keyboard-controller";
......@@ -13,7 +8,6 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
138import { createStaticNavigation, DarkTheme, DefaultTheme } from "@react-navigation/native";
149
1510import { RegistryContext } from "@effect/atom-react";
16import { useEffect } from "react";
1711import { CloudAuthProvider } from "./features/cloud/CloudAuthProvider";
1812import { AppearancePreferencesProvider } from "./features/settings/appearance/AppearancePreferencesProvider";
1913import { RootStack } from "./Stack";
......@@ -29,17 +23,12 @@ const appLinking = {
2923const Navigation = createStaticNavigation(RootStack);
3024
3125export default function App() {
32 const [fontsLoaded] = useFonts({
33 DMSans_400Regular,
34 DMSans_500Medium,
35 DMSans_700Bold,
36 });
3726 const colorScheme = useColorScheme();
3827 const statusBarBg = useThemeColor("--color-status-bar");
3928
4029 useEffect(() => {
41 if (fontsLoaded) SplashScreen.hide();
42 }, [fontsLoaded]);
30 SplashScreen.hide();
31 }, []);
4332
4433 return (
4534 <RegistryContext.Provider value={appAtomRegistry}>
apps/mobile/src/components/AppText.tsx+3-11
......@@ -4,7 +4,6 @@ import {
44 type TextInputProps as RNTextInputProps,
55 type TextProps as RNTextProps,
66} from "react-native";
7import { useThemeColor } from "../lib/useThemeColor";
87
98import { cn } from "../lib/cn";
109
......@@ -18,7 +17,7 @@ export function AppText({ className, ...props }: AppTextProps) {
1817 return <RNText className={cn("font-sans text-foreground", className)} {...props} />;
1918}
2019
21export type AppTextInputProps = RNTextInputProps & {
20export type AppTextInputProps = Omit<RNTextInputProps, "placeholderTextColor"> & {
2221 readonly className?: string;
2322 readonly ref?: React.Ref<RNTextInput>;
2423};
......@@ -27,14 +26,7 @@ export type AppTextInputProps = RNTextInputProps & {
2726 * Thin wrapper around RN TextInput with default input styling.
2827 * Uses Uniwind className — no manual style parsing.
2928 */
30export function AppTextInput({
31 className,
32 placeholderTextColor,
33 ref,
34 ...props
35}: AppTextInputProps) {
36 const placeholderColor = useThemeColor("--color-placeholder");
37
29export function AppTextInput({ className, ref, ...props }: AppTextInputProps) {
3830 return (
3931 <RNTextInput
4032 ref={ref}
......@@ -42,7 +34,7 @@ export function AppTextInput({
4234 "min-h-13.5 rounded-2xl border border-input-border bg-input px-3.5 py-3 font-sans text-base text-foreground",
4335 className,
4436 )}
45 placeholderTextColor={placeholderTextColor ?? placeholderColor}
37 placeholderTextColorClassName="accent-placeholder"
4638 {...props}
4739 />
4840 );
apps/mobile/src/components/BrandMark.tsx+2-7
......@@ -22,14 +22,9 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab
2222 />
2323 <View className="gap-1">
2424 <View className="flex-row items-center gap-2">
25 <Text className="text-lg font-t3-bold text-foreground" style={{ letterSpacing: -0.4 }}>
26 T3 Code
27 </Text>
25 <Text className="text-lg font-t3-bold tracking-[-0.4px] text-foreground">T3 Code</Text>
2826 <View className="rounded-full bg-subtle px-2 py-1">
29 <Text
30 className="text-3xs font-t3-bold uppercase text-foreground-muted"
31 style={{ letterSpacing: 1.1 }}
32 >
27 <Text className="text-3xs font-t3-bold tracking-[1.1px] uppercase text-foreground-muted">
3328 {stageLabel}
3429 </Text>
3530 </View>
apps/mobile/src/components/ComposerAttachmentStrip.tsx+4-10
......@@ -39,14 +39,14 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {
3939 horizontal
4040 showsHorizontalScrollIndicator={false}
4141 keyboardShouldPersistTaps="always"
42 style={{ flexGrow: 0 }}
42 className="grow-0"
4343 >
44 <View style={{ flexDirection: "row", gap: 10 }}>
44 <View className="flex-row gap-2.5">
4545 {props.attachments.map((image) => (
4646 <View
4747 key={image.id}
48 className="relative"
4849 style={{
49 position: "relative",
5050 paddingTop: removeButtonGutter,
5151 paddingRight: removeButtonGutter,
5252 }}
......@@ -66,16 +66,10 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {
6666 />
6767 </Pressable>
6868 <Pressable
69 className="absolute h-[22px] w-[22px] items-center justify-center rounded-[11px] bg-black/55"
6970 style={{
70 position: "absolute",
7171 top: removeButtonPlacement === "gutter" ? 0 : 4,
7272 right: removeButtonPlacement === "gutter" ? 0 : 4,
73 width: 22,
74 height: 22,
75 borderRadius: 11,
76 backgroundColor: "rgba(0,0,0,0.55)",
77 alignItems: "center",
78 justifyContent: "center",
7973 }}
8074 hitSlop={6}
8175 onPress={() => props.onRemove(image.id)}
apps/mobile/src/components/ComposerToolbarTrigger.tsx+2-4
......@@ -31,11 +31,9 @@ export function ComposerToolbarRow(props: {
3131}) {
3232 return (
3333 <View
34 className="flex-row items-center gap-1.5"
3435 style={[
3536 {
36 alignItems: "center",
37 flexDirection: "row",
38 gap: 6,
3937 paddingBottom: props.paddingBottom ?? 8,
4038 paddingHorizontal: props.paddingHorizontal ?? 6,
4139 paddingTop: props.paddingTop ?? 8,
......@@ -89,7 +87,7 @@ export function ComposerToolbarScroller(props: {
8987 }, []);
9088
9189 return (
92 <View className="min-w-0 flex-1" style={{ position: "relative" }}>
90 <View className="relative min-w-0 flex-1">
9391 <ScrollView
9492 horizontal
9593 keyboardShouldPersistTaps="always"
apps/mobile/src/components/GlassSafeAreaView.tsx+5-15
......@@ -39,22 +39,12 @@ export function GlassSafeAreaView({
3939 style={{ borderRadius: 0, backgroundColor: "transparent" }}
4040 >
4141 <View
42 style={{
43 flexDirection: "row",
44 alignItems: "center",
45 paddingHorizontal: 20,
46 paddingTop: headerPaddingTop,
47 paddingBottom: 16,
48 gap: 10,
49 }}
42 className="flex-row items-center gap-2.5 px-5 pb-4"
43 style={{ paddingTop: headerPaddingTop }}
5044 >
51 <View style={{ alignItems: "flex-start", justifyContent: "center" }}>{leftSlot}</View>
52 <View
53 style={{ flex: 1, alignItems: "center", justifyContent: "center", overflow: "hidden" }}
54 >
55 {centerSlot}
56 </View>
57 <View style={{ alignItems: "flex-end", justifyContent: "center" }}>{rightSlot}</View>
45 <View className="items-start justify-center">{leftSlot}</View>
46 <View className="flex-1 items-center justify-center overflow-hidden">{centerSlot}</View>
47 <View className="items-end justify-center">{rightSlot}</View>
5848 </View>
5949 </GlassSurface>
6050 </View>
apps/mobile/src/components/GlassSurface.tsx+1-1
......@@ -10,7 +10,7 @@ import {
1010} from "react-native";
1111import { useThemeColor } from "../lib/useThemeColor";
1212
13export interface GlassSurfaceProps extends ViewProps {
13export interface GlassSurfaceProps extends Omit<ViewProps, "className"> {
1414 readonly children: ReactNode;
1515 readonly glassEffectStyle?: "clear" | "regular" | "none";
1616 readonly tintColor?: ColorValue;
apps/mobile/src/components/ProjectFavicon.tsx+3-2
......@@ -1,6 +1,7 @@
11import { SymbolView } from "expo-symbols";
2import { Image } from "expo-image";
23import { useState } from "react";
3import { Image, View } from "react-native";
4import { View } from "react-native";
45import type { EnvironmentId } from "@t3tools/contracts";
56import { useThemeColor } from "../lib/useThemeColor";
67import { useAssetUrl } from "../state/assets";
......@@ -78,7 +79,7 @@ function ProjectFaviconImage(props: {
7879 borderRadius: props.size * 0.16,
7980 ...(showImage ? {} : { position: "absolute" as const, opacity: 0 }),
8081 }}
81 resizeMode="contain"
82 contentFit="contain"
8283 onLoad={() => {
8384 if (props.faviconUrl) loadedFaviconUrls.add(props.faviconUrl);
8485 setStatus("loaded");
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+3-8
......@@ -244,9 +244,8 @@ function ProjectGroupLabel(props: {
244244 workspaceRoot={props.project.workspaceRoot}
245245 />
246246 <Text
247 className="flex-1 text-xs font-t3-medium uppercase text-foreground-muted"
247 className="flex-1 text-xs font-t3-medium tracking-[0.5px] uppercase text-foreground-muted"
248248 numberOfLines={1}
249 style={{ letterSpacing: 0.5 }}
250249 >
251250 {props.project.title}
252251 </Text>
......@@ -331,10 +330,7 @@ function ArchivedThreadRow(props: {
331330 >
332331 {props.thread.title}
333332 </Text>
334 <Text
335 className="text-xs text-foreground-tertiary"
336 style={{ fontVariant: ["tabular-nums"], minWidth: 30, textAlign: "right" }}
337 >
333 <Text className="min-w-[30px] text-right text-xs tabular-nums text-foreground-tertiary">
338334 {timestamp}
339335 </Text>
340336 </View>
......@@ -347,9 +343,8 @@ function ArchivedThreadRow(props: {
347343 type="monochrome"
348344 />
349345 <Text
350 className="min-w-0 flex-1 text-2xs text-foreground-tertiary"
346 className="min-w-0 flex-1 font-mono text-2xs text-foreground-tertiary"
351347 numberOfLines={1}
352 style={{ fontFamily: "monospace" }}
353348 >
354349 {subtitle.join(" · ")}
355350 </Text>
apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx+15-70
......@@ -1,13 +1,12 @@
11import { useWaitlist } from "@clerk/expo";
2import { ActivityIndicator, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
2import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native";
33import { useState } from "react";
44
5import { useThemeColor } from "../../lib/useThemeColor";
5import { cn } from "../../lib/cn";
66import { CloudWaitlistJoinRejectedError, joinCloudWaitlist } from "./cloudWaitlistJoin";
77
88export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {
99 const { errors, fetchStatus, waitlist } = useWaitlist();
10 const colors = useCloudWaitlistColors();
1110 const [emailAddress, setEmailAddress] = useState("");
1211 const [requestError, setRequestError] = useState<string | null>(null);
1312 const isSubmitting = fetchStatus === "fetching";
......@@ -34,7 +33,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
3433
3534 if (waitlist.id) {
3635 return (
37 <View style={styles.content}>
36 <View className="gap-[18px]">
3837 <Text className="text-center font-t3-bold text-xl text-foreground">
3938 You are on the waitlist
4039 </Text>
......@@ -47,19 +46,22 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
4746 }
4847
4948 return (
50 <View style={styles.content}>
49 <View className="gap-[18px]">
5150 <Text className="font-sans text-base text-foreground-secondary">
5251 Enter your email and we will let you know when access is ready.
5352 </Text>
5453
55 <View style={styles.field}>
54 <View className="gap-2">
5655 <Text className="font-t3-bold text-sm text-foreground-secondary">Email address</Text>
5756 <TextInput
5857 accessibilityLabel="Email address"
5958 autoCapitalize="none"
6059 autoComplete="email"
6160 autoCorrect={false}
62 className="font-sans text-lg text-foreground"
61 className={cn(
62 "min-h-[54px] rounded-2xl border border-input-border bg-input px-4 py-3.5 font-sans text-lg text-foreground border-continuous",
63 (fieldError || requestError) && "border-danger-foreground",
64 )}
6365 keyboardType="email-address"
6466 onChangeText={(value) => {
6567 setEmailAddress(value);
......@@ -67,16 +69,8 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
6769 }}
6870 onSubmitEditing={() => void joinWaitlist()}
6971 placeholder="Enter your email address"
70 placeholderTextColor={colors.placeholder}
72 placeholderTextColorClassName="accent-placeholder"
7173 returnKeyType="join"
72 style={[
73 styles.input,
74 {
75 backgroundColor: colors.input,
76 borderColor:
77 fieldError || requestError ? colors.dangerForeground : colors.inputBorder,
78 },
79 ]}
8074 textContentType="emailAddress"
8175 value={emailAddress}
8276 />
......@@ -99,15 +93,11 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
9993 }}
10094 disabled={isSubmitting || emailAddress.trim().length === 0}
10195 onPress={() => void joinWaitlist()}
102 style={[
103 styles.primaryButton,
104 {
105 backgroundColor: colors.primary,
106 opacity: isSubmitting || emailAddress.trim().length === 0 ? 0.45 : 1,
107 },
108 ]}
96 className="min-h-[54px] flex-row items-center justify-center gap-2 rounded-full bg-primary px-5 py-3 disabled:opacity-[0.45]"
10997 >
110 {isSubmitting ? <ActivityIndicator color={colors.primaryForeground} size="small" /> : null}
98 {isSubmitting ? (
99 <ActivityIndicator colorClassName="accent-primary-foreground" size="small" />
100 ) : null}
111101 <Text className="font-t3-bold text-base text-primary-foreground">
112102 {isSubmitting ? "Joining" : "Join the waitlist"}
113103 </Text>
......@@ -120,7 +110,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
120110
121111function SignInAction(props: { readonly onPress: () => void }) {
122112 return (
123 <View style={styles.signInRow}>
113 <View className="flex-row items-center justify-center gap-1 pt-1">
124114 <Text className="font-sans text-base text-foreground-secondary">Already have access?</Text>
125115 <Pressable accessibilityRole="button" hitSlop={8} onPress={props.onPress}>
126116 <Text className="font-t3-bold text-base text-foreground">Sign in</Text>
......@@ -128,48 +118,3 @@ function SignInAction(props: { readonly onPress: () => void }) {
128118 </View>
129119 );
130120}
131
132function useCloudWaitlistColors() {
133 return {
134 dangerForeground: String(useThemeColor("--color-danger-foreground")),
135 input: String(useThemeColor("--color-input")),
136 inputBorder: String(useThemeColor("--color-input-border")),
137 placeholder: String(useThemeColor("--color-placeholder")),
138 primary: String(useThemeColor("--color-primary")),
139 primaryForeground: String(useThemeColor("--color-primary-foreground")),
140 };
141}
142
143const styles = StyleSheet.create({
144 content: {
145 gap: 18,
146 },
147 field: {
148 gap: 8,
149 },
150 input: {
151 borderCurve: "continuous",
152 borderRadius: 16,
153 borderWidth: 1,
154 minHeight: 54,
155 paddingHorizontal: 16,
156 paddingVertical: 14,
157 },
158 primaryButton: {
159 alignItems: "center",
160 borderRadius: 999,
161 flexDirection: "row",
162 gap: 8,
163 justifyContent: "center",
164 minHeight: 54,
165 paddingHorizontal: 20,
166 paddingVertical: 12,
167 },
168 signInRow: {
169 alignItems: "center",
170 flexDirection: "row",
171 gap: 4,
172 justifyContent: "center",
173 paddingTop: 4,
174 },
175});
apps/mobile/src/features/cloud/ConnectOnboardingRouteScreen.tsx+1-1
......@@ -88,7 +88,7 @@ function ConfiguredConnectOnboardingRouteScreen() {
8888 alwaysBounceVertical
8989 contentInsetAdjustmentBehavior="automatic"
9090 showsVerticalScrollIndicator={false}
91 style={{ flex: 1 }}
91 className="flex-1"
9292 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
9393 contentContainerStyle={{
9494 gap: 16,
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+2-4
......@@ -297,9 +297,8 @@ function CloudEnvironmentRowShell(props: {
297297 {props.connectionError ? (
298298 <Text
299299 aria-hidden
300 className={cn("absolute left-0 right-0 text-xs", statusClassName)}
301300 onTextLayout={onMeasuredErrorTextLayout}
302 style={{ opacity: 0, zIndex: -1 }}
301 className={cn("absolute inset-x-0 -z-[1] text-xs opacity-0", statusClassName)}
303302 >
304303 {measuredErrorText}
305304 </Text>
......@@ -321,7 +320,7 @@ function CloudEnvironmentRowShell(props: {
321320 <Text
322321 accessibilityHint="Copies the trace ID"
323322 accessibilityRole="button"
324 className={cn("text-xs underline", statusClassName)}
323 className={cn("text-xs underline decoration-dotted", statusClassName)}
325324 onLongPress={(event) => {
326325 event.stopPropagation();
327326 copyTextWithHaptic(errorTraceId, { target: "connection-trace-id" });
......@@ -329,7 +328,6 @@ function CloudEnvironmentRowShell(props: {
329328 onPress={(event) => {
330329 event.stopPropagation();
331330 }}
332 style={{ textDecorationStyle: "dotted" }}
333331 >
334332 {errorTraceId}
335333 </Text>
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+4-17
......@@ -38,7 +38,6 @@ export function ConnectionEnvironmentRow(props: {
3838 const [url, setUrl] = useState(props.environment.displayUrl);
3939
4040 const mutedColor = useThemeColor("--color-icon-subtle");
41 const placeholderColor = useThemeColor("--color-placeholder");
4241 const primaryFg = useThemeColor("--color-primary-foreground");
4342 const dangerFg = useThemeColor("--color-danger-foreground");
4443 const statusLabel = connectionStatusLabel(props.environment);
......@@ -98,7 +97,7 @@ export function ConnectionEnvironmentRow(props: {
9897 <Text
9998 accessibilityHint="Copies the trace ID"
10099 accessibilityRole="button"
101 className="underline"
100 className="underline decoration-dotted"
102101 onLongPress={(event) => {
103102 event.stopPropagation();
104103 copyTextWithHaptic(statusTraceId, { target: "connection-trace-id" });
......@@ -106,7 +105,6 @@ export function ConnectionEnvironmentRow(props: {
106105 onPress={(event) => {
107106 event.stopPropagation();
108107 }}
109 style={{ textDecorationStyle: "dotted" }}
110108 >
111109 {statusTraceId}
112110 </Text>
......@@ -140,17 +138,13 @@ export function ConnectionEnvironmentRow(props: {
140138 ) : (
141139 <>
142140 <View className="gap-1.5">
143 <Text
144 className="text-2xs font-t3-bold uppercase text-foreground-muted"
145 style={{ letterSpacing: 0.8 }}
146 >
141 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
147142 Label
148143 </Text>
149144 <TextInput
150145 autoCapitalize="words"
151146 autoCorrect={false}
152147 placeholder="My MacBook"
153 placeholderTextColor={placeholderColor}
154148 value={label}
155149 onChangeText={setLabel}
156150 className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"
......@@ -158,10 +152,7 @@ export function ConnectionEnvironmentRow(props: {
158152 </View>
159153
160154 <View className="gap-1.5">
161 <Text
162 className="text-2xs font-t3-bold uppercase text-foreground-muted"
163 style={{ letterSpacing: 0.8 }}
164 >
155 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
165156 URL
166157 </Text>
167158 <TextInput
......@@ -169,7 +160,6 @@ export function ConnectionEnvironmentRow(props: {
169160 autoCorrect={false}
170161 keyboardType="url"
171162 placeholder="192.168.1.100:8080"
172 placeholderTextColor={placeholderColor}
173163 value={url}
174164 onChangeText={setUrl}
175165 className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"
......@@ -185,10 +175,7 @@ export function ConnectionEnvironmentRow(props: {
185175 onPress={handleSave}
186176 >
187177 <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" />
188 <Text
189 className="text-xs font-t3-bold uppercase text-primary-foreground"
190 style={{ letterSpacing: 0.8 }}
191 >
178 <Text className="text-xs font-t3-bold tracking-[0.8px] uppercase text-primary-foreground">
192179 Save
193180 </Text>
194181 </Pressable>
apps/mobile/src/features/connection/ConnectionSheetButton.tsx+17-35
......@@ -37,33 +37,11 @@ export function ConnectionSheetButton(props: {
3737}) {
3838 const tone = props.tone ?? "secondary";
3939
40 const primaryBg = useThemeColor("--color-primary");
4140 const primaryFg = useThemeColor("--color-primary-foreground");
42 const dangerBg = useThemeColor("--color-danger");
43 const dangerBorderColor = useThemeColor("--color-danger-border");
4441 const dangerFg = useThemeColor("--color-danger-foreground");
45 const secondaryBg = useThemeColor("--color-secondary");
4642 const secondaryFg = useThemeColor("--color-secondary-foreground");
47 const borderColor = useThemeColor("--color-border");
4843
49 const colors =
50 tone === "primary"
51 ? {
52 backgroundColor: primaryBg,
53 borderColor: "transparent",
54 textColor: primaryFg,
55 }
56 : tone === "danger"
57 ? {
58 backgroundColor: dangerBg,
59 borderColor: dangerBorderColor,
60 textColor: dangerFg,
61 }
62 : {
63 backgroundColor: secondaryBg,
64 borderColor: borderColor,
65 textColor: secondaryFg,
66 };
44 const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg;
6745
6846 const primaryShadow =
6947 tone === "primary"
......@@ -84,28 +62,32 @@ export function ConnectionSheetButton(props: {
8462 props.compact
8563 ? "min-h-[42px] flex-row items-center justify-center gap-1.5 rounded-[14px] px-3.5 py-2.5"
8664 : "min-h-[48px] flex-row items-center justify-center gap-2 rounded-[16px] px-4 py-3",
65 "disabled:opacity-50",
66 tone === "primary"
67 ? "bg-primary"
68 : tone === "danger"
69 ? "border border-danger-border bg-danger"
70 : "border border-border bg-secondary",
8771 )}
8872 disabled={props.disabled}
8973 onPress={props.onPress}
90 style={[
91 {
92 backgroundColor: colors.backgroundColor,
93 borderWidth: tone === "primary" ? 0 : 1,
94 borderColor: colors.borderColor,
95 opacity: props.disabled ? 0.5 : 1,
96 },
97 primaryShadow,
98 ]}
74 style={primaryShadow}
9975 >
10076 <SymbolView
10177 name={props.icon}
10278 size={props.compact ? 13 : 14}
103 tintColor={colors.textColor}
79 tintColor={textColor}
10480 type="monochrome"
10581 />
10682 <Text
107 className="text-xs font-t3-bold uppercase"
108 style={{ color: colors.textColor, letterSpacing: 0.8 }}
83 className={cn(
84 "text-xs font-t3-bold tracking-[0.8px] uppercase",
85 tone === "primary"
86 ? "text-primary-foreground"
87 : tone === "danger"
88 ? "text-danger-foreground"
89 : "text-secondary-foreground",
90 )}
10991 >
11092 {props.label}
11193 </Text>
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+5-20
......@@ -38,7 +38,6 @@ export function ConnectionsNewRouteScreen({
3838 const [scannerLocked, setScannerLocked] = useState(false);
3939
4040 const headerIconColor = useThemeColor("--color-icon");
41 const placeholderColor = useThemeColor("--color-placeholder");
4241
4342 const connectDisabled = isSubmitting || hostInput.trim().length === 0;
4443
......@@ -158,7 +157,7 @@ export function ConnectionsNewRouteScreen({
158157 <ScrollView
159158 contentInsetAdjustmentBehavior="automatic"
160159 showsVerticalScrollIndicator={false}
161 style={{ flex: 1 }}
160 className="flex-1"
162161 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
163162 contentContainerStyle={{
164163 paddingHorizontal: 20,
......@@ -168,10 +167,7 @@ export function ConnectionsNewRouteScreen({
168167 <View collapsable={false} className="gap-5">
169168 {showScanner ? (
170169 cameraPermission?.granted ? (
171 <View
172 className="overflow-hidden rounded-[24px]"
173 style={{ borderCurve: "continuous" }}
174 >
170 <View className="overflow-hidden rounded-[24px] border-continuous">
175171 <CameraView
176172 barcodeScannerSettings={{ barcodeTypes: ["qr"] }}
177173 onBarcodeScanned={handleQrScan}
......@@ -179,10 +175,7 @@ export function ConnectionsNewRouteScreen({
179175 />
180176 </View>
181177 ) : (
182 <View
183 className="items-center gap-3 rounded-[24px] bg-card px-5 py-8"
184 style={{ borderCurve: "continuous" }}
185 >
178 <View className="items-center gap-3 rounded-[24px] border-continuous bg-card px-5 py-8">
186179 <Text className="text-center text-sm leading-normal text-foreground-muted">
187180 Camera permission is required to scan a QR code.
188181 </Text>
......@@ -200,10 +193,7 @@ export function ConnectionsNewRouteScreen({
200193 ) : (
201194 <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4">
202195 <View collapsable={false} className="gap-1.5">
203 <Text
204 className="text-2xs font-t3-bold uppercase text-foreground-muted"
205 style={{ letterSpacing: 0.8 }}
206 >
196 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
207197 Host
208198 </Text>
209199 <TextInput
......@@ -211,7 +201,6 @@ export function ConnectionsNewRouteScreen({
211201 autoCorrect={false}
212202 keyboardType="url"
213203 placeholder="192.168.1.100:8080"
214 placeholderTextColor={placeholderColor}
215204 value={hostInput}
216205 onChangeText={handleHostChange}
217206 className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"
......@@ -219,17 +208,13 @@ export function ConnectionsNewRouteScreen({
219208 </View>
220209
221210 <View collapsable={false} className="gap-1.5">
222 <Text
223 className="text-2xs font-t3-bold uppercase text-foreground-muted"
224 style={{ letterSpacing: 0.8 }}
225 >
211 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
226212 Pairing code
227213 </Text>
228214 <TextInput
229215 autoCapitalize="none"
230216 autoCorrect={false}
231217 placeholder="abc-123-xyz"
232 placeholderTextColor={placeholderColor}
233218 value={codeInput}
234219 onChangeText={handleCodeChange}
235220 className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"
apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx+2-5
......@@ -42,7 +42,7 @@ export function ConnectionsRouteScreen() {
4242 <ScrollView
4343 contentInsetAdjustmentBehavior="automatic"
4444 showsVerticalScrollIndicator={false}
45 style={{ flex: 1 }}
45 className="flex-1"
4646 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
4747 contentContainerStyle={{
4848 paddingHorizontal: 20,
......@@ -55,10 +55,7 @@ export function ConnectionsRouteScreen() {
5555 <View
5656 key={environment.environmentId}
5757 collapsable={false}
58 style={{
59 borderTopWidth: index === 0 ? 0 : 1,
60 }}
61 className={cn(index !== 0 && "border-border")}
58 className={cn(index !== 0 && "border-t border-border")}
6259 >
6360 <ConnectionEnvironmentRow
6461 environment={environment}
apps/mobile/src/features/files/FileMarkdownPreview.tsx+15-8
......@@ -8,6 +8,7 @@ import {
88import { RefreshControl, ScrollView, Text as NativeText, View } from "react-native";
99
1010import { tryOpenExternalUrl } from "../../lib/openExternalUrl";
11import { useFontFamily } from "../../lib/useFontFamily";
1112import {
1213 resolveMarkdownFontSizes,
1314 resolveNativeMarkdownTypography,
......@@ -45,11 +46,15 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
4546 const codeBackground = String(useThemeColor("--color-md-code-bg"));
4647 const codeText = String(useThemeColor("--color-md-code-text"));
4748 const horizontalRule = String(useThemeColor("--color-md-hr"));
49 const regularFontFamily = useFontFamily("regular");
50 const mediumFontFamily = useFontFamily("medium");
51 const boldFontFamily = useFontFamily("bold");
4852
4953 return useMemo(() => {
5054 const renderers: CustomRenderers = {
5155 link: ({ href, children }) => (
5256 <NativeText
57 className="font-t3-medium"
5358 onPress={() => {
5459 if (href) {
5560 void tryOpenExternalUrl(href, "markdown-link");
......@@ -57,7 +62,6 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
5762 }}
5863 style={{
5964 color: link,
60 fontFamily: "DMSans_500Medium",
6165 textDecorationLine: "none",
6266 }}
6367 >
......@@ -86,21 +90,21 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
8690 styles: {
8791 text: {
8892 color: body,
89 fontFamily: "DMSans_400Regular",
93 fontFamily: regularFontFamily,
9094 fontSize: markdownFontSizes.m,
9195 lineHeight: markdownFontSizes.bodyLineHeight,
9296 },
9397 heading: {
9498 color: strong,
95 fontFamily: "DMSans_700Bold",
99 fontFamily: boldFontFamily,
96100 },
97101 strong: {
98102 color: strong,
99 fontFamily: "DMSans_700Bold",
103 fontFamily: boldFontFamily,
100104 },
101105 link: {
102106 color: link,
103 fontFamily: "DMSans_500Medium",
107 fontFamily: mediumFontFamily,
104108 },
105109 blockquote: {
106110 backgroundColor: blockquoteBackground,
......@@ -141,9 +145,9 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
141145 fontSize: nativeMarkdownTypography.fontSize,
142146 lineHeight: nativeMarkdownTypography.lineHeight,
143147 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
144 fontFamily: "DMSans_400Regular",
145 headingFontFamily: "DMSans_700Bold",
146 boldFontFamily: "DMSans_700Bold",
148 fontFamily: regularFontFamily,
149 headingFontFamily: boldFontFamily,
150 boldFontFamily,
147151 },
148152 };
149153 }, [
......@@ -155,8 +159,11 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
155159 horizontalRule,
156160 link,
157161 markdownFontSizes,
162 mediumFontFamily,
158163 nativeMarkdownTypography,
164 regularFontFamily,
159165 strong,
166 boldFontFamily,
160167 ]);
161168}
162169
apps/mobile/src/features/files/FileTreeBrowser.tsx+1-1
......@@ -246,7 +246,7 @@ export function FileTreeBrowser(props: {
246246 // flex-1 Views is ignored, which is why the tree rendered under the header with no blur.
247247 return (
248248 <FlatList
249 style={{ flex: 1 }}
249 className="flex-1"
250250 data={visibleNodes}
251251 keyExtractor={(item) => item.node.path}
252252 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx+1-8
......@@ -224,14 +224,7 @@ function FilesToolbarBottomFade() {
224224 pointerEvents="none"
225225 accessibilityElementsHidden
226226 importantForAccessibility="no-hide-descendants"
227 style={{
228 bottom: 0,
229 height: 112,
230 left: 0,
231 position: "absolute",
232 right: 0,
233 zIndex: 1,
234 }}
227 className="absolute inset-x-0 bottom-0 z-[1] h-28"
235228 >
236229 <Svg width="100%" height="100%">
237230 <Defs>
apps/mobile/src/features/home/HomeScreen.tsx+1-1
......@@ -412,7 +412,7 @@ export function HomeScreen(props: HomeScreenProps) {
412412 {Platform.OS === "ios" ? null : <HomeTopContentSpacer topInset={insets.top} />}
413413
414414 {shouldShowConnectionStatus && Platform.OS === "ios" ? (
415 <View style={{ paddingBottom: 16 }}>
415 <View className="pb-4">
416416 <WorkspaceConnectionStatus
417417 state={props.catalogState}
418418 onPress={props.onOpenEnvironments}
apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx+4-3
......@@ -476,16 +476,17 @@ export function AdaptiveWorkspaceLayout(props: {
476476 return (
477477 <HomeListOptionsProvider>
478478 <AdaptiveWorkspaceContext.Provider value={contextValue}>
479 <View testID="adaptive-workspace-layout" style={{ flex: 1, flexDirection: "row" }}>
479 <View testID="adaptive-workspace-layout" className="flex-1 flex-row">
480480 {shouldRenderPrimarySidebar && layout.listPaneWidth !== null ? (
481481 <Animated.View
482 className="self-stretch overflow-hidden"
482483 accessibilityElementsHidden={!panes.primarySidebarVisible}
483484 collapsable={false}
484485 importantForAccessibility={
485486 panes.primarySidebarVisible ? "auto" : "no-hide-descendants"
486487 }
487488 pointerEvents={panes.primarySidebarVisible ? "auto" : "none"}
488 style={[{ alignSelf: "stretch", overflow: "hidden" }, sidebarAnimatedStyle]}
489 style={sidebarAnimatedStyle}
489490 >
490491 <ThreadNavigationSidebar
491492 width={layout.listPaneWidth}
......@@ -501,7 +502,7 @@ export function AdaptiveWorkspaceLayout(props: {
501502 />
502503 </Animated.View>
503504 ) : null}
504 <View className="bg-screen" collapsable={false} style={{ flex: 1, overflow: "hidden" }}>
505 <View className="flex-1 overflow-hidden bg-screen" collapsable={false}>
505506 <View
506507 collapsable={false}
507508 style={
apps/mobile/src/features/layout/workspace-inspector-pane.tsx+3-2
......@@ -129,13 +129,14 @@ export function WorkspaceInspectorPane(props: {
129129 ) : null}
130130 {inspectorSupported ? (
131131 <Animated.View
132 className="shrink-0 overflow-hidden"
132133 accessibilityElementsHidden={!inspectorVisible}
133134 collapsable={false}
134135 importantForAccessibility={inspectorVisible ? "auto" : "no-hide-descendants"}
135136 pointerEvents={inspectorVisible ? "auto" : "none"}
136 style={[{ flexShrink: 0, overflow: "hidden" }, inspectorStyle]}
137 style={inspectorStyle}
137138 >
138 <Animated.View style={[{ flex: 1 }, inspectorContentStyle]}>
139 <Animated.View className="flex-1" style={inspectorContentStyle}>
139140 {props.renderInspector?.()}
140141 </Animated.View>
141142 </Animated.View>
apps/mobile/src/features/layout/workspace-pane-divider.tsx+1-10
......@@ -69,6 +69,7 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
6969 return (
7070 <GestureDetector gesture={resizeGesture}>
7171 <Pressable
72 className="relative z-[100] -mx-[22px] w-11 self-stretch cursor-pointer justify-center"
7273 accessibilityActions={[
7374 { name: "increment", label: "Make pane wider" },
7475 { name: "decrement", label: "Make pane narrower" },
......@@ -82,7 +83,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
8283 onAccessibilityAction={handleAccessibilityAction}
8384 onHoverIn={() => setHovered(true)}
8485 onHoverOut={() => setHovered(false)}
85 style={styles.hitTarget}
8686 >
8787 <View style={[styles.line, (hovered || dragging) && styles.activeLine]} />
8888 </Pressable>
......@@ -91,15 +91,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
9191}
9292
9393const styles = StyleSheet.create({
94 hitTarget: {
95 alignSelf: "stretch",
96 cursor: "pointer",
97 justifyContent: "center",
98 marginHorizontal: -22,
99 position: "relative",
100 width: 44,
101 zIndex: 100,
102 },
10394 line: {
10495 alignSelf: "center",
10596 backgroundColor:
apps/mobile/src/features/projects/AddProjectScreen.tsx+7-11
......@@ -31,6 +31,7 @@ import * as Arr from "effect/Array";
3131import * as Cause from "effect/Cause";
3232import * as Order from "effect/Order";
3333import { AsyncResult } from "effect/unstable/reactivity";
34import { cn } from "../../lib/cn";
3435
3536import { useProjects, useServerConfigs } from "../../state/entities";
3637import { filesystemEnvironment } from "../../state/filesystem";
......@@ -99,10 +100,7 @@ function sourceFromParam(value: string | string[] | undefined): AddProjectRemote
99100
100101function SectionTitle(props: { readonly children: string }) {
101102 return (
102 <Text
103 className="px-1 text-2xs font-t3-bold uppercase text-foreground-muted"
104 style={{ letterSpacing: 0.7 }}
105 >
103 <Text className="px-1 text-2xs font-t3-bold tracking-[0.7px] uppercase text-foreground-muted">
106104 {props.children}
107105 </Text>
108106 );
......@@ -148,19 +146,17 @@ function ListRow(props: {
148146 readonly right?: ReactNode;
149147 readonly onPress?: () => void;
150148}) {
151 const borderColor = useThemeColor("--color-border-subtle");
152149 const chevronColor = useThemeColor("--color-chevron");
153150
154151 return (
155152 <Pressable
156153 disabled={props.disabled}
157154 onPress={props.onPress}
158 className="bg-card px-3.5 py-2.5 active:opacity-70"
159 style={{
160 opacity: props.disabled ? 0.45 : 1,
161 borderTopWidth: props.isFirst ? 0 : 1,
162 borderTopColor: borderColor,
163 }}
155 className={cn(
156 "bg-card px-3.5 py-2.5 active:opacity-70",
157 !props.isFirst && "border-t border-border-subtle",
158 props.disabled && "opacity-[0.45]",
159 )}
164160 >
165161 <View className="flex-row items-center gap-3">
166162 <View
apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx+5-10
......@@ -144,15 +144,11 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp
144144 }
145145
146146 return (
147 <View style={{ flex: 1 }}>
148 <KeyboardAvoidingView automaticOffset behavior="padding" style={{ flex: 1 }}>
147 <View className="flex-1">
148 <KeyboardAvoidingView automaticOffset behavior="padding" className="flex-1">
149149 <View
150 style={{
151 flex: 1,
152 paddingHorizontal: 20,
153 paddingTop: 8,
154 paddingBottom: target ? 0 : Math.max(insets.bottom, 18),
155 }}
150 className="flex-1 px-5 pt-2"
151 style={{ paddingBottom: target ? 0 : Math.max(insets.bottom, 18) }}
156152 >
157153 <View className="flex-row items-center justify-between py-2">
158154 <Pressable
......@@ -253,8 +249,7 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp
253249 textAlignVertical="top"
254250 value={commentText}
255251 onChangeText={setCommentText}
256 className="h-full flex-1 border-0 bg-transparent px-0 py-0 font-sans text-base"
257 style={{ flex: 1, minHeight: 0 }}
252 className="h-full min-h-0 flex-1 border-0 bg-transparent px-0 py-0 font-sans text-base"
258253 />
259254 </TextInputWrapper>
260255 </View>
apps/mobile/src/features/review/ReviewSheet.tsx+2-2
......@@ -655,7 +655,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
655655
656656 <View className="flex-1 bg-sheet">
657657 {showConnectionNotice ? (
658 <View style={{ flex: 1, paddingTop: topContentInset }}>
658 <View className="flex-1" style={{ paddingTop: topContentInset }}>
659659 <EnvironmentConnectionNotice
660660 environmentLabel={environment.presentation?.entry.target.label ?? "Environment"}
661661 connection={
......@@ -722,7 +722,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
722722 bottom: Math.max(insets.bottom, 18) + 18,
723723 }}
724724 showsVerticalScrollIndicator={false}
725 style={{ flex: 1 }}
725 className="flex-1"
726726 >
727727 {listHeader}
728728 {!selectedSection ? (
apps/mobile/src/features/review/reviewDiffRendering.tsx+2-2
......@@ -48,8 +48,8 @@ export function ReviewChangeBar(props: {
4848 <View>
4949 {Array.from({ length: Math.ceil(height / 2) }, (_, index) => (
5050 <View key={index}>
51 <View className="w-[5px] bg-rose-400" style={{ height: 1 }} />
52 <View style={{ height: 1 }} />
51 <View className="h-px w-[5px] bg-rose-400" />
52 <View className="h-px" />
5353 </View>
5454 ))}
5555 </View>
apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx+2-4
......@@ -13,12 +13,10 @@ export function SettingsAppearanceRouteScreen() {
1313 <ScrollView
1414 contentInsetAdjustmentBehavior="automatic"
1515 showsVerticalScrollIndicator={false}
16 style={{ flex: 1 }}
16 className="flex-1"
17 contentContainerClassName="gap-6 px-5 pt-4"
1718 contentContainerStyle={{
18 gap: 24,
1919 paddingBottom: Math.max(insets.bottom, 18) + 18,
20 paddingHorizontal: 20,
21 paddingTop: 16,
2220 }}
2321 >
2422 <TextAppearanceSection />
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+3-13
......@@ -75,13 +75,8 @@ export function SettingsClientStorageRouteScreen() {
7575 contentInsetAdjustmentBehavior="automatic"
7676 contentInset={{ bottom: Math.max(insets.bottom, 18) }}
7777 showsVerticalScrollIndicator={false}
78 style={{ flex: 1 }}
79 contentContainerStyle={{
80 gap: 24,
81 paddingBottom: 18,
82 paddingHorizontal: 20,
83 paddingTop: 16,
84 }}
78 className="flex-1"
79 contentContainerClassName="gap-6 px-5 pt-4 pb-[18px]"
8580 >
8681 <SettingsSection title="Environment caches">
8782 {AsyncResult.isFailure(summaryResult) ? (
......@@ -180,7 +175,6 @@ function CacheEnvironmentRow(props: {
180175 readonly onClear: () => void;
181176}) {
182177 const iconColor = useThemeColor("--color-icon");
183 const dangerForegroundColor = useThemeColor("--color-danger-foreground");
184178 return (
185179 <View
186180 className={
......@@ -206,11 +200,7 @@ function CacheEnvironmentRow(props: {
206200 onPress={props.onClear}
207201 className="rounded-full px-3 py-2 disabled:opacity-40"
208202 >
209 <Text
210 className="font-t3-medium tabular-nums text-danger-foreground"
211 numberOfLines={1}
212 style={{ color: dangerForegroundColor }}
213 >
203 <Text className="font-t3-medium tabular-nums text-danger-foreground" numberOfLines={1}>
214204 Clear {formatBytes(props.environment.payloadBytes)}
215205 </Text>
216206 </Pressable>
apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx+3-7
......@@ -50,11 +50,10 @@ export function SettingsEnvironmentsRouteScreen() {
5050 <ScrollView
5151 contentInsetAdjustmentBehavior="automatic"
5252 showsVerticalScrollIndicator={false}
53 style={{ flex: 1 }}
53 className="flex-1"
54 contentContainerClassName="px-5 pt-4"
5455 contentContainerStyle={{
5556 paddingBottom: Math.max(insets.bottom, 18) + 18,
56 paddingHorizontal: 20,
57 paddingTop: 16,
5857 }}
5958 >
6059 {hasLocalEnvironments ? (
......@@ -63,10 +62,7 @@ export function SettingsEnvironmentsRouteScreen() {
6362 <View
6463 key={environment.environmentId}
6564 collapsable={false}
66 style={{
67 borderTopWidth: index === 0 ? 0 : 1,
68 }}
69 className={cn(index !== 0 && "border-border")}
65 className={cn(index !== 0 && "border-t border-border")}
7066 >
7167 <ConnectionEnvironmentRow
7268 environment={environment}
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+4-8
......@@ -94,12 +94,10 @@ function LocalSettingsRouteScreen() {
9494 <ScrollView
9595 contentInsetAdjustmentBehavior="automatic"
9696 showsVerticalScrollIndicator={false}
97 style={{ flex: 1 }}
97 className="flex-1"
98 contentContainerClassName="gap-6 px-5 pt-4"
9899 contentContainerStyle={{
99 gap: 24,
100100 paddingBottom: Math.max(insets.bottom, 18) + 18,
101 paddingHorizontal: 20,
102 paddingTop: 16,
103101 }}
104102 >
105103 <SettingsSection title="Configuration">
......@@ -430,12 +428,10 @@ function ConfiguredSettingsRouteScreen() {
430428 <ScrollView
431429 contentInsetAdjustmentBehavior="automatic"
432430 showsVerticalScrollIndicator={false}
433 style={{ flex: 1 }}
431 className="flex-1"
432 contentContainerClassName="gap-6 px-5 pt-4"
434433 contentContainerStyle={{
435 gap: 24,
436434 paddingBottom: Math.max(insets.bottom, 18) + 18,
437 paddingHorizontal: 20,
438 paddingTop: 16,
439435 }}
440436 >
441437 <View className="gap-3">
apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx+1-1
......@@ -136,7 +136,7 @@ export function FontSizeSliderRow(props: {
136136 };
137137
138138 return (
139 <View className="gap-1 p-4" style={{ opacity: disabled ? 0.45 : 1 }}>
139 <View className={disabled ? "gap-1 p-4 opacity-[0.45]" : "gap-1 p-4"}>
140140 <View className="flex-row items-center gap-4">
141141 <SymbolView
142142 name={props.icon}
apps/mobile/src/features/settings/components/SettingsRow.tsx+5-2
......@@ -22,8 +22,11 @@ export function SettingsRow(props: {
2222 const chevron = useThemeColor("--color-chevron");
2323 const content = (
2424 <View
25 className="flex-row items-center gap-4 p-4"
26 style={{ opacity: props.disabled ? 0.45 : 1 }}
25 className={
26 props.disabled
27 ? "flex-row items-center gap-4 p-4 opacity-[0.45]"
28 : "flex-row items-center gap-4 p-4"
29 }
2730 >
2831 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
2932 <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}>
apps/mobile/src/features/settings/components/SettingsSection.tsx+1-4
......@@ -7,10 +7,7 @@ export function SettingsSection(props: { readonly title: string; readonly childr
77 return (
88 <View className="gap-2">
99 <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text>
10 <View
11 className="overflow-hidden rounded-[28px] bg-card"
12 style={{ borderCurve: "continuous" }}
13 >
10 <View className="overflow-hidden rounded-[28px] border-continuous bg-card">
1411 {props.children}
1512 </View>
1613 </View>
apps/mobile/src/features/settings/components/SettingsSwitchRow.tsx+5-2
......@@ -20,8 +20,11 @@ export function SettingsSwitchRow(props: {
2020
2121 return (
2222 <View
23 className="flex-row items-center gap-4 p-4"
24 style={{ opacity: props.disabled ? 0.45 : 1 }}
23 className={
24 props.disabled
25 ? "flex-row items-center gap-4 p-4 opacity-[0.45]"
26 : "flex-row items-center gap-4 p-4"
27 }
2528 >
2629 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
2730 <Text className="flex-1 text-lg text-foreground">{props.label}</Text>
apps/mobile/src/features/terminal/NativeTerminalSurface.tsx+7-18
......@@ -82,9 +82,9 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
8282
8383 return (
8484 <View
85 className="flex-1"
8586 style={[
8687 {
87 flex: 1,
8888 backgroundColor: theme.background,
8989 borderRadius: 8,
9090 overflow: "hidden",
......@@ -93,19 +93,18 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
9393 ]}
9494 onLayout={handleLayout}
9595 >
96 <View style={{ flex: 1, paddingHorizontal: 10, paddingVertical: 8 }}>
96 <View className="flex-1 px-2.5 py-2">
9797 <Text
98 className="text-2xs"
98 className="pb-2 text-2xs"
9999 style={{
100100 color: theme.mutedForeground,
101 paddingBottom: 8,
102101 }}
103102 >
104103 {statusLabel}
105104 </Text>
106105 <ScrollView
107 style={{ flex: 1 }}
108 contentContainerStyle={{ paddingBottom: 12 }}
106 className="flex-1"
107 contentContainerClassName="pb-3"
109108 showsVerticalScrollIndicator={false}
110109 >
111110 <Text
......@@ -122,13 +121,9 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
122121 </ScrollView>
123122 </View>
124123 <View
124 className="flex-row items-center gap-2 border-t p-2"
125125 style={{
126 borderTopWidth: 1,
127126 borderTopColor: theme.border,
128 flexDirection: "row",
129 alignItems: "center",
130 gap: 8,
131 padding: 8,
132127 }}
133128 >
134129 <TextInput
......@@ -165,13 +160,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
165160 })}
166161 onPress={() => props.onInput("\u0003")}
167162 >
168 <Text
169 className="text-2xs"
170 style={{
171 color: theme.foreground,
172 fontFamily: "DMSans_700Bold",
173 }}
174 >
163 <Text className="text-2xs font-t3-bold" style={{ color: theme.foreground }}>
175164 Ctrl-C
176165 </Text>
177166 </Pressable>
apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx+3-3
......@@ -929,7 +929,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
929929 </NativeHeaderToolbar>
930930 ) : null}
931931
932 <View style={{ flex: 1, backgroundColor: terminalTheme.background }}>
932 <View className="flex-1" style={{ backgroundColor: terminalTheme.background }}>
933933 {!isEnvironmentReady ? (
934934 <EnvironmentConnectionNotice
935935 environmentLabel={
......@@ -949,7 +949,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
949949 />
950950 ) : (
951951 <>
952 <View style={{ flex: 1, paddingBottom: terminalBottomInset }}>
952 <View className="flex-1" style={{ paddingBottom: terminalBottomInset }}>
953953 <TerminalSurface
954954 buffer={terminalSurfaceBuffer}
955955 fontSize={fontSize}
......@@ -968,10 +968,10 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
968968 offset={{ closed: 0, opened: 0 }}
969969 >
970970 <View
971 className="border-t"
971972 style={{
972973 backgroundColor: terminalTheme.background,
973974 borderTopColor: terminalTheme.border,
974 borderTopWidth: 1,
975975 minHeight: TERMINAL_ACCESSORY_HEIGHT,
976976 }}
977977 >
apps/mobile/src/features/threads/ComposerCommandPopover.tsx+6-21
......@@ -154,23 +154,11 @@ const CommandRow = memo(function CommandRow(props: {
154154 ) : iconName ? (
155155 <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" />
156156 ) : null}
157 <Text
158 className="text-base font-t3-medium text-foreground"
159 numberOfLines={1}
160 style={{ flexShrink: 0 }}
161 >
157 <Text className="shrink-0 text-base font-t3-medium text-foreground" numberOfLines={1}>
162158 {props.item.label}
163159 </Text>
164160 {props.item.description ? (
165 <Text
166 className="text-xs"
167 numberOfLines={1}
168 style={{
169 flex: 1,
170 minWidth: 0,
171 color: "#a1a1aa",
172 }}
173 >
161 <Text className="min-w-0 flex-1 text-xs text-zinc-400" numberOfLines={1}>
174162 {props.item.description}
175163 </Text>
176164 ) : null}
......@@ -187,18 +175,15 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(
187175 return (
188176 <PopoverSurface isDarkMode={isDarkMode}>
189177 {label ? (
190 <View style={{ paddingHorizontal: 14, paddingTop: 10, paddingBottom: 4 }}>
191 <Text
192 className="text-3xs font-t3-bold text-foreground-muted"
193 style={{ letterSpacing: 0.8, textTransform: "uppercase" }}
194 >
178 <View className="px-3.5 pt-2.5 pb-1">
179 <Text className="text-3xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
195180 {label}
196181 </Text>
197182 </View>
198183 ) : null}
199184 {props.items.length > 0 ? (
200185 <ScrollView
201 style={{ maxHeight: 180 }}
186 className="max-h-[180px]"
202187 keyboardShouldPersistTaps="always"
203188 showsVerticalScrollIndicator={false}
204189 >
......@@ -212,7 +197,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(
212197 ))}
213198 </ScrollView>
214199 ) : (
215 <View style={{ paddingHorizontal: 14, paddingVertical: 10 }}>
200 <View className="px-3.5 py-2.5">
216201 <Text className="text-xs text-foreground-tertiary">
217202 {emptyText(props.triggerKind, props.isLoading)}
218203 </Text>
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+2-1
......@@ -47,7 +47,8 @@ export function GitActionProgressOverlay(props: {
4747 <Animated.View
4848 entering={FadeIn.duration(200)}
4949 exiting={FadeOut.duration(150)}
50 style={{ top: insets.top + 48, left: 12, right: 12, position: "absolute", zIndex: 100 }}
50 className="absolute inset-x-3 z-[100]"
51 style={{ top: insets.top + 48 }}
5152 pointerEvents="box-none"
5253 >
5354 <Pressable onPress={handlePress}>
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+4-12
......@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
44import { Alert, InteractionManager, View, useColorScheme } from "react-native";
55import { KeyboardAvoidingView, useKeyboardState } from "react-native-keyboard-controller";
66import { useSafeAreaInsets } from "react-native-safe-area-context";
7import { useThemeColor } from "../../lib/useThemeColor";
87
98import { EnvironmentId } from "@t3tools/contracts";
109import {
......@@ -112,7 +111,6 @@ export function NewTaskDraftScreen(props: {
112111 };
113112 }, [props.pendingTaskId, cancelEditingPendingTask]);
114113
115 const borderColor = useThemeColor("--color-border");
116114 const headlineText = useScaledTextRole("headline");
117115 const sheetFadeOpaque = colorScheme === "dark" ? "rgba(14,14,14,0.98)" : "rgba(242,242,247,0.98)";
118116 const sheetFadeTransparent = colorScheme === "dark" ? "rgba(14,14,14,0)" : "rgba(242,242,247,0)";
......@@ -600,8 +598,8 @@ export function NewTaskDraftScreen(props: {
600598 <View className="flex-1 bg-sheet">
601599 <NativeStackScreenOptions options={{ title: selectedProject.title }} />
602600
603 <KeyboardAvoidingView automaticOffset behavior="padding" style={{ flex: 1 }}>
604 <View style={{ flex: 1, minHeight: 0, paddingHorizontal: 20, paddingTop: 8 }}>
601 <KeyboardAvoidingView automaticOffset behavior="padding" className="flex-1">
602 <View className="min-h-0 flex-1 px-5 pt-2">
605603 <ComposerEditor
606604 ref={promptInputRef}
607605 autoFocus
......@@ -617,15 +615,9 @@ export function NewTaskDraftScreen(props: {
617615 />
618616 </View>
619617
620 <View
621 style={{
622 borderTopWidth: 1,
623 borderTopColor: borderColor,
624 paddingBottom: controlsBottomPadding,
625 }}
626 >
618 <View className="border-t border-border" style={{ paddingBottom: controlsBottomPadding }}>
627619 {flow.attachments.length > 0 ? (
628 <View style={{ paddingHorizontal: 16, paddingTop: 12 }}>
620 <View className="px-4 pt-3">
629621 <ComposerAttachmentStrip
630622 attachments={flow.attachments}
631623 onRemove={flow.removeAttachment}
apps/mobile/src/features/threads/NewTaskRouteScreen.tsx+8-13
......@@ -6,6 +6,7 @@ import { useMemo } from "react";
66import { ActivityIndicator, Pressable, ScrollView, View } from "react-native";
77import { useSafeAreaInsets } from "react-native-safe-area-context";
88import { useThemeColor } from "../../lib/useThemeColor";
9import { cn } from "../../lib/cn";
910
1011import { AppText as Text } from "../../components/AppText";
1112import { ProjectFavicon } from "../../components/ProjectFavicon";
......@@ -79,7 +80,6 @@ export function NewTaskRouteScreen() {
7980 const insets = useSafeAreaInsets();
8081 const chevronColor = useThemeColor("--color-chevron");
8182 const accentColor = useThemeColor("--color-icon-muted");
82 const borderSubtleColor = useThemeColor("--color-border-subtle");
8383 const repositoryGroups = useMemo(
8484 () => groupProjectsByRepository({ projects, threads }),
8585 [projects, threads],
......@@ -130,7 +130,7 @@ export function NewTaskRouteScreen() {
130130 <ScrollView
131131 contentInsetAdjustmentBehavior="automatic"
132132 showsVerticalScrollIndicator={false}
133 style={{ flex: 1 }}
133 className="flex-1"
134134 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
135135 contentContainerStyle={{
136136 paddingHorizontal: 20,
......@@ -175,7 +175,6 @@ export function NewTaskRouteScreen() {
175175 return (
176176 <Pressable
177177 key={item.key}
178 className="bg-card"
179178 onPress={() =>
180179 navigation.navigate("NewTaskSheet", {
181180 screen: "NewTaskDraft",
......@@ -186,16 +185,12 @@ export function NewTaskRouteScreen() {
186185 },
187186 })
188187 }
189 style={{
190 paddingHorizontal: 16,
191 paddingVertical: 14,
192 borderTopWidth: isFirst ? 0 : 1,
193 borderTopColor: borderSubtleColor,
194 borderTopLeftRadius: isFirst ? 24 : 0,
195 borderTopRightRadius: isFirst ? 24 : 0,
196 borderBottomLeftRadius: isLast ? 24 : 0,
197 borderBottomRightRadius: isLast ? 24 : 0,
198 }}
188 className={cn(
189 "bg-card px-4 py-3.5",
190 !isFirst && "border-t border-border-subtle",
191 isFirst && "rounded-t-[24px]",
192 isLast && "rounded-b-[24px]",
193 )}
199194 >
200195 <View className="flex-row items-center justify-between gap-3">
201196 <View className="h-7 w-7 items-center justify-center">
apps/mobile/src/features/threads/ThreadComposer.tsx+10-34
......@@ -688,9 +688,9 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
688688
689689 return (
690690 <Animated.View
691 className="px-4"
691692 layout={COMPOSER_LAYOUT_TRANSITION}
692693 style={{
693 paddingHorizontal: 16,
694694 paddingTop: isExpanded ? 8 : 6,
695695 paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6),
696696 experimental_backgroundImage: isDarkMode
......@@ -699,21 +699,12 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
699699 }}
700700 >
701701 <Animated.View
702 className="w-full"
702 className="relative w-full self-center"
703703 layout={COMPOSER_LAYOUT_TRANSITION}
704 style={{ alignSelf: "center", maxWidth: props.contentMaxWidth, position: "relative" }}
704 style={{ maxWidth: props.contentMaxWidth }}
705705 >
706706 {composerTrigger && composerMenuItems.length > 0 ? (
707 <View
708 style={{
709 position: "absolute",
710 bottom: "100%",
711 left: 0,
712 right: 0,
713 marginBottom: 8,
714 zIndex: 10,
715 }}
716 >
707 <View className="absolute inset-x-0 bottom-full z-10 mb-2">
717708 <ComposerCommandPopover
718709 items={composerMenuItems}
719710 triggerKind={composerTrigger.kind}
......@@ -754,9 +745,9 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
754745 {/* Attachment strip — inside the card, above the text input */}
755746 {isExpanded ? (
756747 <Animated.View
748 className={props.draftAttachments.length > 0 ? "pb-2.5" : undefined}
757749 entering={FadeIn.duration(160)}
758750 exiting={FadeOut.duration(120)}
759 style={{ paddingBottom: props.draftAttachments.length > 0 ? 10 : 0 }}
760751 >
761752 <ComposerAttachmentStrip
762753 attachments={props.draftAttachments}
......@@ -766,7 +757,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
766757 </Animated.View>
767758 ) : null}
768759
769 <View style={isExpanded ? undefined : { flex: 1, minWidth: 0 }}>
760 <View className={isExpanded ? undefined : "min-w-0 flex-1"}>
770761 <ComposerEditor
771762 ref={inputRef}
772763 multiline
......@@ -797,37 +788,22 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
797788 textStyle={{
798789 ...bodyText,
799790 color: foregroundColor,
800 fontFamily: "DMSans_400Regular",
801791 }}
802792 />
803793 </View>
804794 {!isExpanded && props.draftAttachments.length > 0 ? (
805 <View style={{ flexDirection: "row", gap: 4, paddingLeft: 4 }}>
795 <View className="flex-row gap-1 pl-1">
806796 {props.draftAttachments.slice(0, 3).map((image) => (
807797 <Pressable key={image.id} onPress={() => onPressImage(image.previewUri)}>
808798 <Image
809799 source={{ uri: image.previewUri }}
810 className="bg-subtle"
811 style={{
812 width: 30,
813 height: 30,
814 borderRadius: 8,
815 }}
800 className="size-[30px] rounded-lg bg-subtle"
816801 resizeMode="cover"
817802 />
818803 </Pressable>
819804 ))}
820805 {props.draftAttachments.length > 3 ? (
821 <View
822 className="bg-subtle-strong"
823 style={{
824 width: 30,
825 height: 30,
826 borderRadius: 8,
827 alignItems: "center",
828 justifyContent: "center",
829 }}
830 >
806 <View className="size-[30px] items-center justify-center rounded-lg bg-subtle-strong">
831807 <Text className="text-foreground-muted text-2xs font-t3-bold">
832808 +{props.draftAttachments.length - 3}
833809 </Text>
......@@ -910,7 +886,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
910886 {/* Queue count */}
911887 {props.queueCount > 0 ? (
912888 <Animated.View entering={FadeIn.duration(180)} exiting={FadeOut.duration(120)}>
913 <Text className="text-xs text-foreground-muted" style={{ paddingTop: 8 }}>
889 <Text className="pt-2 text-xs text-foreground-muted">
914890 {props.queueCount} queued message{props.queueCount === 1 ? "" : "s"} will send
915891 automatically.
916892 </Text>
apps/mobile/src/features/threads/ThreadDetailScreen.tsx+7-8
......@@ -193,10 +193,9 @@ const WorkingDurationPill = memo(function WorkingDurationPill(props: {
193193
194194 return (
195195 <Animated.View
196 className="px-4 pb-2 pt-2"
196 className="shrink-0 px-4 pb-2 pt-2"
197197 entering={FadeInDown.duration(200)}
198198 exiting={FadeOut.duration(140)}
199 style={{ flexShrink: 0 }}
200199 >
201200 <View className="self-start rounded-full border border-neutral-200/80 bg-neutral-50/90 px-3 py-2 dark:border-white/[0.08] dark:bg-white/[0.04]">
202201 <View className="flex-row items-center gap-2">
......@@ -390,7 +389,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
390389 <View className="flex-1">
391390 {showContent ? (
392391 <View
393 style={{ flex: 1 }}
392 className="flex-1"
394393 onTouchStart={handleFeedTouchStart}
395394 onTouchMove={handleFeedTouchMove}
396395 onTouchEnd={handleFeedTouchEnd}
......@@ -419,7 +418,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
419418 />
420419 </View>
421420 ) : (
422 <View style={{ flex: 1 }} />
421 <View className="flex-1" />
423422 )}
424423
425424 {/* Floating composer — sticks to keyboard via KeyboardStickyView */}
......@@ -431,10 +430,11 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
431430 {/* No paddingTop here: the overlay's measured height becomes the
432431 list's bottom inset, so any padding above the pill/composer
433432 pushes the resting content floor up by the same amount. */}
434 <View ref={composerOverlayRef} onLayout={onComposerLayout} style={{ width: "100%" }}>
433 <View ref={composerOverlayRef} onLayout={onComposerLayout} className="w-full">
435434 <Animated.View
435 className="w-full self-center"
436436 layout={LinearTransition.duration(220)}
437 style={{ alignSelf: "center", maxWidth: contentMaxWidth, width: "100%" }}
437 style={{ maxWidth: contentMaxWidth }}
438438 >
439439 {props.activeWorkStartedAt ? (
440440 <WorkingDurationPill startedAt={props.activeWorkStartedAt} />
......@@ -442,10 +442,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
442442
443443 {props.activePendingApproval || props.activePendingUserInput ? (
444444 <Animated.View
445 className="gap-3 px-4 pb-3"
445 className="shrink-0 gap-3 px-4 pb-3"
446446 entering={FadeInDown.duration(220)}
447447 exiting={FadeOut.duration(140)}
448 style={{ flexShrink: 0 }}
449448 >
450449 {props.activePendingApproval ? (
451450 <PendingApprovalCard
apps/mobile/src/features/threads/ThreadFeed.tsx+44-53
......@@ -52,6 +52,7 @@ import Animated, {
5252 type SharedValue,
5353} from "react-native-reanimated";
5454import { useThemeColor } from "../../lib/useThemeColor";
55import { useFontFamily } from "../../lib/useFontFamily";
5556import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic";
5657import {
5758 hasNativeSelectableMarkdownText,
......@@ -234,10 +235,6 @@ const markdownLinkStyles = StyleSheet.create({
234235 favicon: {
235236 borderRadius: 3,
236237 },
237 file: {
238 fontFamily: "DMSans_700Bold",
239 fontWeight: "700",
240 },
241238});
242239
243240const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {
......@@ -250,12 +247,12 @@ const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {
250247
251248 return (
252249 <NativeText
250 className="font-sans"
253251 onPress={() => {
254252 void Linking.openURL(props.href);
255253 }}
256254 style={{
257255 color: props.color,
258 fontFamily: "DMSans_400Regular",
259256 textDecorationLine: "none",
260257 }}
261258 >
......@@ -314,6 +311,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
314311 );
315312 const colors = MARKDOWN_COLORS[colorScheme === "dark" ? "dark" : "light"];
316313 const inlineSkillForeground = String(useThemeColor("--color-inline-skill-foreground"));
314 const regularFontFamily = useFontFamily("regular");
315 const boldFontFamily = useFontFamily("bold");
317316
318317 return useMemo(() => {
319318 const markdownBodyColor = colors.body;
......@@ -365,8 +364,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
365364 h6: markdownFontSizes.h6,
366365 },
367366 fontFamilies: {
368 regular: "DMSans_400Regular",
369 heading: "DMSans_700Bold",
367 regular: regularFontFamily,
368 heading: boldFontFamily,
370369 mono: "ui-monospace",
371370 },
372371 headingWeight: "700",
......@@ -388,7 +387,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
388387 bold: {
389388 fontWeight: "700",
390389 color: markdownStrongColor,
391 fontFamily: "DMSans_700Bold",
390 fontFamily: boldFontFamily,
392391 },
393392 italic: { fontStyle: "italic" },
394393 link: {
......@@ -404,7 +403,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
404403 marginVertical: 10,
405404 },
406405 heading: {
407 fontFamily: "DMSans_700Bold",
406 fontFamily: boldFontFamily,
408407 color: markdownStrongColor,
409408 marginTop: 18,
410409 marginBottom: 8,
......@@ -428,8 +427,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
428427 if (presentation.kind === "file") {
429428 return (
430429 <NativeText
430 className="font-t3-bold"
431431 onPress={() => onLinkPress(href)}
432 style={[markdownLinkStyles.file, { color: inlineTextColor }]}
432 style={{ color: inlineTextColor }}
433433 >
434434 <Image
435435 source={markdownFileIconSource(presentation.icon)}
......@@ -453,6 +453,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
453453 const linkHref = presentation.href;
454454 return (
455455 <NativeText
456 className="underline"
456457 onPress={
457458 linkHref
458459 ? () => {
......@@ -460,17 +461,14 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
460461 }
461462 : undefined
462463 }
463 style={{
464 color: markdownLinkColor,
465 textDecorationLine: "underline",
466 }}
464 style={{ color: markdownLinkColor }}
467465 >
468466 {children}
469467 </NativeText>
470468 );
471469 },
472470 list: ({ node, Renderer, ordered = false, start = 1 }) => (
473 <View style={{ marginTop: 2, marginBottom: 8 }}>
471 <View className="mt-0.5 mb-2">
474472 {node.children?.map((child, index) => {
475473 const childKey = `${child.type}:${child.beg ?? "unknown"}:${child.end ?? "unknown"}`;
476474 if (child.type === "task_list_item") {
......@@ -479,20 +477,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
479477 );
480478 }
481479 return (
482 <View
483 key={childKey}
484 style={{
485 flexDirection: "row",
486 alignItems: "flex-start",
487 marginBottom: 3,
488 }}
489 >
480 <View className="mb-[3px] flex-row items-start" key={childKey}>
490481 <NativeText
482 className="font-sans"
491483 style={{
492484 width: ordered ? 22 : 12,
493485 marginRight: 5,
494486 color: inlineTextColor,
495 fontFamily: "DMSans_400Regular",
496487 fontSize: markdownFontSizes.m,
497488 lineHeight: markdownFontSizes.bodyLineHeight,
498489 textAlign: ordered ? "right" : "center",
......@@ -500,7 +491,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
500491 >
501492 {ordered ? `${start + index}.` : "•"}
502493 </NativeText>
503 <View style={{ flex: 1, minWidth: 0 }}>
494 <View className="min-w-0 flex-1">
504495 <Renderer node={child} depth={1} inListItem parentIsText={false} />
505496 </View>
506497 </View>
......@@ -512,9 +503,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
512503 const value = content ?? "";
513504 return (
514505 <NativeText
506 className="font-mono"
515507 style={{
516508 color: inlineCodeTextColor,
517 fontFamily: "ui-monospace",
518509 fontSize: markdownFontSizes.codeBlockFontSize,
519510 lineHeight: markdownFontSizes.bodyLineHeight,
520511 }}
......@@ -530,31 +521,24 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
530521 : {}),
531522 code_block: ({ content, language }) => (
532523 <View
524 className="my-3 overflow-hidden rounded-[10px] border"
533525 style={{
534526 backgroundColor: blockBackgroundColor,
535 borderRadius: 10,
536 borderWidth: 1,
537527 borderColor: markdownHrColor,
538 marginVertical: 12,
539 overflow: "hidden",
540528 }}
541529 >
542530 {language ? (
543531 <View
532 className="border-b px-3.5 py-2"
544533 style={{
545 borderBottomWidth: 1,
546534 borderBottomColor: markdownHrColor,
547 paddingHorizontal: 14,
548 paddingVertical: 8,
549535 }}
550536 >
551537 <NativeText
538 className="font-mono uppercase opacity-70"
552539 style={{
553540 color: markdownBodyColor,
554 fontFamily: "ui-monospace",
555541 fontSize: markdownFontSizes.codeBlockFontSize,
556 opacity: 0.7,
557 textTransform: "uppercase",
558542 }}
559543 >
560544 {language}
......@@ -565,13 +549,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
565549 horizontal
566550 showsHorizontalScrollIndicator={false}
567551 bounces={false}
568 contentContainerStyle={{ paddingHorizontal: 14, paddingVertical: 12 }}
552 contentContainerClassName="px-3.5 py-3"
569553 >
570554 <NativeText
571555 selectable
556 className="font-mono"
572557 style={{
573558 color: blockTextColor,
574 fontFamily: "ui-monospace",
575559 fontSize: markdownFontSizes.codeBlockFontSize,
576560 lineHeight: markdownFontSizes.codeBlockLineHeight,
577561 }}
......@@ -601,7 +585,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
601585 bold: {
602586 fontWeight: "700",
603587 color: markdownUserBodyColor,
604 fontFamily: "DMSans_700Bold",
588 fontFamily: boldFontFamily,
605589 },
606590 heading: {
607591 ...baseStyles.heading,
......@@ -655,9 +639,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
655639 fontSize: nativeMarkdownTypography.fontSize,
656640 lineHeight: nativeMarkdownTypography.lineHeight,
657641 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
658 fontFamily: "DMSans_400Regular",
659 headingFontFamily: "DMSans_700Bold",
660 boldFontFamily: "DMSans_700Bold",
642 fontFamily: regularFontFamily,
643 headingFontFamily: boldFontFamily,
644 boldFontFamily,
661645 },
662646 },
663647 assistant: {
......@@ -686,13 +670,21 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
686670 fontSize: nativeMarkdownTypography.fontSize,
687671 lineHeight: nativeMarkdownTypography.lineHeight,
688672 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
689 fontFamily: "DMSans_400Regular",
690 headingFontFamily: "DMSans_700Bold",
691 boldFontFamily: "DMSans_700Bold",
673 fontFamily: regularFontFamily,
674 headingFontFamily: boldFontFamily,
675 boldFontFamily,
692676 },
693677 },
694678 };
695 }, [colors, inlineSkillForeground, markdownFontSizes, nativeMarkdownTypography, onLinkPress]);
679 }, [
680 boldFontFamily,
681 colors,
682 inlineSkillForeground,
683 markdownFontSizes,
684 nativeMarkdownTypography,
685 onLinkPress,
686 regularFontFamily,
687 ]);
696688}
697689
698690function renderFeedEntry(
......@@ -1019,11 +1011,10 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
10191011
10201012 return (
10211013 <View
1022 className="w-full overflow-hidden rounded-[16px] border"
1014 className="w-full overflow-hidden rounded-[16px] border border-continuous"
10231015 style={{
10241016 backgroundColor: props.colors.background,
10251017 borderColor: props.colors.border,
1026 borderCurve: "continuous",
10271018 }}
10281019 >
10291020 <View
......@@ -1031,8 +1022,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
10311022 style={{ borderColor: props.colors.border }}
10321023 >
10331024 <View
1034 className="size-6 items-center justify-center rounded-[7px]"
1035 style={{ backgroundColor: props.colors.mutedBackground, borderCurve: "continuous" }}
1025 className="size-6 items-center justify-center rounded-[7px] border-continuous"
1026 style={{ backgroundColor: props.colors.mutedBackground }}
10361027 >
10371028 <SymbolView
10381029 name="doc.text"
......@@ -1085,9 +1076,9 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
10851076 >
10861077 <NativeText
10871078 selectable
1079 className="font-mono"
10881080 style={{
10891081 color: props.colors.text,
1090 fontFamily: "ui-monospace",
10911082 fontSize: codeSurface.fontSize,
10921083 lineHeight: codeSurface.rowHeight,
10931084 }}
......@@ -1559,8 +1550,8 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
15591550
15601551 return (
15611552 <>
1562 <View style={{ flex: 1 }} onLayout={handleViewportLayout}>
1563 <View style={{ flex: 1 }}>
1553 <View className="flex-1" onLayout={handleViewportLayout}>
1554 <View className="flex-1">
15641555 <KeyboardAwareLegendList
15651556 ref={props.listRef}
15661557 // The empty↔filled key remounts the list when messages first
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+27-107
......@@ -97,8 +97,6 @@ function SidebarHeaderButtonGroup(props: {
9797
9898const SIDEBAR_STICKY_HEADER_HEIGHT = 106;
9999const SIDEBAR_STICKY_HEADER_FADE_HEIGHT = 44;
100const IOS_SEARCH_FILL_DARK = "rgba(118, 118, 128, 0.24)";
101const IOS_SEARCH_FILL_LIGHT = "rgba(118, 118, 128, 0.12)";
102100const SIDEBAR_HEADER_WASH_OPACITY = {
103101 dark: [0.22, 0.14, 0.04],
104102 light: [0.46, 0.3, 0.08],
......@@ -140,15 +138,13 @@ function NativeSidebarContainer(props: ThreadNavigationSidebarProps) {
140138 return (
141139 <View
142140 testID="thread-navigation-sidebar"
143 style={[
144 styles.container,
145 {
146 width: props.width,
147 backgroundColor,
148 borderRightColor: borderColor,
149 borderRightWidth: StyleSheet.hairlineWidth,
150 },
151 ]}
141 className="flex-1"
142 style={{
143 width: props.width,
144 backgroundColor,
145 borderRightColor: borderColor,
146 borderRightWidth: StyleSheet.hairlineWidth,
147 }}
152148 >
153149 <SidebarNavigationShell>
154150 <ThreadNavigationSidebarPane {...props} nativeChrome />
......@@ -338,11 +334,8 @@ function ThreadNavigationSidebarPane(
338334
339335 const backgroundColor = useThemeColor("--color-drawer");
340336 const borderColor = useThemeColor("--color-border");
341 const foregroundColor = useThemeColor("--color-foreground");
342337 const mutedColor = useThemeColor("--color-foreground-muted");
343338 const placeholderColor = useThemeColor("--color-placeholder");
344 const searchBackgroundColor =
345 colorScheme === "dark" ? IOS_SEARCH_FILL_DARK : IOS_SEARCH_FILL_LIGHT;
346339 const headerFadeColor = String(backgroundColor);
347340 const headerWashOpacity = SIDEBAR_HEADER_WASH_OPACITY[colorScheme];
348341 const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null);
......@@ -532,7 +525,7 @@ function ThreadNavigationSidebarPane(
532525 [filterIcon, filterMenu, props.onOpenSettings],
533526 );
534527 const listEmpty = (
535 <Text className="px-2 py-4 text-sm" style={{ color: mutedColor }}>
528 <Text className="px-2 py-4 text-sm text-foreground-muted">
536529 {catalogState.isLoadingConnections
537530 ? "Loading threads…"
538531 : props.searchQuery.trim().length > 0
......@@ -566,7 +559,7 @@ function ThreadNavigationSidebarPane(
566559 unstable_headerRightItems: () => nativeHeaderItems,
567560 }}
568561 />
569 <View style={styles.container}>
562 <View className="flex-1">
570563 <SwipeableScrollGateProvider enabled={swipeEnabled}>
571564 <GestureDetector gesture={sidebarScrollGesture}>
572565 <LegendList
......@@ -596,7 +589,7 @@ function ThreadNavigationSidebarPane(
596589 style={styles.threadList}
597590 ListHeaderComponent={
598591 showsConnectionStatus ? (
599 <View style={styles.connectionStatusNative}>
592 <View className="px-1.5 pt-0.5 pb-2">
600593 <WorkspaceConnectionStatus
601594 onPress={props.onOpenEnvironmentSettings}
602595 state={catalogState}
......@@ -617,17 +610,15 @@ function ThreadNavigationSidebarPane(
617610 return (
618611 <View
619612 testID="thread-navigation-sidebar"
620 style={[
621 styles.container,
622 {
623 width: props.width,
624 backgroundColor,
625 borderRightColor: borderColor,
626 borderRightWidth: StyleSheet.hairlineWidth,
627 },
628 ]}
613 className="flex-1"
614 style={{
615 width: props.width,
616 backgroundColor,
617 borderRightColor: borderColor,
618 borderRightWidth: StyleSheet.hairlineWidth,
619 }}
629620 >
630 <View style={{ flex: 1, paddingBottom: insets.bottom }}>
621 <View className="flex-1" style={{ paddingBottom: insets.bottom }}>
631622 <SwipeableScrollGateProvider enabled={swipeEnabled}>
632623 <GestureDetector gesture={sidebarScrollGesture}>
633624 <LegendList
......@@ -660,25 +651,17 @@ function ThreadNavigationSidebarPane(
660651 </View>
661652
662653 <View
654 className="absolute inset-x-0 top-0 z-[4]"
663655 onLayout={handleStickyHeaderLayout}
664656 pointerEvents="box-none"
665 style={[
666 styles.stickyHeader,
667 {
668 paddingTop: insets.top,
669 },
670 ]}
657 style={{ paddingTop: insets.top }}
671658 >
672659 <View
660 className="absolute inset-x-0 top-0"
673661 pointerEvents="none"
674662 accessibilityElementsHidden
675663 importantForAccessibility="no-hide-descendants"
676 style={[
677 styles.stickyHeaderWash,
678 {
679 height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT,
680 },
681 ]}
664 style={{ height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT }}
682665 >
683666 <Svg width="100%" height="100%">
684667 <Defs>
......@@ -704,12 +687,8 @@ function ThreadNavigationSidebarPane(
704687 <Rect width="100%" height="100%" fill="url(#sidebar-header-wash)" />
705688 </Svg>
706689 </View>
707 <View style={styles.header}>
708 <Text
709 className="flex-1 text-[34px] font-t3-bold"
710 numberOfLines={1}
711 style={{ color: foregroundColor }}
712 >
690 <View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5">
691 <Text className="flex-1 text-[34px] font-t3-bold text-foreground" numberOfLines={1}>
713692 Threads
714693 </Text>
715694 <SidebarHeaderButtonGroup colorScheme={colorScheme}>
......@@ -724,14 +703,7 @@ function ThreadNavigationSidebarPane(
724703 </SidebarHeaderButtonGroup>
725704 </View>
726705
727 <View
728 style={[
729 styles.searchField,
730 {
731 backgroundColor: searchBackgroundColor,
732 },
733 ]}
734 >
706 <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]">
735707 <SymbolView name="magnifyingglass" size={15} tintColor={mutedColor} type="monochrome" />
736708 <TextInput
737709 ref={searchInputRef}
......@@ -743,14 +715,13 @@ function ThreadNavigationSidebarPane(
743715 placeholder="Search"
744716 placeholderTextColor={placeholderColor}
745717 returnKeyType="search"
746 className="text-base"
747 style={[styles.searchInput, { color: foregroundColor }]}
718 className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground"
748719 value={props.searchQuery}
749720 />
750721 </View>
751722
752723 {showsConnectionStatus ? (
753 <View style={styles.connectionStatus}>
724 <View className="px-3.5 pt-2.5">
754725 <WorkspaceConnectionStatus
755726 onPress={props.onOpenEnvironmentSettings}
756727 state={catalogState}
......@@ -764,63 +735,12 @@ function ThreadNavigationSidebarPane(
764735}
765736
766737const styles = StyleSheet.create({
767 container: {
768 flex: 1,
769 },
770 stickyHeader: {
771 left: 0,
772 position: "absolute",
773 right: 0,
774 top: 0,
775 zIndex: 4,
776 },
777 stickyHeaderWash: {
778 left: 0,
779 position: "absolute",
780 right: 0,
781 top: 0,
782 },
783 header: {
784 height: 50,
785 paddingLeft: 20,
786 paddingRight: 8,
787 flexDirection: "row",
788 alignItems: "flex-end",
789 gap: 2,
790 },
791738 headerButtonGroup: {
792739 alignItems: "center",
793740 borderRadius: 22,
794741 flexDirection: "row",
795742 overflow: "hidden",
796743 },
797 connectionStatus: {
798 paddingTop: 10,
799 paddingHorizontal: 14,
800 },
801 connectionStatusNative: {
802 paddingBottom: 8,
803 paddingHorizontal: 6,
804 paddingTop: 2,
805 },
806 searchField: {
807 height: 38,
808 marginTop: 9,
809 marginHorizontal: 16,
810 paddingLeft: 11,
811 paddingRight: 10,
812 borderRadius: 12,
813 flexDirection: "row",
814 alignItems: "center",
815 gap: 6,
816 },
817 searchInput: {
818 flex: 1,
819 height: 34,
820 paddingVertical: 0,
821 paddingHorizontal: 0,
822 fontFamily: "DMSans_400Regular",
823 },
824744 threadList: {
825745 flex: 1,
826746 },
apps/mobile/src/features/threads/git/GitBranchesSheet.tsx+11-45
......@@ -3,9 +3,9 @@ import { useNavigation, type StaticScreenProps } from "@react-navigation/native"
33import { useState } from "react";
44import { Pressable, ScrollView, View } from "react-native";
55import { useSafeAreaInsets } from "react-native-safe-area-context";
6import { useThemeColor } from "../../../lib/useThemeColor";
76
87import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";
8import { cn } from "../../../lib/cn";
99import { useEnvironmentQuery } from "../../../state/query";
1010import { useThreadSelection } from "../../../state/use-thread-selection";
1111import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";
......@@ -27,12 +27,6 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
2727 const gitState = useSelectedThreadGitState();
2828 const gitActions = useSelectedThreadGitActions();
2929
30 const borderColor = useThemeColor("--color-border");
31 const inputBorderColor = useThemeColor("--color-input-border");
32 const inputBg = useThemeColor("--color-input");
33 const foregroundColor = useThemeColor("--color-foreground");
34 const subtleStrongColor = useThemeColor("--color-subtle-strong");
35
3630 const gitStatus = useEnvironmentQuery(
3731 selectedThread !== null && selectedThreadCwd !== null
3832 ? vcsEnvironment.status({
......@@ -73,23 +67,14 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
7367 }}
7468 >
7569 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
76 <Text
77 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
78 style={{ letterSpacing: 1 }}
79 >
70 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
8071 New branch
8172 </Text>
8273 <TextInput
8374 value={newBranchName}
8475 onChangeText={setNewBranchName}
8576 placeholder="feature/mobile-polish"
86 className="rounded-[18px] px-3.5 py-3 font-sans text-base"
87 style={{
88 borderWidth: 1,
89 borderColor: inputBorderColor,
90 backgroundColor: inputBg,
91 color: foregroundColor,
92 }}
77 className="rounded-[18px]"
9378 />
9479 <SheetActionButton
9580 icon="plus"
......@@ -108,35 +93,20 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
10893 </View>
10994
11095 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
111 <Text
112 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
113 style={{ letterSpacing: 1 }}
114 >
96 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
11597 New worktree
11698 </Text>
11799 <TextInput
118100 value={worktreeBaseBranch}
119101 onChangeText={setWorktreeBaseBranch}
120102 placeholder="main"
121 className="rounded-[18px] px-3.5 py-3 font-sans text-base"
122 style={{
123 borderWidth: 1,
124 borderColor: inputBorderColor,
125 backgroundColor: inputBg,
126 color: foregroundColor,
127 }}
103 className="rounded-[18px]"
128104 />
129105 <TextInput
130106 value={worktreeBranchName}
131107 onChangeText={setWorktreeBranchName}
132108 placeholder="feature/mobile-thread"
133 className="rounded-[18px] px-3.5 py-3 font-sans text-base"
134 style={{
135 borderWidth: 1,
136 borderColor: inputBorderColor,
137 backgroundColor: inputBg,
138 color: foregroundColor,
139 }}
109 className="rounded-[18px]"
140110 />
141111 <SheetActionButton
142112 icon="square.split.2x1"
......@@ -158,10 +128,7 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
158128 </View>
159129
160130 <View className="gap-2">
161 <Text
162 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
163 style={{ letterSpacing: 1 }}
164 >
131 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
165132 Existing branches
166133 </Text>
167134 {branchesLoading ? (
......@@ -185,12 +152,11 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
185152 return (
186153 <Pressable
187154 key={branch.name}
188 className="gap-1 rounded-[18px] border px-4 py-3"
155 className={cn(
156 "gap-1 rounded-[18px] border px-4 py-3 disabled:opacity-[0.45]",
157 branch.current ? "border-subtle-strong" : "border-border",
158 )}
189159 disabled={busy || disabled}
190 style={{
191 borderColor: branch.current ? subtleStrongColor : borderColor,
192 opacity: busy || disabled ? 0.45 : 1,
193 }}
194160 onPress={() => {
195161 void gitActions.onCheckoutSelectedThreadBranch(branch.name).then(() => {
196162 navigation.goBack();
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+12-35
......@@ -1,10 +1,10 @@
11import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
22import { useCallback, useState } from "react";
3import { Pressable, ScrollView, View, useColorScheme } from "react-native";
3import { Pressable, ScrollView, View } from "react-native";
44import { useSafeAreaInsets } from "react-native-safe-area-context";
5import { useThemeColor } from "../../../lib/useThemeColor";
65
76import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";
7import { cn } from "../../../lib/cn";
88import { useEnvironmentQuery } from "../../../state/query";
99import { useThreadSelection } from "../../../state/use-thread-selection";
1010import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";
......@@ -21,18 +21,11 @@ type GitCommitSheetProps = StaticScreenProps<{
2121export function GitCommitSheet(_props: GitCommitSheetProps) {
2222 const navigation = useNavigation();
2323 const insets = useSafeAreaInsets();
24 const isDarkMode = useColorScheme() === "dark";
2524 const { selectedThread } = useThreadSelection();
2625 const { selectedThreadCwd } = useSelectedThreadWorktree();
2726 const gitState = useSelectedThreadGitState();
2827 const gitActions = useSelectedThreadGitActions();
2928
30 const borderColor = useThemeColor("--color-border");
31 const borderSubtleColor = useThemeColor("--color-border-subtle");
32 const inputBorderColor = useThemeColor("--color-input-border");
33 const inputBg = useThemeColor("--color-input");
34 const foregroundColor = useThemeColor("--color-foreground");
35
3629 const gitStatus = useEnvironmentQuery(
3730 selectedThread !== null && selectedThreadCwd !== null
3831 ? vcsEnvironment.status({
......@@ -90,10 +83,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
9083 </Text>
9184 </View>
9285 {isDefaultRef ? (
93 <Text
94 className="text-xs leading-normal"
95 style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }}
96 >
86 <Text className="text-xs leading-normal text-amber-700 dark:text-amber-400">
9787 Warning: this is the default branch.
9888 </Text>
9989 ) : null}
......@@ -138,12 +128,8 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
138128 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>
139129 {file.path}
140130 </Text>
141 <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}>
142 +{file.insertions}
143 </Text>
144 <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>
145 -{file.deletions}
146 </Text>
131 <Text className="text-xs font-t3-bold text-emerald-500">+{file.insertions}</Text>
132 <Text className="text-xs font-t3-bold text-rose-500">-{file.deletions}</Text>
147133 </View>
148134 ))}
149135 {selectedFiles.length > selectedFilePreview.length ? (
......@@ -159,10 +145,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
159145 return (
160146 <Pressable
161147 key={file.path}
162 className="rounded-[18px] border px-4 py-3"
163 style={{
164 borderColor: included ? borderColor : borderSubtleColor,
165 }}
148 className={cn(
149 "rounded-[18px] border px-4 py-3",
150 included ? "border-border" : "border-border-subtle",
151 )}
166152 onPress={() => {
167153 setExcludedFiles((current) => {
168154 const next = new Set(current);
......@@ -193,12 +179,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
193179 ) : null}
194180 </View>
195181 <View className="items-end gap-1">
196 <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}>
182 <Text className="text-xs font-t3-bold text-emerald-500">
197183 +{file.insertions}
198184 </Text>
199 <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>
200 -{file.deletions}
201 </Text>
185 <Text className="text-xs font-t3-bold text-rose-500">-{file.deletions}</Text>
202186 </View>
203187 </View>
204188 </Pressable>
......@@ -216,14 +200,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
216200 onChangeText={setDialogCommitMessage}
217201 placeholder="Leave empty to auto-generate"
218202 textAlignVertical="top"
219 className="min-h-[128px] rounded-[20px] px-4 py-3.5 font-sans text-base"
220 style={{
221 minHeight: 128,
222 borderWidth: 1,
223 borderColor: inputBorderColor,
224 backgroundColor: inputBg,
225 color: foregroundColor,
226 }}
203 className="min-h-[128px] rounded-[20px] px-4 py-3.5"
227204 />
228205 </View>
229206
apps/mobile/src/features/threads/git/GitConfirmSheet.tsx+3-9
......@@ -101,13 +101,10 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
101101
102102 return (
103103 <View collapsable={false} className="flex-1 bg-sheet">
104 <View style={{ minHeight: 16, paddingTop: 8 }} />
104 <View className="min-h-4 pt-2" />
105105
106106 <View className="items-center gap-1 px-5 pb-3 pt-4">
107 <Text
108 className="text-xs font-t3-bold uppercase text-foreground-muted"
109 style={{ letterSpacing: 1 }}
110 >
107 <Text className="text-xs font-t3-bold tracking-[1px] uppercase text-foreground-muted">
111108 Confirm
112109 </Text>
113110 <Text className="text-center text-3xl font-t3-bold">
......@@ -118,10 +115,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
118115 </Text>
119116 </View>
120117
121 <View
122 className="gap-3 px-5"
123 style={{ paddingBottom: Math.max(insets.bottom, 18) + 8, paddingTop: 8 }}
124 >
118 <View className="gap-3 px-5 pt-2" style={{ paddingBottom: Math.max(insets.bottom, 18) + 8 }}>
125119 <SheetActionButton
126120 icon="arrow.right.circle"
127121 label={copy?.continueLabel ?? "Continue"}
apps/mobile/src/features/threads/git/GitOverviewSheet.tsx+11-14
......@@ -47,7 +47,6 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
4747 const gitActions = useSelectedThreadGitActions();
4848
4949 const iconColor = useThemeColor("--color-icon");
50 const borderColor = useThemeColor("--color-border");
5150 const foregroundColor = String(useThemeColor("--color-foreground"));
5251 const sheetColor = String(useThemeColor("--color-sheet"));
5352
......@@ -205,9 +204,9 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
205204
206205 const content = (
207206 <ScrollView
207 className="flex-1 bg-screen"
208208 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
209209 showsVerticalScrollIndicator={false}
210 style={{ flex: 1, backgroundColor: sheetColor }}
211210 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
212211 contentContainerStyle={{
213212 paddingHorizontal: isInspector ? 12 : 20,
......@@ -227,9 +226,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
227226 >
228227 {sheetMenuItems.map(({ item, disabledReason }, index) => (
229228 <View key={`${item.id}-${item.label}`}>
230 {index > 0 ? (
231 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />
232 ) : null}
229 {index > 0 ? <View className="ml-12 h-px bg-border" /> : null}
233230 <SheetListRow
234231 icon={menuItemIconName(item.icon)}
235232 title={item.label}
......@@ -241,7 +238,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
241238 ))}
242239 {behindCount > 0 ? (
243240 <>
244 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />
241 <View className="ml-12 h-px bg-border" />
245242 <SheetListRow
246243 icon="arrow.down.circle"
247244 title="Pull latest"
......@@ -251,7 +248,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
251248 />
252249 </>
253250 ) : null}
254 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />
251 <View className="ml-12 h-px bg-border" />
255252 <SheetListRow
256253 icon="text.bubble"
257254 title="Review changes"
......@@ -259,7 +256,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
259256 disabled={busy || !isRepo}
260257 onPress={() => navigation.navigate("ThreadReview", { environmentId, threadId })}
261258 />
262 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />
259 <View className="ml-12 h-px bg-border" />
263260 <SheetListRow
264261 icon="point.topleft.down.curvedto.point.bottomright.up"
265262 title="Branches & worktrees"
......@@ -365,8 +362,11 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
365362 }
366363 >
367364 <Pressable
368 className="absolute right-3 top-4 h-9 w-9 items-center justify-center rounded-full bg-subtle"
369 style={{ zIndex: 1, opacity: busy ? 0.45 : 1 }}
365 className={
366 busy
367 ? "absolute right-3 top-4 z-[1] h-9 w-9 items-center justify-center rounded-full bg-subtle opacity-[0.45]"
368 : "absolute right-3 top-4 z-[1] h-9 w-9 items-center justify-center rounded-full bg-subtle"
369 }
370370 disabled={busy}
371371 onPress={() => void gitActions.refreshSelectedThreadGitStatus()}
372372 >
......@@ -378,10 +378,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
378378 weight="medium"
379379 />
380380 </Pressable>
381 <Text
382 className="text-xs font-t3-bold uppercase text-foreground-muted"
383 style={{ letterSpacing: 1 }}
384 >
381 <Text className="text-xs font-t3-bold tracking-[1px] uppercase text-foreground-muted">
385382 {isInspector ? "Repository" : "Branch"}
386383 </Text>
387384 <Text className={isInspector ? "pr-10 text-xl font-t3-bold" : "text-3xl font-t3-bold"}>
apps/mobile/src/features/threads/git/gitSheetComponents.tsx+21-39
......@@ -3,6 +3,7 @@ import type { ComponentProps } from "react";
33import { Pressable, View } from "react-native";
44import { useThemeColor } from "../../../lib/useThemeColor";
55import { AppText as Text } from "../../../components/AppText";
6import { cn } from "../../../lib/cn";
67
78/* ─── Shared sheet components ──────────────────────────────────────── */
89
......@@ -13,51 +14,36 @@ export function SheetActionButton(props: {
1314 readonly tone?: "primary" | "secondary" | "danger";
1415 readonly onPress: () => void;
1516}) {
16 const primaryBg = useThemeColor("--color-primary");
1717 const primaryFg = useThemeColor("--color-primary-foreground");
18 const dangerBg = useThemeColor("--color-danger");
19 const dangerBorder = useThemeColor("--color-danger-border");
2018 const dangerFg = useThemeColor("--color-danger-foreground");
21 const secondaryBg = useThemeColor("--color-secondary");
22 const secondaryBorder = useThemeColor("--color-secondary-border");
2319 const secondaryFg = useThemeColor("--color-secondary-foreground");
2420
2521 const tone = props.tone ?? "secondary";
26 const colors =
27 tone === "primary"
28 ? {
29 backgroundColor: primaryBg,
30 borderColor: "transparent",
31 textColor: primaryFg,
32 }
33 : tone === "danger"
34 ? {
35 backgroundColor: dangerBg,
36 borderColor: dangerBorder,
37 textColor: dangerFg,
38 }
39 : {
40 backgroundColor: secondaryBg,
41 borderColor: secondaryBorder,
42 textColor: secondaryFg,
43 };
22 const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg;
4423
4524 return (
4625 <Pressable
47 className="min-h-[48px] flex-1 flex-row items-center justify-center gap-2 rounded-[18px] px-4 py-3"
26 className={cn(
27 "min-h-[48px] flex-1 flex-row items-center justify-center gap-2 rounded-[18px] px-4 py-3 disabled:opacity-[0.45]",
28 tone === "primary"
29 ? "bg-primary"
30 : tone === "danger"
31 ? "border border-danger-border bg-danger"
32 : "border border-secondary-border bg-secondary",
33 )}
4834 disabled={props.disabled}
49 style={{
50 backgroundColor: colors.backgroundColor,
51 borderWidth: tone === "primary" ? 0 : 1,
52 borderColor: colors.borderColor,
53 opacity: props.disabled ? 0.45 : 1,
54 }}
5535 onPress={props.onPress}
5636 >
57 <SymbolView name={props.icon} size={16} tintColor={colors.textColor} type="monochrome" />
37 <SymbolView name={props.icon} size={16} tintColor={textColor} type="monochrome" />
5838 <Text
59 className="text-xs font-t3-bold uppercase"
60 style={{ color: colors.textColor, letterSpacing: 0.9 }}
39 className={cn(
40 "text-xs font-t3-bold tracking-[0.9px] uppercase",
41 tone === "primary"
42 ? "text-primary-foreground"
43 : tone === "danger"
44 ? "text-danger-foreground"
45 : "text-secondary-foreground",
46 )}
6147 >
6248 {props.label}
6349 </Text>
......@@ -68,10 +54,7 @@ export function SheetActionButton(props: {
6854export function MetaCard(props: { readonly label: string; readonly value: string }) {
6955 return (
7056 <View className="rounded-[18px] border border-border bg-card px-4 py-3">
71 <Text
72 className="text-foreground-muted text-2xs font-t3-bold uppercase"
73 style={{ letterSpacing: 0.9 }}
74 >
57 <Text className="text-foreground-muted text-2xs font-t3-bold tracking-[0.9px] uppercase">
7558 {props.label}
7659 </Text>
7760 <Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}>
......@@ -93,9 +76,8 @@ export function SheetListRow(props: {
9376
9477 return (
9578 <Pressable
96 className="flex-row items-center gap-3 px-1 py-3"
79 className="flex-row items-center gap-3 px-1 py-3 disabled:opacity-[0.45]"
9780 disabled={props.disabled}
98 style={{ opacity: props.disabled ? 0.45 : 1 }}
9981 onPress={props.onPress}
10082 >
10183 <View className="bg-subtle h-9 w-9 items-center justify-center rounded-full">
apps/mobile/src/features/threads/sidebar-filter-button.tsx+2-15
......@@ -22,16 +22,17 @@ export function SidebarFilterButton(props: {
2222
2323 return (
2424 <Pressable
25 className="h-11 w-[50px] cursor-pointer items-center justify-center rounded-[22px]"
2526 accessibilityLabel={props.accessibilityLabel}
2627 accessibilityRole="button"
2728 hitSlop={4}
2829 style={({ pressed }) => [
29 styles.button,
3030 props.grouped
3131 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }
3232 : {
3333 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,
3434 borderColor,
35 borderWidth: StyleSheet.hairlineWidth,
3536 },
3637 ]}
3738 >
......@@ -39,17 +40,3 @@ export function SidebarFilterButton(props: {
3940 </Pressable>
4041 );
4142}
42
43const styles = StyleSheet.create({
44 button: {
45 // Match the native glass UIBarButtonItem group metrics (~50pt slots,
46 // 44pt bar height, label-colored ~20pt glyphs).
47 width: 50,
48 height: 44,
49 borderRadius: 22,
50 borderWidth: StyleSheet.hairlineWidth,
51 alignItems: "center",
52 justifyContent: "center",
53 cursor: "pointer",
54 },
55});
apps/mobile/src/features/threads/sidebar-header-actions.tsx+3-19
......@@ -24,17 +24,18 @@ function FallbackHeaderButton(props: {
2424
2525 return (
2626 <Pressable
27 className="h-11 w-[50px] items-center justify-center rounded-[22px]"
2728 accessibilityLabel={props.accessibilityLabel}
2829 accessibilityRole="button"
2930 hitSlop={4}
3031 onPress={props.onPress}
3132 style={({ pressed }) => [
32 styles.button,
3333 props.grouped
3434 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }
3535 : {
3636 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,
3737 borderColor,
38 borderWidth: StyleSheet.hairlineWidth,
3839 },
3940 ]}
4041 >
......@@ -45,7 +46,7 @@ function FallbackHeaderButton(props: {
4546
4647export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {
4748 return (
48 <View style={styles.actions}>
49 <View className="flex-row items-center gap-0.5">
4950 <FallbackHeaderButton
5051 accessibilityLabel="Open settings"
5152 grouped={props.grouped}
......@@ -55,20 +56,3 @@ export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {
5556 </View>
5657 );
5758}
58
59const styles = StyleSheet.create({
60 actions: {
61 flexDirection: "row",
62 alignItems: "center",
63 gap: 2,
64 },
65 button: {
66 // Match the native glass UIBarButtonItem group metrics.
67 width: 50,
68 height: 44,
69 borderRadius: 22,
70 borderWidth: StyleSheet.hairlineWidth,
71 alignItems: "center",
72 justifyContent: "center",
73 },
74});
apps/mobile/src/features/threads/thread-inspector-content-stack.tsx+1-1
......@@ -75,7 +75,7 @@ export function ThreadInspectorContentStack(props: {
7575 const Route = props.Route;
7676
7777 return (
78 <View style={{ flex: 1 }}>
78 <View className="flex-1">
7979 <InspectorContentPane
8080 mounted={mountedModes.has("files") || props.mode === "files"}
8181 visible={props.mode === "files"}
apps/mobile/src/features/threads/thread-list-items.tsx+32-49
......@@ -12,6 +12,7 @@ import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSw
1212import { AppText as Text } from "../../components/AppText";
1313import { ControlPillMenu } from "../../components/ControlPill";
1414import { ProjectFavicon } from "../../components/ProjectFavicon";
15import { cn } from "../../lib/cn";
1516import { relativeTime } from "../../lib/time";
1617import { useThemeColor } from "../../lib/useThemeColor";
1718import type { PendingNewTask } from "../../state/use-pending-new-tasks";
......@@ -101,10 +102,9 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
101102 <Text
102103 className={
103104 compact
104 ? "flex-shrink text-base font-t3-bold text-foreground-muted"
105 : "flex-shrink text-sm font-t3-bold text-foreground-muted"
105 ? "flex-shrink text-base font-t3-bold tracking-[0.2px] text-foreground-muted"
106 : "flex-shrink text-sm font-t3-bold tracking-[0.2px] text-foreground-muted"
106107 }
107 style={{ letterSpacing: 0.2 }}
108108 numberOfLines={1}
109109 >
110110 {props.title}
......@@ -236,7 +236,6 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
236236 const compact = props.variant === "compact";
237237 const separatorColor = useThemeColor("--color-separator");
238238 const iconSubtleColor = useThemeColor("--color-icon-subtle");
239 const foregroundColor = useThemeColor("--color-foreground");
240239 const mutedColor = useThemeColor("--color-foreground-muted");
241240 const pressedBackgroundColor = useThemeColor("--color-subtle");
242241
......@@ -254,17 +253,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
254253 );
255254
256255 const statusPill = (
257 <View
258 className="bg-zinc-500/12 dark:bg-zinc-500/16"
259 style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }}
260 >
256 <View className="rounded-full bg-zinc-500/12 px-1.5 py-0.5 dark:bg-zinc-500/16">
261257 <Text className="text-3xs font-t3-bold text-zinc-600 dark:text-zinc-300">Pending</Text>
262258 </View>
263259 );
264260
265261 const subtitleRow =
266262 subtitleParts.length > 0 ? (
267 <View className="flex-row items-center gap-1.5" style={{ marginTop: 1 }}>
263 <View className="mt-px flex-row items-center gap-1.5">
268264 <SymbolView
269265 name="tray.and.arrow.up"
270266 size={10}
......@@ -272,9 +268,12 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
272268 type="monochrome"
273269 />
274270 <Text
275 className={compact ? "text-sm text-foreground-muted" : "text-xs"}
271 className={
272 compact
273 ? "shrink text-sm text-foreground-muted"
274 : "shrink text-xs text-foreground-muted"
275 }
276276 numberOfLines={1}
277 style={compact ? { flexShrink: 1 } : { flexShrink: 1, color: mutedColor }}
278277 >
279278 {subtitleParts.join(" · ")}
280279 </Text>
......@@ -311,12 +310,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
311310 </Text>
312311 <View className="flex-row items-center gap-2">
313312 {statusPill}
314 <Text
315 className="text-base text-foreground-tertiary"
316 style={{ fontVariant: ["tabular-nums"] }}
317 >
318 {timestamp}
319 </Text>
313 <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text>
320314 <SymbolView
321315 name="chevron.right"
322316 size={13}
......@@ -345,22 +339,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
345339 paddingVertical: 10,
346340 })}
347341 >
348 <View style={{ gap: 3 }}>
342 <View className="gap-[3px]">
349343 <View className="flex-row items-center justify-between gap-2">
350 <Text
351 className="flex-1 text-base font-t3-medium"
352 numberOfLines={1}
353 style={{ color: foregroundColor }}
354 >
344 <Text className="flex-1 text-base font-t3-medium text-foreground" numberOfLines={1}>
355345 {pendingTask.title}
356346 </Text>
357347 <View className="flex-row items-center gap-2">
358348 {statusPill}
359 <Text
360 className="text-xs"
361 numberOfLines={1}
362 style={{ color: mutedColor, fontVariant: ["tabular-nums"] }}
363 >
349 <Text className="text-xs tabular-nums text-foreground-muted" numberOfLines={1}>
364350 {timestamp}
365351 </Text>
366352 </View>
......@@ -418,11 +404,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
418404 const iconSubtleColor = useThemeColor("--color-icon-subtle");
419405 const screenColor = useThemeColor("--color-screen");
420406 const drawerColor = useThemeColor("--color-drawer");
421 const foregroundColor = useThemeColor("--color-foreground");
422407 const mutedColor = useThemeColor("--color-foreground-muted");
423408 const pressedBackgroundColor = useThemeColor("--color-subtle");
424409 const selectedBackgroundColor = useThemeColor("--color-user-bubble");
425 const selectedForegroundColor = useThemeColor("--color-user-bubble-foreground");
426410 const selectedMutedColor = useThemeColor("--color-user-bubble-foreground-muted");
427411
428412 const { thread, onSelectThread, onArchiveThread, onDeleteThread } = props;
......@@ -436,7 +420,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
436420 );
437421
438422 const backgroundColor = compact ? screenColor : drawerColor;
439 const effectiveForeground = selected ? selectedForegroundColor : foregroundColor;
440423 const effectiveMuted = selected ? selectedMutedColor : mutedColor;
441424 const effectivePressedBackground = selected ? "rgba(255,255,255,0.16)" : pressedBackgroundColor;
442425 const effectiveStatus =
......@@ -464,10 +447,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
464447 );
465448
466449 const statusPill = effectiveStatus ? (
467 <View
468 className={effectiveStatus.pillClassName}
469 style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }}
470 >
450 <View className={`${effectiveStatus.pillClassName} rounded-full px-1.5 py-0.5`}>
471451 <Text className={`text-3xs font-t3-bold ${effectiveStatus.textClassName}`}>
472452 {effectiveStatus.label}
473453 </Text>
......@@ -476,7 +456,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
476456
477457 const subtitleRow =
478458 subtitleParts.length > 0 || pr !== null ? (
479 <View className="flex-row items-center gap-1.5" style={{ marginTop: 1 }}>
459 <View className="mt-px flex-row items-center gap-1.5">
480460 {subtitleParts.length > 0 ? (
481461 <>
482462 <SymbolView
......@@ -486,9 +466,13 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
486466 type="monochrome"
487467 />
488468 <Text
489 className={compact ? "text-sm text-foreground-muted" : "text-xs"}
469 className={cn(
470 "shrink",
471 compact ? "text-sm text-foreground-muted" : "text-xs",
472 !compact &&
473 (selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted"),
474 )}
490475 numberOfLines={1}
491 style={compact ? { flexShrink: 1 } : { flexShrink: 1, color: effectiveMuted }}
492476 >
493477 {subtitleParts.join(" · ")}
494478 </Text>
......@@ -540,12 +524,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
540524 </Text>
541525 <View className="flex-row items-center gap-2">
542526 {statusPill}
543 <Text
544 className="text-base text-foreground-tertiary"
545 style={{ fontVariant: ["tabular-nums"] }}
546 >
547 {timestamp}
548 </Text>
527 <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text>
549528 <SymbolView
550529 name="chevron.right"
551530 size={13}
......@@ -584,21 +563,25 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
584563 paddingVertical: 10,
585564 })}
586565 >
587 <View style={{ gap: 3 }}>
566 <View className="gap-[3px]">
588567 <View className="flex-row items-center justify-between gap-2">
589568 <Text
590 className="flex-1 text-base font-t3-medium"
569 className={cn(
570 "flex-1 text-base font-t3-medium",
571 selected ? "text-user-bubble-foreground" : "text-foreground",
572 )}
591573 numberOfLines={1}
592 style={{ color: effectiveForeground }}
593574 >
594575 {thread.title}
595576 </Text>
596577 <View className="flex-row items-center gap-2">
597578 {statusPill}
598579 <Text
599 className="text-xs"
580 className={cn(
581 "text-xs tabular-nums",
582 selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted",
583 )}
600584 numberOfLines={1}
601 style={{ color: effectiveMuted, fontVariant: ["tabular-nums"] }}
602585 >
603586 {timestamp}
604587 </Text>
apps/mobile/src/features/threads/thread-work-log.tsx+2-3
......@@ -206,13 +206,12 @@ export function ThreadWorkLog(props: {
206206 nestedScrollEnabled
207207 directionalLockEnabled
208208 showsVerticalScrollIndicator
209 style={{ maxHeight: 240 }}
209 className="max-h-60"
210210 contentContainerStyle={{ paddingRight: 8 }}
211211 >
212212 <Text
213213 selectable
214 className="text-2xs leading-normal text-foreground-muted"
215 style={{ fontFamily: "ui-monospace" }}
214 className="font-mono text-2xs leading-normal text-foreground-muted"
216215 >
217216 {row.fullDetail}
218217 </Text>
apps/mobile/src/lib/useFontFamily.ts created+15
......@@ -0,0 +1,15 @@
1import { useCSSVariable } from "uniwind";
2
3const FONT_FAMILY_VARIABLES = {
4 regular: "--font-sans",
5 medium: "--font-medium",
6 bold: "--font-bold",
7} as const;
8
9/**
10 * Resolves a font family for APIs that require a style object or native prop.
11 * Prefer Uniwind font classes when the target component accepts `className`.
12 */
13export function useFontFamily(weight: keyof typeof FONT_FAMILY_VARIABLES): string {
14 return useCSSVariable(FONT_FAMILY_VARIABLES[weight]) as string;
15}
apps/mobile/src/native/T3ComposerEditor.ios.tsx+3-3
......@@ -15,6 +15,7 @@ import { Image, StyleSheet } from "react-native";
1515import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";
1616import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
1717import { useThemeColor } from "../lib/useThemeColor";
18import { useFontFamily } from "../lib/useFontFamily";
1819import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
1920import {
2021 acknowledgeComposerNativeEvent,
......@@ -117,6 +118,7 @@ export function ComposerEditor({
117118 const skillBorder = useThemeColor("--color-inline-skill-border");
118119 const skillText = useThemeColor("--color-inline-skill-foreground");
119120 const fileTint = useThemeColor("--color-icon-muted");
121 const fontFamily = useFontFamily("regular");
120122
121123 useImperativeHandle(
122124 ref,
......@@ -226,9 +228,7 @@ export function ComposerEditor({
226228 themeJson={themeJson}
227229 placeholder={props.placeholder ?? ""}
228230 fontFamily={
229 typeof resolvedTextStyle.fontFamily === "string"
230 ? resolvedTextStyle.fontFamily
231 : "DMSans_400Regular"
231 typeof resolvedTextStyle.fontFamily === "string" ? resolvedTextStyle.fontFamily : fontFamily
232232 }
233233 fontSize={
234234 typeof resolvedTextStyle.fontSize === "number"
apps/mobile/src/native/T3ComposerEditor.tsx+3-1
......@@ -3,6 +3,7 @@ import { useImperativeHandle, useRef } from "react";
33import { TextInput, type TextInput as RNTextInput } from "react-native";
44
55import { useThemeColor } from "../lib/useThemeColor";
6import { useFontFamily } from "../lib/useFontFamily";
67import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
78import { useNativePaste } from "../lib/useNativePaste";
89import type { ComposerEditorProps } from "./T3ComposerEditor.types";
......@@ -21,6 +22,7 @@ export function ComposerEditor({
2122 const bodyText = useScaledTextRole("body");
2223 const foregroundColor = useThemeColor("--color-foreground");
2324 const placeholderColor = useThemeColor("--color-placeholder");
25 const fontFamily = useFontFamily("regular");
2426 const handlePaste = useNativePaste((uris) => onPasteImages?.(uris));
2527
2628 useImperativeHandle(
......@@ -48,7 +50,7 @@ export function ComposerEditor({
4850 flex: 1,
4951 minHeight: 0,
5052 color: foregroundColor,
51 fontFamily: "DMSans_400Regular",
53 fontFamily,
5254 ...bodyText,
5355 paddingVertical: contentInsetVertical,
5456 },
apps/mobile/src/native/T3KeyboardCommands.tsx+1-1
......@@ -9,5 +9,5 @@ export function T3KeyboardCommands(
99 readonly onCommand: (command: HardwareKeyboardCommand) => void;
1010 }>,
1111) {
12 return <View style={{ flex: 1 }}>{props.children}</View>;
12 return <View className="flex-1">{props.children}</View>;
1313}
pnpm-lock.yaml+21
......@@ -306,6 +306,9 @@ importers:
306306 expo-haptics:
307307 specifier: ~56.0.3
308308 version: 56.0.3(expo@56.0.12)
309 expo-image:
310 specifier: ~56.0.11
311 version: 56.0.11(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
309312 expo-image-picker:
310313 specifier: ~56.0.18
311314 version: 56.0.18(expo@56.0.12)
......@@ -6562,6 +6565,17 @@ packages:
65626565 peerDependencies:
65636566 expo: '*'
65646567
6568 expo-image@56.0.11:
6569 resolution: {integrity: sha512-k2xwxGk14xi6zxmEGAU4rUTb1lK5qf0y0Qb8+Jaggnul0KaJJxcq9qvyDp9iyJBW35cp9isONAUnNtIiooZ/Pw==}
6570 peerDependencies:
6571 expo: '*'
6572 react: '*'
6573 react-native: '*'
6574 react-native-web: '*'
6575 peerDependenciesMeta:
6576 react-native-web:
6577 optional: true
6578
65656579 expo-json-utils@56.0.0:
65666580 resolution: {integrity: sha512-lUqyv9aIGDbYTQ5Nux2FnH2/Dz0w5uJ8Pr080eS0StXi2jr5OmuMNErpzUnpfnYOU55xKotd4AHv68PfV/ludg==}
65676581
......@@ -16695,6 +16709,13 @@ snapshots:
1669516709 expo: 56.0.12(8895228379997a2a064f9644cda56ed0)
1669616710 expo-image-loader: 56.0.3(expo@56.0.12)
1669716711
16712 expo-image@56.0.11(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
16713 dependencies:
16714 expo: 56.0.12(8895228379997a2a064f9644cda56ed0)
16715 react: 19.2.3
16716 react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
16717 sf-symbols-typescript: 2.2.0
16718
1669816719 expo-json-utils@56.0.0: {}
1669916720
1670016721 expo-keep-awake@56.0.3(expo@56.0.12)(react@19.2.3):