Skip to content

Commit 89672b3

Browse files
Refactor mobile ordering and catalog assembly
- Replace ad hoc sorts with shared Effect order helpers - Rework remote catalog and review/terminal lists for clearer data flow - Keep threaded/review selection and diff adapters aligned with the new ordering logic
1 parent 90fea7b commit 89672b3

7 files changed

Lines changed: 123 additions & 70 deletions

File tree

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type {
66
import { SymbolView } from "expo-symbols";
77
import { useCallback, useMemo, useState } from "react";
88
import { Pressable, ScrollView, View } from "react-native";
9+
import * as Arr from "effect/Array";
10+
import * as Order from "effect/Order";
911
import { useThemeColor } from "../../lib/useThemeColor";
1012

1113
import { AppText as Text } from "../../components/AppText";
@@ -33,6 +35,15 @@ interface ProjectGroup {
3335
readonly threads: ReadonlyArray<EnvironmentScopedThreadShell>;
3436
}
3537

38+
const projectGroupActivityOrder = Order.mapInput(
39+
Order.Struct({
40+
activityAt: Order.flip(Order.Number),
41+
}),
42+
(group: ProjectGroup) => ({
43+
activityAt: new Date(group.threads[0]!.updatedAt ?? group.threads[0]!.createdAt).getTime(),
44+
}),
45+
);
46+
3647
/* ─── Status indicator colors ────────────────────────────────────────── */
3748

3849
function statusColors(thread: EnvironmentScopedThreadShell): { bg: string; fg: string } {
@@ -271,13 +282,7 @@ export function HomeScreen(props: HomeScreenProps) {
271282
}
272283
}
273284

274-
groups.sort((a, b) => {
275-
const aTime = new Date(a.threads[0]!.updatedAt ?? a.threads[0]!.createdAt).getTime();
276-
const bTime = new Date(b.threads[0]!.updatedAt ?? b.threads[0]!.createdAt).getTime();
277-
return bTime - aTime;
278-
});
279-
280-
return groups;
285+
return Arr.sort(groups, projectGroupActivityOrder);
281286
}, [props.projects, filteredThreads]);
282287

283288
/* Empty states */

apps/mobile/src/features/review/reviewModel.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ChangeTypes, FileDiffMetadata } from "@pierre/diffs/types";
22
import { parsePatchFiles } from "@pierre/diffs/utils/parsePatchFiles";
33
import type { OrchestrationCheckpointSummary, ReviewDiffPreviewSource } from "@t3tools/contracts";
44
import * as Arr from "effect/Array";
5+
import { pipe } from "effect/Function";
56
import * as Order from "effect/Order";
67

78
export type ReviewSectionKind = "turn" | "working-tree" | "branch-range";
@@ -513,9 +514,10 @@ export function getReviewSectionIdForCheckpoint(
513514
export function getReadyReviewCheckpoints(
514515
checkpoints: ReadonlyArray<OrchestrationCheckpointSummary>,
515516
): ReadonlyArray<OrchestrationCheckpointSummary> {
516-
return Arr.sort(
517-
checkpoints.filter((checkpoint) => checkpoint.status === "ready"),
518-
readyCheckpointOrder,
517+
return pipe(
518+
checkpoints,
519+
Arr.filter((checkpoint) => checkpoint.status === "ready"),
520+
Arr.sort(readyCheckpointOrder),
519521
);
520522
}
521523

@@ -579,7 +581,11 @@ export function buildReviewParsedDiff(
579581
const parsedPatches = runDiffParserSilently(() =>
580582
parsePatchFiles(text, buildPatchCacheKey(text, cacheScope)),
581583
);
582-
const files = parsedPatches.flatMap((patch) => patch.files).map(mapRenderableFile);
584+
const files = pipe(
585+
parsedPatches,
586+
Arr.flatMap((patch) => patch.files),
587+
Arr.map(mapRenderableFile),
588+
);
583589

584590
if (files.length === 0) {
585591
return {

apps/mobile/src/features/review/useReviewCommentSelectionController.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useCallback, useEffect, useMemo, useState } from "react";
22
import type { NativeSyntheticEvent } from "react-native";
33
import { useRouter } from "expo-router";
4+
import * as Arr from "effect/Array";
5+
import { pipe } from "effect/Function";
6+
import * as Result from "effect/Result";
47

58
import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
69

@@ -53,10 +56,13 @@ export function useReviewCommentSelectionController(input: {
5356
activeCommentTarget.sectionTitle === selectedSection?.title &&
5457
activeCommentTarget.startIndex !== activeCommentTarget.endIndex
5558
) {
56-
return getSelectedReviewCommentLines(activeCommentTarget).flatMap((line) => {
57-
const rowId = nativeReviewDiffData.rowIdByCommentLineId.get(line.id);
58-
return rowId ? [rowId] : [];
59-
});
59+
return pipe(
60+
getSelectedReviewCommentLines(activeCommentTarget),
61+
Arr.filterMap((line) => {
62+
const rowId = nativeReviewDiffData.rowIdByCommentLineId.get(line.id);
63+
return rowId ? Result.succeed(rowId) : Result.failVoid;
64+
}),
65+
);
6066
}
6167

6268
return pendingNativeCommentSelection ? [pendingNativeCommentSelection.rowId] : [];

apps/mobile/src/features/terminal/terminalMenu.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { KnownTerminalSession } from "@t3tools/client-runtime";
22
import { DEFAULT_TERMINAL_ID, type ProjectScript } from "@t3tools/contracts";
33
import { nextTerminalId, resolveTerminalSessionLabel } from "@t3tools/shared/terminalLabels";
4+
import * as Arr from "effect/Array";
5+
import * as Order from "effect/Order";
46

57
export {
68
getTerminalLabel,
@@ -18,6 +20,14 @@ export interface TerminalMenuSession {
1820
readonly updatedAt: string | null;
1921
}
2022

23+
const terminalMenuSessionOrder = Order.make<TerminalMenuSession>((left, right) => {
24+
const comparison = left.terminalId.localeCompare(right.terminalId, undefined, { numeric: true });
25+
if (comparison === 0) {
26+
return 0;
27+
}
28+
return comparison < 0 ? -1 : 1;
29+
});
30+
2131
export function basename(input: string | null): string | null {
2232
if (!input) {
2333
return null;
@@ -101,9 +111,7 @@ export function buildTerminalMenuSessions(input: {
101111
sessionsById.set(input.currentSession.terminalId, input.currentSession);
102112
}
103113

104-
return Array.from(sessionsById.values()).sort((left, right) =>
105-
left.terminalId.localeCompare(right.terminalId, undefined, { numeric: true }),
106-
);
114+
return Arr.sort(sessionsById.values(), terminalMenuSessionOrder);
107115
}
108116

109117
export function resolveProjectScriptTerminalId(input: {

apps/mobile/src/features/threads/ThreadNavigationDrawer.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { SymbolView } from "expo-symbols";
22
import { useCallback, useEffect, useMemo, useState } from "react";
33
import { Modal, Pressable, ScrollView, useWindowDimensions, View } from "react-native";
4+
import * as Arr from "effect/Array";
5+
import * as Order from "effect/Order";
46
import { Gesture, GestureDetector } from "react-native-gesture-handler";
57
import { useSafeAreaInsets } from "react-native-safe-area-context";
68
import Animated, {
@@ -22,15 +24,16 @@ import {
2224
EnvironmentScopedThreadShell,
2325
} from "@t3tools/client-runtime";
2426

25-
function compareThreadActivity(
26-
left: EnvironmentScopedThreadShell,
27-
right: EnvironmentScopedThreadShell,
28-
): number {
29-
return (
30-
new Date(right.updatedAt ?? right.createdAt).getTime() -
31-
new Date(left.updatedAt ?? left.createdAt).getTime() || left.title.localeCompare(right.title)
32-
);
33-
}
27+
const threadActivityOrder = Order.mapInput(
28+
Order.Struct({
29+
activityAt: Order.flip(Order.Number),
30+
title: Order.String,
31+
}),
32+
(thread: EnvironmentScopedThreadShell) => ({
33+
activityAt: new Date(thread.updatedAt ?? thread.createdAt).getTime(),
34+
title: thread.title,
35+
}),
36+
);
3437

3538
export function ThreadNavigationDrawer(props: {
3639
readonly visible: boolean;
@@ -60,13 +63,17 @@ export function ThreadNavigationDrawer(props: {
6063
);
6164
const groupedThreads = useMemo(
6265
() =>
63-
repositoryGroups.map((group) => ({
64-
key: group.key,
65-
title: group.projects[0]?.project.title ?? group.title,
66-
threads: group.projects
67-
.flatMap((projectGroup) => projectGroup.threads)
68-
.sort(compareThreadActivity),
69-
})),
66+
repositoryGroups.map((group) => {
67+
const threads: EnvironmentScopedThreadShell[] = [];
68+
for (const projectGroup of group.projects) {
69+
threads.push(...projectGroup.threads);
70+
}
71+
return {
72+
key: group.key,
73+
title: group.projects[0]?.project.title ?? group.title,
74+
threads: Arr.sort(threads, threadActivityOrder),
75+
};
76+
}),
7077
[repositoryGroups],
7178
);
7279

apps/mobile/src/state/use-remote-catalog.ts

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,33 @@ import {
1111
} from "@t3tools/client-runtime";
1212

1313
import { ConnectedEnvironmentSummary } from "./remote-runtime-types";
14+
import type { SavedRemoteConnection } from "../lib/connection";
1415
import { useShellSnapshotStates } from "./use-shell-snapshot";
1516
import {
1617
useRemoteConnectionStatus,
1718
useRemoteEnvironmentState,
1819
} from "./use-remote-environment-registry";
1920

20-
const projectsSortOrder = Order.make<EnvironmentScopedProjectShell>(
21-
(left, right) =>
22-
(left.title.localeCompare(right.title) as -1 | 0 | 1) ||
23-
(left.environmentId.localeCompare(right.environmentId) as -1 | 0 | 1),
21+
const projectsSortOrder = Order.mapInput(
22+
Order.Struct({
23+
title: Order.String,
24+
environmentId: Order.String,
25+
}),
26+
(project: EnvironmentScopedProjectShell) => ({
27+
title: project.title,
28+
environmentId: project.environmentId,
29+
}),
2430
);
2531

26-
const threadsSortOrder = Order.make<EnvironmentScopedThreadShell>(
27-
(left, right) =>
28-
((new Date(right.updatedAt ?? right.createdAt).getTime() -
29-
new Date(left.updatedAt ?? left.createdAt).getTime()) as -1 | 0 | 1) ||
30-
(left.environmentId.localeCompare(right.environmentId) as -1 | 0 | 1),
32+
const threadsSortOrder = Order.mapInput(
33+
Order.Struct({
34+
activityAt: Order.flip(Order.Number),
35+
environmentId: Order.String,
36+
}),
37+
(thread: EnvironmentScopedThreadShell) => ({
38+
activityAt: new Date(thread.updatedAt ?? thread.createdAt).getTime(),
39+
environmentId: thread.environmentId,
40+
}),
3141
);
3242

3343
function deriveOverallConnectionState(
@@ -48,38 +58,44 @@ function deriveOverallConnectionState(
4858
return "disconnected";
4959
}
5060

61+
function listRemoteCatalogEnvironmentIds(
62+
savedConnectionsById: Readonly<Record<string, SavedRemoteConnection>>,
63+
): ReadonlyArray<SavedRemoteConnection["environmentId"]> {
64+
const environmentIds: SavedRemoteConnection["environmentId"][] = [];
65+
for (const connection of Object.values(savedConnectionsById)) {
66+
environmentIds.push(connection.environmentId);
67+
}
68+
return environmentIds;
69+
}
70+
5171
export function useRemoteCatalog() {
5272
const { connectedEnvironments, connectionState } = useRemoteConnectionStatus();
5373
const { environmentStateById, savedConnectionsById } = useRemoteEnvironmentState();
5474
const shellSnapshotStates = useShellSnapshotStates(
55-
Object.values(savedConnectionsById).map((connection) => connection.environmentId),
75+
listRemoteCatalogEnvironmentIds(savedConnectionsById),
5676
);
5777

58-
const projects = useMemo(
59-
() =>
60-
Arr.sort(
61-
Object.values(savedConnectionsById).flatMap((connection) =>
62-
(shellSnapshotStates[connection.environmentId]?.data?.projects ?? []).map((project) =>
63-
scopeProjectShell(connection.environmentId, project),
64-
),
65-
),
66-
projectsSortOrder,
67-
),
68-
[savedConnectionsById, shellSnapshotStates],
69-
);
78+
const projects = useMemo(() => {
79+
const scopedProjects: EnvironmentScopedProjectShell[] = [];
80+
for (const connection of Object.values(savedConnectionsById)) {
81+
const projects = shellSnapshotStates[connection.environmentId]?.data?.projects ?? [];
82+
for (const project of projects) {
83+
scopedProjects.push(scopeProjectShell(connection.environmentId, project));
84+
}
85+
}
86+
return Arr.sort(scopedProjects, projectsSortOrder);
87+
}, [savedConnectionsById, shellSnapshotStates]);
7088

71-
const threads = useMemo(
72-
() =>
73-
Arr.sort(
74-
Object.values(savedConnectionsById).flatMap((connection) =>
75-
(shellSnapshotStates[connection.environmentId]?.data?.threads ?? []).map((thread) =>
76-
scopeThreadShell(connection.environmentId, thread),
77-
),
78-
),
79-
threadsSortOrder,
80-
),
81-
[savedConnectionsById, shellSnapshotStates],
82-
);
89+
const threads = useMemo(() => {
90+
const scopedThreads: EnvironmentScopedThreadShell[] = [];
91+
for (const connection of Object.values(savedConnectionsById)) {
92+
const threads = shellSnapshotStates[connection.environmentId]?.data?.threads ?? [];
93+
for (const thread of threads) {
94+
scopedThreads.push(scopeThreadShell(connection.environmentId, thread));
95+
}
96+
}
97+
return Arr.sort(scopedThreads, threadsSortOrder);
98+
}, [savedConnectionsById, shellSnapshotStates]);
8399

84100
const serverConfigByEnvironmentId = useMemo(
85101
() =>

apps/mobile/src/state/use-remote-environment-registry.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,13 @@ export async function connectSavedEnvironment(
274274
}
275275
}
276276

277-
const environmentsSortOrder = Order.make<ConnectedEnvironmentSummary>(
278-
(left, right) => left.environmentLabel.localeCompare(right.environmentLabel) as -1 | 0 | 1,
277+
const environmentsSortOrder = Order.mapInput(
278+
Order.Struct({
279+
environmentLabel: Order.String,
280+
}),
281+
(environment: ConnectedEnvironmentSummary) => ({
282+
environmentLabel: environment.environmentLabel,
283+
}),
279284
);
280285

281286
function deriveConnectedEnvironments(

0 commit comments

Comments
 (0)