Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const clientSettings: ClientSettings = {
showSkillsInSlashMenu: false,
providerModelPreferences: {},
sidebarAutoSettleAfterDays: 3,
sidebarAutoSettleOnMerge: true,
sidebarAutoSettleMode: "change-request",
sidebarProjectGroupingMode: "repository_path",
sidebarProjectGroupingOverrides: {
"environment-1:/tmp/project-a": "separate",
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
import { NewTaskFlowProvider } from "./features/threads/new-task-flow-provider";
import { NewTaskRouteScreen } from "./features/threads/NewTaskRouteScreen";
import { SettingsAppearanceRouteScreen } from "./features/settings/SettingsAppearanceRouteScreen";
import { SettingsAutoSettleRouteScreen } from "./features/settings/SettingsAutoSettleRouteScreen";
import { SettingsClientStorageRouteScreen } from "./features/settings/SettingsClientStorageRouteScreen";
import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteScreen";
import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";
Expand Down Expand Up @@ -177,6 +178,13 @@ const SettingsContentStack = createNativeStackNavigator({
title: "Appearance",
},
}),
SettingsAutoSettle: createNativeStackScreen({
screen: SettingsAutoSettleRouteScreen,
linking: "thread-settling",
options: {
title: "Thread Settling",
},
}),
SettingsProjectGrouping: createNativeStackScreen({
screen: SettingsProjectGroupingRouteScreen,
linking: "project-grouping",
Expand Down
13 changes: 8 additions & 5 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { WorkspaceEnvironment, WorkspaceState } from "../../state/workspace
import type { SavedRemoteConnection } from "../../lib/connection";
import { scopedProjectKey } from "../../lib/scopedEntities";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
import { resolveMobileAutoSettlePreferences } from "../../persistence/mobile-preferences";
import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences";
import { useThreadSearch } from "../../state/queries";
import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled";
Expand Down Expand Up @@ -209,9 +210,9 @@ export function HomeScreen(props: HomeScreenProps) {
>(() => new Map());
const preferencesResult = useAtomValue(mobilePreferencesAtom);
const threadListV2Enabled = useThreadListV2Enabled();
const autoSettleOnMerge =
!AsyncResult.isSuccess(preferencesResult) ||
preferencesResult.value.autoSettleOnMerge !== false;
const autoSettlePreferences = resolveMobileAutoSettlePreferences(
AsyncResult.isSuccess(preferencesResult) ? preferencesResult.value : {},
);
const savePreferences = useAtomSet(updateMobilePreferencesAtom);
const openSwipeableRef = useRef<SwipeableMethods | null>(null);
const listRef = useRef<LegendListRef | null>(null);
Expand Down Expand Up @@ -680,7 +681,8 @@ export function HomeScreen(props: HomeScreenProps) {
searchQuery: props.searchQuery,
matchedThreadKeys,
changeRequestByKey,
autoSettleOnMerge,
autoSettleAfterDays: autoSettlePreferences.autoSettleAfterDays,
autoSettleMode: autoSettlePreferences.autoSettleMode,
settlementEnvironmentIds,
snoozeEnvironmentIds,
settledLimit: settledVisibleCount,
Expand All @@ -692,7 +694,8 @@ export function HomeScreen(props: HomeScreenProps) {
});
}, [
changeRequestByKey,
autoSettleOnMerge,
autoSettlePreferences.autoSettleAfterDays,
autoSettlePreferences.autoSettleMode,
nowMinute,
snoozeWakeTick,
snoozedShelfExpanded,
Expand Down
173 changes: 173 additions & 0 deletions apps/mobile/src/features/settings/SettingsAutoSettleRouteScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { useAtomSet, useAtomValue } from "@effect/atom-react";
import { useNavigation } from "@react-navigation/native";
import {
MAX_SIDEBAR_AUTO_SETTLE_AFTER_DAYS,
MIN_SIDEBAR_AUTO_SETTLE_AFTER_DAYS,
type SidebarAutoSettleMode,
} from "@t3tools/contracts";
import { AsyncResult } from "effect/unstable/reactivity";
import { Platform, Pressable, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
import { AppText as Text } from "../../components/AppText";
import { SymbolView } from "../../components/AppSymbol";
import { useThemeColor } from "../../lib/useThemeColor";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { resolveMobileAutoSettlePreferences } from "../../persistence/mobile-preferences";
import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences";
import { SettingsSection } from "./components/SettingsSection";

const AUTO_SETTLE_OPTIONS: ReadonlyArray<{
readonly mode: SidebarAutoSettleMode;
readonly label: string;
readonly description: string;
}> = [
{
mode: "never",
label: "Never",
description: "Threads settle only when you choose Settle.",
},
{
mode: "change-request",
label: "When a pull request merges or closes",
description: "A missing or uncertain pull-request timestamp keeps the thread active.",
},
{
mode: "inactivity",
label: "After inactivity",
description: "Running, blocked, and open-pull-request threads stay active.",
},
];

export function mobileAutoSettleModeLabel(mode: SidebarAutoSettleMode): string {
if (mode === "change-request") return "PR merged or closed";
if (mode === "inactivity") return "After inactivity";
return "Never";
}

export function SettingsAutoSettleRouteScreen() {
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const checkmarkColor = useThemeColor("--color-icon");
const preferencesResult = useAtomValue(mobilePreferencesAtom);
const savePreferences = useAtomSet(updateMobilePreferencesAtom);
const preferencesReady = AsyncResult.isSuccess(preferencesResult) && !preferencesResult.waiting;
const resolved = resolveMobileAutoSettlePreferences(
AsyncResult.isSuccess(preferencesResult) ? preferencesResult.value : {},
);

const setDays = (days: number) => {
if (!preferencesReady) return;
savePreferences({
autoSettleAfterDays: Math.min(
MAX_SIDEBAR_AUTO_SETTLE_AFTER_DAYS,
Math.max(MIN_SIDEBAR_AUTO_SETTLE_AFTER_DAYS, days),
),
});
};

return (
<View collapsable={false} className="flex-1 bg-sheet">
{Platform.OS === "android" ? (
<>
<NativeStackScreenOptions options={{ headerShown: false }} />
<AndroidScreenHeader title="Thread Settling" onBack={() => navigation.goBack()} />
</>
) : null}
<ScrollView
contentInsetAdjustmentBehavior="automatic"
showsVerticalScrollIndicator={false}
className="flex-1"
contentContainerClassName="gap-4 px-5 pt-4"
contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }}
>
<SettingsSection title="Automatic settling">
{AUTO_SETTLE_OPTIONS.map((option, index) => (
<Pressable
key={option.mode}
accessibilityRole="radio"
accessibilityState={{
checked: resolved.autoSettleMode === option.mode,
disabled: !preferencesReady,
}}
disabled={!preferencesReady}
onPress={() => savePreferences({ autoSettleMode: option.mode })}
className={
index === 0
? "flex-row items-center gap-4 p-4"
: "flex-row items-center gap-4 border-t border-border-subtle p-4"
}
>
<View className="min-w-0 flex-1 gap-1">
<Text className="text-lg text-foreground">{option.label}</Text>
<Text className="text-sm leading-normal text-foreground-muted">
{option.description}
</Text>
</View>
{resolved.autoSettleMode === option.mode ? (
<SymbolView
name="checkmark"
size={18}
tintColor={checkmarkColor}
type="monochrome"
weight="semibold"
/>
) : null}
</Pressable>
))}
</SettingsSection>

{resolved.autoSettleMode === "inactivity" ? (
<SettingsSection title="Inactivity window">
<View className="flex-row items-center gap-4 p-4">
<View className="min-w-0 flex-1 gap-1">
<Text className="text-lg text-foreground">Days without activity</Text>
<Text className="text-sm text-foreground-muted">Between 1 and 90 days</Text>
</View>
<Pressable
accessibilityLabel="Decrease inactivity days"
accessibilityRole="button"
disabled={
!preferencesReady ||
resolved.autoSettleAfterDays <= MIN_SIDEBAR_AUTO_SETTLE_AFTER_DAYS
}
onPress={() => setDays(resolved.autoSettleAfterDays - 1)}
className="size-10 items-center justify-center rounded-full bg-fill-secondary disabled:opacity-40"
>
<SymbolView
name="minus"
size={16}
tintColor={checkmarkColor}
type="monochrome"
weight="semibold"
/>
</Pressable>
<Text className="w-8 text-center text-lg text-foreground">
{resolved.autoSettleAfterDays}
</Text>
<Pressable
accessibilityLabel="Increase inactivity days"
accessibilityRole="button"
disabled={
!preferencesReady ||
resolved.autoSettleAfterDays >= MAX_SIDEBAR_AUTO_SETTLE_AFTER_DAYS
}
onPress={() => setDays(resolved.autoSettleAfterDays + 1)}
className="size-10 items-center justify-center rounded-full bg-fill-secondary disabled:opacity-40"
>
<SymbolView
name="plus"
size={16}
tintColor={checkmarkColor}
type="monochrome"
weight="semibold"
/>
</Pressable>
</View>
</SettingsSection>
) : null}
</ScrollView>
</View>
);
}
17 changes: 9 additions & 8 deletions apps/mobile/src/features/settings/SettingsRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Constants from "expo-constants";
import * as Notifications from "expo-notifications";
import { useNavigation } from "@react-navigation/native";
import { NativeStackScreenOptions } from "../../native/StackHeader";
import { resolveMobileAutoSettlePreferences } from "../../persistence/mobile-preferences";
import { SymbolView } from "../../components/AppSymbol";
import * as Effect from "effect/Effect";
import { AsyncResult } from "effect/unstable/reactivity";
Expand Down Expand Up @@ -46,6 +47,7 @@ import { useSavedRemoteConnections } from "../../state/use-remote-environment-re
import { SettingsRow } from "./components/SettingsRow";
import { SettingsSection } from "./components/SettingsSection";
import { SettingsSwitchRow } from "./components/SettingsSwitchRow";
import { mobileAutoSettleModeLabel } from "./SettingsAutoSettleRouteScreen";
import { resolveAgentAwarenessPlatformPresentation } from "./SettingsRouteScreen.logic";

type NotificationStatus = "checking" | "enabled" | "disabled" | "unsupported";
Expand Down Expand Up @@ -529,19 +531,18 @@ function ConfiguredSettingsRouteScreen() {

function GeneralSettingsSection() {
const preferencesResult = useAtomValue(mobilePreferencesAtom);
const savePreferences = useAtomSet(updateMobilePreferencesAtom);
const autoSettleOnMerge =
!AsyncResult.isSuccess(preferencesResult) ||
preferencesResult.value.autoSettleOnMerge !== false;
const autoSettlePreferences = resolveMobileAutoSettlePreferences(
AsyncResult.isSuccess(preferencesResult) ? preferencesResult.value : {},
);

return (
<SettingsSection title="General">
<SettingsRow icon="folder" label="Project Grouping" target="SettingsProjectGrouping" />
<SettingsSwitchRow
<SettingsRow
icon="arrow.triangle.branch"
label="Auto-settle merged threads"
value={autoSettleOnMerge}
onValueChange={(value) => savePreferences({ autoSettleOnMerge: value })}
label="Thread Settling"
value={mobileAutoSettleModeLabel(autoSettlePreferences.autoSettleMode)}
target="SettingsAutoSettle"
/>
<SettingsRow icon="chart.bar.xaxis" label="Usage" target="SettingsUsage" />
</SettingsSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type SettingsSheetTarget =
| "SettingsEnvironments"
| "SettingsArchive"
| "SettingsAppearance"
| "SettingsAutoSettle"
| "SettingsProjectGrouping"
| "SettingsClientStorage"
| "SettingsUsage";
Expand Down
13 changes: 8 additions & 5 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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 { resolveMobileAutoSettlePreferences } from "../../persistence/mobile-preferences";
import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
import { useThemeColor } from "../../lib/useThemeColor";
import { useProjects, useThreadShells } from "../../state/entities";
Expand Down Expand Up @@ -174,9 +175,9 @@ function ThreadNavigationSidebarPane(
} = useThreadListActions();
const threadListV2Enabled = useThreadListV2Enabled();
const preferencesResult = useAtomValue(mobilePreferencesAtom);
const autoSettleOnMerge =
!AsyncResult.isSuccess(preferencesResult) ||
preferencesResult.value.autoSettleOnMerge !== false;
const autoSettlePreferences = resolveMobileAutoSettlePreferences(
AsyncResult.isSuccess(preferencesResult) ? preferencesResult.value : {},
);
const pendingTasks = usePendingNewTasks();
const { openPendingTask, confirmDeletePendingTask } = usePendingTaskListActions();
const environments = useMemo(
Expand Down Expand Up @@ -519,7 +520,8 @@ function ThreadNavigationSidebarPane(
searchQuery: props.searchQuery,
matchedThreadKeys,
changeRequestByKey,
autoSettleOnMerge,
autoSettleAfterDays: autoSettlePreferences.autoSettleAfterDays,
autoSettleMode: autoSettlePreferences.autoSettleMode,
settlementEnvironmentIds,
snoozeEnvironmentIds,
settledLimit: settledVisibleCount,
Expand All @@ -531,7 +533,8 @@ function ThreadNavigationSidebarPane(
});
}, [
changeRequestByKey,
autoSettleOnMerge,
autoSettlePreferences.autoSettleAfterDays,
autoSettlePreferences.autoSettleMode,
nowMinute,
snoozeWakeTick,
snoozedShelfExpanded,
Expand Down
Loading
Loading