History

fix(mobile): support Hermes collection sorting

Reuses the focused compatibility approach from closed PR #3923 and covers the remaining hardware-keyboard reverse iteration.

Julius Marminge committed 3 months ago

Commit cd2556b1 parent 6c344388 files · +72 −24Browse files

Unified · Split

8 files
  1. apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts +4 −1
  2. apps/mobile/src/features/threads/ThreadRelationshipsBanner.tsx +4 −2
  3. apps/mobile/src/state/use-thread-selection.ts +16 −2
  4. packages/client-runtime/src/state/threadWorkflows.ts +12 −13
  5. packages/shared/package.json +4 −0
  6. packages/shared/src/Array.test.ts +12 −0
  7. packages/shared/src/Array.ts +6 −0
  8. packages/shared/src/model.ts +14 −6
apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts+4−1
@@ -56,7 +56,10 @@ export function subscribeToHardwareKeyboardCommandRegistrations(listener: () => · 55 unmodified lines
56export function dispatchHardwareKeyboardCommand(command: HardwareKeyboardCommand): boolean {56export function dispatchHardwareKeyboardCommand(command: HardwareKeyboardCommand): boolean {
57 const commandHandlers = handlers.get(command);57 const commandHandlers = handlers.get(command);
58 if (!commandHandlers) return false;58 if (!commandHandlers) return false;
59 for (const handler of [...commandHandlers].toReversed()) {59 const handlersInRegistrationOrder = Array.from(commandHandlers);
60 for (let index = handlersInRegistrationOrder.length - 1; index >= 0; index -= 1) {
61 const handler = handlersInRegistrationOrder[index];
62 if (!handler) continue;
60 if (handler() !== false) return true;63 if (handler() !== false) return true;
61 }64 }
62 return false;65 return false;
apps/mobile/src/features/threads/ThreadRelationshipsBanner.tsx+4−2
@@ -8,7 +8,8 @@ import { · 7 unmodified lines
8} from "@t3tools/client-runtime/state/thread-relationships";8} from "@t3tools/client-runtime/state/thread-relationships";
9import { canDetachThreadProviderSession } from "@t3tools/client-runtime/state/thread-workflows";9import { canDetachThreadProviderSession } from "@t3tools/client-runtime/state/thread-workflows";
10import type { EnvironmentId, OrchestrationV2ThreadShell, ThreadId } from "@t3tools/contracts";10import type { EnvironmentId, OrchestrationV2ThreadShell, ThreadId } from "@t3tools/contracts";
11import { useRouter } from "expo-router";11import { copySorted } from "@t3tools/shared/Array";
12import { useNavigation } from "@react-navigation/native";
12import { useMemo, useState } from "react";13import { useMemo, useState } from "react";
13import { ActivityIndicator, Modal, Pressable, ScrollView, View } from "react-native";14import { ActivityIndicator, Modal, Pressable, ScrollView, View } from "react-native";
14import { useSafeAreaInsets } from "react-native-safe-area-context";15import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -79,7 +80,8 @@ export function ThreadRelationshipsBanner(props: { · 64 unmodified lines
79 const mergeTargetThreadId = resolveMergeBackTargetThreadId(projection);80 const mergeTargetThreadId = resolveMergeBackTargetThreadId(projection);
80 const rows = useMemo(81 const rows = useMemo(
81 () =>82 () =>
82 immediateThreadRelationships(graph, props.threadId).toSorted(83 copySorted(
84 immediateThreadRelationships(graph, props.threadId),
83 (left, right) =>85 (left, right) =>
84 Number(right.threadId === mergeTargetThreadId) -86 Number(right.threadId === mergeTargetThreadId) -
85 Number(left.threadId === mergeTargetThreadId),87 Number(left.threadId === mergeTargetThreadId),
apps/mobile/src/state/use-thread-selection.ts+16−2
@@ -9,6 +9,7 @@ import { · 8 unmodified lines
9} from "@t3tools/contracts";9} from "@t3tools/contracts";
10import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";10import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
11import * as Option from "effect/Option";11import * as Option from "effect/Option";
12import { copySorted } from "@t3tools/shared/Array";
12 13
13import { useProject, useThreadShell } from "../state/entities";14import { useProject, useThreadShell } from "../state/entities";
14import { useEnvironmentThread } from "../state/threads";15import { useEnvironmentThread } from "../state/threads";
@@ -44,8 +45,21 @@ function threadDetailToShell( · 29 unmodified lines
44 environmentId: EnvironmentId,45 environmentId: EnvironmentId,
45 thread: OrchestrationThread,46 thread: OrchestrationThread,
46): EnvironmentThreadShell {47): EnvironmentThreadShell {
47 return {48 const thread = projection.thread;
48 environmentId,49 const runsByOrdinal = copySorted(projection.runs, (left, right) => right.ordinal - left.ordinal);
50 const latestRun = runsByOrdinal[0] ?? null;
51 const activeRun =
52 runsByOrdinal.find(
53 (run) =>
54 run.status === "preparing" ||
55 run.status === "queued" ||
56 run.status === "starting" ||
57 run.status === "running" ||
58 run.status === "waiting",
59 ) ?? null;
60 const pendingRequest =
61 projection.runtimeRequests.find((request) => request.status === "pending") ?? null;
62 return presentThreadShell(environmentId, {
49 id: thread.id,63 id: thread.id,
50 projectId: thread.projectId,64 projectId: thread.projectId,
51 title: thread.title,65 title: thread.title,
packages/client-runtime/src/state/threadWorkflows.ts+12−13
@@ -3,6 +3,7 @@ import type { · 2 unmodified lines
3 OrchestrationV2ProviderCapabilities,3 OrchestrationV2ProviderCapabilities,
4 OrchestrationV2ThreadProjection,4 OrchestrationV2ThreadProjection,
5} from "@t3tools/contracts";5} from "@t3tools/contracts";
6import { copySorted } from "@t3tools/shared/Array";
6 7
7type Projection = OrchestrationV2ThreadProjection;8type Projection = OrchestrationV2ThreadProjection;
8type Run = Projection["runs"][number];9type Run = Projection["runs"][number];
@@ -54,19 +55,17 @@ export function deriveThreadQueueWorkflowState(projection: Projection): ThreadQu · 45 unmodified lines
54 const activeRun = resolveActiveThreadRun(projection);55 const activeRun = resolveActiveThreadRun(projection);
55 const session = resolveThreadProviderSession(projection);56 const session = resolveThreadProviderSession(projection);
56 const capabilities = session?.capabilities.turns;57 const capabilities = session?.capabilities.turns;
57 const queuedRuns = projection.runs58 const queuedRuns = copySorted(
58 .filter((run) => run.status === "queued")59 projection.runs.filter((run) => run.status === "queued"),
59 .toSorted(60 (left, right) =>
60 (left, right) =>61 (left.queuePosition ?? left.ordinal) - (right.queuePosition ?? right.ordinal) ||
61 (left.queuePosition ?? left.ordinal) - (right.queuePosition ?? right.ordinal) ||62 left.ordinal - right.ordinal,
62 left.ordinal - right.ordinal,63 ).map((run) => ({
63 )64 run,
64 .map((run) => ({65 text:
65 run,66 projection.messages.find((message) => message.id === run.userMessageId)?.text ??
66 text:67 "Queued message",
67 projection.messages.find((message) => message.id === run.userMessageId)?.text ??68 }));
68 "Queued message",
69 }));
70 69
71 return {70 return {
72 activeRun,71 activeRun,
packages/shared/package.json+4−0
@@ -3,6 +3,10 @@ · 2 unmodified lines
3 "private": true,3 "private": true,
4 "type": "module",4 "type": "module",
5 "exports": {5 "exports": {
6 "./Array": {
7 "types": "./src/Array.ts",
8 "import": "./src/Array.ts"
9 },
6 "./themePalettes": {10 "./themePalettes": {
7 "types": "./src/themePalettes.ts",11 "types": "./src/themePalettes.ts",
8 "import": "./src/themePalettes.ts"12 "import": "./src/themePalettes.ts"
packages/shared/src/Array.test.ts+12−0
@@ -0,0 +1,12 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { copySorted } from "./Array.ts";
4
5describe("array copies", () => {
6 it("sorts a copy without mutating the source", () => {
7 const source = [3, 1, 2];
8
9 expect(copySorted(source, (left: number, right: number) => left - right)).toEqual([1, 2, 3]);
10 expect(source).toEqual([3, 1, 2]);
11 });
12});
packages/shared/src/Array.ts+6−0
@@ -0,0 +1,6 @@
1export function copySorted<T>(
2 values: ReadonlyArray<T>,
3 compareFn?: (left: T, right: T) => number,
4): T[] {
5 return [...values].sort(compareFn);
6}
packages/shared/src/model.ts+14−6
@@ -8,8 +8,7 @@ import { · 7 unmodified lines
8 type ProviderOptionDescriptor,8 type ProviderOptionDescriptor,
9 type ProviderOptionSelection,9 type ProviderOptionSelection,
10} from "@t3tools/contracts";10} from "@t3tools/contracts";
11import * as Option from "effect/Option";11import { copySorted } from "./Array.ts";
12import * as Schema from "effect/Schema";
13 12
14const DEFAULT_PROVIDER_DRIVER_KIND = ProviderDriverKind.make("codex");13const DEFAULT_PROVIDER_DRIVER_KIND = ProviderDriverKind.make("codex");
15 14
@@ -75,12 +74,21 @@ export function getModelSelectionBooleanOptionValue( · 59 unmodified lines
75function canonicalModelSelectionOptions(74function canonicalModelSelectionOptions(
76 modelSelection: ModelSelection,75 modelSelection: ModelSelection,
77): ReadonlyArray<readonly [id: string, value: string | boolean]> {76): ReadonlyArray<readonly [id: string, value: string | boolean]> {
78 return (modelSelection.options ?? [])77 return copySorted(
79 .map((selection) => [selection.id, selection.value] as const)78 (modelSelection.options ?? []).map(
80 .toSorted(([leftId, leftValue], [rightId, rightValue]) => {79 (selection): readonly [id: string, value: string | boolean] => [
80 selection.id,
81 selection.value,
82 ],
83 ),
84 (
85 [leftId, leftValue]: readonly [id: string, value: string | boolean],
86 [rightId, rightValue]: readonly [id: string, value: string | boolean],
87 ) => {
81 const idOrder = leftId.localeCompare(rightId);88 const idOrder = leftId.localeCompare(rightId);
82 return idOrder !== 0 ? idOrder : String(leftValue).localeCompare(String(rightValue));89 return idOrder !== 0 ? idOrder : String(leftValue).localeCompare(String(rightValue));
83 });90 },
91 );
84}92}
85 93
86/**94/**

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t