Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
SettingsLegalDocumentExternalHeaderButton,
} from "./features/settings/components/SettingsLegalDocumentRouteScreen";
import { useAppShortcuts } from "./features/shortcuts/useAppShortcuts";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "./native/native-glass";
import { nativeHeaderScrollEdgeEffects } from "./native/StackHeader";
import { useThreadOutboxDrain } from "./state/use-thread-outbox-drain";

Expand All @@ -71,19 +72,24 @@ type AppScreenOptions = NativeStackNavigationOptions & {
// Shared header presets. Screens only override genuinely dynamic values (titles,
// subtitles, toolbar items, search callbacks) via NativeStackScreenOptions.
//
// GLASS: transparent header over the screen's primary scroll view, with the iOS 26
// scroll-edge blur sampling the content (Home, Thread, Files tree, settings sheet).
// GLASS: transparent header over the screen's primary scroll view on supported
// iOS versions. Pre-glass iOS gets the same solid material as internal-scroll
// surfaces so content is laid out below the bar instead of underlapping it.
const GLASS_HEADER_OPTIONS: AppScreenOptions = {
headerBackButtonDisplayMode: "minimal",
headerBackTitle: "",
headerLargeTitle: false,
headerShadowVisible: false,
headerShown: true,
headerStyle: Platform.OS === "ios" ? { backgroundColor: "transparent" } : undefined,
headerStyle: NATIVE_LIQUID_GLASS_SUPPORTED
? { backgroundColor: "transparent" }
: SHEET_BACKGROUND_COLOR !== undefined
? { backgroundColor: SHEET_BACKGROUND_COLOR as unknown as string }
: undefined,
headerTitleStyle: { fontSize: 18, fontWeight: "800" },
headerTransparent: Platform.OS === "ios",
scrollEdgeEffects: Platform.OS === "ios" ? HEADER_SCROLL_EDGE_EFFECTS : undefined,
unstable_navigationItemStyle: Platform.OS === "ios" ? "editor" : undefined,
headerTransparent: NATIVE_LIQUID_GLASS_SUPPORTED,
scrollEdgeEffects: NATIVE_LIQUID_GLASS_SUPPORTED ? HEADER_SCROLL_EDGE_EFFECTS : undefined,
unstable_navigationItemStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? "editor" : undefined,
};

// SOLID: opaque sheet-colored header for surfaces whose content scrolls internally
Expand Down
18 changes: 7 additions & 11 deletions apps/mobile/src/features/files/FileTreeBrowser.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import type { ProjectEntry } from "@t3tools/contracts";
import { SymbolView } from "../../components/AppSymbol";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
ActivityIndicator,
FlatList,
Platform,
Pressable,
RefreshControl,
View,
} from "react-native";
import { ActivityIndicator, FlatList, Pressable, RefreshControl, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AppText as Text } from "../../components/AppText";
import { PierreEntryIcon } from "../../components/PierreEntryIcon";
import { cn } from "../../lib/cn";
import { useThemeColor } from "../../lib/useThemeColor";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import {
buildFileTree,
defaultExpandedTreePaths,
Expand Down Expand Up @@ -129,7 +123,7 @@ export function FileTreeBrowser(props: {
const insets = useSafeAreaInsets();
// Native transparent-header height ≈ safe-area top + nav bar (~44). Matches the
// observed adjustedContentInset bottom (~102) seen in the native trace.
const headerInset = Platform.OS === "ios" ? insets.top + 44 : 0;
const headerInset = NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 44 : 0;
const iconColor = String(useThemeColor("--color-icon-muted"));
const { onPreviewFile, onSelectFile, selectedPath: controlledSelectedPath } = props;
const controlledSelectedPathRef = useRef(controlledSelectedPath);
Expand Down Expand Up @@ -249,9 +243,11 @@ export function FileTreeBrowser(props: {
className="flex-1"
data={visibleNodes}
keyExtractor={(item) => item.node.path}
contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
contentInsetAdjustmentBehavior={NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High files/FileTreeBrowser.tsx:246

On iOS devices that don't support liquid glass, contentInsetAdjustmentBehavior is now set to "never", so the FlatList receives no automatic top inset and the first file rows render beneath the native header. This regresses from the previous Platform.OS === "ios" ? "automatic" : "never" behavior, which applied the inset on all iOS versions. The gating now uses NATIVE_LIQUID_GLASS_SUPPORTED instead of Platform.OS === "ios", so pre-glass iOS loses the inset even though the header is still translucent. Consider gating on Platform.OS === "ios" (or header translucency) rather than liquid-glass capability.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/files/FileTreeBrowser.tsx around line 246:

On iOS devices that don't support liquid glass, `contentInsetAdjustmentBehavior` is now set to `"never"`, so the FlatList receives no automatic top inset and the first file rows render beneath the native header. This regresses from the previous `Platform.OS === "ios" ? "automatic" : "never"` behavior, which applied the inset on all iOS versions. The gating now uses `NATIVE_LIQUID_GLASS_SUPPORTED` instead of `Platform.OS === "ios"`, so pre-glass iOS loses the inset even though the header is still translucent. Consider gating on `Platform.OS === "ios"` (or header translucency) rather than liquid-glass capability.

scrollIndicatorInsets={
Platform.OS === "ios" ? { top: headerInset, left: 0, right: 0, bottom: 0 } : undefined
NATIVE_LIQUID_GLASS_SUPPORTED
? { top: headerInset, left: 0, right: 0, bottom: 0 }
: undefined
}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { EmptyState } from "../../components/EmptyState";
import type { WorkspaceState } from "../../state/workspaceModel";
import type { SavedRemoteConnection } from "../../lib/connection";
import { scopedProjectKey } from "../../lib/scopedEntities";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences";
import type { PendingNewTask } from "../../state/use-pending-new-tasks";
import {
Expand Down Expand Up @@ -389,7 +390,7 @@ export function HomeScreen(props: HomeScreenProps) {
className="flex-1 items-center justify-center bg-screen px-8"
style={{
paddingBottom: Math.max(insets.bottom, 24),
paddingTop: Platform.OS === "ios" ? insets.top + 72 : 0,
paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0,
}}
>
<View className="w-full max-w-[430px]">
Expand Down Expand Up @@ -461,8 +462,8 @@ export function HomeScreen(props: HomeScreenProps) {
ListHeaderComponent={listHeader}
ListEmptyComponent={listEmpty}
style={{ flex: 1 }}
automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"}
contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"}
automaticallyAdjustsScrollIndicatorInsets={NATIVE_LIQUID_GLASS_SUPPORTED}
contentInsetAdjustmentBehavior={NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never"}
showsVerticalScrollIndicator={false}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
Expand Down
9 changes: 8 additions & 1 deletion apps/mobile/src/features/threads/ThreadDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { StatusTone } from "../../components/StatusPill";
import type { DraftComposerImageAttachment } from "../../lib/composerImages";
import { CHAT_CONTENT_MAX_WIDTH, type LayoutVariant } from "../../lib/layout";
import { scopedThreadKey } from "../../lib/scopedEntities";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import type {
PendingApproval,
PendingUserInput,
Expand Down Expand Up @@ -197,7 +198,13 @@ const WorkingDurationPill = memo(function WorkingDurationPill(props: {
entering={FadeInDown.duration(200)}
exiting={FadeOut.duration(140)}
>
<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]">
<View
className={
Platform.OS === "ios" && !NATIVE_LIQUID_GLASS_SUPPORTED
? "self-start rounded-full border border-border bg-card px-3 py-2"
: "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]"
}
>
<View className="flex-row items-center gap-2">
<View className="flex-row items-center gap-1">
<View className="h-1.5 w-1.5 rounded-full bg-neutral-400 dark:bg-neutral-500" />
Expand Down
7 changes: 5 additions & 2 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
import { AppText as Text } from "../../components/AppText";
import { ControlPillMenu } from "../../components/ControlPill";
import { SymbolView } from "../../components/AppSymbol";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
import { useThemeColor } from "../../lib/useThemeColor";
Expand Down Expand Up @@ -571,8 +572,10 @@ function ThreadNavigationSidebarPane(
itemsAreEqual={homeListItemsAreEqual}
keyExtractor={(item) => item.key}
renderItem={renderListItem}
automaticallyAdjustsScrollIndicatorInsets
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustsScrollIndicatorInsets={NATIVE_LIQUID_GLASS_SUPPORTED}
contentInsetAdjustmentBehavior={
NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never"
}
contentContainerStyle={[
styles.threadListContent,
{
Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/src/features/threads/ThreadRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "../../components/AndroidScreenHeader";
import { LoadingScreen } from "../../components/LoadingScreen";
import { scopedThreadKey } from "../../lib/scopedEntities";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import { connectionTone } from "../connection/connectionTone";

import {
Expand Down Expand Up @@ -278,7 +279,7 @@ function ThreadRouteContent(
);

/* ─── Native header theming ──────────────────────────────────────── */
const usesNativeHeaderGlass = Platform.OS === "ios";
const usesNativeHeaderGlass = NATIVE_LIQUID_GLASS_SUPPORTED;
const headerSubtitle = [
selectedThreadProject?.title ?? null,
selectedEnvironmentConnection?.environmentLabel ?? null,
Expand Down
9 changes: 5 additions & 4 deletions apps/mobile/src/features/threads/sidebar-navigation-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import type { ReactNode } from "react";
import { Platform, useColorScheme } from "react-native";

import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";

const SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version);
Expand All @@ -33,12 +34,12 @@ const SIDEBAR_SCREEN_OPTIONS: SidebarScreenOptions = {
headerLargeTitle: false,
headerShadowVisible: false,
headerShown: true,
headerStyle: { backgroundColor: "transparent" },
headerStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? { backgroundColor: "transparent" } : undefined,
headerTitleStyle: { fontSize: 18, fontWeight: "800" },
headerTransparent: true,
scrollEdgeEffects: SCROLL_EDGE_EFFECTS,
headerTransparent: NATIVE_LIQUID_GLASS_SUPPORTED,
scrollEdgeEffects: NATIVE_LIQUID_GLASS_SUPPORTED ? SCROLL_EDGE_EFFECTS : undefined,
title: "Threads",
unstable_navigationItemStyle: "editor",
unstable_navigationItemStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? "editor" : undefined,
};

const SidebarStack = createNativeStackNavigator();
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/src/lib/native-glass-capability.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vite-plus/test";

import { supportsNativeLiquidGlass } from "./native-glass-capability";

describe("supportsNativeLiquidGlass", () => {
it("uses native liquid glass when iOS reports the capability", () => {
expect(supportsNativeLiquidGlass("ios", true)).toBe(true);
});

it("keeps pre-glass iOS on the solid fallback", () => {
expect(supportsNativeLiquidGlass("ios", false)).toBe(false);
});

it("does not enable iOS liquid-glass layout behavior on other platforms", () => {
expect(supportsNativeLiquidGlass("android", true)).toBe(false);
expect(supportsNativeLiquidGlass("web", true)).toBe(false);
});
});
6 changes: 6 additions & 0 deletions apps/mobile/src/lib/native-glass-capability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function supportsNativeLiquidGlass(
platform: string,
nativeCapabilityAvailable: boolean,
): boolean {
return platform === "ios" && nativeCapabilityAvailable;
}
9 changes: 9 additions & 0 deletions apps/mobile/src/native/native-glass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { isLiquidGlassSupported } from "@callstack/liquid-glass";
import { Platform } from "react-native";

import { supportsNativeLiquidGlass } from "../lib/native-glass-capability";

export const NATIVE_LIQUID_GLASS_SUPPORTED = supportsNativeLiquidGlass(
Platform.OS,
isLiquidGlassSupported,
);
Loading