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