Skip to content

Commit 27fbff5

Browse files
Merge branch 'main' into fix/uncached-thread-links
2 parents daba3ff + dd5f98e commit 27fbff5

64 files changed

Lines changed: 8700 additions & 21102 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/desktop/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@t3tools/desktop",
3-
"version": "0.1.6",
3+
"version": "0.1.7",
44
"private": true,
55
"type": "module",
66
"main": "dist-electron/boot.cjs",

‎apps/mobile/src/components/ui/ScreenContent.tsx‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,46 @@
11
import type { ReactNode } from "react";
2-
import { Platform, View } from "react-native";
2+
import { Platform, View, type LayoutChangeEvent } from "react-native";
33

4+
const SURFACE_CLASS_NAMES = {
5+
sheet: "bg-sheet-solid",
6+
screen: "bg-screen",
7+
canvas: "bg-thread-canvas",
8+
drawer: "bg-drawer",
9+
} as const;
10+
11+
/**
12+
* The body below a screen's AppBar. On Android it is the rounded card every pane and sheet
13+
* draws under its bar; a screen picks only the card's surface.
14+
*/
415
export function ScreenContent({
516
children,
17+
surface = "sheet",
618
insetHorizontal = false,
719
fitToContents = false,
20+
onLayout,
821
}: {
922
readonly children: ReactNode;
23+
readonly surface?: keyof typeof SURFACE_CLASS_NAMES;
1024
readonly insetHorizontal?: boolean;
1125
readonly fitToContents?: boolean;
26+
readonly onLayout?: (event: LayoutChangeEvent) => void;
1227
}) {
13-
if (Platform.OS !== "android") return children;
28+
if (Platform.OS !== "android") {
29+
return onLayout ? (
30+
<View className="flex-1" onLayout={onLayout}>
31+
{children}
32+
</View>
33+
) : (
34+
children
35+
);
36+
}
1437

1538
return (
1639
<View className={fitToContents ? "shrink bg-header" : "flex-1 bg-header"}>
1740
<View
18-
className="overflow-hidden rounded-t-[28px] bg-sheet-solid"
41+
className={`overflow-hidden rounded-t-[28px] ${SURFACE_CLASS_NAMES[surface]}`}
42+
collapsable={false}
43+
onLayout={onLayout}
1944
style={{
2045
flexShrink: 1,
2146
flexGrow: fitToContents ? 0 : 1,

‎apps/mobile/src/features/home/HomeScreen.tsx‎

Lines changed: 122 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { usePaneRole } from "../../components/ui/Pane";
2+
import { ScreenContent } from "../../components/ui/ScreenContent";
23
import { useActiveNewTask } from "../../state/active-new-task";
34
import { AppActivityIndicator } from "../../components/ui/AppActivityIndicator";
45
import { useAndroidControlSizing } from "../../components/ui/useAndroidControlSizing";
@@ -672,45 +673,51 @@ export function HomeScreen(props: HomeScreenProps) {
672673
if (!hasAnyThreads) {
673674
return (
674675
<View className={containerClassName}>
675-
<View
676-
className={cn(
677-
"flex-1 items-center justify-center px-8",
678-
contentBackground,
679-
Platform.OS === "android" && "overflow-hidden rounded-t-[28px]",
680-
)}
681-
style={{
682-
paddingBottom: Platform.OS === "ios" ? iosBottomClearance : Math.max(insets.bottom, 24),
683-
paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0,
684-
paddingLeft: 32 + (contentSideInsets?.left ?? 0),
685-
paddingRight: 32 + (contentSideInsets?.right ?? 0),
686-
}}
687-
>
688-
<View className="w-full max-w-[430px]">
689-
<EmptyState
690-
title={emptyState.title}
691-
detail={emptyState.detail}
692-
actionLabel={!props.catalogState.hasReadyEnvironment ? "Add environment" : undefined}
693-
onAction={!props.catalogState.hasReadyEnvironment ? props.onAddConnection : undefined}
694-
action={
695-
Platform.OS === "android" && !props.catalogState.hasReadyEnvironment ? (
696-
<FloatingActionButton
697-
label="Add environment"
698-
icon="plus"
699-
variant="extended"
700-
tone="primary"
701-
onPress={props.onAddConnection}
702-
/>
703-
) : undefined
704-
}
705-
variant="plain"
706-
/>
707-
{emptyState.loading ? (
708-
<View className="mt-4 items-center">
709-
<AppActivityIndicator colorClassName="accent-icon-muted" />
710-
</View>
711-
) : null}
676+
<ScreenContent surface={sidebarSurface ? "drawer" : "screen"}>
677+
<View
678+
className={cn(
679+
"flex-1 items-center justify-center px-8",
680+
Platform.OS !== "android" && contentBackground,
681+
)}
682+
style={{
683+
paddingBottom:
684+
Platform.OS === "ios" ? iosBottomClearance : Math.max(insets.bottom, 24),
685+
paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0,
686+
paddingLeft: 32 + (contentSideInsets?.left ?? 0),
687+
paddingRight: 32 + (contentSideInsets?.right ?? 0),
688+
}}
689+
>
690+
<View className="w-full max-w-[430px]">
691+
<EmptyState
692+
title={emptyState.title}
693+
detail={emptyState.detail}
694+
actionLabel={
695+
!props.catalogState.hasReadyEnvironment ? "Add environment" : undefined
696+
}
697+
onAction={
698+
!props.catalogState.hasReadyEnvironment ? props.onAddConnection : undefined
699+
}
700+
action={
701+
Platform.OS === "android" && !props.catalogState.hasReadyEnvironment ? (
702+
<FloatingActionButton
703+
label="Add environment"
704+
icon="plus"
705+
variant="extended"
706+
tone="primary"
707+
onPress={props.onAddConnection}
708+
/>
709+
) : undefined
710+
}
711+
variant="plain"
712+
/>
713+
{emptyState.loading ? (
714+
<View className="mt-4 items-center">
715+
<AppActivityIndicator colorClassName="accent-icon-muted" />
716+
</View>
717+
) : null}
718+
</View>
712719
</View>
713-
</View>
720+
</ScreenContent>
714721
</View>
715722
);
716723
}
@@ -766,97 +773,93 @@ export function HomeScreen(props: HomeScreenProps) {
766773

767774
if (Platform.OS === "android" && threadListV2Items.length === 0) {
768775
return (
769-
<View className="flex-1 bg-header">
776+
<ScreenContent surface="screen">
770777
<View
771-
className="flex-1 items-center justify-center overflow-hidden rounded-t-[28px] bg-screen px-4"
778+
className="flex-1 items-center justify-center px-4"
772779
style={{ paddingBottom: insets.bottom }}
773780
>
774781
{v2ListEmpty}
775782
</View>
776-
</View>
783+
</ScreenContent>
777784
);
778785
}
779786

780787
return (
781788
<View className={containerClassName}>
782-
<View
783-
className={
784-
Platform.OS === "android"
785-
? "flex-1 overflow-hidden rounded-t-[28px] bg-screen"
786-
: cn("flex-1", contentBackground)
787-
}
788-
>
789-
<SwipeableScrollGateProvider enabled={swipeEnabled} activation={swipeRowActivation}>
790-
<GestureDetector gesture={homeScrollGesture}>
791-
<AnimatedLegendList
792-
itemLayoutAnimation={sidebarSurface ? THREAD_SIDEBAR_ROW_LAYOUT : undefined}
793-
ref={listRef}
794-
onLoad={() => activateVisibleRows(threadListV2Items)}
795-
onTouchStart={(event) => trackListTouches(event, true)}
796-
onTouchEnd={(event) => trackListTouches(event, false)}
797-
onTouchCancel={(event) => trackListTouches(event, false)}
798-
renderScrollComponent={Platform.OS === "ios" ? renderHomeScrollView : undefined}
799-
data={threadListV2Items}
800-
renderItem={renderV2Item}
801-
keyExtractor={v2KeyExtractor}
802-
getItemType={(item) => item.type}
803-
itemsAreEqual={threadListV2ListItemsAreEqual}
804-
estimatedItemSize={ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT}
805-
drawDistance={THREAD_LIST_V2_DRAW_DISTANCE}
806-
recycleItems
807-
extraData={v2ExtraData}
808-
ListHeaderComponent={v2ListHeader}
809-
ListFooterComponent={
810-
settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? (
811-
<ThreadListV2ShowMoreRow
812-
hiddenCount={threadListV2Layout.hiddenSettledCount}
813-
{...(olderSettledShelf.loading
814-
? { state: "loading" as const }
815-
: olderSettledShelf.failed
816-
? { state: "failed" as const }
817-
: {})}
818-
onPress={() => {
819-
olderSettledShelf.retry();
820-
showMoreSettled();
821-
}}
822-
/>
823-
) : null
824-
}
825-
ListEmptyComponent={v2ListEmpty}
826-
style={{ flex: 1 }}
827-
automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"}
828-
// UIKit owns the bar's inset, so a minimizing bar and a resized column move
829-
// the list in the same frame as the bar.
830-
contentInsetAdjustmentBehavior="automatic"
831-
contentInsetStartAdjustment={Platform.OS === "ios" ? insets.top : 0}
832-
// RN scrollTo clamps against raw insets and ignores the automatic bar inset.
833-
scrollToOverflowEnabled
834-
initialScrollOffset={
835-
initialScrollOffset > 0
836-
? initialScrollOffset - (Platform.OS === "ios" ? insets.top : 0)
837-
: undefined
838-
}
839-
showsVerticalScrollIndicator={false}
840-
keyboardDismissMode="on-drag"
841-
keyboardShouldPersistTaps="handled"
842-
{...scrollGateHandlers}
843-
scrollEventThrottle={16}
844-
contentContainerStyle={{
845-
paddingLeft:
846-
(contentSideInsets?.left ?? 0) + (sidebarSurface ? 8 : 0) + androidListGutter,
847-
paddingRight:
848-
(contentSideInsets?.right ?? 0) + (sidebarSurface ? 8 : 0) + androidListGutter,
849-
paddingBottom:
850-
Platform.OS === "ios"
851-
? iosBottomClearance - (columnMetrics?.safeArea.bottom ?? insets.bottom)
852-
: (workspace?.bottomClearance ??
853-
Math.max(insets.bottom, 16) +
854-
(Platform.OS === "android" ? fabClearance : 88)),
855-
}}
856-
/>
857-
</GestureDetector>
858-
</SwipeableScrollGateProvider>
859-
</View>
789+
<ScreenContent surface="screen">
790+
<View className={cn("flex-1", Platform.OS !== "android" && contentBackground)}>
791+
<SwipeableScrollGateProvider enabled={swipeEnabled} activation={swipeRowActivation}>
792+
<GestureDetector gesture={homeScrollGesture}>
793+
<AnimatedLegendList
794+
itemLayoutAnimation={sidebarSurface ? THREAD_SIDEBAR_ROW_LAYOUT : undefined}
795+
ref={listRef}
796+
onLoad={() => activateVisibleRows(threadListV2Items)}
797+
onTouchStart={(event) => trackListTouches(event, true)}
798+
onTouchEnd={(event) => trackListTouches(event, false)}
799+
onTouchCancel={(event) => trackListTouches(event, false)}
800+
renderScrollComponent={Platform.OS === "ios" ? renderHomeScrollView : undefined}
801+
data={threadListV2Items}
802+
renderItem={renderV2Item}
803+
keyExtractor={v2KeyExtractor}
804+
getItemType={(item) => item.type}
805+
itemsAreEqual={threadListV2ListItemsAreEqual}
806+
estimatedItemSize={ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT}
807+
drawDistance={THREAD_LIST_V2_DRAW_DISTANCE}
808+
recycleItems
809+
extraData={v2ExtraData}
810+
ListHeaderComponent={v2ListHeader}
811+
ListFooterComponent={
812+
settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? (
813+
<ThreadListV2ShowMoreRow
814+
hiddenCount={threadListV2Layout.hiddenSettledCount}
815+
{...(olderSettledShelf.loading
816+
? { state: "loading" as const }
817+
: olderSettledShelf.failed
818+
? { state: "failed" as const }
819+
: {})}
820+
onPress={() => {
821+
olderSettledShelf.retry();
822+
showMoreSettled();
823+
}}
824+
/>
825+
) : null
826+
}
827+
ListEmptyComponent={v2ListEmpty}
828+
style={{ flex: 1 }}
829+
automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"}
830+
// UIKit owns the bar's inset, so a minimizing bar and a resized column move
831+
// the list in the same frame as the bar.
832+
contentInsetAdjustmentBehavior="automatic"
833+
contentInsetStartAdjustment={Platform.OS === "ios" ? insets.top : 0}
834+
// RN scrollTo clamps against raw insets and ignores the automatic bar inset.
835+
scrollToOverflowEnabled
836+
initialScrollOffset={
837+
initialScrollOffset > 0
838+
? initialScrollOffset - (Platform.OS === "ios" ? insets.top : 0)
839+
: undefined
840+
}
841+
showsVerticalScrollIndicator={false}
842+
keyboardDismissMode="on-drag"
843+
keyboardShouldPersistTaps="handled"
844+
{...scrollGateHandlers}
845+
scrollEventThrottle={16}
846+
contentContainerStyle={{
847+
paddingLeft:
848+
(contentSideInsets?.left ?? 0) + (sidebarSurface ? 8 : 0) + androidListGutter,
849+
paddingRight:
850+
(contentSideInsets?.right ?? 0) + (sidebarSurface ? 8 : 0) + androidListGutter,
851+
paddingBottom:
852+
Platform.OS === "ios"
853+
? iosBottomClearance - (columnMetrics?.safeArea.bottom ?? insets.bottom)
854+
: (workspace?.bottomClearance ??
855+
Math.max(insets.bottom, 16) +
856+
(Platform.OS === "android" ? fabClearance : 88)),
857+
}}
858+
/>
859+
</GestureDetector>
860+
</SwipeableScrollGateProvider>
861+
</View>
862+
</ScreenContent>
860863
</View>
861864
);
862865
}

‎apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,10 +933,11 @@ function AdaptiveWorkspaceLayoutContent(
933933
// Android can resize the native row before React removes its split panes on a fold.
934934
const androidSplit = Platform.OS === "android" && layout.usesSplitView;
935935
// In the strip the column itself slides with the sidebar: Android only delivers touches inside a
936-
// parent's bounds, so content may not slide out of its column.
936+
// parent's bounds, so content may not slide out of its column. Every branch sets every key:
937+
// Reanimated keeps a key a branch drops, so a fold mid-slide would leave the column shifted.
937938
const androidContentSlotStyle = useAnimatedStyle(() =>
938939
!androidSplit
939-
? { width: "100%" as const }
940+
? { width: "100%" as const, marginRight: 0, transform: [{ translateX: 0 }] }
940941
: stripSidebar
941942
? {
942943
// Wide enough for the pane at either width; the margin gives the sidebar's share
@@ -948,7 +949,7 @@ function AdaptiveWorkspaceLayoutContent(
948949
marginRight: -sidebarSlotWidth,
949950
transform: [{ translateX: stripOffset.value }],
950951
}
951-
: { width: renderedContentWidth.value },
952+
: { width: renderedContentWidth.value, marginRight: 0, transform: [{ translateX: 0 }] },
952953
);
953954
// While the sidebar animates, clip the settled navigator instead of reflowing its text every frame.
954955
// In the strip it lifts entirely: the content outgrows its column, and a cap that follows

0 commit comments

Comments
 (0)