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 {...@@ -63,6 +63,16 @@ function resolveAppVariant(value: string | undefined): AppVariant {
6363
64const variant = VARIANT_CONFIG[APP_VARIANT];64const 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
66const config: ExpoConfig = {76const config: ExpoConfig = {
67 name: variant.appName,77 name: variant.appName,
68 slug: "t3-code",78 slug: "t3-code",
...@@ -121,7 +131,30 @@ const config: ExpoConfig = {...@@ -121,7 +131,30 @@ const config: ExpoConfig = {
121 favicon: "./assets/favicon.png",131 favicon: "./assets/favicon.png",
122 },132 },
123 plugins: [133 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 ],
125 "expo-secure-store",158 "expo-secure-store",
126 "expo-sqlite",159 "expo-sqlite",
127 ["@clerk/expo", { theme: "./clerk-theme.json" }],160 ["@clerk/expo", { theme: "./clerk-theme.json" }],
apps/mobile/global.css+9-6
...@@ -51,6 +51,7 @@...@@ -51,6 +51,7 @@
51 /* Inputs */51 /* Inputs */
52 --color-input: #ffffff;52 --color-input: #ffffff;
53 --color-input-border: rgba(0, 0, 0, 0.1);53 --color-input-border: rgba(0, 0, 0, 0.1);
54 --color-sidebar-search: rgba(118, 118, 128, 0.12);
54 --color-placeholder: #a3a3a3;55 --color-placeholder: #a3a3a3;
5556
56 /* Icons */57 /* Icons */
...@@ -144,6 +145,7 @@...@@ -144,6 +145,7 @@
144 /* Inputs */145 /* Inputs */
145 --color-input: #141414;146 --color-input: #141414;
146 --color-input-border: rgba(255, 255, 255, 0.08);147 --color-input-border: rgba(255, 255, 255, 0.08);
148 --color-sidebar-search: rgba(118, 118, 128, 0.24);
147 --color-placeholder: #8e8e93;149 --color-placeholder: #8e8e93;
148150
149 /* Icons */151 /* Icons */
...@@ -194,9 +196,10 @@...@@ -194,9 +196,10 @@
194196
195/* ─── Typography ────────────────────────────────────────────────────── */197/* ─── Typography ────────────────────────────────────────────────────── */
196@theme {198@theme {
197 --font-sans: "DMSans_400Regular";199 /* Keep these native family names aligned with app.config.ts. */
198 --font-medium: "DMSans_500Medium";200 --font-sans: "DMSans-Regular";
199 --font-bold: "DMSans_700Bold";201 --font-medium: "DMSans-Medium";
202 --font-bold: "DMSans-Bold";
200203
201 /* Keep this scale aligned with src/lib/typography.ts for native style props. */204 /* Keep this scale aligned with src/lib/typography.ts for native style props. */
202 --text-3xs: 11px;205 --text-3xs: 11px;
...@@ -221,14 +224,14 @@...@@ -221,14 +224,14 @@
221224
222/* ─── Custom utilities ──────────────────────────────────────────────── */225/* ─── Custom utilities ──────────────────────────────────────────────── */
223@utility font-t3-medium {226@utility font-t3-medium {
224 font-family: "DMSans_500Medium";227 font-family: var(--font-medium);
225}228}
226229
227@utility font-t3-bold {230@utility font-t3-bold {
228 font-family: "DMSans_700Bold";231 font-family: var(--font-bold);
229}232}
230233
231@utility font-t3-extrabold {234@utility font-t3-extrabold {
232 font-family: "DMSans_700Bold";235 font-family: var(--font-bold);
233 font-weight: 800;236 font-weight: 800;
234}237}
apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift+2-2
...@@ -300,7 +300,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {...@@ -300,7 +300,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
300 skillText: "#a21caf",300 skillText: "#a21caf",
301 fileTint: "#737373"301 fileTint: "#737373"
302 )302 )
303 private var fontFamily = "DMSans_400Regular"303 private var fontFamily = "DMSans-Regular"
304 private var fontSize: CGFloat = 14304 private var fontSize: CGFloat = 14
305 private var lineHeight: CGFloat = 20305 private var lineHeight: CGFloat = 20
306 private var contentInsetVertical: CGFloat = 0306 private var contentInsetVertical: CGFloat = 0
...@@ -608,7 +608,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {...@@ -608,7 +608,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
608 iconImage: UIImage?,608 iconImage: UIImage?,
609 style: ComposerChipStyle609 style: ComposerChipStyle
610 ) -> UIImage {610 ) -> 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))
612 ?? UIFont.systemFont(ofSize: max(12, fontSize - 2), weight: .medium)612 ?? UIFont.systemFont(ofSize: max(12, fontSize - 2), weight: .medium)
613 let fallbackIcon = UIImage(613 let fallbackIcon = UIImage(
614 systemName: iconName,614 systemName: iconName,
apps/mobile/package.json+2-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "scripts": {6 "scripts": {
7 "dev": "expo start --clear",7 "dev": "expo start --clear",
8 "dev:client": "APP_VARIANT=development expo start --dev-client --scheme t3code-dev --clear",8 "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'",
10 "start": "expo start",10 "start": "expo start",
11 "start:dev": "APP_VARIANT=development expo start",11 "start:dev": "APP_VARIANT=development expo start",
12 "start:preview": "APP_VARIANT=preview expo start",12 "start:preview": "APP_VARIANT=preview expo start",
...@@ -80,6 +80,7 @@...@@ -80,6 +80,7 @@
80 "expo-font": "~56.0.7",80 "expo-font": "~56.0.7",
81 "expo-glass-effect": "~56.0.4",81 "expo-glass-effect": "~56.0.4",
82 "expo-haptics": "~56.0.3",82 "expo-haptics": "~56.0.3",
83 "expo-image": "~56.0.11",
83 "expo-image-picker": "~56.0.18",84 "expo-image-picker": "~56.0.18",
84 "expo-linking": "~56.0.14",85 "expo-linking": "~56.0.14",
85 "expo-network": "~56.0.5",86 "expo-network": "~56.0.5",
apps/mobile/src/App.tsx+3-14
...@@ -1,11 +1,6 @@...@@ -1,11 +1,6 @@
1import {
2 DMSans_400Regular,
3 DMSans_500Medium,
4 DMSans_700Bold,
5 useFonts,
6} from "@expo-google-fonts/dm-sans";
7import * as Linking from "expo-linking";1import * as Linking from "expo-linking";
8import * as SplashScreen from "expo-splash-screen";2import * as SplashScreen from "expo-splash-screen";
3import { useEffect } from "react";
9import { StatusBar, useColorScheme } from "react-native";4import { StatusBar, useColorScheme } from "react-native";
10import { GestureHandlerRootView } from "react-native-gesture-handler";5import { GestureHandlerRootView } from "react-native-gesture-handler";
11import { KeyboardProvider } from "react-native-keyboard-controller";6import { KeyboardProvider } from "react-native-keyboard-controller";
...@@ -13,7 +8,6 @@ import { SafeAreaProvider } from "react-native-safe-area-context";...@@ -13,7 +8,6 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
13import { createStaticNavigation, DarkTheme, DefaultTheme } from "@react-navigation/native";8import { createStaticNavigation, DarkTheme, DefaultTheme } from "@react-navigation/native";
149
15import { RegistryContext } from "@effect/atom-react";10import { RegistryContext } from "@effect/atom-react";
16import { useEffect } from "react";
17import { CloudAuthProvider } from "./features/cloud/CloudAuthProvider";11import { CloudAuthProvider } from "./features/cloud/CloudAuthProvider";
18import { AppearancePreferencesProvider } from "./features/settings/appearance/AppearancePreferencesProvider";12import { AppearancePreferencesProvider } from "./features/settings/appearance/AppearancePreferencesProvider";
19import { RootStack } from "./Stack";13import { RootStack } from "./Stack";
...@@ -29,17 +23,12 @@ const appLinking = {...@@ -29,17 +23,12 @@ const appLinking = {
29const Navigation = createStaticNavigation(RootStack);23const Navigation = createStaticNavigation(RootStack);
3024
31export default function App() {25export default function App() {
32 const [fontsLoaded] = useFonts({
33 DMSans_400Regular,
34 DMSans_500Medium,
35 DMSans_700Bold,
36 });
37 const colorScheme = useColorScheme();26 const colorScheme = useColorScheme();
38 const statusBarBg = useThemeColor("--color-status-bar");27 const statusBarBg = useThemeColor("--color-status-bar");
3928
40 useEffect(() => {29 useEffect(() => {
41 if (fontsLoaded) SplashScreen.hide();30 SplashScreen.hide();
42 }, [fontsLoaded]);31 }, []);
4332
44 return (33 return (
45 <RegistryContext.Provider value={appAtomRegistry}>34 <RegistryContext.Provider value={appAtomRegistry}>
apps/mobile/src/components/AppText.tsx+3-11
...@@ -4,7 +4,6 @@ import {...@@ -4,7 +4,6 @@ import {
4 type TextInputProps as RNTextInputProps,4 type TextInputProps as RNTextInputProps,
5 type TextProps as RNTextProps,5 type TextProps as RNTextProps,
6} from "react-native";6} from "react-native";
7import { useThemeColor } from "../lib/useThemeColor";
87
9import { cn } from "../lib/cn";8import { cn } from "../lib/cn";
109
...@@ -18,7 +17,7 @@ export function AppText({ className, ...props }: AppTextProps) {...@@ -18,7 +17,7 @@ export function AppText({ className, ...props }: AppTextProps) {
18 return <RNText className={cn("font-sans text-foreground", className)} {...props} />;17 return <RNText className={cn("font-sans text-foreground", className)} {...props} />;
19}18}
2019
21export type AppTextInputProps = RNTextInputProps & {20export type AppTextInputProps = Omit<RNTextInputProps, "placeholderTextColor"> & {
22 readonly className?: string;21 readonly className?: string;
23 readonly ref?: React.Ref<RNTextInput>;22 readonly ref?: React.Ref<RNTextInput>;
24};23};
...@@ -27,14 +26,7 @@ export type AppTextInputProps = RNTextInputProps & {...@@ -27,14 +26,7 @@ export type AppTextInputProps = RNTextInputProps & {
27 * Thin wrapper around RN TextInput with default input styling.26 * Thin wrapper around RN TextInput with default input styling.
28 * Uses Uniwind className — no manual style parsing.27 * Uses Uniwind className — no manual style parsing.
29 */28 */
30export function AppTextInput({29export function AppTextInput({ className, ref, ...props }: AppTextInputProps) {
31 className,
32 placeholderTextColor,
33 ref,
34 ...props
35}: AppTextInputProps) {
36 const placeholderColor = useThemeColor("--color-placeholder");
37
38 return (30 return (
39 <RNTextInput31 <RNTextInput
40 ref={ref}32 ref={ref}
...@@ -42,7 +34,7 @@ export function AppTextInput({...@@ -42,7 +34,7 @@ export function AppTextInput({
42 "min-h-13.5 rounded-2xl border border-input-border bg-input px-3.5 py-3 font-sans text-base text-foreground",34 "min-h-13.5 rounded-2xl border border-input-border bg-input px-3.5 py-3 font-sans text-base text-foreground",
43 className,35 className,
44 )}36 )}
45 placeholderTextColor={placeholderTextColor ?? placeholderColor}37 placeholderTextColorClassName="accent-placeholder"
46 {...props}38 {...props}
47 />39 />
48 );40 );
apps/mobile/src/components/BrandMark.tsx+2-7
...@@ -22,14 +22,9 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab...@@ -22,14 +22,9 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab
22 />22 />
23 <View className="gap-1">23 <View className="gap-1">
24 <View className="flex-row items-center gap-2">24 <View className="flex-row items-center gap-2">
25 <Text className="text-lg font-t3-bold text-foreground" style={{ letterSpacing: -0.4 }}>25 <Text className="text-lg font-t3-bold tracking-[-0.4px] text-foreground">T3 Code</Text>
26 T3 Code
27 </Text>
28 <View className="rounded-full bg-subtle px-2 py-1">26 <View className="rounded-full bg-subtle px-2 py-1">
29 <Text27 <Text className="text-3xs font-t3-bold tracking-[1.1px] uppercase text-foreground-muted">
30 className="text-3xs font-t3-bold uppercase text-foreground-muted"
31 style={{ letterSpacing: 1.1 }}
32 >
33 {stageLabel}28 {stageLabel}
34 </Text>29 </Text>
35 </View>30 </View>
apps/mobile/src/components/ComposerAttachmentStrip.tsx+4-10
...@@ -39,14 +39,14 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {...@@ -39,14 +39,14 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {
39 horizontal39 horizontal
40 showsHorizontalScrollIndicator={false}40 showsHorizontalScrollIndicator={false}
41 keyboardShouldPersistTaps="always"41 keyboardShouldPersistTaps="always"
42 style={{ flexGrow: 0 }}42 className="grow-0"
43 >43 >
44 <View style={{ flexDirection: "row", gap: 10 }}>44 <View className="flex-row gap-2.5">
45 {props.attachments.map((image) => (45 {props.attachments.map((image) => (
46 <View46 <View
47 key={image.id}47 key={image.id}
48 className="relative"
48 style={{49 style={{
49 position: "relative",
50 paddingTop: removeButtonGutter,50 paddingTop: removeButtonGutter,
51 paddingRight: removeButtonGutter,51 paddingRight: removeButtonGutter,
52 }}52 }}
...@@ -66,16 +66,10 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {...@@ -66,16 +66,10 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) {
66 />66 />
67 </Pressable>67 </Pressable>
68 <Pressable68 <Pressable
69 className="absolute h-[22px] w-[22px] items-center justify-center rounded-[11px] bg-black/55"
69 style={{70 style={{
70 position: "absolute",
71 top: removeButtonPlacement === "gutter" ? 0 : 4,71 top: removeButtonPlacement === "gutter" ? 0 : 4,
72 right: removeButtonPlacement === "gutter" ? 0 : 4,72 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",
79 }}73 }}
80 hitSlop={6}74 hitSlop={6}
81 onPress={() => props.onRemove(image.id)}75 onPress={() => props.onRemove(image.id)}
apps/mobile/src/components/ComposerToolbarTrigger.tsx+2-4
...@@ -31,11 +31,9 @@ export function ComposerToolbarRow(props: {...@@ -31,11 +31,9 @@ export function ComposerToolbarRow(props: {
31}) {31}) {
32 return (32 return (
33 <View33 <View
34 className="flex-row items-center gap-1.5"
34 style={[35 style={[
35 {36 {
36 alignItems: "center",
37 flexDirection: "row",
38 gap: 6,
39 paddingBottom: props.paddingBottom ?? 8,37 paddingBottom: props.paddingBottom ?? 8,
40 paddingHorizontal: props.paddingHorizontal ?? 6,38 paddingHorizontal: props.paddingHorizontal ?? 6,
41 paddingTop: props.paddingTop ?? 8,39 paddingTop: props.paddingTop ?? 8,
...@@ -89,7 +87,7 @@ export function ComposerToolbarScroller(props: {...@@ -89,7 +87,7 @@ export function ComposerToolbarScroller(props: {
89 }, []);87 }, []);
9088
91 return (89 return (
92 <View className="min-w-0 flex-1" style={{ position: "relative" }}>90 <View className="relative min-w-0 flex-1">
93 <ScrollView91 <ScrollView
94 horizontal92 horizontal
95 keyboardShouldPersistTaps="always"93 keyboardShouldPersistTaps="always"
apps/mobile/src/components/GlassSafeAreaView.tsx+5-15
...@@ -39,22 +39,12 @@ export function GlassSafeAreaView({...@@ -39,22 +39,12 @@ export function GlassSafeAreaView({
39 style={{ borderRadius: 0, backgroundColor: "transparent" }}39 style={{ borderRadius: 0, backgroundColor: "transparent" }}
40 >40 >
41 <View41 <View
42 style={{42 className="flex-row items-center gap-2.5 px-5 pb-4"
43 flexDirection: "row",43 style={{ paddingTop: headerPaddingTop }}
44 alignItems: "center",
45 paddingHorizontal: 20,
46 paddingTop: headerPaddingTop,
47 paddingBottom: 16,
48 gap: 10,
49 }}
50 >44 >
51 <View style={{ alignItems: "flex-start", justifyContent: "center" }}>{leftSlot}</View>45 <View className="items-start justify-center">{leftSlot}</View>
52 <View46 <View className="flex-1 items-center justify-center overflow-hidden">{centerSlot}</View>
53 style={{ flex: 1, alignItems: "center", justifyContent: "center", overflow: "hidden" }}47 <View className="items-end justify-center">{rightSlot}</View>
54 >
55 {centerSlot}
56 </View>
57 <View style={{ alignItems: "flex-end", justifyContent: "center" }}>{rightSlot}</View>
58 </View>48 </View>
59 </GlassSurface>49 </GlassSurface>
60 </View>50 </View>
apps/mobile/src/components/GlassSurface.tsx+1-1
...@@ -10,7 +10,7 @@ import {...@@ -10,7 +10,7 @@ import {
10} from "react-native";10} from "react-native";
11import { useThemeColor } from "../lib/useThemeColor";11import { useThemeColor } from "../lib/useThemeColor";
1212
13export interface GlassSurfaceProps extends ViewProps {13export interface GlassSurfaceProps extends Omit<ViewProps, "className"> {
14 readonly children: ReactNode;14 readonly children: ReactNode;
15 readonly glassEffectStyle?: "clear" | "regular" | "none";15 readonly glassEffectStyle?: "clear" | "regular" | "none";
16 readonly tintColor?: ColorValue;16 readonly tintColor?: ColorValue;
apps/mobile/src/components/ProjectFavicon.tsx+3-2
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import { SymbolView } from "expo-symbols";1import { SymbolView } from "expo-symbols";
2import { Image } from "expo-image";
2import { useState } from "react";3import { useState } from "react";
3import { Image, View } from "react-native";4import { View } from "react-native";
4import type { EnvironmentId } from "@t3tools/contracts";5import type { EnvironmentId } from "@t3tools/contracts";
5import { useThemeColor } from "../lib/useThemeColor";6import { useThemeColor } from "../lib/useThemeColor";
6import { useAssetUrl } from "../state/assets";7import { useAssetUrl } from "../state/assets";
...@@ -78,7 +79,7 @@ function ProjectFaviconImage(props: {...@@ -78,7 +79,7 @@ function ProjectFaviconImage(props: {
78 borderRadius: props.size * 0.16,79 borderRadius: props.size * 0.16,
79 ...(showImage ? {} : { position: "absolute" as const, opacity: 0 }),80 ...(showImage ? {} : { position: "absolute" as const, opacity: 0 }),
80 }}81 }}
81 resizeMode="contain"82 contentFit="contain"
82 onLoad={() => {83 onLoad={() => {
83 if (props.faviconUrl) loadedFaviconUrls.add(props.faviconUrl);84 if (props.faviconUrl) loadedFaviconUrls.add(props.faviconUrl);
84 setStatus("loaded");85 setStatus("loaded");
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+3-8
...@@ -244,9 +244,8 @@ function ProjectGroupLabel(props: {...@@ -244,9 +244,8 @@ function ProjectGroupLabel(props: {
244 workspaceRoot={props.project.workspaceRoot}244 workspaceRoot={props.project.workspaceRoot}
245 />245 />
246 <Text246 <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"
248 numberOfLines={1}248 numberOfLines={1}
249 style={{ letterSpacing: 0.5 }}
250 >249 >
251 {props.project.title}250 {props.project.title}
252 </Text>251 </Text>
...@@ -331,10 +330,7 @@ function ArchivedThreadRow(props: {...@@ -331,10 +330,7 @@ function ArchivedThreadRow(props: {
331 >330 >
332 {props.thread.title}331 {props.thread.title}
333 </Text>332 </Text>
334 <Text333 <Text className="min-w-[30px] text-right text-xs tabular-nums text-foreground-tertiary">
335 className="text-xs text-foreground-tertiary"
336 style={{ fontVariant: ["tabular-nums"], minWidth: 30, textAlign: "right" }}
337 >
338 {timestamp}334 {timestamp}
339 </Text>335 </Text>
340 </View>336 </View>
...@@ -347,9 +343,8 @@ function ArchivedThreadRow(props: {...@@ -347,9 +343,8 @@ function ArchivedThreadRow(props: {
347 type="monochrome"343 type="monochrome"
348 />344 />
349 <Text345 <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"
351 numberOfLines={1}347 numberOfLines={1}
352 style={{ fontFamily: "monospace" }}
353 >348 >
354 {subtitle.join(" · ")}349 {subtitle.join(" · ")}
355 </Text>350 </Text>
apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx+15-70
...@@ -1,13 +1,12 @@...@@ -1,13 +1,12 @@
1import { useWaitlist } from "@clerk/expo";1import { useWaitlist } from "@clerk/expo";
2import { ActivityIndicator, Pressable, StyleSheet, Text, TextInput, View } from "react-native";2import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native";
3import { useState } from "react";3import { useState } from "react";
44
5import { useThemeColor } from "../../lib/useThemeColor";5import { cn } from "../../lib/cn";
6import { CloudWaitlistJoinRejectedError, joinCloudWaitlist } from "./cloudWaitlistJoin";6import { CloudWaitlistJoinRejectedError, joinCloudWaitlist } from "./cloudWaitlistJoin";
77
8export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {8export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {
9 const { errors, fetchStatus, waitlist } = useWaitlist();9 const { errors, fetchStatus, waitlist } = useWaitlist();
10 const colors = useCloudWaitlistColors();
11 const [emailAddress, setEmailAddress] = useState("");10 const [emailAddress, setEmailAddress] = useState("");
12 const [requestError, setRequestError] = useState<string | null>(null);11 const [requestError, setRequestError] = useState<string | null>(null);
13 const isSubmitting = fetchStatus === "fetching";12 const isSubmitting = fetchStatus === "fetching";
...@@ -34,7 +33,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }...@@ -34,7 +33,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
3433
35 if (waitlist.id) {34 if (waitlist.id) {
36 return (35 return (
37 <View style={styles.content}>36 <View className="gap-[18px]">
38 <Text className="text-center font-t3-bold text-xl text-foreground">37 <Text className="text-center font-t3-bold text-xl text-foreground">
39 You are on the waitlist38 You are on the waitlist
40 </Text>39 </Text>
...@@ -47,19 +46,22 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }...@@ -47,19 +46,22 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
47 }46 }
4847
49 return (48 return (
50 <View style={styles.content}>49 <View className="gap-[18px]">
51 <Text className="font-sans text-base text-foreground-secondary">50 <Text className="font-sans text-base text-foreground-secondary">
52 Enter your email and we will let you know when access is ready.51 Enter your email and we will let you know when access is ready.
53 </Text>52 </Text>
5453
55 <View style={styles.field}>54 <View className="gap-2">
56 <Text className="font-t3-bold text-sm text-foreground-secondary">Email address</Text>55 <Text className="font-t3-bold text-sm text-foreground-secondary">Email address</Text>
57 <TextInput56 <TextInput
58 accessibilityLabel="Email address"57 accessibilityLabel="Email address"
59 autoCapitalize="none"58 autoCapitalize="none"
60 autoComplete="email"59 autoComplete="email"
61 autoCorrect={false}60 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 )}
63 keyboardType="email-address"65 keyboardType="email-address"
64 onChangeText={(value) => {66 onChangeText={(value) => {
65 setEmailAddress(value);67 setEmailAddress(value);
...@@ -67,16 +69,8 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }...@@ -67,16 +69,8 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
67 }}69 }}
68 onSubmitEditing={() => void joinWaitlist()}70 onSubmitEditing={() => void joinWaitlist()}
69 placeholder="Enter your email address"71 placeholder="Enter your email address"
70 placeholderTextColor={colors.placeholder}72 placeholderTextColorClassName="accent-placeholder"
71 returnKeyType="join"73 returnKeyType="join"
72 style={[
73 styles.input,
74 {
75 backgroundColor: colors.input,
76 borderColor:
77 fieldError || requestError ? colors.dangerForeground : colors.inputBorder,
78 },
79 ]}
80 textContentType="emailAddress"74 textContentType="emailAddress"
81 value={emailAddress}75 value={emailAddress}
82 />76 />
...@@ -99,15 +93,11 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }...@@ -99,15 +93,11 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
99 }}93 }}
100 disabled={isSubmitting || emailAddress.trim().length === 0}94 disabled={isSubmitting || emailAddress.trim().length === 0}
101 onPress={() => void joinWaitlist()}95 onPress={() => void joinWaitlist()}
102 style={[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]"
103 styles.primaryButton,
104 {
105 backgroundColor: colors.primary,
106 opacity: isSubmitting || emailAddress.trim().length === 0 ? 0.45 : 1,
107 },
108 ]}
109 >97 >
110 {isSubmitting ? <ActivityIndicator color={colors.primaryForeground} size="small" /> : null}98 {isSubmitting ? (
99 <ActivityIndicator colorClassName="accent-primary-foreground" size="small" />
100 ) : null}
111 <Text className="font-t3-bold text-base text-primary-foreground">101 <Text className="font-t3-bold text-base text-primary-foreground">
112 {isSubmitting ? "Joining" : "Join the waitlist"}102 {isSubmitting ? "Joining" : "Join the waitlist"}
113 </Text>103 </Text>
...@@ -120,7 +110,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }...@@ -120,7 +110,7 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }
120110
121function SignInAction(props: { readonly onPress: () => void }) {111function SignInAction(props: { readonly onPress: () => void }) {
122 return (112 return (
123 <View style={styles.signInRow}>113 <View className="flex-row items-center justify-center gap-1 pt-1">
124 <Text className="font-sans text-base text-foreground-secondary">Already have access?</Text>114 <Text className="font-sans text-base text-foreground-secondary">Already have access?</Text>
125 <Pressable accessibilityRole="button" hitSlop={8} onPress={props.onPress}>115 <Pressable accessibilityRole="button" hitSlop={8} onPress={props.onPress}>
126 <Text className="font-t3-bold text-base text-foreground">Sign in</Text>116 <Text className="font-t3-bold text-base text-foreground">Sign in</Text>
...@@ -128,48 +118,3 @@ function SignInAction(props: { readonly onPress: () => void }) {...@@ -128,48 +118,3 @@ function SignInAction(props: { readonly onPress: () => void }) {
128 </View>118 </View>
129 );119 );
130}120}
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() {...@@ -88,7 +88,7 @@ function ConfiguredConnectOnboardingRouteScreen() {
88 alwaysBounceVertical88 alwaysBounceVertical
89 contentInsetAdjustmentBehavior="automatic"89 contentInsetAdjustmentBehavior="automatic"
90 showsVerticalScrollIndicator={false}90 showsVerticalScrollIndicator={false}
91 style={{ flex: 1 }}91 className="flex-1"
92 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}92 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
93 contentContainerStyle={{93 contentContainerStyle={{
94 gap: 16,94 gap: 16,
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+2-4
...@@ -297,9 +297,8 @@ function CloudEnvironmentRowShell(props: {...@@ -297,9 +297,8 @@ function CloudEnvironmentRowShell(props: {
297 {props.connectionError ? (297 {props.connectionError ? (
298 <Text298 <Text
299 aria-hidden299 aria-hidden
300 className={cn("absolute left-0 right-0 text-xs", statusClassName)}
301 onTextLayout={onMeasuredErrorTextLayout}300 onTextLayout={onMeasuredErrorTextLayout}
302 style={{ opacity: 0, zIndex: -1 }}301 className={cn("absolute inset-x-0 -z-[1] text-xs opacity-0", statusClassName)}
303 >302 >
304 {measuredErrorText}303 {measuredErrorText}
305 </Text>304 </Text>
...@@ -321,7 +320,7 @@ function CloudEnvironmentRowShell(props: {...@@ -321,7 +320,7 @@ function CloudEnvironmentRowShell(props: {
321 <Text320 <Text
322 accessibilityHint="Copies the trace ID"321 accessibilityHint="Copies the trace ID"
323 accessibilityRole="button"322 accessibilityRole="button"
324 className={cn("text-xs underline", statusClassName)}323 className={cn("text-xs underline decoration-dotted", statusClassName)}
325 onLongPress={(event) => {324 onLongPress={(event) => {
326 event.stopPropagation();325 event.stopPropagation();
327 copyTextWithHaptic(errorTraceId, { target: "connection-trace-id" });326 copyTextWithHaptic(errorTraceId, { target: "connection-trace-id" });
...@@ -329,7 +328,6 @@ function CloudEnvironmentRowShell(props: {...@@ -329,7 +328,6 @@ function CloudEnvironmentRowShell(props: {
329 onPress={(event) => {328 onPress={(event) => {
330 event.stopPropagation();329 event.stopPropagation();
331 }}330 }}
332 style={{ textDecorationStyle: "dotted" }}
333 >331 >
334 {errorTraceId}332 {errorTraceId}
335 </Text>333 </Text>
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+4-17
...@@ -38,7 +38,6 @@ export function ConnectionEnvironmentRow(props: {...@@ -38,7 +38,6 @@ export function ConnectionEnvironmentRow(props: {
38 const [url, setUrl] = useState(props.environment.displayUrl);38 const [url, setUrl] = useState(props.environment.displayUrl);
3939
40 const mutedColor = useThemeColor("--color-icon-subtle");40 const mutedColor = useThemeColor("--color-icon-subtle");
41 const placeholderColor = useThemeColor("--color-placeholder");
42 const primaryFg = useThemeColor("--color-primary-foreground");41 const primaryFg = useThemeColor("--color-primary-foreground");
43 const dangerFg = useThemeColor("--color-danger-foreground");42 const dangerFg = useThemeColor("--color-danger-foreground");
44 const statusLabel = connectionStatusLabel(props.environment);43 const statusLabel = connectionStatusLabel(props.environment);
...@@ -98,7 +97,7 @@ export function ConnectionEnvironmentRow(props: {...@@ -98,7 +97,7 @@ export function ConnectionEnvironmentRow(props: {
98 <Text97 <Text
99 accessibilityHint="Copies the trace ID"98 accessibilityHint="Copies the trace ID"
100 accessibilityRole="button"99 accessibilityRole="button"
101 className="underline"100 className="underline decoration-dotted"
102 onLongPress={(event) => {101 onLongPress={(event) => {
103 event.stopPropagation();102 event.stopPropagation();
104 copyTextWithHaptic(statusTraceId, { target: "connection-trace-id" });103 copyTextWithHaptic(statusTraceId, { target: "connection-trace-id" });
...@@ -106,7 +105,6 @@ export function ConnectionEnvironmentRow(props: {...@@ -106,7 +105,6 @@ export function ConnectionEnvironmentRow(props: {
106 onPress={(event) => {105 onPress={(event) => {
107 event.stopPropagation();106 event.stopPropagation();
108 }}107 }}
109 style={{ textDecorationStyle: "dotted" }}
110 >108 >
111 {statusTraceId}109 {statusTraceId}
112 </Text>110 </Text>
...@@ -140,17 +138,13 @@ export function ConnectionEnvironmentRow(props: {...@@ -140,17 +138,13 @@ export function ConnectionEnvironmentRow(props: {
140 ) : (138 ) : (
141 <>139 <>
142 <View className="gap-1.5">140 <View className="gap-1.5">
143 <Text141 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
144 className="text-2xs font-t3-bold uppercase text-foreground-muted"
145 style={{ letterSpacing: 0.8 }}
146 >
147 Label142 Label
148 </Text>143 </Text>
149 <TextInput144 <TextInput
150 autoCapitalize="words"145 autoCapitalize="words"
151 autoCorrect={false}146 autoCorrect={false}
152 placeholder="My MacBook"147 placeholder="My MacBook"
153 placeholderTextColor={placeholderColor}
154 value={label}148 value={label}
155 onChangeText={setLabel}149 onChangeText={setLabel}
156 className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"150 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: {...@@ -158,10 +152,7 @@ export function ConnectionEnvironmentRow(props: {
158 </View>152 </View>
159153
160 <View className="gap-1.5">154 <View className="gap-1.5">
161 <Text155 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
162 className="text-2xs font-t3-bold uppercase text-foreground-muted"
163 style={{ letterSpacing: 0.8 }}
164 >
165 URL156 URL
166 </Text>157 </Text>
167 <TextInput158 <TextInput
...@@ -169,7 +160,6 @@ export function ConnectionEnvironmentRow(props: {...@@ -169,7 +160,6 @@ export function ConnectionEnvironmentRow(props: {
169 autoCorrect={false}160 autoCorrect={false}
170 keyboardType="url"161 keyboardType="url"
171 placeholder="192.168.1.100:8080"162 placeholder="192.168.1.100:8080"
172 placeholderTextColor={placeholderColor}
173 value={url}163 value={url}
174 onChangeText={setUrl}164 onChangeText={setUrl}
175 className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"165 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: {...@@ -185,10 +175,7 @@ export function ConnectionEnvironmentRow(props: {
185 onPress={handleSave}175 onPress={handleSave}
186 >176 >
187 <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" />177 <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" />
188 <Text178 <Text className="text-xs font-t3-bold tracking-[0.8px] uppercase text-primary-foreground">
189 className="text-xs font-t3-bold uppercase text-primary-foreground"
190 style={{ letterSpacing: 0.8 }}
191 >
192 Save179 Save
193 </Text>180 </Text>
194 </Pressable>181 </Pressable>
apps/mobile/src/features/connection/ConnectionSheetButton.tsx+17-35
...@@ -37,33 +37,11 @@ export function ConnectionSheetButton(props: {...@@ -37,33 +37,11 @@ export function ConnectionSheetButton(props: {
37}) {37}) {
38 const tone = props.tone ?? "secondary";38 const tone = props.tone ?? "secondary";
3939
40 const primaryBg = useThemeColor("--color-primary");
41 const primaryFg = useThemeColor("--color-primary-foreground");40 const primaryFg = useThemeColor("--color-primary-foreground");
42 const dangerBg = useThemeColor("--color-danger");
43 const dangerBorderColor = useThemeColor("--color-danger-border");
44 const dangerFg = useThemeColor("--color-danger-foreground");41 const dangerFg = useThemeColor("--color-danger-foreground");
45 const secondaryBg = useThemeColor("--color-secondary");
46 const secondaryFg = useThemeColor("--color-secondary-foreground");42 const secondaryFg = useThemeColor("--color-secondary-foreground");
47 const borderColor = useThemeColor("--color-border");
4843
49 const colors =44 const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg;
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 };
6745
68 const primaryShadow =46 const primaryShadow =
69 tone === "primary"47 tone === "primary"
...@@ -84,28 +62,32 @@ export function ConnectionSheetButton(props: {...@@ -84,28 +62,32 @@ export function ConnectionSheetButton(props: {
84 props.compact62 props.compact
85 ? "min-h-[42px] flex-row items-center justify-center gap-1.5 rounded-[14px] px-3.5 py-2.5"63 ? "min-h-[42px] flex-row items-center justify-center gap-1.5 rounded-[14px] px-3.5 py-2.5"
86 : "min-h-[48px] flex-row items-center justify-center gap-2 rounded-[16px] px-4 py-3",64 : "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",
87 )}71 )}
88 disabled={props.disabled}72 disabled={props.disabled}
89 onPress={props.onPress}73 onPress={props.onPress}
90 style={[74 style={primaryShadow}
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 ]}
99 >75 >
100 <SymbolView76 <SymbolView
101 name={props.icon}77 name={props.icon}
102 size={props.compact ? 13 : 14}78 size={props.compact ? 13 : 14}
103 tintColor={colors.textColor}79 tintColor={textColor}
104 type="monochrome"80 type="monochrome"
105 />81 />
106 <Text82 <Text
107 className="text-xs font-t3-bold uppercase"83 className={cn(
108 style={{ color: colors.textColor, letterSpacing: 0.8 }}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 )}
109 >91 >
110 {props.label}92 {props.label}
111 </Text>93 </Text>
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+5-20
...@@ -38,7 +38,6 @@ export function ConnectionsNewRouteScreen({...@@ -38,7 +38,6 @@ export function ConnectionsNewRouteScreen({
38 const [scannerLocked, setScannerLocked] = useState(false);38 const [scannerLocked, setScannerLocked] = useState(false);
3939
40 const headerIconColor = useThemeColor("--color-icon");40 const headerIconColor = useThemeColor("--color-icon");
41 const placeholderColor = useThemeColor("--color-placeholder");
4241
43 const connectDisabled = isSubmitting || hostInput.trim().length === 0;42 const connectDisabled = isSubmitting || hostInput.trim().length === 0;
4443
...@@ -158,7 +157,7 @@ export function ConnectionsNewRouteScreen({...@@ -158,7 +157,7 @@ export function ConnectionsNewRouteScreen({
158 <ScrollView157 <ScrollView
159 contentInsetAdjustmentBehavior="automatic"158 contentInsetAdjustmentBehavior="automatic"
160 showsVerticalScrollIndicator={false}159 showsVerticalScrollIndicator={false}
161 style={{ flex: 1 }}160 className="flex-1"
162 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}161 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
163 contentContainerStyle={{162 contentContainerStyle={{
164 paddingHorizontal: 20,163 paddingHorizontal: 20,
...@@ -168,10 +167,7 @@ export function ConnectionsNewRouteScreen({...@@ -168,10 +167,7 @@ export function ConnectionsNewRouteScreen({
168 <View collapsable={false} className="gap-5">167 <View collapsable={false} className="gap-5">
169 {showScanner ? (168 {showScanner ? (
170 cameraPermission?.granted ? (169 cameraPermission?.granted ? (
171 <View170 <View className="overflow-hidden rounded-[24px] border-continuous">
172 className="overflow-hidden rounded-[24px]"
173 style={{ borderCurve: "continuous" }}
174 >
175 <CameraView171 <CameraView
176 barcodeScannerSettings={{ barcodeTypes: ["qr"] }}172 barcodeScannerSettings={{ barcodeTypes: ["qr"] }}
177 onBarcodeScanned={handleQrScan}173 onBarcodeScanned={handleQrScan}
...@@ -179,10 +175,7 @@ export function ConnectionsNewRouteScreen({...@@ -179,10 +175,7 @@ export function ConnectionsNewRouteScreen({
179 />175 />
180 </View>176 </View>
181 ) : (177 ) : (
182 <View178 <View className="items-center gap-3 rounded-[24px] border-continuous bg-card px-5 py-8">
183 className="items-center gap-3 rounded-[24px] bg-card px-5 py-8"
184 style={{ borderCurve: "continuous" }}
185 >
186 <Text className="text-center text-sm leading-normal text-foreground-muted">179 <Text className="text-center text-sm leading-normal text-foreground-muted">
187 Camera permission is required to scan a QR code.180 Camera permission is required to scan a QR code.
188 </Text>181 </Text>
...@@ -200,10 +193,7 @@ export function ConnectionsNewRouteScreen({...@@ -200,10 +193,7 @@ export function ConnectionsNewRouteScreen({
200 ) : (193 ) : (
201 <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4">194 <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4">
202 <View collapsable={false} className="gap-1.5">195 <View collapsable={false} className="gap-1.5">
203 <Text196 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
204 className="text-2xs font-t3-bold uppercase text-foreground-muted"
205 style={{ letterSpacing: 0.8 }}
206 >
207 Host197 Host
208 </Text>198 </Text>
209 <TextInput199 <TextInput
...@@ -211,7 +201,6 @@ export function ConnectionsNewRouteScreen({...@@ -211,7 +201,6 @@ export function ConnectionsNewRouteScreen({
211 autoCorrect={false}201 autoCorrect={false}
212 keyboardType="url"202 keyboardType="url"
213 placeholder="192.168.1.100:8080"203 placeholder="192.168.1.100:8080"
214 placeholderTextColor={placeholderColor}
215 value={hostInput}204 value={hostInput}
216 onChangeText={handleHostChange}205 onChangeText={handleHostChange}
217 className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"206 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({...@@ -219,17 +208,13 @@ export function ConnectionsNewRouteScreen({
219 </View>208 </View>
220209
221 <View collapsable={false} className="gap-1.5">210 <View collapsable={false} className="gap-1.5">
222 <Text211 <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
223 className="text-2xs font-t3-bold uppercase text-foreground-muted"
224 style={{ letterSpacing: 0.8 }}
225 >
226 Pairing code212 Pairing code
227 </Text>213 </Text>
228 <TextInput214 <TextInput
229 autoCapitalize="none"215 autoCapitalize="none"
230 autoCorrect={false}216 autoCorrect={false}
231 placeholder="abc-123-xyz"217 placeholder="abc-123-xyz"
232 placeholderTextColor={placeholderColor}
233 value={codeInput}218 value={codeInput}
234 onChangeText={handleCodeChange}219 onChangeText={handleCodeChange}
235 className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"220 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() {...@@ -42,7 +42,7 @@ export function ConnectionsRouteScreen() {
42 <ScrollView42 <ScrollView
43 contentInsetAdjustmentBehavior="automatic"43 contentInsetAdjustmentBehavior="automatic"
44 showsVerticalScrollIndicator={false}44 showsVerticalScrollIndicator={false}
45 style={{ flex: 1 }}45 className="flex-1"
46 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}46 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
47 contentContainerStyle={{47 contentContainerStyle={{
48 paddingHorizontal: 20,48 paddingHorizontal: 20,
...@@ -55,10 +55,7 @@ export function ConnectionsRouteScreen() {...@@ -55,10 +55,7 @@ export function ConnectionsRouteScreen() {
55 <View55 <View
56 key={environment.environmentId}56 key={environment.environmentId}
57 collapsable={false}57 collapsable={false}
58 style={{58 className={cn(index !== 0 && "border-t border-border")}
59 borderTopWidth: index === 0 ? 0 : 1,
60 }}
61 className={cn(index !== 0 && "border-border")}
62 >59 >
63 <ConnectionEnvironmentRow60 <ConnectionEnvironmentRow
64 environment={environment}61 environment={environment}
apps/mobile/src/features/files/FileMarkdownPreview.tsx+15-8
...@@ -8,6 +8,7 @@ import {...@@ -8,6 +8,7 @@ import {
8import { RefreshControl, ScrollView, Text as NativeText, View } from "react-native";8import { RefreshControl, ScrollView, Text as NativeText, View } from "react-native";
99
10import { tryOpenExternalUrl } from "../../lib/openExternalUrl";10import { tryOpenExternalUrl } from "../../lib/openExternalUrl";
11import { useFontFamily } from "../../lib/useFontFamily";
11import {12import {
12 resolveMarkdownFontSizes,13 resolveMarkdownFontSizes,
13 resolveNativeMarkdownTypography,14 resolveNativeMarkdownTypography,
...@@ -45,11 +46,15 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {...@@ -45,11 +46,15 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
45 const codeBackground = String(useThemeColor("--color-md-code-bg"));46 const codeBackground = String(useThemeColor("--color-md-code-bg"));
46 const codeText = String(useThemeColor("--color-md-code-text"));47 const codeText = String(useThemeColor("--color-md-code-text"));
47 const horizontalRule = String(useThemeColor("--color-md-hr"));48 const horizontalRule = String(useThemeColor("--color-md-hr"));
49 const regularFontFamily = useFontFamily("regular");
50 const mediumFontFamily = useFontFamily("medium");
51 const boldFontFamily = useFontFamily("bold");
4852
49 return useMemo(() => {53 return useMemo(() => {
50 const renderers: CustomRenderers = {54 const renderers: CustomRenderers = {
51 link: ({ href, children }) => (55 link: ({ href, children }) => (
52 <NativeText56 <NativeText
57 className="font-t3-medium"
53 onPress={() => {58 onPress={() => {
54 if (href) {59 if (href) {
55 void tryOpenExternalUrl(href, "markdown-link");60 void tryOpenExternalUrl(href, "markdown-link");
...@@ -57,7 +62,6 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {...@@ -57,7 +62,6 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
57 }}62 }}
58 style={{63 style={{
59 color: link,64 color: link,
60 fontFamily: "DMSans_500Medium",
61 textDecorationLine: "none",65 textDecorationLine: "none",
62 }}66 }}
63 >67 >
...@@ -86,21 +90,21 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {...@@ -86,21 +90,21 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
86 styles: {90 styles: {
87 text: {91 text: {
88 color: body,92 color: body,
89 fontFamily: "DMSans_400Regular",93 fontFamily: regularFontFamily,
90 fontSize: markdownFontSizes.m,94 fontSize: markdownFontSizes.m,
91 lineHeight: markdownFontSizes.bodyLineHeight,95 lineHeight: markdownFontSizes.bodyLineHeight,
92 },96 },
93 heading: {97 heading: {
94 color: strong,98 color: strong,
95 fontFamily: "DMSans_700Bold",99 fontFamily: boldFontFamily,
96 },100 },
97 strong: {101 strong: {
98 color: strong,102 color: strong,
99 fontFamily: "DMSans_700Bold",103 fontFamily: boldFontFamily,
100 },104 },
101 link: {105 link: {
102 color: link,106 color: link,
103 fontFamily: "DMSans_500Medium",107 fontFamily: mediumFontFamily,
104 },108 },
105 blockquote: {109 blockquote: {
106 backgroundColor: blockquoteBackground,110 backgroundColor: blockquoteBackground,
...@@ -141,9 +145,9 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {...@@ -141,9 +145,9 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
141 fontSize: nativeMarkdownTypography.fontSize,145 fontSize: nativeMarkdownTypography.fontSize,
142 lineHeight: nativeMarkdownTypography.lineHeight,146 lineHeight: nativeMarkdownTypography.lineHeight,
143 headingFontSizes: nativeMarkdownTypography.headingFontSizes,147 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
144 fontFamily: "DMSans_400Regular",148 fontFamily: regularFontFamily,
145 headingFontFamily: "DMSans_700Bold",149 headingFontFamily: boldFontFamily,
146 boldFontFamily: "DMSans_700Bold",150 boldFontFamily,
147 },151 },
148 };152 };
149 }, [153 }, [
...@@ -155,8 +159,11 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {...@@ -155,8 +159,11 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles {
155 horizontalRule,159 horizontalRule,
156 link,160 link,
157 markdownFontSizes,161 markdownFontSizes,
162 mediumFontFamily,
158 nativeMarkdownTypography,163 nativeMarkdownTypography,
164 regularFontFamily,
159 strong,165 strong,
166 boldFontFamily,
160 ]);167 ]);
161}168}
162169
apps/mobile/src/features/files/FileTreeBrowser.tsx+1-1
...@@ -246,7 +246,7 @@ export function FileTreeBrowser(props: {...@@ -246,7 +246,7 @@ export function FileTreeBrowser(props: {
246 // flex-1 Views is ignored, which is why the tree rendered under the header with no blur.246 // flex-1 Views is ignored, which is why the tree rendered under the header with no blur.
247 return (247 return (
248 <FlatList248 <FlatList
249 style={{ flex: 1 }}249 className="flex-1"
250 data={visibleNodes}250 data={visibleNodes}
251 keyExtractor={(item) => item.node.path}251 keyExtractor={(item) => item.node.path}
252 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}252 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx+1-8
...@@ -224,14 +224,7 @@ function FilesToolbarBottomFade() {...@@ -224,14 +224,7 @@ function FilesToolbarBottomFade() {
224 pointerEvents="none"224 pointerEvents="none"
225 accessibilityElementsHidden225 accessibilityElementsHidden
226 importantForAccessibility="no-hide-descendants"226 importantForAccessibility="no-hide-descendants"
227 style={{227 className="absolute inset-x-0 bottom-0 z-[1] h-28"
228 bottom: 0,
229 height: 112,
230 left: 0,
231 position: "absolute",
232 right: 0,
233 zIndex: 1,
234 }}
235 >228 >
236 <Svg width="100%" height="100%">229 <Svg width="100%" height="100%">
237 <Defs>230 <Defs>
apps/mobile/src/features/home/HomeScreen.tsx+1-1
...@@ -412,7 +412,7 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -412,7 +412,7 @@ export function HomeScreen(props: HomeScreenProps) {
412 {Platform.OS === "ios" ? null : <HomeTopContentSpacer topInset={insets.top} />}412 {Platform.OS === "ios" ? null : <HomeTopContentSpacer topInset={insets.top} />}
413413
414 {shouldShowConnectionStatus && Platform.OS === "ios" ? (414 {shouldShowConnectionStatus && Platform.OS === "ios" ? (
415 <View style={{ paddingBottom: 16 }}>415 <View className="pb-4">
416 <WorkspaceConnectionStatus416 <WorkspaceConnectionStatus
417 state={props.catalogState}417 state={props.catalogState}
418 onPress={props.onOpenEnvironments}418 onPress={props.onOpenEnvironments}
apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx+4-3
...@@ -476,16 +476,17 @@ export function AdaptiveWorkspaceLayout(props: {...@@ -476,16 +476,17 @@ export function AdaptiveWorkspaceLayout(props: {
476 return (476 return (
477 <HomeListOptionsProvider>477 <HomeListOptionsProvider>
478 <AdaptiveWorkspaceContext.Provider value={contextValue}>478 <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">
480 {shouldRenderPrimarySidebar && layout.listPaneWidth !== null ? (480 {shouldRenderPrimarySidebar && layout.listPaneWidth !== null ? (
481 <Animated.View481 <Animated.View
482 className="self-stretch overflow-hidden"
482 accessibilityElementsHidden={!panes.primarySidebarVisible}483 accessibilityElementsHidden={!panes.primarySidebarVisible}
483 collapsable={false}484 collapsable={false}
484 importantForAccessibility={485 importantForAccessibility={
485 panes.primarySidebarVisible ? "auto" : "no-hide-descendants"486 panes.primarySidebarVisible ? "auto" : "no-hide-descendants"
486 }487 }
487 pointerEvents={panes.primarySidebarVisible ? "auto" : "none"}488 pointerEvents={panes.primarySidebarVisible ? "auto" : "none"}
488 style={[{ alignSelf: "stretch", overflow: "hidden" }, sidebarAnimatedStyle]}489 style={sidebarAnimatedStyle}
489 >490 >
490 <ThreadNavigationSidebar491 <ThreadNavigationSidebar
491 width={layout.listPaneWidth}492 width={layout.listPaneWidth}
...@@ -501,7 +502,7 @@ export function AdaptiveWorkspaceLayout(props: {...@@ -501,7 +502,7 @@ export function AdaptiveWorkspaceLayout(props: {
501 />502 />
502 </Animated.View>503 </Animated.View>
503 ) : null}504 ) : null}
504 <View className="bg-screen" collapsable={false} style={{ flex: 1, overflow: "hidden" }}>505 <View className="flex-1 overflow-hidden bg-screen" collapsable={false}>
505 <View506 <View
506 collapsable={false}507 collapsable={false}
507 style={508 style={
apps/mobile/src/features/layout/workspace-inspector-pane.tsx+3-2
...@@ -129,13 +129,14 @@ export function WorkspaceInspectorPane(props: {...@@ -129,13 +129,14 @@ export function WorkspaceInspectorPane(props: {
129 ) : null}129 ) : null}
130 {inspectorSupported ? (130 {inspectorSupported ? (
131 <Animated.View131 <Animated.View
132 className="shrink-0 overflow-hidden"
132 accessibilityElementsHidden={!inspectorVisible}133 accessibilityElementsHidden={!inspectorVisible}
133 collapsable={false}134 collapsable={false}
134 importantForAccessibility={inspectorVisible ? "auto" : "no-hide-descendants"}135 importantForAccessibility={inspectorVisible ? "auto" : "no-hide-descendants"}
135 pointerEvents={inspectorVisible ? "auto" : "none"}136 pointerEvents={inspectorVisible ? "auto" : "none"}
136 style={[{ flexShrink: 0, overflow: "hidden" }, inspectorStyle]}137 style={inspectorStyle}
137 >138 >
138 <Animated.View style={[{ flex: 1 }, inspectorContentStyle]}>139 <Animated.View className="flex-1" style={inspectorContentStyle}>
139 {props.renderInspector?.()}140 {props.renderInspector?.()}
140 </Animated.View>141 </Animated.View>
141 </Animated.View>142 </Animated.View>
apps/mobile/src/features/layout/workspace-pane-divider.tsx+1-10
...@@ -69,6 +69,7 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {...@@ -69,6 +69,7 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
69 return (69 return (
70 <GestureDetector gesture={resizeGesture}>70 <GestureDetector gesture={resizeGesture}>
71 <Pressable71 <Pressable
72 className="relative z-[100] -mx-[22px] w-11 self-stretch cursor-pointer justify-center"
72 accessibilityActions={[73 accessibilityActions={[
73 { name: "increment", label: "Make pane wider" },74 { name: "increment", label: "Make pane wider" },
74 { name: "decrement", label: "Make pane narrower" },75 { name: "decrement", label: "Make pane narrower" },
...@@ -82,7 +83,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {...@@ -82,7 +83,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
82 onAccessibilityAction={handleAccessibilityAction}83 onAccessibilityAction={handleAccessibilityAction}
83 onHoverIn={() => setHovered(true)}84 onHoverIn={() => setHovered(true)}
84 onHoverOut={() => setHovered(false)}85 onHoverOut={() => setHovered(false)}
85 style={styles.hitTarget}
86 >86 >
87 <View style={[styles.line, (hovered || dragging) && styles.activeLine]} />87 <View style={[styles.line, (hovered || dragging) && styles.activeLine]} />
88 </Pressable>88 </Pressable>
...@@ -91,15 +91,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {...@@ -91,15 +91,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
91}91}
9292
93const styles = StyleSheet.create({93const 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 },
103 line: {94 line: {
104 alignSelf: "center",95 alignSelf: "center",
105 backgroundColor:96 backgroundColor:
apps/mobile/src/features/projects/AddProjectScreen.tsx+7-11
...@@ -31,6 +31,7 @@ import * as Arr from "effect/Array";...@@ -31,6 +31,7 @@ import * as Arr from "effect/Array";
31import * as Cause from "effect/Cause";31import * as Cause from "effect/Cause";
32import * as Order from "effect/Order";32import * as Order from "effect/Order";
33import { AsyncResult } from "effect/unstable/reactivity";33import { AsyncResult } from "effect/unstable/reactivity";
34import { cn } from "../../lib/cn";
3435
35import { useProjects, useServerConfigs } from "../../state/entities";36import { useProjects, useServerConfigs } from "../../state/entities";
36import { filesystemEnvironment } from "../../state/filesystem";37import { filesystemEnvironment } from "../../state/filesystem";
...@@ -99,10 +100,7 @@ function sourceFromParam(value: string | string[] | undefined): AddProjectRemote...@@ -99,10 +100,7 @@ function sourceFromParam(value: string | string[] | undefined): AddProjectRemote
99100
100function SectionTitle(props: { readonly children: string }) {101function SectionTitle(props: { readonly children: string }) {
101 return (102 return (
102 <Text103 <Text className="px-1 text-2xs font-t3-bold tracking-[0.7px] uppercase text-foreground-muted">
103 className="px-1 text-2xs font-t3-bold uppercase text-foreground-muted"
104 style={{ letterSpacing: 0.7 }}
105 >
106 {props.children}104 {props.children}
107 </Text>105 </Text>
108 );106 );
...@@ -148,19 +146,17 @@ function ListRow(props: {...@@ -148,19 +146,17 @@ function ListRow(props: {
148 readonly right?: ReactNode;146 readonly right?: ReactNode;
149 readonly onPress?: () => void;147 readonly onPress?: () => void;
150}) {148}) {
151 const borderColor = useThemeColor("--color-border-subtle");
152 const chevronColor = useThemeColor("--color-chevron");149 const chevronColor = useThemeColor("--color-chevron");
153150
154 return (151 return (
155 <Pressable152 <Pressable
156 disabled={props.disabled}153 disabled={props.disabled}
157 onPress={props.onPress}154 onPress={props.onPress}
158 className="bg-card px-3.5 py-2.5 active:opacity-70"155 className={cn(
159 style={{156 "bg-card px-3.5 py-2.5 active:opacity-70",
160 opacity: props.disabled ? 0.45 : 1,157 !props.isFirst && "border-t border-border-subtle",
161 borderTopWidth: props.isFirst ? 0 : 1,158 props.disabled && "opacity-[0.45]",
162 borderTopColor: borderColor,159 )}
163 }}
164 >160 >
165 <View className="flex-row items-center gap-3">161 <View className="flex-row items-center gap-3">
166 <View162 <View
apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx+5-10
...@@ -144,15 +144,11 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp...@@ -144,15 +144,11 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp
144 }144 }
145145
146 return (146 return (
147 <View style={{ flex: 1 }}>147 <View className="flex-1">
148 <KeyboardAvoidingView automaticOffset behavior="padding" style={{ flex: 1 }}>148 <KeyboardAvoidingView automaticOffset behavior="padding" className="flex-1">
149 <View149 <View
150 style={{150 className="flex-1 px-5 pt-2"
151 flex: 1,151 style={{ paddingBottom: target ? 0 : Math.max(insets.bottom, 18) }}
152 paddingHorizontal: 20,
153 paddingTop: 8,
154 paddingBottom: target ? 0 : Math.max(insets.bottom, 18),
155 }}
156 >152 >
157 <View className="flex-row items-center justify-between py-2">153 <View className="flex-row items-center justify-between py-2">
158 <Pressable154 <Pressable
...@@ -253,8 +249,7 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp...@@ -253,8 +249,7 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp
253 textAlignVertical="top"249 textAlignVertical="top"
254 value={commentText}250 value={commentText}
255 onChangeText={setCommentText}251 onChangeText={setCommentText}
256 className="h-full flex-1 border-0 bg-transparent px-0 py-0 font-sans text-base"252 className="h-full min-h-0 flex-1 border-0 bg-transparent px-0 py-0 font-sans text-base"
257 style={{ flex: 1, minHeight: 0 }}
258 />253 />
259 </TextInputWrapper>254 </TextInputWrapper>
260 </View>255 </View>
apps/mobile/src/features/review/ReviewSheet.tsx+2-2
...@@ -655,7 +655,7 @@ export function ReviewSheet(props: ReviewSheetProps) {...@@ -655,7 +655,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
655655
656 <View className="flex-1 bg-sheet">656 <View className="flex-1 bg-sheet">
657 {showConnectionNotice ? (657 {showConnectionNotice ? (
658 <View style={{ flex: 1, paddingTop: topContentInset }}>658 <View className="flex-1" style={{ paddingTop: topContentInset }}>
659 <EnvironmentConnectionNotice659 <EnvironmentConnectionNotice
660 environmentLabel={environment.presentation?.entry.target.label ?? "Environment"}660 environmentLabel={environment.presentation?.entry.target.label ?? "Environment"}
661 connection={661 connection={
...@@ -722,7 +722,7 @@ export function ReviewSheet(props: ReviewSheetProps) {...@@ -722,7 +722,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
722 bottom: Math.max(insets.bottom, 18) + 18,722 bottom: Math.max(insets.bottom, 18) + 18,
723 }}723 }}
724 showsVerticalScrollIndicator={false}724 showsVerticalScrollIndicator={false}
725 style={{ flex: 1 }}725 className="flex-1"
726 >726 >
727 {listHeader}727 {listHeader}
728 {!selectedSection ? (728 {!selectedSection ? (
apps/mobile/src/features/review/reviewDiffRendering.tsx+2-2
...@@ -48,8 +48,8 @@ export function ReviewChangeBar(props: {...@@ -48,8 +48,8 @@ export function ReviewChangeBar(props: {
48 <View>48 <View>
49 {Array.from({ length: Math.ceil(height / 2) }, (_, index) => (49 {Array.from({ length: Math.ceil(height / 2) }, (_, index) => (
50 <View key={index}>50 <View key={index}>
51 <View className="w-[5px] bg-rose-400" style={{ height: 1 }} />51 <View className="h-px w-[5px] bg-rose-400" />
52 <View style={{ height: 1 }} />52 <View className="h-px" />
53 </View>53 </View>
54 ))}54 ))}
55 </View>55 </View>
apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx+2-4
...@@ -13,12 +13,10 @@ export function SettingsAppearanceRouteScreen() {...@@ -13,12 +13,10 @@ export function SettingsAppearanceRouteScreen() {
13 <ScrollView13 <ScrollView
14 contentInsetAdjustmentBehavior="automatic"14 contentInsetAdjustmentBehavior="automatic"
15 showsVerticalScrollIndicator={false}15 showsVerticalScrollIndicator={false}
16 style={{ flex: 1 }}16 className="flex-1"
17 contentContainerClassName="gap-6 px-5 pt-4"
17 contentContainerStyle={{18 contentContainerStyle={{
18 gap: 24,
19 paddingBottom: Math.max(insets.bottom, 18) + 18,19 paddingBottom: Math.max(insets.bottom, 18) + 18,
20 paddingHorizontal: 20,
21 paddingTop: 16,
22 }}20 }}
23 >21 >
24 <TextAppearanceSection />22 <TextAppearanceSection />
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+3-13
...@@ -75,13 +75,8 @@ export function SettingsClientStorageRouteScreen() {...@@ -75,13 +75,8 @@ export function SettingsClientStorageRouteScreen() {
75 contentInsetAdjustmentBehavior="automatic"75 contentInsetAdjustmentBehavior="automatic"
76 contentInset={{ bottom: Math.max(insets.bottom, 18) }}76 contentInset={{ bottom: Math.max(insets.bottom, 18) }}
77 showsVerticalScrollIndicator={false}77 showsVerticalScrollIndicator={false}
78 style={{ flex: 1 }}78 className="flex-1"
79 contentContainerStyle={{79 contentContainerClassName="gap-6 px-5 pt-4 pb-[18px]"
80 gap: 24,
81 paddingBottom: 18,
82 paddingHorizontal: 20,
83 paddingTop: 16,
84 }}
85 >80 >
86 <SettingsSection title="Environment caches">81 <SettingsSection title="Environment caches">
87 {AsyncResult.isFailure(summaryResult) ? (82 {AsyncResult.isFailure(summaryResult) ? (
...@@ -180,7 +175,6 @@ function CacheEnvironmentRow(props: {...@@ -180,7 +175,6 @@ function CacheEnvironmentRow(props: {
180 readonly onClear: () => void;175 readonly onClear: () => void;
181}) {176}) {
182 const iconColor = useThemeColor("--color-icon");177 const iconColor = useThemeColor("--color-icon");
183 const dangerForegroundColor = useThemeColor("--color-danger-foreground");
184 return (178 return (
185 <View179 <View
186 className={180 className={
...@@ -206,11 +200,7 @@ function CacheEnvironmentRow(props: {...@@ -206,11 +200,7 @@ function CacheEnvironmentRow(props: {
206 onPress={props.onClear}200 onPress={props.onClear}
207 className="rounded-full px-3 py-2 disabled:opacity-40"201 className="rounded-full px-3 py-2 disabled:opacity-40"
208 >202 >
209 <Text203 <Text className="font-t3-medium tabular-nums text-danger-foreground" numberOfLines={1}>
210 className="font-t3-medium tabular-nums text-danger-foreground"
211 numberOfLines={1}
212 style={{ color: dangerForegroundColor }}
213 >
214 Clear {formatBytes(props.environment.payloadBytes)}204 Clear {formatBytes(props.environment.payloadBytes)}
215 </Text>205 </Text>
216 </Pressable>206 </Pressable>
apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx+3-7
...@@ -50,11 +50,10 @@ export function SettingsEnvironmentsRouteScreen() {...@@ -50,11 +50,10 @@ export function SettingsEnvironmentsRouteScreen() {
50 <ScrollView50 <ScrollView
51 contentInsetAdjustmentBehavior="automatic"51 contentInsetAdjustmentBehavior="automatic"
52 showsVerticalScrollIndicator={false}52 showsVerticalScrollIndicator={false}
53 style={{ flex: 1 }}53 className="flex-1"
54 contentContainerClassName="px-5 pt-4"
54 contentContainerStyle={{55 contentContainerStyle={{
55 paddingBottom: Math.max(insets.bottom, 18) + 18,56 paddingBottom: Math.max(insets.bottom, 18) + 18,
56 paddingHorizontal: 20,
57 paddingTop: 16,
58 }}57 }}
59 >58 >
60 {hasLocalEnvironments ? (59 {hasLocalEnvironments ? (
...@@ -63,10 +62,7 @@ export function SettingsEnvironmentsRouteScreen() {...@@ -63,10 +62,7 @@ export function SettingsEnvironmentsRouteScreen() {
63 <View62 <View
64 key={environment.environmentId}63 key={environment.environmentId}
65 collapsable={false}64 collapsable={false}
66 style={{65 className={cn(index !== 0 && "border-t border-border")}
67 borderTopWidth: index === 0 ? 0 : 1,
68 }}
69 className={cn(index !== 0 && "border-border")}
70 >66 >
71 <ConnectionEnvironmentRow67 <ConnectionEnvironmentRow
72 environment={environment}68 environment={environment}
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+4-8
...@@ -94,12 +94,10 @@ function LocalSettingsRouteScreen() {...@@ -94,12 +94,10 @@ function LocalSettingsRouteScreen() {
94 <ScrollView94 <ScrollView
95 contentInsetAdjustmentBehavior="automatic"95 contentInsetAdjustmentBehavior="automatic"
96 showsVerticalScrollIndicator={false}96 showsVerticalScrollIndicator={false}
97 style={{ flex: 1 }}97 className="flex-1"
98 contentContainerClassName="gap-6 px-5 pt-4"
98 contentContainerStyle={{99 contentContainerStyle={{
99 gap: 24,
100 paddingBottom: Math.max(insets.bottom, 18) + 18,100 paddingBottom: Math.max(insets.bottom, 18) + 18,
101 paddingHorizontal: 20,
102 paddingTop: 16,
103 }}101 }}
104 >102 >
105 <SettingsSection title="Configuration">103 <SettingsSection title="Configuration">
...@@ -430,12 +428,10 @@ function ConfiguredSettingsRouteScreen() {...@@ -430,12 +428,10 @@ function ConfiguredSettingsRouteScreen() {
430 <ScrollView428 <ScrollView
431 contentInsetAdjustmentBehavior="automatic"429 contentInsetAdjustmentBehavior="automatic"
432 showsVerticalScrollIndicator={false}430 showsVerticalScrollIndicator={false}
433 style={{ flex: 1 }}431 className="flex-1"
432 contentContainerClassName="gap-6 px-5 pt-4"
434 contentContainerStyle={{433 contentContainerStyle={{
435 gap: 24,
436 paddingBottom: Math.max(insets.bottom, 18) + 18,434 paddingBottom: Math.max(insets.bottom, 18) + 18,
437 paddingHorizontal: 20,
438 paddingTop: 16,
439 }}435 }}
440 >436 >
441 <View className="gap-3">437 <View className="gap-3">
apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx+1-1
...@@ -136,7 +136,7 @@ export function FontSizeSliderRow(props: {...@@ -136,7 +136,7 @@ export function FontSizeSliderRow(props: {
136 };136 };
137137
138 return (138 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"}>
140 <View className="flex-row items-center gap-4">140 <View className="flex-row items-center gap-4">
141 <SymbolView141 <SymbolView
142 name={props.icon}142 name={props.icon}
apps/mobile/src/features/settings/components/SettingsRow.tsx+5-2
...@@ -22,8 +22,11 @@ export function SettingsRow(props: {...@@ -22,8 +22,11 @@ export function SettingsRow(props: {
22 const chevron = useThemeColor("--color-chevron");22 const chevron = useThemeColor("--color-chevron");
23 const content = (23 const content = (
24 <View24 <View
25 className="flex-row items-center gap-4 p-4"25 className={
26 style={{ opacity: props.disabled ? 0.45 : 1 }}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 }
27 >30 >
28 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />31 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
29 <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}>32 <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...@@ -7,10 +7,7 @@ export function SettingsSection(props: { readonly title: string; readonly childr
7 return (7 return (
8 <View className="gap-2">8 <View className="gap-2">
9 <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text>9 <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text>
10 <View10 <View className="overflow-hidden rounded-[28px] border-continuous bg-card">
11 className="overflow-hidden rounded-[28px] bg-card"
12 style={{ borderCurve: "continuous" }}
13 >
14 {props.children}11 {props.children}
15 </View>12 </View>
16 </View>13 </View>
apps/mobile/src/features/settings/components/SettingsSwitchRow.tsx+5-2
...@@ -20,8 +20,11 @@ export function SettingsSwitchRow(props: {...@@ -20,8 +20,11 @@ export function SettingsSwitchRow(props: {
2020
21 return (21 return (
22 <View22 <View
23 className="flex-row items-center gap-4 p-4"23 className={
24 style={{ opacity: props.disabled ? 0.45 : 1 }}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 }
25 >28 >
26 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />29 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
27 <Text className="flex-1 text-lg text-foreground">{props.label}</Text>30 <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...@@ -82,9 +82,9 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
8282
83 return (83 return (
84 <View84 <View
85 className="flex-1"
85 style={[86 style={[
86 {87 {
87 flex: 1,
88 backgroundColor: theme.background,88 backgroundColor: theme.background,
89 borderRadius: 8,89 borderRadius: 8,
90 overflow: "hidden",90 overflow: "hidden",
...@@ -93,19 +93,18 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter...@@ -93,19 +93,18 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
93 ]}93 ]}
94 onLayout={handleLayout}94 onLayout={handleLayout}
95 >95 >
96 <View style={{ flex: 1, paddingHorizontal: 10, paddingVertical: 8 }}>96 <View className="flex-1 px-2.5 py-2">
97 <Text97 <Text
98 className="text-2xs"98 className="pb-2 text-2xs"
99 style={{99 style={{
100 color: theme.mutedForeground,100 color: theme.mutedForeground,
101 paddingBottom: 8,
102 }}101 }}
103 >102 >
104 {statusLabel}103 {statusLabel}
105 </Text>104 </Text>
106 <ScrollView105 <ScrollView
107 style={{ flex: 1 }}106 className="flex-1"
108 contentContainerStyle={{ paddingBottom: 12 }}107 contentContainerClassName="pb-3"
109 showsVerticalScrollIndicator={false}108 showsVerticalScrollIndicator={false}
110 >109 >
111 <Text110 <Text
...@@ -122,13 +121,9 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter...@@ -122,13 +121,9 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
122 </ScrollView>121 </ScrollView>
123 </View>122 </View>
124 <View123 <View
124 className="flex-row items-center gap-2 border-t p-2"
125 style={{125 style={{
126 borderTopWidth: 1,
127 borderTopColor: theme.border,126 borderTopColor: theme.border,
128 flexDirection: "row",
129 alignItems: "center",
130 gap: 8,
131 padding: 8,
132 }}127 }}
133 >128 >
134 <TextInput129 <TextInput
...@@ -165,13 +160,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter...@@ -165,13 +160,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter
165 })}160 })}
166 onPress={() => props.onInput("\u0003")}161 onPress={() => props.onInput("\u0003")}
167 >162 >
168 <Text163 <Text className="text-2xs font-t3-bold" style={{ color: theme.foreground }}>
169 className="text-2xs"
170 style={{
171 color: theme.foreground,
172 fontFamily: "DMSans_700Bold",
173 }}
174 >
175 Ctrl-C164 Ctrl-C
176 </Text>165 </Text>
177 </Pressable>166 </Pressable>
apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx+3-3
...@@ -929,7 +929,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)...@@ -929,7 +929,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
929 </NativeHeaderToolbar>929 </NativeHeaderToolbar>
930 ) : null}930 ) : null}
931931
932 <View style={{ flex: 1, backgroundColor: terminalTheme.background }}>932 <View className="flex-1" style={{ backgroundColor: terminalTheme.background }}>
933 {!isEnvironmentReady ? (933 {!isEnvironmentReady ? (
934 <EnvironmentConnectionNotice934 <EnvironmentConnectionNotice
935 environmentLabel={935 environmentLabel={
...@@ -949,7 +949,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)...@@ -949,7 +949,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
949 />949 />
950 ) : (950 ) : (
951 <>951 <>
952 <View style={{ flex: 1, paddingBottom: terminalBottomInset }}>952 <View className="flex-1" style={{ paddingBottom: terminalBottomInset }}>
953 <TerminalSurface953 <TerminalSurface
954 buffer={terminalSurfaceBuffer}954 buffer={terminalSurfaceBuffer}
955 fontSize={fontSize}955 fontSize={fontSize}
...@@ -968,10 +968,10 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)...@@ -968,10 +968,10 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
968 offset={{ closed: 0, opened: 0 }}968 offset={{ closed: 0, opened: 0 }}
969 >969 >
970 <View970 <View
971 className="border-t"
971 style={{972 style={{
972 backgroundColor: terminalTheme.background,973 backgroundColor: terminalTheme.background,
973 borderTopColor: terminalTheme.border,974 borderTopColor: terminalTheme.border,
974 borderTopWidth: 1,
975 minHeight: TERMINAL_ACCESSORY_HEIGHT,975 minHeight: TERMINAL_ACCESSORY_HEIGHT,
976 }}976 }}
977 >977 >
apps/mobile/src/features/threads/ComposerCommandPopover.tsx+6-21
...@@ -154,23 +154,11 @@ const CommandRow = memo(function CommandRow(props: {...@@ -154,23 +154,11 @@ const CommandRow = memo(function CommandRow(props: {
154 ) : iconName ? (154 ) : iconName ? (
155 <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" />155 <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" />
156 ) : null}156 ) : null}
157 <Text157 <Text className="shrink-0 text-base font-t3-medium text-foreground" numberOfLines={1}>
158 className="text-base font-t3-medium text-foreground"
159 numberOfLines={1}
160 style={{ flexShrink: 0 }}
161 >
162 {props.item.label}158 {props.item.label}
163 </Text>159 </Text>
164 {props.item.description ? (160 {props.item.description ? (
165 <Text161 <Text className="min-w-0 flex-1 text-xs text-zinc-400" numberOfLines={1}>
166 className="text-xs"
167 numberOfLines={1}
168 style={{
169 flex: 1,
170 minWidth: 0,
171 color: "#a1a1aa",
172 }}
173 >
174 {props.item.description}162 {props.item.description}
175 </Text>163 </Text>
176 ) : null}164 ) : null}
...@@ -187,18 +175,15 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(...@@ -187,18 +175,15 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(
187 return (175 return (
188 <PopoverSurface isDarkMode={isDarkMode}>176 <PopoverSurface isDarkMode={isDarkMode}>
189 {label ? (177 {label ? (
190 <View style={{ paddingHorizontal: 14, paddingTop: 10, paddingBottom: 4 }}>178 <View className="px-3.5 pt-2.5 pb-1">
191 <Text179 <Text className="text-3xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted">
192 className="text-3xs font-t3-bold text-foreground-muted"
193 style={{ letterSpacing: 0.8, textTransform: "uppercase" }}
194 >
195 {label}180 {label}
196 </Text>181 </Text>
197 </View>182 </View>
198 ) : null}183 ) : null}
199 {props.items.length > 0 ? (184 {props.items.length > 0 ? (
200 <ScrollView185 <ScrollView
201 style={{ maxHeight: 180 }}186 className="max-h-[180px]"
202 keyboardShouldPersistTaps="always"187 keyboardShouldPersistTaps="always"
203 showsVerticalScrollIndicator={false}188 showsVerticalScrollIndicator={false}
204 >189 >
...@@ -212,7 +197,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(...@@ -212,7 +197,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover(
212 ))}197 ))}
213 </ScrollView>198 </ScrollView>
214 ) : (199 ) : (
215 <View style={{ paddingHorizontal: 14, paddingVertical: 10 }}>200 <View className="px-3.5 py-2.5">
216 <Text className="text-xs text-foreground-tertiary">201 <Text className="text-xs text-foreground-tertiary">
217 {emptyText(props.triggerKind, props.isLoading)}202 {emptyText(props.triggerKind, props.isLoading)}
218 </Text>203 </Text>
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+2-1
...@@ -47,7 +47,8 @@ export function GitActionProgressOverlay(props: {...@@ -47,7 +47,8 @@ export function GitActionProgressOverlay(props: {
47 <Animated.View47 <Animated.View
48 entering={FadeIn.duration(200)}48 entering={FadeIn.duration(200)}
49 exiting={FadeOut.duration(150)}49 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 }}
51 pointerEvents="box-none"52 pointerEvents="box-none"
52 >53 >
53 <Pressable onPress={handlePress}>54 <Pressable onPress={handlePress}>
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+4-12
...@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useRef } from "react";...@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
4import { Alert, InteractionManager, View, useColorScheme } from "react-native";4import { Alert, InteractionManager, View, useColorScheme } from "react-native";
5import { KeyboardAvoidingView, useKeyboardState } from "react-native-keyboard-controller";5import { KeyboardAvoidingView, useKeyboardState } from "react-native-keyboard-controller";
6import { useSafeAreaInsets } from "react-native-safe-area-context";6import { useSafeAreaInsets } from "react-native-safe-area-context";
7import { useThemeColor } from "../../lib/useThemeColor";
87
9import { EnvironmentId } from "@t3tools/contracts";8import { EnvironmentId } from "@t3tools/contracts";
10import {9import {
...@@ -112,7 +111,6 @@ export function NewTaskDraftScreen(props: {...@@ -112,7 +111,6 @@ export function NewTaskDraftScreen(props: {
112 };111 };
113 }, [props.pendingTaskId, cancelEditingPendingTask]);112 }, [props.pendingTaskId, cancelEditingPendingTask]);
114113
115 const borderColor = useThemeColor("--color-border");
116 const headlineText = useScaledTextRole("headline");114 const headlineText = useScaledTextRole("headline");
117 const sheetFadeOpaque = colorScheme === "dark" ? "rgba(14,14,14,0.98)" : "rgba(242,242,247,0.98)";115 const sheetFadeOpaque = colorScheme === "dark" ? "rgba(14,14,14,0.98)" : "rgba(242,242,247,0.98)";
118 const sheetFadeTransparent = colorScheme === "dark" ? "rgba(14,14,14,0)" : "rgba(242,242,247,0)";116 const sheetFadeTransparent = colorScheme === "dark" ? "rgba(14,14,14,0)" : "rgba(242,242,247,0)";
...@@ -600,8 +598,8 @@ export function NewTaskDraftScreen(props: {...@@ -600,8 +598,8 @@ export function NewTaskDraftScreen(props: {
600 <View className="flex-1 bg-sheet">598 <View className="flex-1 bg-sheet">
601 <NativeStackScreenOptions options={{ title: selectedProject.title }} />599 <NativeStackScreenOptions options={{ title: selectedProject.title }} />
602600
603 <KeyboardAvoidingView automaticOffset behavior="padding" style={{ flex: 1 }}>601 <KeyboardAvoidingView automaticOffset behavior="padding" className="flex-1">
604 <View style={{ flex: 1, minHeight: 0, paddingHorizontal: 20, paddingTop: 8 }}>602 <View className="min-h-0 flex-1 px-5 pt-2">
605 <ComposerEditor603 <ComposerEditor
606 ref={promptInputRef}604 ref={promptInputRef}
607 autoFocus605 autoFocus
...@@ -617,15 +615,9 @@ export function NewTaskDraftScreen(props: {...@@ -617,15 +615,9 @@ export function NewTaskDraftScreen(props: {
617 />615 />
618 </View>616 </View>
619617
620 <View618 <View className="border-t border-border" style={{ paddingBottom: controlsBottomPadding }}>
621 style={{
622 borderTopWidth: 1,
623 borderTopColor: borderColor,
624 paddingBottom: controlsBottomPadding,
625 }}
626 >
627 {flow.attachments.length > 0 ? (619 {flow.attachments.length > 0 ? (
628 <View style={{ paddingHorizontal: 16, paddingTop: 12 }}>620 <View className="px-4 pt-3">
629 <ComposerAttachmentStrip621 <ComposerAttachmentStrip
630 attachments={flow.attachments}622 attachments={flow.attachments}
631 onRemove={flow.removeAttachment}623 onRemove={flow.removeAttachment}
apps/mobile/src/features/threads/NewTaskRouteScreen.tsx+8-13
...@@ -6,6 +6,7 @@ import { useMemo } from "react";...@@ -6,6 +6,7 @@ import { useMemo } from "react";
6import { ActivityIndicator, Pressable, ScrollView, View } from "react-native";6import { ActivityIndicator, Pressable, ScrollView, View } from "react-native";
7import { useSafeAreaInsets } from "react-native-safe-area-context";7import { useSafeAreaInsets } from "react-native-safe-area-context";
8import { useThemeColor } from "../../lib/useThemeColor";8import { useThemeColor } from "../../lib/useThemeColor";
9import { cn } from "../../lib/cn";
910
10import { AppText as Text } from "../../components/AppText";11import { AppText as Text } from "../../components/AppText";
11import { ProjectFavicon } from "../../components/ProjectFavicon";12import { ProjectFavicon } from "../../components/ProjectFavicon";
...@@ -79,7 +80,6 @@ export function NewTaskRouteScreen() {...@@ -79,7 +80,6 @@ export function NewTaskRouteScreen() {
79 const insets = useSafeAreaInsets();80 const insets = useSafeAreaInsets();
80 const chevronColor = useThemeColor("--color-chevron");81 const chevronColor = useThemeColor("--color-chevron");
81 const accentColor = useThemeColor("--color-icon-muted");82 const accentColor = useThemeColor("--color-icon-muted");
82 const borderSubtleColor = useThemeColor("--color-border-subtle");
83 const repositoryGroups = useMemo(83 const repositoryGroups = useMemo(
84 () => groupProjectsByRepository({ projects, threads }),84 () => groupProjectsByRepository({ projects, threads }),
85 [projects, threads],85 [projects, threads],
...@@ -130,7 +130,7 @@ export function NewTaskRouteScreen() {...@@ -130,7 +130,7 @@ export function NewTaskRouteScreen() {
130 <ScrollView130 <ScrollView
131 contentInsetAdjustmentBehavior="automatic"131 contentInsetAdjustmentBehavior="automatic"
132 showsVerticalScrollIndicator={false}132 showsVerticalScrollIndicator={false}
133 style={{ flex: 1 }}133 className="flex-1"
134 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}134 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
135 contentContainerStyle={{135 contentContainerStyle={{
136 paddingHorizontal: 20,136 paddingHorizontal: 20,
...@@ -175,7 +175,6 @@ export function NewTaskRouteScreen() {...@@ -175,7 +175,6 @@ export function NewTaskRouteScreen() {
175 return (175 return (
176 <Pressable176 <Pressable
177 key={item.key}177 key={item.key}
178 className="bg-card"
179 onPress={() =>178 onPress={() =>
180 navigation.navigate("NewTaskSheet", {179 navigation.navigate("NewTaskSheet", {
181 screen: "NewTaskDraft",180 screen: "NewTaskDraft",
...@@ -186,16 +185,12 @@ export function NewTaskRouteScreen() {...@@ -186,16 +185,12 @@ export function NewTaskRouteScreen() {
186 },185 },
187 })186 })
188 }187 }
189 style={{188 className={cn(
190 paddingHorizontal: 16,189 "bg-card px-4 py-3.5",
191 paddingVertical: 14,190 !isFirst && "border-t border-border-subtle",
192 borderTopWidth: isFirst ? 0 : 1,191 isFirst && "rounded-t-[24px]",
193 borderTopColor: borderSubtleColor,192 isLast && "rounded-b-[24px]",
194 borderTopLeftRadius: isFirst ? 24 : 0,193 )}
195 borderTopRightRadius: isFirst ? 24 : 0,
196 borderBottomLeftRadius: isLast ? 24 : 0,
197 borderBottomRightRadius: isLast ? 24 : 0,
198 }}
199 >194 >
200 <View className="flex-row items-center justify-between gap-3">195 <View className="flex-row items-center justify-between gap-3">
201 <View className="h-7 w-7 items-center justify-center">196 <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...@@ -688,9 +688,9 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
688688
689 return (689 return (
690 <Animated.View690 <Animated.View
691 className="px-4"
691 layout={COMPOSER_LAYOUT_TRANSITION}692 layout={COMPOSER_LAYOUT_TRANSITION}
692 style={{693 style={{
693 paddingHorizontal: 16,
694 paddingTop: isExpanded ? 8 : 6,694 paddingTop: isExpanded ? 8 : 6,
695 paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6),695 paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6),
696 experimental_backgroundImage: isDarkMode696 experimental_backgroundImage: isDarkMode
...@@ -699,21 +699,12 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer...@@ -699,21 +699,12 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
699 }}699 }}
700 >700 >
701 <Animated.View701 <Animated.View
702 className="w-full"702 className="relative w-full self-center"
703 layout={COMPOSER_LAYOUT_TRANSITION}703 layout={COMPOSER_LAYOUT_TRANSITION}
704 style={{ alignSelf: "center", maxWidth: props.contentMaxWidth, position: "relative" }}704 style={{ maxWidth: props.contentMaxWidth }}
705 >705 >
706 {composerTrigger && composerMenuItems.length > 0 ? (706 {composerTrigger && composerMenuItems.length > 0 ? (
707 <View707 <View className="absolute inset-x-0 bottom-full z-10 mb-2">
708 style={{
709 position: "absolute",
710 bottom: "100%",
711 left: 0,
712 right: 0,
713 marginBottom: 8,
714 zIndex: 10,
715 }}
716 >
717 <ComposerCommandPopover708 <ComposerCommandPopover
718 items={composerMenuItems}709 items={composerMenuItems}
719 triggerKind={composerTrigger.kind}710 triggerKind={composerTrigger.kind}
...@@ -754,9 +745,9 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer...@@ -754,9 +745,9 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
754 {/* Attachment strip — inside the card, above the text input */}745 {/* Attachment strip — inside the card, above the text input */}
755 {isExpanded ? (746 {isExpanded ? (
756 <Animated.View747 <Animated.View
748 className={props.draftAttachments.length > 0 ? "pb-2.5" : undefined}
757 entering={FadeIn.duration(160)}749 entering={FadeIn.duration(160)}
758 exiting={FadeOut.duration(120)}750 exiting={FadeOut.duration(120)}
759 style={{ paddingBottom: props.draftAttachments.length > 0 ? 10 : 0 }}
760 >751 >
761 <ComposerAttachmentStrip752 <ComposerAttachmentStrip
762 attachments={props.draftAttachments}753 attachments={props.draftAttachments}
...@@ -766,7 +757,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer...@@ -766,7 +757,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
766 </Animated.View>757 </Animated.View>
767 ) : null}758 ) : null}
768759
769 <View style={isExpanded ? undefined : { flex: 1, minWidth: 0 }}>760 <View className={isExpanded ? undefined : "min-w-0 flex-1"}>
770 <ComposerEditor761 <ComposerEditor
771 ref={inputRef}762 ref={inputRef}
772 multiline763 multiline
...@@ -797,37 +788,22 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer...@@ -797,37 +788,22 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
797 textStyle={{788 textStyle={{
798 ...bodyText,789 ...bodyText,
799 color: foregroundColor,790 color: foregroundColor,
800 fontFamily: "DMSans_400Regular",
801 }}791 }}
802 />792 />
803 </View>793 </View>
804 {!isExpanded && props.draftAttachments.length > 0 ? (794 {!isExpanded && props.draftAttachments.length > 0 ? (
805 <View style={{ flexDirection: "row", gap: 4, paddingLeft: 4 }}>795 <View className="flex-row gap-1 pl-1">
806 {props.draftAttachments.slice(0, 3).map((image) => (796 {props.draftAttachments.slice(0, 3).map((image) => (
807 <Pressable key={image.id} onPress={() => onPressImage(image.previewUri)}>797 <Pressable key={image.id} onPress={() => onPressImage(image.previewUri)}>
808 <Image798 <Image
809 source={{ uri: image.previewUri }}799 source={{ uri: image.previewUri }}
810 className="bg-subtle"800 className="size-[30px] rounded-lg bg-subtle"
811 style={{
812 width: 30,
813 height: 30,
814 borderRadius: 8,
815 }}
816 resizeMode="cover"801 resizeMode="cover"
817 />802 />
818 </Pressable>803 </Pressable>
819 ))}804 ))}
820 {props.draftAttachments.length > 3 ? (805 {props.draftAttachments.length > 3 ? (
821 <View806 <View className="size-[30px] items-center justify-center rounded-lg bg-subtle-strong">
822 className="bg-subtle-strong"
823 style={{
824 width: 30,
825 height: 30,
826 borderRadius: 8,
827 alignItems: "center",
828 justifyContent: "center",
829 }}
830 >
831 <Text className="text-foreground-muted text-2xs font-t3-bold">807 <Text className="text-foreground-muted text-2xs font-t3-bold">
832 +{props.draftAttachments.length - 3}808 +{props.draftAttachments.length - 3}
833 </Text>809 </Text>
...@@ -910,7 +886,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer...@@ -910,7 +886,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
910 {/* Queue count */}886 {/* Queue count */}
911 {props.queueCount > 0 ? (887 {props.queueCount > 0 ? (
912 <Animated.View entering={FadeIn.duration(180)} exiting={FadeOut.duration(120)}>888 <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">
914 {props.queueCount} queued message{props.queueCount === 1 ? "" : "s"} will send890 {props.queueCount} queued message{props.queueCount === 1 ? "" : "s"} will send
915 automatically.891 automatically.
916 </Text>892 </Text>
apps/mobile/src/features/threads/ThreadDetailScreen.tsx+7-8
...@@ -193,10 +193,9 @@ const WorkingDurationPill = memo(function WorkingDurationPill(props: {...@@ -193,10 +193,9 @@ const WorkingDurationPill = memo(function WorkingDurationPill(props: {
193193
194 return (194 return (
195 <Animated.View195 <Animated.View
196 className="px-4 pb-2 pt-2"196 className="shrink-0 px-4 pb-2 pt-2"
197 entering={FadeInDown.duration(200)}197 entering={FadeInDown.duration(200)}
198 exiting={FadeOut.duration(140)}198 exiting={FadeOut.duration(140)}
199 style={{ flexShrink: 0 }}
200 >199 >
201 <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]">200 <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]">
202 <View className="flex-row items-center gap-2">201 <View className="flex-row items-center gap-2">
...@@ -390,7 +389,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread...@@ -390,7 +389,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
390 <View className="flex-1">389 <View className="flex-1">
391 {showContent ? (390 {showContent ? (
392 <View391 <View
393 style={{ flex: 1 }}392 className="flex-1"
394 onTouchStart={handleFeedTouchStart}393 onTouchStart={handleFeedTouchStart}
395 onTouchMove={handleFeedTouchMove}394 onTouchMove={handleFeedTouchMove}
396 onTouchEnd={handleFeedTouchEnd}395 onTouchEnd={handleFeedTouchEnd}
...@@ -419,7 +418,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread...@@ -419,7 +418,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
419 />418 />
420 </View>419 </View>
421 ) : (420 ) : (
422 <View style={{ flex: 1 }} />421 <View className="flex-1" />
423 )}422 )}
424423
425 {/* Floating composer — sticks to keyboard via KeyboardStickyView */}424 {/* Floating composer — sticks to keyboard via KeyboardStickyView */}
...@@ -431,10 +430,11 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread...@@ -431,10 +430,11 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
431 {/* No paddingTop here: the overlay's measured height becomes the430 {/* No paddingTop here: the overlay's measured height becomes the
432 list's bottom inset, so any padding above the pill/composer431 list's bottom inset, so any padding above the pill/composer
433 pushes the resting content floor up by the same amount. */}432 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">
435 <Animated.View434 <Animated.View
435 className="w-full self-center"
436 layout={LinearTransition.duration(220)}436 layout={LinearTransition.duration(220)}
437 style={{ alignSelf: "center", maxWidth: contentMaxWidth, width: "100%" }}437 style={{ maxWidth: contentMaxWidth }}
438 >438 >
439 {props.activeWorkStartedAt ? (439 {props.activeWorkStartedAt ? (
440 <WorkingDurationPill startedAt={props.activeWorkStartedAt} />440 <WorkingDurationPill startedAt={props.activeWorkStartedAt} />
...@@ -442,10 +442,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread...@@ -442,10 +442,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
442442
443 {props.activePendingApproval || props.activePendingUserInput ? (443 {props.activePendingApproval || props.activePendingUserInput ? (
444 <Animated.View444 <Animated.View
445 className="gap-3 px-4 pb-3"445 className="shrink-0 gap-3 px-4 pb-3"
446 entering={FadeInDown.duration(220)}446 entering={FadeInDown.duration(220)}
447 exiting={FadeOut.duration(140)}447 exiting={FadeOut.duration(140)}
448 style={{ flexShrink: 0 }}
449 >448 >
450 {props.activePendingApproval ? (449 {props.activePendingApproval ? (
451 <PendingApprovalCard450 <PendingApprovalCard
apps/mobile/src/features/threads/ThreadFeed.tsx+44-53
...@@ -52,6 +52,7 @@ import Animated, {...@@ -52,6 +52,7 @@ import Animated, {
52 type SharedValue,52 type SharedValue,
53} from "react-native-reanimated";53} from "react-native-reanimated";
54import { useThemeColor } from "../../lib/useThemeColor";54import { useThemeColor } from "../../lib/useThemeColor";
55import { useFontFamily } from "../../lib/useFontFamily";
55import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic";56import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic";
56import {57import {
57 hasNativeSelectableMarkdownText,58 hasNativeSelectableMarkdownText,
...@@ -234,10 +235,6 @@ const markdownLinkStyles = StyleSheet.create({...@@ -234,10 +235,6 @@ const markdownLinkStyles = StyleSheet.create({
234 favicon: {235 favicon: {
235 borderRadius: 3,236 borderRadius: 3,
236 },237 },
237 file: {
238 fontFamily: "DMSans_700Bold",
239 fontWeight: "700",
240 },
241});238});
242239
243const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {240const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {
...@@ -250,12 +247,12 @@ const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {...@@ -250,12 +247,12 @@ const MarkdownExternalLink = memo(function MarkdownExternalLink(props: {
250247
251 return (248 return (
252 <NativeText249 <NativeText
250 className="font-sans"
253 onPress={() => {251 onPress={() => {
254 void Linking.openURL(props.href);252 void Linking.openURL(props.href);
255 }}253 }}
256 style={{254 style={{
257 color: props.color,255 color: props.color,
258 fontFamily: "DMSans_400Regular",
259 textDecorationLine: "none",256 textDecorationLine: "none",
260 }}257 }}
261 >258 >
...@@ -314,6 +311,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -314,6 +311,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
314 );311 );
315 const colors = MARKDOWN_COLORS[colorScheme === "dark" ? "dark" : "light"];312 const colors = MARKDOWN_COLORS[colorScheme === "dark" ? "dark" : "light"];
316 const inlineSkillForeground = String(useThemeColor("--color-inline-skill-foreground"));313 const inlineSkillForeground = String(useThemeColor("--color-inline-skill-foreground"));
314 const regularFontFamily = useFontFamily("regular");
315 const boldFontFamily = useFontFamily("bold");
317316
318 return useMemo(() => {317 return useMemo(() => {
319 const markdownBodyColor = colors.body;318 const markdownBodyColor = colors.body;
...@@ -365,8 +364,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -365,8 +364,8 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
365 h6: markdownFontSizes.h6,364 h6: markdownFontSizes.h6,
366 },365 },
367 fontFamilies: {366 fontFamilies: {
368 regular: "DMSans_400Regular",367 regular: regularFontFamily,
369 heading: "DMSans_700Bold",368 heading: boldFontFamily,
370 mono: "ui-monospace",369 mono: "ui-monospace",
371 },370 },
372 headingWeight: "700",371 headingWeight: "700",
...@@ -388,7 +387,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -388,7 +387,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
388 bold: {387 bold: {
389 fontWeight: "700",388 fontWeight: "700",
390 color: markdownStrongColor,389 color: markdownStrongColor,
391 fontFamily: "DMSans_700Bold",390 fontFamily: boldFontFamily,
392 },391 },
393 italic: { fontStyle: "italic" },392 italic: { fontStyle: "italic" },
394 link: {393 link: {
...@@ -404,7 +403,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -404,7 +403,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
404 marginVertical: 10,403 marginVertical: 10,
405 },404 },
406 heading: {405 heading: {
407 fontFamily: "DMSans_700Bold",406 fontFamily: boldFontFamily,
408 color: markdownStrongColor,407 color: markdownStrongColor,
409 marginTop: 18,408 marginTop: 18,
410 marginBottom: 8,409 marginBottom: 8,
...@@ -428,8 +427,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -428,8 +427,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
428 if (presentation.kind === "file") {427 if (presentation.kind === "file") {
429 return (428 return (
430 <NativeText429 <NativeText
430 className="font-t3-bold"
431 onPress={() => onLinkPress(href)}431 onPress={() => onLinkPress(href)}
432 style={[markdownLinkStyles.file, { color: inlineTextColor }]}432 style={{ color: inlineTextColor }}
433 >433 >
434 <Image434 <Image
435 source={markdownFileIconSource(presentation.icon)}435 source={markdownFileIconSource(presentation.icon)}
...@@ -453,6 +453,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -453,6 +453,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
453 const linkHref = presentation.href;453 const linkHref = presentation.href;
454 return (454 return (
455 <NativeText455 <NativeText
456 className="underline"
456 onPress={457 onPress={
457 linkHref458 linkHref
458 ? () => {459 ? () => {
...@@ -460,17 +461,14 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -460,17 +461,14 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
460 }461 }
461 : undefined462 : undefined
462 }463 }
463 style={{464 style={{ color: markdownLinkColor }}
464 color: markdownLinkColor,
465 textDecorationLine: "underline",
466 }}
467 >465 >
468 {children}466 {children}
469 </NativeText>467 </NativeText>
470 );468 );
471 },469 },
472 list: ({ node, Renderer, ordered = false, start = 1 }) => (470 list: ({ node, Renderer, ordered = false, start = 1 }) => (
473 <View style={{ marginTop: 2, marginBottom: 8 }}>471 <View className="mt-0.5 mb-2">
474 {node.children?.map((child, index) => {472 {node.children?.map((child, index) => {
475 const childKey = `${child.type}:${child.beg ?? "unknown"}:${child.end ?? "unknown"}`;473 const childKey = `${child.type}:${child.beg ?? "unknown"}:${child.end ?? "unknown"}`;
476 if (child.type === "task_list_item") {474 if (child.type === "task_list_item") {
...@@ -479,20 +477,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -479,20 +477,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
479 );477 );
480 }478 }
481 return (479 return (
482 <View480 <View className="mb-[3px] flex-row items-start" key={childKey}>
483 key={childKey}
484 style={{
485 flexDirection: "row",
486 alignItems: "flex-start",
487 marginBottom: 3,
488 }}
489 >
490 <NativeText481 <NativeText
482 className="font-sans"
491 style={{483 style={{
492 width: ordered ? 22 : 12,484 width: ordered ? 22 : 12,
493 marginRight: 5,485 marginRight: 5,
494 color: inlineTextColor,486 color: inlineTextColor,
495 fontFamily: "DMSans_400Regular",
496 fontSize: markdownFontSizes.m,487 fontSize: markdownFontSizes.m,
497 lineHeight: markdownFontSizes.bodyLineHeight,488 lineHeight: markdownFontSizes.bodyLineHeight,
498 textAlign: ordered ? "right" : "center",489 textAlign: ordered ? "right" : "center",
...@@ -500,7 +491,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -500,7 +491,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
500 >491 >
501 {ordered ? `${start + index}.` : "•"}492 {ordered ? `${start + index}.` : "•"}
502 </NativeText>493 </NativeText>
503 <View style={{ flex: 1, minWidth: 0 }}>494 <View className="min-w-0 flex-1">
504 <Renderer node={child} depth={1} inListItem parentIsText={false} />495 <Renderer node={child} depth={1} inListItem parentIsText={false} />
505 </View>496 </View>
506 </View>497 </View>
...@@ -512,9 +503,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -512,9 +503,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
512 const value = content ?? "";503 const value = content ?? "";
513 return (504 return (
514 <NativeText505 <NativeText
506 className="font-mono"
515 style={{507 style={{
516 color: inlineCodeTextColor,508 color: inlineCodeTextColor,
517 fontFamily: "ui-monospace",
518 fontSize: markdownFontSizes.codeBlockFontSize,509 fontSize: markdownFontSizes.codeBlockFontSize,
519 lineHeight: markdownFontSizes.bodyLineHeight,510 lineHeight: markdownFontSizes.bodyLineHeight,
520 }}511 }}
...@@ -530,31 +521,24 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -530,31 +521,24 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
530 : {}),521 : {}),
531 code_block: ({ content, language }) => (522 code_block: ({ content, language }) => (
532 <View523 <View
524 className="my-3 overflow-hidden rounded-[10px] border"
533 style={{525 style={{
534 backgroundColor: blockBackgroundColor,526 backgroundColor: blockBackgroundColor,
535 borderRadius: 10,
536 borderWidth: 1,
537 borderColor: markdownHrColor,527 borderColor: markdownHrColor,
538 marginVertical: 12,
539 overflow: "hidden",
540 }}528 }}
541 >529 >
542 {language ? (530 {language ? (
543 <View531 <View
532 className="border-b px-3.5 py-2"
544 style={{533 style={{
545 borderBottomWidth: 1,
546 borderBottomColor: markdownHrColor,534 borderBottomColor: markdownHrColor,
547 paddingHorizontal: 14,
548 paddingVertical: 8,
549 }}535 }}
550 >536 >
551 <NativeText537 <NativeText
538 className="font-mono uppercase opacity-70"
552 style={{539 style={{
553 color: markdownBodyColor,540 color: markdownBodyColor,
554 fontFamily: "ui-monospace",
555 fontSize: markdownFontSizes.codeBlockFontSize,541 fontSize: markdownFontSizes.codeBlockFontSize,
556 opacity: 0.7,
557 textTransform: "uppercase",
558 }}542 }}
559 >543 >
560 {language}544 {language}
...@@ -565,13 +549,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -565,13 +549,13 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
565 horizontal549 horizontal
566 showsHorizontalScrollIndicator={false}550 showsHorizontalScrollIndicator={false}
567 bounces={false}551 bounces={false}
568 contentContainerStyle={{ paddingHorizontal: 14, paddingVertical: 12 }}552 contentContainerClassName="px-3.5 py-3"
569 >553 >
570 <NativeText554 <NativeText
571 selectable555 selectable
556 className="font-mono"
572 style={{557 style={{
573 color: blockTextColor,558 color: blockTextColor,
574 fontFamily: "ui-monospace",
575 fontSize: markdownFontSizes.codeBlockFontSize,559 fontSize: markdownFontSizes.codeBlockFontSize,
576 lineHeight: markdownFontSizes.codeBlockLineHeight,560 lineHeight: markdownFontSizes.codeBlockLineHeight,
577 }}561 }}
...@@ -601,7 +585,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -601,7 +585,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
601 bold: {585 bold: {
602 fontWeight: "700",586 fontWeight: "700",
603 color: markdownUserBodyColor,587 color: markdownUserBodyColor,
604 fontFamily: "DMSans_700Bold",588 fontFamily: boldFontFamily,
605 },589 },
606 heading: {590 heading: {
607 ...baseStyles.heading,591 ...baseStyles.heading,
...@@ -655,9 +639,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -655,9 +639,9 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
655 fontSize: nativeMarkdownTypography.fontSize,639 fontSize: nativeMarkdownTypography.fontSize,
656 lineHeight: nativeMarkdownTypography.lineHeight,640 lineHeight: nativeMarkdownTypography.lineHeight,
657 headingFontSizes: nativeMarkdownTypography.headingFontSizes,641 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
658 fontFamily: "DMSans_400Regular",642 fontFamily: regularFontFamily,
659 headingFontFamily: "DMSans_700Bold",643 headingFontFamily: boldFontFamily,
660 boldFontFamily: "DMSans_700Bold",644 boldFontFamily,
661 },645 },
662 },646 },
663 assistant: {647 assistant: {
...@@ -686,13 +670,21 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe...@@ -686,13 +670,21 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe
686 fontSize: nativeMarkdownTypography.fontSize,670 fontSize: nativeMarkdownTypography.fontSize,
687 lineHeight: nativeMarkdownTypography.lineHeight,671 lineHeight: nativeMarkdownTypography.lineHeight,
688 headingFontSizes: nativeMarkdownTypography.headingFontSizes,672 headingFontSizes: nativeMarkdownTypography.headingFontSizes,
689 fontFamily: "DMSans_400Regular",673 fontFamily: regularFontFamily,
690 headingFontFamily: "DMSans_700Bold",674 headingFontFamily: boldFontFamily,
691 boldFontFamily: "DMSans_700Bold",675 boldFontFamily,
692 },676 },
693 },677 },
694 };678 };
695 }, [colors, inlineSkillForeground, markdownFontSizes, nativeMarkdownTypography, onLinkPress]);679 }, [
680 boldFontFamily,
681 colors,
682 inlineSkillForeground,
683 markdownFontSizes,
684 nativeMarkdownTypography,
685 onLinkPress,
686 regularFontFamily,
687 ]);
696}688}
697689
698function renderFeedEntry(690function renderFeedEntry(
...@@ -1019,11 +1011,10 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {...@@ -1019,11 +1011,10 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
10191011
1020 return (1012 return (
1021 <View1013 <View
1022 className="w-full overflow-hidden rounded-[16px] border"1014 className="w-full overflow-hidden rounded-[16px] border border-continuous"
1023 style={{1015 style={{
1024 backgroundColor: props.colors.background,1016 backgroundColor: props.colors.background,
1025 borderColor: props.colors.border,1017 borderColor: props.colors.border,
1026 borderCurve: "continuous",
1027 }}1018 }}
1028 >1019 >
1029 <View1020 <View
...@@ -1031,8 +1022,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {...@@ -1031,8 +1022,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
1031 style={{ borderColor: props.colors.border }}1022 style={{ borderColor: props.colors.border }}
1032 >1023 >
1033 <View1024 <View
1034 className="size-6 items-center justify-center rounded-[7px]"1025 className="size-6 items-center justify-center rounded-[7px] border-continuous"
1035 style={{ backgroundColor: props.colors.mutedBackground, borderCurve: "continuous" }}1026 style={{ backgroundColor: props.colors.mutedBackground }}
1036 >1027 >
1037 <SymbolView1028 <SymbolView
1038 name="doc.text"1029 name="doc.text"
...@@ -1085,9 +1076,9 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {...@@ -1085,9 +1076,9 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: {
1085 >1076 >
1086 <NativeText1077 <NativeText
1087 selectable1078 selectable
1079 className="font-mono"
1088 style={{1080 style={{
1089 color: props.colors.text,1081 color: props.colors.text,
1090 fontFamily: "ui-monospace",
1091 fontSize: codeSurface.fontSize,1082 fontSize: codeSurface.fontSize,
1092 lineHeight: codeSurface.rowHeight,1083 lineHeight: codeSurface.rowHeight,
1093 }}1084 }}
...@@ -1559,8 +1550,8 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {...@@ -1559,8 +1550,8 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) {
15591550
1560 return (1551 return (
1561 <>1552 <>
1562 <View style={{ flex: 1 }} onLayout={handleViewportLayout}>1553 <View className="flex-1" onLayout={handleViewportLayout}>
1563 <View style={{ flex: 1 }}>1554 <View className="flex-1">
1564 <KeyboardAwareLegendList1555 <KeyboardAwareLegendList
1565 ref={props.listRef}1556 ref={props.listRef}
1566 // The empty↔filled key remounts the list when messages first1557 // 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: {...@@ -97,8 +97,6 @@ function SidebarHeaderButtonGroup(props: {
9797
98const SIDEBAR_STICKY_HEADER_HEIGHT = 106;98const SIDEBAR_STICKY_HEADER_HEIGHT = 106;
99const SIDEBAR_STICKY_HEADER_FADE_HEIGHT = 44;99const 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)";
102const SIDEBAR_HEADER_WASH_OPACITY = {100const SIDEBAR_HEADER_WASH_OPACITY = {
103 dark: [0.22, 0.14, 0.04],101 dark: [0.22, 0.14, 0.04],
104 light: [0.46, 0.3, 0.08],102 light: [0.46, 0.3, 0.08],
...@@ -140,15 +138,13 @@ function NativeSidebarContainer(props: ThreadNavigationSidebarProps) {...@@ -140,15 +138,13 @@ function NativeSidebarContainer(props: ThreadNavigationSidebarProps) {
140 return (138 return (
141 <View139 <View
142 testID="thread-navigation-sidebar"140 testID="thread-navigation-sidebar"
143 style={[141 className="flex-1"
144 styles.container,142 style={{
145 {143 width: props.width,
146 width: props.width,144 backgroundColor,
147 backgroundColor,145 borderRightColor: borderColor,
148 borderRightColor: borderColor,146 borderRightWidth: StyleSheet.hairlineWidth,
149 borderRightWidth: StyleSheet.hairlineWidth,147 }}
150 },
151 ]}
152 >148 >
153 <SidebarNavigationShell>149 <SidebarNavigationShell>
154 <ThreadNavigationSidebarPane {...props} nativeChrome />150 <ThreadNavigationSidebarPane {...props} nativeChrome />
...@@ -338,11 +334,8 @@ function ThreadNavigationSidebarPane(...@@ -338,11 +334,8 @@ function ThreadNavigationSidebarPane(
338334
339 const backgroundColor = useThemeColor("--color-drawer");335 const backgroundColor = useThemeColor("--color-drawer");
340 const borderColor = useThemeColor("--color-border");336 const borderColor = useThemeColor("--color-border");
341 const foregroundColor = useThemeColor("--color-foreground");
342 const mutedColor = useThemeColor("--color-foreground-muted");337 const mutedColor = useThemeColor("--color-foreground-muted");
343 const placeholderColor = useThemeColor("--color-placeholder");338 const placeholderColor = useThemeColor("--color-placeholder");
344 const searchBackgroundColor =
345 colorScheme === "dark" ? IOS_SEARCH_FILL_DARK : IOS_SEARCH_FILL_LIGHT;
346 const headerFadeColor = String(backgroundColor);339 const headerFadeColor = String(backgroundColor);
347 const headerWashOpacity = SIDEBAR_HEADER_WASH_OPACITY[colorScheme];340 const headerWashOpacity = SIDEBAR_HEADER_WASH_OPACITY[colorScheme];
348 const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null);341 const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null);
...@@ -532,7 +525,7 @@ function ThreadNavigationSidebarPane(...@@ -532,7 +525,7 @@ function ThreadNavigationSidebarPane(
532 [filterIcon, filterMenu, props.onOpenSettings],525 [filterIcon, filterMenu, props.onOpenSettings],
533 );526 );
534 const listEmpty = (527 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">
536 {catalogState.isLoadingConnections529 {catalogState.isLoadingConnections
537 ? "Loading threads…"530 ? "Loading threads…"
538 : props.searchQuery.trim().length > 0531 : props.searchQuery.trim().length > 0
...@@ -566,7 +559,7 @@ function ThreadNavigationSidebarPane(...@@ -566,7 +559,7 @@ function ThreadNavigationSidebarPane(
566 unstable_headerRightItems: () => nativeHeaderItems,559 unstable_headerRightItems: () => nativeHeaderItems,
567 }}560 }}
568 />561 />
569 <View style={styles.container}>562 <View className="flex-1">
570 <SwipeableScrollGateProvider enabled={swipeEnabled}>563 <SwipeableScrollGateProvider enabled={swipeEnabled}>
571 <GestureDetector gesture={sidebarScrollGesture}>564 <GestureDetector gesture={sidebarScrollGesture}>
572 <LegendList565 <LegendList
...@@ -596,7 +589,7 @@ function ThreadNavigationSidebarPane(...@@ -596,7 +589,7 @@ function ThreadNavigationSidebarPane(
596 style={styles.threadList}589 style={styles.threadList}
597 ListHeaderComponent={590 ListHeaderComponent={
598 showsConnectionStatus ? (591 showsConnectionStatus ? (
599 <View style={styles.connectionStatusNative}>592 <View className="px-1.5 pt-0.5 pb-2">
600 <WorkspaceConnectionStatus593 <WorkspaceConnectionStatus
601 onPress={props.onOpenEnvironmentSettings}594 onPress={props.onOpenEnvironmentSettings}
602 state={catalogState}595 state={catalogState}
...@@ -617,17 +610,15 @@ function ThreadNavigationSidebarPane(...@@ -617,17 +610,15 @@ function ThreadNavigationSidebarPane(
617 return (610 return (
618 <View611 <View
619 testID="thread-navigation-sidebar"612 testID="thread-navigation-sidebar"
620 style={[613 className="flex-1"
621 styles.container,614 style={{
622 {615 width: props.width,
623 width: props.width,616 backgroundColor,
624 backgroundColor,617 borderRightColor: borderColor,
625 borderRightColor: borderColor,618 borderRightWidth: StyleSheet.hairlineWidth,
626 borderRightWidth: StyleSheet.hairlineWidth,619 }}
627 },
628 ]}
629 >620 >
630 <View style={{ flex: 1, paddingBottom: insets.bottom }}>621 <View className="flex-1" style={{ paddingBottom: insets.bottom }}>
631 <SwipeableScrollGateProvider enabled={swipeEnabled}>622 <SwipeableScrollGateProvider enabled={swipeEnabled}>
632 <GestureDetector gesture={sidebarScrollGesture}>623 <GestureDetector gesture={sidebarScrollGesture}>
633 <LegendList624 <LegendList
...@@ -660,25 +651,17 @@ function ThreadNavigationSidebarPane(...@@ -660,25 +651,17 @@ function ThreadNavigationSidebarPane(
660 </View>651 </View>
661652
662 <View653 <View
654 className="absolute inset-x-0 top-0 z-[4]"
663 onLayout={handleStickyHeaderLayout}655 onLayout={handleStickyHeaderLayout}
664 pointerEvents="box-none"656 pointerEvents="box-none"
665 style={[657 style={{ paddingTop: insets.top }}
666 styles.stickyHeader,
667 {
668 paddingTop: insets.top,
669 },
670 ]}
671 >658 >
672 <View659 <View
660 className="absolute inset-x-0 top-0"
673 pointerEvents="none"661 pointerEvents="none"
674 accessibilityElementsHidden662 accessibilityElementsHidden
675 importantForAccessibility="no-hide-descendants"663 importantForAccessibility="no-hide-descendants"
676 style={[664 style={{ height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT }}
677 styles.stickyHeaderWash,
678 {
679 height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT,
680 },
681 ]}
682 >665 >
683 <Svg width="100%" height="100%">666 <Svg width="100%" height="100%">
684 <Defs>667 <Defs>
...@@ -704,12 +687,8 @@ function ThreadNavigationSidebarPane(...@@ -704,12 +687,8 @@ function ThreadNavigationSidebarPane(
704 <Rect width="100%" height="100%" fill="url(#sidebar-header-wash)" />687 <Rect width="100%" height="100%" fill="url(#sidebar-header-wash)" />
705 </Svg>688 </Svg>
706 </View>689 </View>
707 <View style={styles.header}>690 <View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5">
708 <Text691 <Text className="flex-1 text-[34px] font-t3-bold text-foreground" numberOfLines={1}>
709 className="flex-1 text-[34px] font-t3-bold"
710 numberOfLines={1}
711 style={{ color: foregroundColor }}
712 >
713 Threads692 Threads
714 </Text>693 </Text>
715 <SidebarHeaderButtonGroup colorScheme={colorScheme}>694 <SidebarHeaderButtonGroup colorScheme={colorScheme}>
...@@ -724,14 +703,7 @@ function ThreadNavigationSidebarPane(...@@ -724,14 +703,7 @@ function ThreadNavigationSidebarPane(
724 </SidebarHeaderButtonGroup>703 </SidebarHeaderButtonGroup>
725 </View>704 </View>
726705
727 <View706 <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]">
728 style={[
729 styles.searchField,
730 {
731 backgroundColor: searchBackgroundColor,
732 },
733 ]}
734 >
735 <SymbolView name="magnifyingglass" size={15} tintColor={mutedColor} type="monochrome" />707 <SymbolView name="magnifyingglass" size={15} tintColor={mutedColor} type="monochrome" />
736 <TextInput708 <TextInput
737 ref={searchInputRef}709 ref={searchInputRef}
...@@ -743,14 +715,13 @@ function ThreadNavigationSidebarPane(...@@ -743,14 +715,13 @@ function ThreadNavigationSidebarPane(
743 placeholder="Search"715 placeholder="Search"
744 placeholderTextColor={placeholderColor}716 placeholderTextColor={placeholderColor}
745 returnKeyType="search"717 returnKeyType="search"
746 className="text-base"718 className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground"
747 style={[styles.searchInput, { color: foregroundColor }]}
748 value={props.searchQuery}719 value={props.searchQuery}
749 />720 />
750 </View>721 </View>
751722
752 {showsConnectionStatus ? (723 {showsConnectionStatus ? (
753 <View style={styles.connectionStatus}>724 <View className="px-3.5 pt-2.5">
754 <WorkspaceConnectionStatus725 <WorkspaceConnectionStatus
755 onPress={props.onOpenEnvironmentSettings}726 onPress={props.onOpenEnvironmentSettings}
756 state={catalogState}727 state={catalogState}
...@@ -764,63 +735,12 @@ function ThreadNavigationSidebarPane(...@@ -764,63 +735,12 @@ function ThreadNavigationSidebarPane(
764}735}
765736
766const styles = StyleSheet.create({737const 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 },
791 headerButtonGroup: {738 headerButtonGroup: {
792 alignItems: "center",739 alignItems: "center",
793 borderRadius: 22,740 borderRadius: 22,
794 flexDirection: "row",741 flexDirection: "row",
795 overflow: "hidden",742 overflow: "hidden",
796 },743 },
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 },
824 threadList: {744 threadList: {
825 flex: 1,745 flex: 1,
826 },746 },
apps/mobile/src/features/threads/git/GitBranchesSheet.tsx+11-45
...@@ -3,9 +3,9 @@ import { useNavigation, type StaticScreenProps } from "@react-navigation/native"...@@ -3,9 +3,9 @@ import { useNavigation, type StaticScreenProps } from "@react-navigation/native"
3import { useState } from "react";3import { useState } from "react";
4import { Pressable, ScrollView, View } from "react-native";4import { Pressable, ScrollView, View } from "react-native";
5import { useSafeAreaInsets } from "react-native-safe-area-context";5import { useSafeAreaInsets } from "react-native-safe-area-context";
6import { useThemeColor } from "../../../lib/useThemeColor";
76
8import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";7import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";
8import { cn } from "../../../lib/cn";
9import { useEnvironmentQuery } from "../../../state/query";9import { useEnvironmentQuery } from "../../../state/query";
10import { useThreadSelection } from "../../../state/use-thread-selection";10import { useThreadSelection } from "../../../state/use-thread-selection";
11import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";11import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";
...@@ -27,12 +27,6 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -27,12 +27,6 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
27 const gitState = useSelectedThreadGitState();27 const gitState = useSelectedThreadGitState();
28 const gitActions = useSelectedThreadGitActions();28 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
36 const gitStatus = useEnvironmentQuery(30 const gitStatus = useEnvironmentQuery(
37 selectedThread !== null && selectedThreadCwd !== null31 selectedThread !== null && selectedThreadCwd !== null
38 ? vcsEnvironment.status({32 ? vcsEnvironment.status({
...@@ -73,23 +67,14 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -73,23 +67,14 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
73 }}67 }}
74 >68 >
75 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">69 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
76 <Text70 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
77 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
78 style={{ letterSpacing: 1 }}
79 >
80 New branch71 New branch
81 </Text>72 </Text>
82 <TextInput73 <TextInput
83 value={newBranchName}74 value={newBranchName}
84 onChangeText={setNewBranchName}75 onChangeText={setNewBranchName}
85 placeholder="feature/mobile-polish"76 placeholder="feature/mobile-polish"
86 className="rounded-[18px] px-3.5 py-3 font-sans text-base"77 className="rounded-[18px]"
87 style={{
88 borderWidth: 1,
89 borderColor: inputBorderColor,
90 backgroundColor: inputBg,
91 color: foregroundColor,
92 }}
93 />78 />
94 <SheetActionButton79 <SheetActionButton
95 icon="plus"80 icon="plus"
...@@ -108,35 +93,20 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -108,35 +93,20 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
108 </View>93 </View>
10994
110 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">95 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
111 <Text96 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
112 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
113 style={{ letterSpacing: 1 }}
114 >
115 New worktree97 New worktree
116 </Text>98 </Text>
117 <TextInput99 <TextInput
118 value={worktreeBaseBranch}100 value={worktreeBaseBranch}
119 onChangeText={setWorktreeBaseBranch}101 onChangeText={setWorktreeBaseBranch}
120 placeholder="main"102 placeholder="main"
121 className="rounded-[18px] px-3.5 py-3 font-sans text-base"103 className="rounded-[18px]"
122 style={{
123 borderWidth: 1,
124 borderColor: inputBorderColor,
125 backgroundColor: inputBg,
126 color: foregroundColor,
127 }}
128 />104 />
129 <TextInput105 <TextInput
130 value={worktreeBranchName}106 value={worktreeBranchName}
131 onChangeText={setWorktreeBranchName}107 onChangeText={setWorktreeBranchName}
132 placeholder="feature/mobile-thread"108 placeholder="feature/mobile-thread"
133 className="rounded-[18px] px-3.5 py-3 font-sans text-base"109 className="rounded-[18px]"
134 style={{
135 borderWidth: 1,
136 borderColor: inputBorderColor,
137 backgroundColor: inputBg,
138 color: foregroundColor,
139 }}
140 />110 />
141 <SheetActionButton111 <SheetActionButton
142 icon="square.split.2x1"112 icon="square.split.2x1"
...@@ -158,10 +128,7 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -158,10 +128,7 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
158 </View>128 </View>
159129
160 <View className="gap-2">130 <View className="gap-2">
161 <Text131 <Text className="text-foreground-secondary text-2xs font-t3-bold tracking-[1px] uppercase">
162 className="text-foreground-secondary text-2xs font-t3-bold uppercase"
163 style={{ letterSpacing: 1 }}
164 >
165 Existing branches132 Existing branches
166 </Text>133 </Text>
167 {branchesLoading ? (134 {branchesLoading ? (
...@@ -185,12 +152,11 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {...@@ -185,12 +152,11 @@ export function GitBranchesSheet(_props: GitBranchesSheetProps) {
185 return (152 return (
186 <Pressable153 <Pressable
187 key={branch.name}154 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 )}
189 disabled={busy || disabled}159 disabled={busy || disabled}
190 style={{
191 borderColor: branch.current ? subtleStrongColor : borderColor,
192 opacity: busy || disabled ? 0.45 : 1,
193 }}
194 onPress={() => {160 onPress={() => {
195 void gitActions.onCheckoutSelectedThreadBranch(branch.name).then(() => {161 void gitActions.onCheckoutSelectedThreadBranch(branch.name).then(() => {
196 navigation.goBack();162 navigation.goBack();
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+12-35
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1import { useNavigation, type StaticScreenProps } from "@react-navigation/native";1import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
2import { useCallback, useState } from "react";2import { useCallback, useState } from "react";
3import { Pressable, ScrollView, View, useColorScheme } from "react-native";3import { Pressable, ScrollView, View } from "react-native";
4import { useSafeAreaInsets } from "react-native-safe-area-context";4import { useSafeAreaInsets } from "react-native-safe-area-context";
5import { useThemeColor } from "../../../lib/useThemeColor";
65
7import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";6import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText";
7import { cn } from "../../../lib/cn";
8import { useEnvironmentQuery } from "../../../state/query";8import { useEnvironmentQuery } from "../../../state/query";
9import { useThreadSelection } from "../../../state/use-thread-selection";9import { useThreadSelection } from "../../../state/use-thread-selection";
10import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";10import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions";
...@@ -21,18 +21,11 @@ type GitCommitSheetProps = StaticScreenProps<{...@@ -21,18 +21,11 @@ type GitCommitSheetProps = StaticScreenProps<{
21export function GitCommitSheet(_props: GitCommitSheetProps) {21export function GitCommitSheet(_props: GitCommitSheetProps) {
22 const navigation = useNavigation();22 const navigation = useNavigation();
23 const insets = useSafeAreaInsets();23 const insets = useSafeAreaInsets();
24 const isDarkMode = useColorScheme() === "dark";
25 const { selectedThread } = useThreadSelection();24 const { selectedThread } = useThreadSelection();
26 const { selectedThreadCwd } = useSelectedThreadWorktree();25 const { selectedThreadCwd } = useSelectedThreadWorktree();
27 const gitState = useSelectedThreadGitState();26 const gitState = useSelectedThreadGitState();
28 const gitActions = useSelectedThreadGitActions();27 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
36 const gitStatus = useEnvironmentQuery(29 const gitStatus = useEnvironmentQuery(
37 selectedThread !== null && selectedThreadCwd !== null30 selectedThread !== null && selectedThreadCwd !== null
38 ? vcsEnvironment.status({31 ? vcsEnvironment.status({
...@@ -90,10 +83,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -90,10 +83,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
90 </Text>83 </Text>
91 </View>84 </View>
92 {isDefaultRef ? (85 {isDefaultRef ? (
93 <Text86 <Text className="text-xs leading-normal text-amber-700 dark:text-amber-400">
94 className="text-xs leading-normal"
95 style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }}
96 >
97 Warning: this is the default branch.87 Warning: this is the default branch.
98 </Text>88 </Text>
99 ) : null}89 ) : null}
...@@ -138,12 +128,8 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -138,12 +128,8 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
138 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>128 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>
139 {file.path}129 {file.path}
140 </Text>130 </Text>
141 <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}>131 <Text className="text-xs font-t3-bold text-emerald-500">+{file.insertions}</Text>
142 +{file.insertions}132 <Text className="text-xs font-t3-bold text-rose-500">-{file.deletions}</Text>
143 </Text>
144 <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>
145 -{file.deletions}
146 </Text>
147 </View>133 </View>
148 ))}134 ))}
149 {selectedFiles.length > selectedFilePreview.length ? (135 {selectedFiles.length > selectedFilePreview.length ? (
...@@ -159,10 +145,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -159,10 +145,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
159 return (145 return (
160 <Pressable146 <Pressable
161 key={file.path}147 key={file.path}
162 className="rounded-[18px] border px-4 py-3"148 className={cn(
163 style={{149 "rounded-[18px] border px-4 py-3",
164 borderColor: included ? borderColor : borderSubtleColor,150 included ? "border-border" : "border-border-subtle",
165 }}151 )}
166 onPress={() => {152 onPress={() => {
167 setExcludedFiles((current) => {153 setExcludedFiles((current) => {
168 const next = new Set(current);154 const next = new Set(current);
...@@ -193,12 +179,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -193,12 +179,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
193 ) : null}179 ) : null}
194 </View>180 </View>
195 <View className="items-end gap-1">181 <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">
197 +{file.insertions}183 +{file.insertions}
198 </Text>184 </Text>
199 <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>185 <Text className="text-xs font-t3-bold text-rose-500">-{file.deletions}</Text>
200 -{file.deletions}
201 </Text>
202 </View>186 </View>
203 </View>187 </View>
204 </Pressable>188 </Pressable>
...@@ -216,14 +200,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -216,14 +200,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
216 onChangeText={setDialogCommitMessage}200 onChangeText={setDialogCommitMessage}
217 placeholder="Leave empty to auto-generate"201 placeholder="Leave empty to auto-generate"
218 textAlignVertical="top"202 textAlignVertical="top"
219 className="min-h-[128px] rounded-[20px] px-4 py-3.5 font-sans text-base"203 className="min-h-[128px] rounded-[20px] px-4 py-3.5"
220 style={{
221 minHeight: 128,
222 borderWidth: 1,
223 borderColor: inputBorderColor,
224 backgroundColor: inputBg,
225 color: foregroundColor,
226 }}
227 />204 />
228 </View>205 </View>
229206
apps/mobile/src/features/threads/git/GitConfirmSheet.tsx+3-9
...@@ -101,13 +101,10 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {...@@ -101,13 +101,10 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
101101
102 return (102 return (
103 <View collapsable={false} className="flex-1 bg-sheet">103 <View collapsable={false} className="flex-1 bg-sheet">
104 <View style={{ minHeight: 16, paddingTop: 8 }} />104 <View className="min-h-4 pt-2" />
105105
106 <View className="items-center gap-1 px-5 pb-3 pt-4">106 <View className="items-center gap-1 px-5 pb-3 pt-4">
107 <Text107 <Text className="text-xs font-t3-bold tracking-[1px] uppercase text-foreground-muted">
108 className="text-xs font-t3-bold uppercase text-foreground-muted"
109 style={{ letterSpacing: 1 }}
110 >
111 Confirm108 Confirm
112 </Text>109 </Text>
113 <Text className="text-center text-3xl font-t3-bold">110 <Text className="text-center text-3xl font-t3-bold">
...@@ -118,10 +115,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {...@@ -118,10 +115,7 @@ export function GitConfirmSheet(props: GitConfirmSheetProps) {
118 </Text>115 </Text>
119 </View>116 </View>
120117
121 <View118 <View className="gap-3 px-5 pt-2" style={{ paddingBottom: Math.max(insets.bottom, 18) + 8 }}>
122 className="gap-3 px-5"
123 style={{ paddingBottom: Math.max(insets.bottom, 18) + 8, paddingTop: 8 }}
124 >
125 <SheetActionButton119 <SheetActionButton
126 icon="arrow.right.circle"120 icon="arrow.right.circle"
127 label={copy?.continueLabel ?? "Continue"}121 label={copy?.continueLabel ?? "Continue"}
apps/mobile/src/features/threads/git/GitOverviewSheet.tsx+11-14
...@@ -47,7 +47,6 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -47,7 +47,6 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
47 const gitActions = useSelectedThreadGitActions();47 const gitActions = useSelectedThreadGitActions();
4848
49 const iconColor = useThemeColor("--color-icon");49 const iconColor = useThemeColor("--color-icon");
50 const borderColor = useThemeColor("--color-border");
51 const foregroundColor = String(useThemeColor("--color-foreground"));50 const foregroundColor = String(useThemeColor("--color-foreground"));
52 const sheetColor = String(useThemeColor("--color-sheet"));51 const sheetColor = String(useThemeColor("--color-sheet"));
5352
...@@ -205,9 +204,9 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -205,9 +204,9 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
205204
206 const content = (205 const content = (
207 <ScrollView206 <ScrollView
207 className="flex-1 bg-screen"
208 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}208 contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
209 showsVerticalScrollIndicator={false}209 showsVerticalScrollIndicator={false}
210 style={{ flex: 1, backgroundColor: sheetColor }}
211 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}210 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
212 contentContainerStyle={{211 contentContainerStyle={{
213 paddingHorizontal: isInspector ? 12 : 20,212 paddingHorizontal: isInspector ? 12 : 20,
...@@ -227,9 +226,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -227,9 +226,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
227 >226 >
228 {sheetMenuItems.map(({ item, disabledReason }, index) => (227 {sheetMenuItems.map(({ item, disabledReason }, index) => (
229 <View key={`${item.id}-${item.label}`}>228 <View key={`${item.id}-${item.label}`}>
230 {index > 0 ? (229 {index > 0 ? <View className="ml-12 h-px bg-border" /> : null}
231 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />
232 ) : null}
233 <SheetListRow230 <SheetListRow
234 icon={menuItemIconName(item.icon)}231 icon={menuItemIconName(item.icon)}
235 title={item.label}232 title={item.label}
...@@ -241,7 +238,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -241,7 +238,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
241 ))}238 ))}
242 {behindCount > 0 ? (239 {behindCount > 0 ? (
243 <>240 <>
244 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />241 <View className="ml-12 h-px bg-border" />
245 <SheetListRow242 <SheetListRow
246 icon="arrow.down.circle"243 icon="arrow.down.circle"
247 title="Pull latest"244 title="Pull latest"
...@@ -251,7 +248,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -251,7 +248,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
251 />248 />
252 </>249 </>
253 ) : null}250 ) : null}
254 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />251 <View className="ml-12 h-px bg-border" />
255 <SheetListRow252 <SheetListRow
256 icon="text.bubble"253 icon="text.bubble"
257 title="Review changes"254 title="Review changes"
...@@ -259,7 +256,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -259,7 +256,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
259 disabled={busy || !isRepo}256 disabled={busy || !isRepo}
260 onPress={() => navigation.navigate("ThreadReview", { environmentId, threadId })}257 onPress={() => navigation.navigate("ThreadReview", { environmentId, threadId })}
261 />258 />
262 <View className="ml-12 h-px" style={{ backgroundColor: borderColor }} />259 <View className="ml-12 h-px bg-border" />
263 <SheetListRow260 <SheetListRow
264 icon="point.topleft.down.curvedto.point.bottomright.up"261 icon="point.topleft.down.curvedto.point.bottomright.up"
265 title="Branches & worktrees"262 title="Branches & worktrees"
...@@ -365,8 +362,11 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -365,8 +362,11 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
365 }362 }
366 >363 >
367 <Pressable364 <Pressable
368 className="absolute right-3 top-4 h-9 w-9 items-center justify-center rounded-full bg-subtle"365 className={
369 style={{ zIndex: 1, opacity: busy ? 0.45 : 1 }}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 }
370 disabled={busy}370 disabled={busy}
371 onPress={() => void gitActions.refreshSelectedThreadGitStatus()}371 onPress={() => void gitActions.refreshSelectedThreadGitStatus()}
372 >372 >
...@@ -378,10 +378,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {...@@ -378,10 +378,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) {
378 weight="medium"378 weight="medium"
379 />379 />
380 </Pressable>380 </Pressable>
381 <Text381 <Text className="text-xs font-t3-bold tracking-[1px] uppercase text-foreground-muted">
382 className="text-xs font-t3-bold uppercase text-foreground-muted"
383 style={{ letterSpacing: 1 }}
384 >
385 {isInspector ? "Repository" : "Branch"}382 {isInspector ? "Repository" : "Branch"}
386 </Text>383 </Text>
387 <Text className={isInspector ? "pr-10 text-xl font-t3-bold" : "text-3xl font-t3-bold"}>384 <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";...@@ -3,6 +3,7 @@ import type { ComponentProps } from "react";
3import { Pressable, View } from "react-native";3import { Pressable, View } from "react-native";
4import { useThemeColor } from "../../../lib/useThemeColor";4import { useThemeColor } from "../../../lib/useThemeColor";
5import { AppText as Text } from "../../../components/AppText";5import { AppText as Text } from "../../../components/AppText";
6import { cn } from "../../../lib/cn";
67
7/* ─── Shared sheet components ──────────────────────────────────────── */8/* ─── Shared sheet components ──────────────────────────────────────── */
89
...@@ -13,51 +14,36 @@ export function SheetActionButton(props: {...@@ -13,51 +14,36 @@ export function SheetActionButton(props: {
13 readonly tone?: "primary" | "secondary" | "danger";14 readonly tone?: "primary" | "secondary" | "danger";
14 readonly onPress: () => void;15 readonly onPress: () => void;
15}) {16}) {
16 const primaryBg = useThemeColor("--color-primary");
17 const primaryFg = useThemeColor("--color-primary-foreground");17 const primaryFg = useThemeColor("--color-primary-foreground");
18 const dangerBg = useThemeColor("--color-danger");
19 const dangerBorder = useThemeColor("--color-danger-border");
20 const dangerFg = useThemeColor("--color-danger-foreground");18 const dangerFg = useThemeColor("--color-danger-foreground");
21 const secondaryBg = useThemeColor("--color-secondary");
22 const secondaryBorder = useThemeColor("--color-secondary-border");
23 const secondaryFg = useThemeColor("--color-secondary-foreground");19 const secondaryFg = useThemeColor("--color-secondary-foreground");
2420
25 const tone = props.tone ?? "secondary";21 const tone = props.tone ?? "secondary";
26 const colors =22 const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg;
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 };
4423
45 return (24 return (
46 <Pressable25 <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 )}
48 disabled={props.disabled}34 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 }}
55 onPress={props.onPress}35 onPress={props.onPress}
56 >36 >
57 <SymbolView name={props.icon} size={16} tintColor={colors.textColor} type="monochrome" />37 <SymbolView name={props.icon} size={16} tintColor={textColor} type="monochrome" />
58 <Text38 <Text
59 className="text-xs font-t3-bold uppercase"39 className={cn(
60 style={{ color: colors.textColor, letterSpacing: 0.9 }}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 )}
61 >47 >
62 {props.label}48 {props.label}
63 </Text>49 </Text>
...@@ -68,10 +54,7 @@ export function SheetActionButton(props: {...@@ -68,10 +54,7 @@ export function SheetActionButton(props: {
68export function MetaCard(props: { readonly label: string; readonly value: string }) {54export function MetaCard(props: { readonly label: string; readonly value: string }) {
69 return (55 return (
70 <View className="rounded-[18px] border border-border bg-card px-4 py-3">56 <View className="rounded-[18px] border border-border bg-card px-4 py-3">
71 <Text57 <Text className="text-foreground-muted text-2xs font-t3-bold tracking-[0.9px] uppercase">
72 className="text-foreground-muted text-2xs font-t3-bold uppercase"
73 style={{ letterSpacing: 0.9 }}
74 >
75 {props.label}58 {props.label}
76 </Text>59 </Text>
77 <Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}>60 <Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}>
...@@ -93,9 +76,8 @@ export function SheetListRow(props: {...@@ -93,9 +76,8 @@ export function SheetListRow(props: {
9376
94 return (77 return (
95 <Pressable78 <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]"
97 disabled={props.disabled}80 disabled={props.disabled}
98 style={{ opacity: props.disabled ? 0.45 : 1 }}
99 onPress={props.onPress}81 onPress={props.onPress}
100 >82 >
101 <View className="bg-subtle h-9 w-9 items-center justify-center rounded-full">83 <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: {...@@ -22,16 +22,17 @@ export function SidebarFilterButton(props: {
2222
23 return (23 return (
24 <Pressable24 <Pressable
25 className="h-11 w-[50px] cursor-pointer items-center justify-center rounded-[22px]"
25 accessibilityLabel={props.accessibilityLabel}26 accessibilityLabel={props.accessibilityLabel}
26 accessibilityRole="button"27 accessibilityRole="button"
27 hitSlop={4}28 hitSlop={4}
28 style={({ pressed }) => [29 style={({ pressed }) => [
29 styles.button,
30 props.grouped30 props.grouped
31 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }31 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }
32 : {32 : {
33 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,33 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,
34 borderColor,34 borderColor,
35 borderWidth: StyleSheet.hairlineWidth,
35 },36 },
36 ]}37 ]}
37 >38 >
...@@ -39,17 +40,3 @@ export function SidebarFilterButton(props: {...@@ -39,17 +40,3 @@ export function SidebarFilterButton(props: {
39 </Pressable>40 </Pressable>
40 );41 );
41}42}
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: {...@@ -24,17 +24,18 @@ function FallbackHeaderButton(props: {
2424
25 return (25 return (
26 <Pressable26 <Pressable
27 className="h-11 w-[50px] items-center justify-center rounded-[22px]"
27 accessibilityLabel={props.accessibilityLabel}28 accessibilityLabel={props.accessibilityLabel}
28 accessibilityRole="button"29 accessibilityRole="button"
29 hitSlop={4}30 hitSlop={4}
30 onPress={props.onPress}31 onPress={props.onPress}
31 style={({ pressed }) => [32 style={({ pressed }) => [
32 styles.button,
33 props.grouped33 props.grouped
34 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }34 ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 }
35 : {35 : {
36 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,36 backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor,
37 borderColor,37 borderColor,
38 borderWidth: StyleSheet.hairlineWidth,
38 },39 },
39 ]}40 ]}
40 >41 >
...@@ -45,7 +46,7 @@ function FallbackHeaderButton(props: {...@@ -45,7 +46,7 @@ function FallbackHeaderButton(props: {
4546
46export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {47export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {
47 return (48 return (
48 <View style={styles.actions}>49 <View className="flex-row items-center gap-0.5">
49 <FallbackHeaderButton50 <FallbackHeaderButton
50 accessibilityLabel="Open settings"51 accessibilityLabel="Open settings"
51 grouped={props.grouped}52 grouped={props.grouped}
...@@ -55,20 +56,3 @@ export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {...@@ -55,20 +56,3 @@ export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {
55 </View>56 </View>
56 );57 );
57}58}
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: {...@@ -75,7 +75,7 @@ export function ThreadInspectorContentStack(props: {
75 const Route = props.Route;75 const Route = props.Route;
7676
77 return (77 return (
78 <View style={{ flex: 1 }}>78 <View className="flex-1">
79 <InspectorContentPane79 <InspectorContentPane
80 mounted={mountedModes.has("files") || props.mode === "files"}80 mounted={mountedModes.has("files") || props.mode === "files"}
81 visible={props.mode === "files"}81 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...@@ -12,6 +12,7 @@ import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSw
12import { AppText as Text } from "../../components/AppText";12import { AppText as Text } from "../../components/AppText";
13import { ControlPillMenu } from "../../components/ControlPill";13import { ControlPillMenu } from "../../components/ControlPill";
14import { ProjectFavicon } from "../../components/ProjectFavicon";14import { ProjectFavicon } from "../../components/ProjectFavicon";
15import { cn } from "../../lib/cn";
15import { relativeTime } from "../../lib/time";16import { relativeTime } from "../../lib/time";
16import { useThemeColor } from "../../lib/useThemeColor";17import { useThemeColor } from "../../lib/useThemeColor";
17import type { PendingNewTask } from "../../state/use-pending-new-tasks";18import type { PendingNewTask } from "../../state/use-pending-new-tasks";
...@@ -101,10 +102,9 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:...@@ -101,10 +102,9 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
101 <Text102 <Text
102 className={103 className={
103 compact104 compact
104 ? "flex-shrink text-base font-t3-bold text-foreground-muted"105 ? "flex-shrink text-base font-t3-bold tracking-[0.2px] text-foreground-muted"
105 : "flex-shrink text-sm font-t3-bold text-foreground-muted"106 : "flex-shrink text-sm font-t3-bold tracking-[0.2px] text-foreground-muted"
106 }107 }
107 style={{ letterSpacing: 0.2 }}
108 numberOfLines={1}108 numberOfLines={1}
109 >109 >
110 {props.title}110 {props.title}
...@@ -236,7 +236,6 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -236,7 +236,6 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
236 const compact = props.variant === "compact";236 const compact = props.variant === "compact";
237 const separatorColor = useThemeColor("--color-separator");237 const separatorColor = useThemeColor("--color-separator");
238 const iconSubtleColor = useThemeColor("--color-icon-subtle");238 const iconSubtleColor = useThemeColor("--color-icon-subtle");
239 const foregroundColor = useThemeColor("--color-foreground");
240 const mutedColor = useThemeColor("--color-foreground-muted");239 const mutedColor = useThemeColor("--color-foreground-muted");
241 const pressedBackgroundColor = useThemeColor("--color-subtle");240 const pressedBackgroundColor = useThemeColor("--color-subtle");
242241
...@@ -254,17 +253,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -254,17 +253,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
254 );253 );
255254
256 const statusPill = (255 const statusPill = (
257 <View256 <View className="rounded-full bg-zinc-500/12 px-1.5 py-0.5 dark:bg-zinc-500/16">
258 className="bg-zinc-500/12 dark:bg-zinc-500/16"
259 style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }}
260 >
261 <Text className="text-3xs font-t3-bold text-zinc-600 dark:text-zinc-300">Pending</Text>257 <Text className="text-3xs font-t3-bold text-zinc-600 dark:text-zinc-300">Pending</Text>
262 </View>258 </View>
263 );259 );
264260
265 const subtitleRow =261 const subtitleRow =
266 subtitleParts.length > 0 ? (262 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">
268 <SymbolView264 <SymbolView
269 name="tray.and.arrow.up"265 name="tray.and.arrow.up"
270 size={10}266 size={10}
...@@ -272,9 +268,12 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -272,9 +268,12 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
272 type="monochrome"268 type="monochrome"
273 />269 />
274 <Text270 <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 }
276 numberOfLines={1}276 numberOfLines={1}
277 style={compact ? { flexShrink: 1 } : { flexShrink: 1, color: mutedColor }}
278 >277 >
279 {subtitleParts.join(" · ")}278 {subtitleParts.join(" · ")}
280 </Text>279 </Text>
...@@ -311,12 +310,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -311,12 +310,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
311 </Text>310 </Text>
312 <View className="flex-row items-center gap-2">311 <View className="flex-row items-center gap-2">
313 {statusPill}312 {statusPill}
314 <Text313 <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text>
315 className="text-base text-foreground-tertiary"
316 style={{ fontVariant: ["tabular-nums"] }}
317 >
318 {timestamp}
319 </Text>
320 <SymbolView314 <SymbolView
321 name="chevron.right"315 name="chevron.right"
322 size={13}316 size={13}
...@@ -345,22 +339,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -345,22 +339,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
345 paddingVertical: 10,339 paddingVertical: 10,
346 })}340 })}
347 >341 >
348 <View style={{ gap: 3 }}>342 <View className="gap-[3px]">
349 <View className="flex-row items-center justify-between gap-2">343 <View className="flex-row items-center justify-between gap-2">
350 <Text344 <Text className="flex-1 text-base font-t3-medium text-foreground" numberOfLines={1}>
351 className="flex-1 text-base font-t3-medium"
352 numberOfLines={1}
353 style={{ color: foregroundColor }}
354 >
355 {pendingTask.title}345 {pendingTask.title}
356 </Text>346 </Text>
357 <View className="flex-row items-center gap-2">347 <View className="flex-row items-center gap-2">
358 {statusPill}348 {statusPill}
359 <Text349 <Text className="text-xs tabular-nums text-foreground-muted" numberOfLines={1}>
360 className="text-xs"
361 numberOfLines={1}
362 style={{ color: mutedColor, fontVariant: ["tabular-nums"] }}
363 >
364 {timestamp}350 {timestamp}
365 </Text>351 </Text>
366 </View>352 </View>
...@@ -418,11 +404,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -418,11 +404,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
418 const iconSubtleColor = useThemeColor("--color-icon-subtle");404 const iconSubtleColor = useThemeColor("--color-icon-subtle");
419 const screenColor = useThemeColor("--color-screen");405 const screenColor = useThemeColor("--color-screen");
420 const drawerColor = useThemeColor("--color-drawer");406 const drawerColor = useThemeColor("--color-drawer");
421 const foregroundColor = useThemeColor("--color-foreground");
422 const mutedColor = useThemeColor("--color-foreground-muted");407 const mutedColor = useThemeColor("--color-foreground-muted");
423 const pressedBackgroundColor = useThemeColor("--color-subtle");408 const pressedBackgroundColor = useThemeColor("--color-subtle");
424 const selectedBackgroundColor = useThemeColor("--color-user-bubble");409 const selectedBackgroundColor = useThemeColor("--color-user-bubble");
425 const selectedForegroundColor = useThemeColor("--color-user-bubble-foreground");
426 const selectedMutedColor = useThemeColor("--color-user-bubble-foreground-muted");410 const selectedMutedColor = useThemeColor("--color-user-bubble-foreground-muted");
427411
428 const { thread, onSelectThread, onArchiveThread, onDeleteThread } = props;412 const { thread, onSelectThread, onArchiveThread, onDeleteThread } = props;
...@@ -436,7 +420,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -436,7 +420,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
436 );420 );
437421
438 const backgroundColor = compact ? screenColor : drawerColor;422 const backgroundColor = compact ? screenColor : drawerColor;
439 const effectiveForeground = selected ? selectedForegroundColor : foregroundColor;
440 const effectiveMuted = selected ? selectedMutedColor : mutedColor;423 const effectiveMuted = selected ? selectedMutedColor : mutedColor;
441 const effectivePressedBackground = selected ? "rgba(255,255,255,0.16)" : pressedBackgroundColor;424 const effectivePressedBackground = selected ? "rgba(255,255,255,0.16)" : pressedBackgroundColor;
442 const effectiveStatus =425 const effectiveStatus =
...@@ -464,10 +447,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -464,10 +447,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
464 );447 );
465448
466 const statusPill = effectiveStatus ? (449 const statusPill = effectiveStatus ? (
467 <View450 <View className={`${effectiveStatus.pillClassName} rounded-full px-1.5 py-0.5`}>
468 className={effectiveStatus.pillClassName}
469 style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }}
470 >
471 <Text className={`text-3xs font-t3-bold ${effectiveStatus.textClassName}`}>451 <Text className={`text-3xs font-t3-bold ${effectiveStatus.textClassName}`}>
472 {effectiveStatus.label}452 {effectiveStatus.label}
473 </Text>453 </Text>
...@@ -476,7 +456,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -476,7 +456,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
476456
477 const subtitleRow =457 const subtitleRow =
478 subtitleParts.length > 0 || pr !== null ? (458 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">
480 {subtitleParts.length > 0 ? (460 {subtitleParts.length > 0 ? (
481 <>461 <>
482 <SymbolView462 <SymbolView
...@@ -486,9 +466,13 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -486,9 +466,13 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
486 type="monochrome"466 type="monochrome"
487 />467 />
488 <Text468 <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 )}
490 numberOfLines={1}475 numberOfLines={1}
491 style={compact ? { flexShrink: 1 } : { flexShrink: 1, color: effectiveMuted }}
492 >476 >
493 {subtitleParts.join(" · ")}477 {subtitleParts.join(" · ")}
494 </Text>478 </Text>
...@@ -540,12 +524,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -540,12 +524,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
540 </Text>524 </Text>
541 <View className="flex-row items-center gap-2">525 <View className="flex-row items-center gap-2">
542 {statusPill}526 {statusPill}
543 <Text527 <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text>
544 className="text-base text-foreground-tertiary"
545 style={{ fontVariant: ["tabular-nums"] }}
546 >
547 {timestamp}
548 </Text>
549 <SymbolView528 <SymbolView
550 name="chevron.right"529 name="chevron.right"
551 size={13}530 size={13}
...@@ -584,21 +563,25 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -584,21 +563,25 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
584 paddingVertical: 10,563 paddingVertical: 10,
585 })}564 })}
586 >565 >
587 <View style={{ gap: 3 }}>566 <View className="gap-[3px]">
588 <View className="flex-row items-center justify-between gap-2">567 <View className="flex-row items-center justify-between gap-2">
589 <Text568 <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 )}
591 numberOfLines={1}573 numberOfLines={1}
592 style={{ color: effectiveForeground }}
593 >574 >
594 {thread.title}575 {thread.title}
595 </Text>576 </Text>
596 <View className="flex-row items-center gap-2">577 <View className="flex-row items-center gap-2">
597 {statusPill}578 {statusPill}
598 <Text579 <Text
599 className="text-xs"580 className={cn(
581 "text-xs tabular-nums",
582 selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted",
583 )}
600 numberOfLines={1}584 numberOfLines={1}
601 style={{ color: effectiveMuted, fontVariant: ["tabular-nums"] }}
602 >585 >
603 {timestamp}586 {timestamp}
604 </Text>587 </Text>
apps/mobile/src/features/threads/thread-work-log.tsx+2-3
...@@ -206,13 +206,12 @@ export function ThreadWorkLog(props: {...@@ -206,13 +206,12 @@ export function ThreadWorkLog(props: {
206 nestedScrollEnabled206 nestedScrollEnabled
207 directionalLockEnabled207 directionalLockEnabled
208 showsVerticalScrollIndicator208 showsVerticalScrollIndicator
209 style={{ maxHeight: 240 }}209 className="max-h-60"
210 contentContainerStyle={{ paddingRight: 8 }}210 contentContainerStyle={{ paddingRight: 8 }}
211 >211 >
212 <Text212 <Text
213 selectable213 selectable
214 className="text-2xs leading-normal text-foreground-muted"214 className="font-mono text-2xs leading-normal text-foreground-muted"
215 style={{ fontFamily: "ui-monospace" }}
216 >215 >
217 {row.fullDetail}216 {row.fullDetail}
218 </Text>217 </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";...@@ -15,6 +15,7 @@ import { Image, StyleSheet } from "react-native";
15import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";15import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";
16import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";16import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
17import { useThemeColor } from "../lib/useThemeColor";17import { useThemeColor } from "../lib/useThemeColor";
18import { useFontFamily } from "../lib/useFontFamily";
18import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";19import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
19import {20import {
20 acknowledgeComposerNativeEvent,21 acknowledgeComposerNativeEvent,
...@@ -117,6 +118,7 @@ export function ComposerEditor({...@@ -117,6 +118,7 @@ export function ComposerEditor({
117 const skillBorder = useThemeColor("--color-inline-skill-border");118 const skillBorder = useThemeColor("--color-inline-skill-border");
118 const skillText = useThemeColor("--color-inline-skill-foreground");119 const skillText = useThemeColor("--color-inline-skill-foreground");
119 const fileTint = useThemeColor("--color-icon-muted");120 const fileTint = useThemeColor("--color-icon-muted");
121 const fontFamily = useFontFamily("regular");
120122
121 useImperativeHandle(123 useImperativeHandle(
122 ref,124 ref,
...@@ -226,9 +228,7 @@ export function ComposerEditor({...@@ -226,9 +228,7 @@ export function ComposerEditor({
226 themeJson={themeJson}228 themeJson={themeJson}
227 placeholder={props.placeholder ?? ""}229 placeholder={props.placeholder ?? ""}
228 fontFamily={230 fontFamily={
229 typeof resolvedTextStyle.fontFamily === "string"231 typeof resolvedTextStyle.fontFamily === "string" ? resolvedTextStyle.fontFamily : fontFamily
230 ? resolvedTextStyle.fontFamily
231 : "DMSans_400Regular"
232 }232 }
233 fontSize={233 fontSize={
234 typeof resolvedTextStyle.fontSize === "number"234 typeof resolvedTextStyle.fontSize === "number"
apps/mobile/src/native/T3ComposerEditor.tsx+3-1
...@@ -3,6 +3,7 @@ import { useImperativeHandle, useRef } from "react";...@@ -3,6 +3,7 @@ import { useImperativeHandle, useRef } from "react";
3import { TextInput, type TextInput as RNTextInput } from "react-native";3import { TextInput, type TextInput as RNTextInput } from "react-native";
44
5import { useThemeColor } from "../lib/useThemeColor";5import { useThemeColor } from "../lib/useThemeColor";
6import { useFontFamily } from "../lib/useFontFamily";
6import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";7import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
7import { useNativePaste } from "../lib/useNativePaste";8import { useNativePaste } from "../lib/useNativePaste";
8import type { ComposerEditorProps } from "./T3ComposerEditor.types";9import type { ComposerEditorProps } from "./T3ComposerEditor.types";
...@@ -21,6 +22,7 @@ export function ComposerEditor({...@@ -21,6 +22,7 @@ export function ComposerEditor({
21 const bodyText = useScaledTextRole("body");22 const bodyText = useScaledTextRole("body");
22 const foregroundColor = useThemeColor("--color-foreground");23 const foregroundColor = useThemeColor("--color-foreground");
23 const placeholderColor = useThemeColor("--color-placeholder");24 const placeholderColor = useThemeColor("--color-placeholder");
25 const fontFamily = useFontFamily("regular");
24 const handlePaste = useNativePaste((uris) => onPasteImages?.(uris));26 const handlePaste = useNativePaste((uris) => onPasteImages?.(uris));
2527
26 useImperativeHandle(28 useImperativeHandle(
...@@ -48,7 +50,7 @@ export function ComposerEditor({...@@ -48,7 +50,7 @@ export function ComposerEditor({
48 flex: 1,50 flex: 1,
49 minHeight: 0,51 minHeight: 0,
50 color: foregroundColor,52 color: foregroundColor,
51 fontFamily: "DMSans_400Regular",53 fontFamily,
52 ...bodyText,54 ...bodyText,
53 paddingVertical: contentInsetVertical,55 paddingVertical: contentInsetVertical,
54 },56 },
apps/mobile/src/native/T3KeyboardCommands.tsx+1-1
...@@ -9,5 +9,5 @@ export function T3KeyboardCommands(...@@ -9,5 +9,5 @@ export function T3KeyboardCommands(
9 readonly onCommand: (command: HardwareKeyboardCommand) => void;9 readonly onCommand: (command: HardwareKeyboardCommand) => void;
10 }>,10 }>,
11) {11) {
12 return <View style={{ flex: 1 }}>{props.children}</View>;12 return <View className="flex-1">{props.children}</View>;
13}13}
pnpm-lock.yaml+21
...@@ -306,6 +306,9 @@ importers:...@@ -306,6 +306,9 @@ importers:
306 expo-haptics:306 expo-haptics:
307 specifier: ~56.0.3307 specifier: ~56.0.3
308 version: 56.0.3(expo@56.0.12)308 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)
309 expo-image-picker:312 expo-image-picker:
310 specifier: ~56.0.18313 specifier: ~56.0.18
311 version: 56.0.18(expo@56.0.12)314 version: 56.0.18(expo@56.0.12)
...@@ -6562,6 +6565,17 @@ packages:...@@ -6562,6 +6565,17 @@ packages:
6562 peerDependencies:6565 peerDependencies:
6563 expo: '*'6566 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
6565 expo-json-utils@56.0.0:6579 expo-json-utils@56.0.0:
6566 resolution: {integrity: sha512-lUqyv9aIGDbYTQ5Nux2FnH2/Dz0w5uJ8Pr080eS0StXi2jr5OmuMNErpzUnpfnYOU55xKotd4AHv68PfV/ludg==}6580 resolution: {integrity: sha512-lUqyv9aIGDbYTQ5Nux2FnH2/Dz0w5uJ8Pr080eS0StXi2jr5OmuMNErpzUnpfnYOU55xKotd4AHv68PfV/ludg==}
65676581
...@@ -16695,6 +16709,13 @@ snapshots:...@@ -16695,6 +16709,13 @@ snapshots:
16695 expo: 56.0.12(8895228379997a2a064f9644cda56ed0)16709 expo: 56.0.12(8895228379997a2a064f9644cda56ed0)
16696 expo-image-loader: 56.0.3(expo@56.0.12)16710 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
16698 expo-json-utils@56.0.0: {}16719 expo-json-utils@56.0.0: {}
1669916720
16700 expo-keep-awake@56.0.3(expo@56.0.12)(react@19.2.3):16721 expo-keep-awake@56.0.3(expo@56.0.12)(react@19.2.3):