Skip to content
Merged
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
101 changes: 20 additions & 81 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from "@t3tools/client-runtime/state/thread-search";
import {
type EnvironmentId,
resolveEnvironmentMachineKind,
type SidebarProjectGroupingMode,
type SidebarThreadSortOrder,
} from "@t3tools/contracts";
Expand All @@ -40,7 +39,7 @@ import { useAppearancePreferences } from "../settings/appearance/AppearancePrefe
import { useThreadJumpShortcuts } from "../keyboard/threadKeyboardShortcuts";
import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled";
import { usePendingThreadOrder } from "../../state/thread-order";
import { environmentServerConfigsAtom } from "../../state/server";
import { threadListEnvironmentsAtom } from "../../state/server";
import type { PendingNewTask } from "../../state/use-pending-new-tasks";
import { useQueuedThreadKeys } from "../../state/use-thread-outbox";
import {
Expand Down Expand Up @@ -587,86 +586,25 @@ export function HomeScreen(props: HomeScreenProps) {
);
// Threads on servers without the settlement capability never classify as
// settled (the user could neither un-settle nor pin them).
const serverConfigs = useAtomValue(environmentServerConfigsAtom);
const settlementEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadSettlement === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const snoozeEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadSnooze === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinningEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadPinning === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadPinReorder === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const activeReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadActiveReorder === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const titleRegenerationEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadTitleRegeneration === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const machineByEnvironmentId = useMemo(
() =>
new Map(
[...serverConfigs].map(
([environmentId, config]) =>
[environmentId, resolveEnvironmentMachineKind(config)] as const,
),
),
[serverConfigs],
);
const listEnvironments = useAtomValue(threadListEnvironmentsAtom);
const {
providersByEnvironmentId,
machineByEnvironmentId,
settlementEnvironmentIds,
snoozeEnvironmentIds,
pinningEnvironmentIds,
pinReorderEnvironmentIds,
activeReorderEnvironmentIds,
titleRegenerationEnvironmentIds,
} = listEnvironments;
const pendingOrder = usePendingThreadOrder(nowMinute, snoozeWakeTick);
const threadMovePlanners = useMemo(() => {
const sectionPlanner = (section: "pinned" | "active") =>
createThreadMovePlanner({
allThreads: props.threads,
section,
reorderableEnvironmentIds: new Set(
[...serverConfigs].flatMap(([id, config]) =>
(section === "pinned"
? config.environment.capabilities.threadPinReorder
: config.environment.capabilities.threadActiveReorder) === true
? [id]
: [],
),
),
reorderableEnvironmentIds:
section === "pinned" ? pinReorderEnvironmentIds : activeReorderEnvironmentIds,
ordered: getThreadListV2OrderedSection({
threads: props.threads,
section,
Expand All @@ -679,7 +617,8 @@ export function HomeScreen(props: HomeScreenProps) {
});
return { pinned: sectionPlanner("pinned"), active: sectionPlanner("active") };
}, [
serverConfigs,
pinReorderEnvironmentIds,
activeReorderEnvironmentIds,
props.threads,
pendingOrder,
queuedThreadKeys,
Expand Down Expand Up @@ -859,7 +798,7 @@ export function HomeScreen(props: HomeScreenProps) {
projectTitle={v2ProjectTitleByProjectKey.get(
scopedProjectKey(thread.environmentId, thread.projectId),
)}
providers={serverConfigs.get(thread.environmentId)?.providers}
providers={providersByEnvironmentId.get(thread.environmentId)}
environmentLabel={
Object.keys(props.savedConnectionsById).length > 1
? (props.savedConnectionsById[thread.environmentId]?.environmentLabel ?? null)
Expand Down Expand Up @@ -928,7 +867,7 @@ export function HomeScreen(props: HomeScreenProps) {
props.onSelectThread,
props.onNewThreadOnBranch,
props.savedConnectionsById,
serverConfigs,
providersByEnvironmentId,
shelfPreferencesLoaded,
settlementEnvironmentIds,
snoozeEnvironmentIds,
Expand All @@ -951,7 +890,7 @@ export function HomeScreen(props: HomeScreenProps) {
() => ({
projectByKey,
projectTitleByProjectKey: v2ProjectTitleByProjectKey,
serverConfigs,
listEnvironments,
savedConnectionsById: props.savedConnectionsById,
searchQuery: props.searchQuery,
snoozePresetMinute: nowMinute,
Expand All @@ -961,7 +900,7 @@ export function HomeScreen(props: HomeScreenProps) {
projectByKey,
props.searchQuery,
props.savedConnectionsById,
serverConfigs,
listEnvironments,
nowMinute,
threadSearchMatchByKey,
v2ProjectTitleByProjectKey,
Expand Down
101 changes: 20 additions & 81 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { LegendList } from "@legendapp/list/react-native";
import type { MenuAction } from "@react-native-menu/menu";
import { useAtomValue } from "@effect/atom-react";
import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { LayoutChangeEvent } from "react-native";
import { Platform, Pressable, StyleSheet, TextInput, View } from "react-native";
Expand All @@ -32,7 +31,7 @@ import { useThreadSearch } from "../../state/queries";
import { useThreadListV2Enabled } from "./use-thread-list-v2-enabled";
import { useThreadListV2ShelfPreferences } from "./use-thread-list-v2-shelf-preferences";
import { usePendingThreadOrder } from "../../state/thread-order";
import { environmentServerConfigsAtom } from "../../state/server";
import { threadListEnvironmentsAtom } from "../../state/server";
import { usePendingNewTasks } from "../../state/use-pending-new-tasks";
import { useQueuedThreadKeys } from "../../state/use-thread-outbox";
import { useWorkspaceState } from "../../state/workspace";
Expand Down Expand Up @@ -415,86 +414,25 @@ function ThreadNavigationSidebarPane(
}, [threadListV2Enabled]);
// Threads on servers without the settlement capability never classify as
// settled (the user could neither un-settle nor pin them).
const serverConfigs = useAtomValue(environmentServerConfigsAtom);
const settlementEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadSettlement === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const snoozeEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadSnooze === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinningEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadPinning === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadPinReorder === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const activeReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadActiveReorder === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const titleRegenerationEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadTitleRegeneration === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const machineByEnvironmentId = useMemo(
() =>
new Map(
[...serverConfigs].map(
([environmentId, config]) =>
[environmentId, resolveEnvironmentMachineKind(config)] as const,
),
),
[serverConfigs],
);
const listEnvironments = useAtomValue(threadListEnvironmentsAtom);
const {
providersByEnvironmentId,
machineByEnvironmentId,
settlementEnvironmentIds,
snoozeEnvironmentIds,
pinningEnvironmentIds,
pinReorderEnvironmentIds,
activeReorderEnvironmentIds,
titleRegenerationEnvironmentIds,
} = listEnvironments;
const pendingOrder = usePendingThreadOrder(nowMinute, snoozeWakeTick);
const threadMovePlanners = useMemo(() => {
const sectionPlanner = (section: "pinned" | "active") =>
createThreadMovePlanner({
allThreads: threads,
section,
reorderableEnvironmentIds: new Set(
[...serverConfigs].flatMap(([id, config]) =>
(section === "pinned"
? config.environment.capabilities.threadPinReorder
: config.environment.capabilities.threadActiveReorder) === true
? [id]
: [],
),
),
reorderableEnvironmentIds:
section === "pinned" ? pinReorderEnvironmentIds : activeReorderEnvironmentIds,
ordered: getThreadListV2OrderedSection({
threads,
section,
Expand All @@ -507,7 +445,8 @@ function ThreadNavigationSidebarPane(
});
return { pinned: sectionPlanner("pinned"), active: sectionPlanner("active") };
}, [
serverConfigs,
pinReorderEnvironmentIds,
activeReorderEnvironmentIds,
threads,
pendingOrder,
queuedThreadKeys,
Expand Down Expand Up @@ -788,7 +727,7 @@ function ThreadNavigationSidebarPane(
projectByKey,
projectTitleByProjectKey,
savedConnectionsById,
serverConfigs,
listEnvironments,
snoozePresetMinute: nowMinute,
threadSearchMatchByKey,
}),
Expand All @@ -797,7 +736,7 @@ function ThreadNavigationSidebarPane(
projectByKey,
projectTitleByProjectKey,
savedConnectionsById,
serverConfigs,
listEnvironments,
nowMinute,
threadSearchMatchByKey,
],
Expand Down Expand Up @@ -910,7 +849,7 @@ function ThreadNavigationSidebarPane(
snoozeWakeLabelText={item.snoozeWakeLabelText}
project={projectByKey.get(scopeKey) ?? null}
projectTitle={projectTitleByProjectKey.get(scopeKey)}
providers={serverConfigs.get(thread.environmentId)?.providers}
providers={providersByEnvironmentId.get(thread.environmentId)}
environmentLabel={
Object.keys(savedConnectionsById).length > 1
? (savedConnectionsById[thread.environmentId]?.environmentLabel ?? null)
Expand Down Expand Up @@ -1100,7 +1039,7 @@ function ThreadNavigationSidebarPane(
props.selectedThreadKey,
props.width,
savedConnectionsById,
serverConfigs,
providersByEnvironmentId,
shelfPreferencesLoaded,
threadSearchMatchByKey,
titleRegenerationEnvironmentIds,
Expand Down
5 changes: 3 additions & 2 deletions apps/mobile/src/features/threads/thread-list-v2-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import type {
EnvironmentThreadShell,
} from "@t3tools/client-runtime/state/shell";
import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search";
import type { EnvironmentMachineKind, ServerConfig } from "@t3tools/contracts";
import type { EnvironmentMachineKind } from "@t3tools/contracts";
import { canSnooze, resolveSnoozePresets } from "@t3tools/client-runtime/state/thread-settled";
import { resolveSettledThreadTimestamp } from "@t3tools/client-runtime/state/thread-sort";
import type { MenuAction } from "@react-native-menu/menu";
import { memo, useCallback, useEffect, useMemo, useState, type ComponentProps } from "react";
import { Alert, Platform, Pressable, useWindowDimensions, View } from "react-native";
import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";

import type { ThreadListProvider } from "../../state/thread-list-environments";
import { SymbolView } from "../../components/AppSymbol";
import { AppText as Text } from "../../components/AppText";
import { ControlPillMenu } from "../../components/ControlPill";
Expand Down Expand Up @@ -360,7 +361,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
readonly project: EnvironmentProject | null;
readonly projectTitle?: string;
/** Keep the environment's provider array stable across unrelated list updates. */
readonly providers: ServerConfig["providers"] | undefined;
readonly providers: ReadonlyArray<ThreadListProvider> | undefined;
/** Which machine hosts the thread. Null when only one environment is
connected — repeating the same label on every row is noise. Mirrors
the web sidebar's remote-environment cloud icon, but as text since
Expand Down
5 changes: 3 additions & 2 deletions apps/mobile/src/features/threads/thread-provider-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
resolveProviderInstanceDisplayName,
shouldShowInstanceBadge,
} from "@t3tools/client-runtime/state/provider-instance-display";
import type { ProviderDriverKind, ServerConfig } from "@t3tools/contracts";
import type { ProviderDriverKind } from "@t3tools/contracts";

import type { ThreadListProvider } from "../../state/thread-list-environments";
/** What a thread row needs to draw the provider glyph and its account badge. */
export interface ThreadRowProviderInstance {
readonly driverKind: ProviderDriverKind;
Expand All @@ -20,7 +21,7 @@ export interface ThreadRowProviderInstance {
* names a different account on every server.
*/
export function resolveThreadProviderInstance(
providers: ServerConfig["providers"] | undefined,
providers: ReadonlyArray<ThreadListProvider> | undefined,
thread: EnvironmentThreadShell,
): ThreadRowProviderInstance | null {
if (providers === undefined) return null;
Expand Down
5 changes: 3 additions & 2 deletions apps/mobile/src/features/threads/threadListV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
resolveSettledThreadTimestamp,
sortPinnedThreadsByOrderKey,
} from "@t3tools/client-runtime/state/thread-sort";
import type { EnvironmentId, ProjectId, ServerConfig } from "@t3tools/contracts";
import type { EnvironmentId, ProjectId } from "@t3tools/contracts";

import type { ThreadListProvider } from "../../state/thread-list-environments";
import type { PendingNewTask } from "../../state/use-pending-new-tasks";

import {
Expand All @@ -34,7 +35,7 @@ export { snoozeWakeLabel };
*/
export function resolveThreadListV2ProviderDrivers(
thread: Pick<EnvironmentThreadShell, "providerInstanceHistory" | "modelSelection" | "runtime">,
providers: ServerConfig["providers"] | undefined,
providers: ReadonlyArray<ThreadListProvider> | undefined,
): ReadonlyArray<string> {
if (providers === undefined) return [];
const stack = resolveThreadProviderStack(thread);
Expand Down
Loading
Loading