- Deduplicate terminal IDs and favorite model slugs with trim-aware iteration - Use effect-based filtering for local branch and built-in model lists - Switch stable array handling to non-mutating `toSorted`
Unified · Split
| @@ -1,5 +1,7 @@ | ||
| 1 | 1 | import { resolveDefaultBranchActionDialogCopy } from "@t3tools/client-runtime"; |
| 2 | 2 | import { resolveAutoFeatureBranchName } from "@t3tools/shared/git"; |
| 3 | +import * as Arr from "effect/Array"; | |
| 4 | +import * as Result from "effect/Result"; | |
| 3 | 5 | import { useLocalSearchParams, useRouter } from "expo-router"; |
| 4 | 6 | import { useCallback, useMemo } from "react"; |
| 5 | 7 | import { View } from "react-native"; |
| @@ -74,7 +76,9 @@ export function GitConfirmSheet() { · 68 unmodified lines | ||
| 74 | 76 | ? gitState.selectedThreadBranches |
| 75 | 77 | : await gitActions.refreshSelectedThreadBranches(); |
| 76 | 78 | const newBranchName = resolveAutoFeatureBranchName( |
| 77 | − branches.filter((branch) => !branch.isRemote).map((branch) => branch.name), | |
| 79 | + Arr.filterMap(branches, (branch) => | |
| 80 | + branch.isRemote ? Result.failVoid : Result.succeed(branch.name), | |
| 81 | + ), | |
| 78 | 82 | ); |
| 79 | 83 | await gitActions.onCreateSelectedThreadBranch(newBranchName); |
| 80 | 84 | await gitActions.onRunSelectedThreadGitAction({ action: confirmAction }); |
| @@ -194,6 +194,7 @@ import { · 193 unmodified lines | ||
| 194 | 194 | isVersionMismatchDismissed, |
| 195 | 195 | resolveServerConfigVersionMismatch, |
| 196 | 196 | } from "../versionSkew"; |
| 197 | +import { equals } from "effect/Equal"; | |
| 197 | 198 | |
| 198 | 199 | const IMAGE_ONLY_BOOTSTRAP_PROMPT = |
| 199 | 200 | "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]"; |
| @@ -438,8 +439,8 @@ function terminalIdListsEqual(left: readonly string[], right: readonly string[]) · 238 unmodified lines | ||
| 438 | 439 | if (left.length === 0) { |
| 439 | 440 | return true; |
| 440 | 441 | } |
| 441 | − const sortedLeft = [...left].sort((a, b) => a.localeCompare(b)); | |
| 442 | − const sortedRight = [...right].sort((a, b) => a.localeCompare(b)); | |
| 442 | + const sortedLeft = left.toSorted((a, b) => a.localeCompare(b)); | |
| 443 | + const sortedRight = right.toSorted((a, b) => a.localeCompare(b)); | |
| 443 | 444 | for (let index = 0; index < sortedLeft.length; index += 1) { |
| 444 | 445 | if (sortedLeft[index] !== sortedRight[index]) { |
| 445 | 446 | return false; |
| @@ -870,7 +870,15 @@ export default function ThreadTerminalDrawer({ · 869 unmodified lines | ||
| 870 | 870 | const didResizeDuringDragRef = useRef(false); |
| 871 | 871 | |
| 872 | 872 | const normalizedTerminalIds = useMemo(() => { |
| 873 | − return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))]; | |
| 873 | + const normalizedIds: string[] = []; | |
| 874 | + const seen = new Set<string>(); | |
| 875 | + for (const id of terminalIds) { | |
| 876 | + const trimmedId = id.trim(); | |
| 877 | + if (trimmedId.length === 0 || seen.has(trimmedId)) continue; | |
| 878 | + seen.add(trimmedId); | |
| 879 | + normalizedIds.push(trimmedId); | |
| 880 | + } | |
| 881 | + return normalizedIds; | |
| 874 | 882 | }, [terminalIds]); |
| 875 | 883 | |
| 876 | 884 | const resolvedActiveTerminalId = |
| @@ -904,13 +912,17 @@ export default function ThreadTerminalDrawer({ · 27 unmodified lines | ||
| 904 | 912 | }; |
| 905 | 913 | |
| 906 | 914 | for (const terminalGroup of terminalGroups) { |
| 907 | − const nextTerminalIds = [ | |
| 908 | − ...new Set(terminalGroup.terminalIds.map((id) => id.trim()).filter((id) => id.length > 0)), | |
| 909 | − ].filter((terminalId) => { | |
| 910 | − if (!validTerminalIdSet.has(terminalId)) return false; | |
| 911 | − if (assignedTerminalIds.has(terminalId)) return false; | |
| 912 | − return true; | |
| 913 | − }); | |
| 915 | + const nextTerminalIds: string[] = []; | |
| 916 | + const seenGroupTerminalIds = new Set<string>(); | |
| 917 | + for (const id of terminalGroup.terminalIds) { | |
| 918 | + const terminalId = id.trim(); | |
| 919 | + if (terminalId.length === 0) continue; | |
| 920 | + if (seenGroupTerminalIds.has(terminalId)) continue; | |
| 921 | + seenGroupTerminalIds.add(terminalId); | |
| 922 | + if (!validTerminalIdSet.has(terminalId)) continue; | |
| 923 | + if (assignedTerminalIds.has(terminalId)) continue; | |
| 924 | + nextTerminalIds.push(terminalId); | |
| 925 | + } | |
| 914 | 926 | if (nextTerminalIds.length === 0) continue; |
| 915 | 927 | |
| 916 | 928 | for (const terminalId of nextTerminalIds) { |
| @@ -14,8 +14,10 @@ import { · 13 unmodified lines | ||
| 14 | 14 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 15 | 15 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; |
| 16 | 16 | import { createModelSelection } from "@t3tools/shared/model"; |
| 17 | +import * as Arr from "effect/Array"; | |
| 17 | 18 | import * as Duration from "effect/Duration"; |
| 18 | 19 | import * as Equal from "effect/Equal"; |
| 20 | +import * as Result from "effect/Result"; | |
| 19 | 21 | import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding"; |
| 20 | 22 | import { |
| 21 | 23 | canCheckForUpdate, |
| @@ -1143,7 +1145,12 @@ export function ProviderSettingsPanel() { · 1121 unmodified lines | ||
| 1143 | 1145 | nextFavoriteModels: ReadonlyArray<string>, |
| 1144 | 1146 | ) => { |
| 1145 | 1147 | const favoriteModels = [ |
| 1146 | − ...new Set(nextFavoriteModels.map((slug) => slug.trim()).filter((slug) => slug.length > 0)), | |
| 1148 | + ...new Set( | |
| 1149 | + Arr.filterMap(nextFavoriteModels, (slug) => { | |
| 1150 | + const trimmedSlug = slug.trim(); | |
| 1151 | + return trimmedSlug.length > 0 ? Result.succeed(trimmedSlug) : Result.failVoid; | |
| 1152 | + }), | |
| 1153 | + ), | |
| 1147 | 1154 | ]; |
| 1148 | 1155 | updateSettings({ |
| 1149 | 1156 | favorites: [ |
| @@ -1249,9 +1256,9 @@ export function ProviderSettingsPanel() { · 99 unmodified lines | ||
| 1249 | 1256 | hiddenModels: [], |
| 1250 | 1257 | modelOrder: [], |
| 1251 | 1258 | }; |
| 1252 | − const favoriteModels = (settings.favorites ?? []) | |
| 1253 | − .filter((favorite) => favorite.provider === row.instanceId) | |
| 1254 | − .map((favorite) => favorite.model); | |
| 1259 | + const favoriteModels = Arr.filterMap(settings.favorites ?? [], (favorite) => | |
| 1260 | + favorite.provider === row.instanceId ? Result.succeed(favorite.model) : Result.failVoid, | |
| 1261 | + ); | |
| 1255 | 1262 | const resetLabel = driverOption?.label ?? String(row.driver); |
| 1256 | 1263 | const headerAction = |
| 1257 | 1264 | row.isDefault && row.isDirty ? ( |
| @@ -204,7 +204,7 @@ export function useSourceControlActionRunning( · 203 unmodified lines | ||
| 204 | 204 | scope: SourceControlActionScope, |
| 205 | 205 | kinds: ReadonlyArray<SourceControlActionKind>, |
| 206 | 206 | ): boolean { |
| 207 | − const stableKinds = useMemo(() => [...kinds].sort(), [kinds]); | |
| 207 | + const stableKinds = useMemo(() => kinds.toSorted(), [kinds]); | |
| 208 | 208 | const appActionRunning = useSyncExternalStore( |
| 209 | 209 | subscribeActionState, |
| 210 | 210 | () => isAnyActionRunning(stableKinds, scope), |
| @@ -14,6 +14,8 @@ import { · 13 unmodified lines | ||
| 14 | 14 | } from "@t3tools/shared/model"; |
| 15 | 15 | import { getComposerProviderState } from "./components/chat/composerProviderState"; |
| 16 | 16 | import { UnifiedSettings } from "@t3tools/contracts/settings"; |
| 17 | +import * as Arr from "effect/Array"; | |
| 18 | +import * as Result from "effect/Result"; | |
| 17 | 19 | import { |
| 18 | 20 | getDefaultServerModel, |
| 19 | 21 | getProviderModels, |
| @@ -150,9 +152,9 @@ export function getAppModelOptions( · 130 unmodified lines | ||
| 150 | 152 | const options: AppModelOption[] = getProviderModels(providers, provider).map(toAppModelOption); |
| 151 | 153 | const seen = new Set(options.map((option) => option.slug)); |
| 152 | 154 | const builtInModelSlugs = new Set( |
| 153 | − getProviderModels(providers, provider) | |
| 154 | − .filter((model) => !model.isCustom) | |
| 155 | − .map((model) => model.slug), | |
| 155 | + Arr.filterMap(getProviderModels(providers, provider), (model) => | |
| 156 | + model.isCustom ? Result.failVoid : Result.succeed(model.slug), | |
| 157 | + ), | |
| 156 | 158 | ); |
| 157 | 159 | |
| 158 | 160 | // Read from the default instance's config first (that's where edits |
| @@ -198,7 +200,9 @@ export function getAppModelOptionsForInstance( · 39 unmodified lines | ||
| 198 | 200 | const options: AppModelOption[] = entry.models.map(toAppModelOption); |
| 199 | 201 | const seen = new Set(options.map((option) => option.slug)); |
| 200 | 202 | const builtInModelSlugs = new Set( |
| 201 | − entry.models.filter((model) => !model.isCustom).map((model) => model.slug), | |
| 203 | + Arr.filterMap(entry.models, (model) => | |
| 204 | + model.isCustom ? Result.failVoid : Result.succeed(model.slug), | |
| 205 | + ), | |
| 202 | 206 | ); |
| 203 | 207 | |
| 204 | 208 | const customModels = readInstanceCustomModels(settings, entry.instanceId, entry.driverKind); |
| @@ -58,7 +58,15 @@ function createTerminalUiStateStorage() { · 57 unmodified lines | ||
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | function normalizeTerminalIds(terminalIds: string[]): string[] { |
| 61 | − return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))]; | |
| 61 | + const normalizedIds: string[] = []; | |
| 62 | + const seen = new Set<string>(); | |
| 63 | + for (const id of terminalIds) { | |
| 64 | + const trimmedId = id.trim(); | |
| 65 | + if (trimmedId.length === 0 || seen.has(trimmedId)) continue; | |
| 66 | + seen.add(trimmedId); | |
| 67 | + normalizedIds.push(trimmedId); | |
| 68 | + } | |
| 69 | + return normalizedIds; | |
| 62 | 70 | } |
| 63 | 71 | |
| 64 | 72 | function fallbackGroupId(terminalId: string): string { |
| @@ -84,7 +92,7 @@ function findGroupIndexByTerminalId( · 19 unmodified lines | ||
| 84 | 92 | } |
| 85 | 93 | |
| 86 | 94 | function normalizeTerminalGroupIds(terminalIds: string[]): string[] { |
| 87 | − return [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))]; | |
| 95 | + return normalizeTerminalIds(terminalIds); | |
| 88 | 96 | } |
| 89 | 97 | |
| 90 | 98 | function normalizeTerminalGroups( |
| @@ -6,8 +6,10 @@ import type { · 5 unmodified lines | ||
| 6 | 6 | VcsStatusResult, |
| 7 | 7 | VcsStatusStreamEvent, |
| 8 | 8 | } from "@t3tools/contracts"; |
| 9 | +import * as Arr from "effect/Array"; | |
| 9 | 10 | import * as Effect from "effect/Effect"; |
| 10 | 11 | import * as Random from "effect/Random"; |
| 12 | +import * as Result from "effect/Result"; | |
| 11 | 13 | import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts"; |
| 12 | 14 | |
| 13 | 15 | export const WORKTREE_BRANCH_PREFIX = "t3code"; |
| @@ -172,7 +174,9 @@ export function dedupeRemoteBranchesWithLocalMatches( · 158 unmodified lines | ||
| 172 | 174 | refs: ReadonlyArray<VcsRef>, |
| 173 | 175 | ): ReadonlyArray<VcsRef> { |
| 174 | 176 | const localBranchNames = new Set( |
| 175 | − refs.filter((refName) => !refName.isRemote).map((refName) => refName.name), | |
| 177 | + Arr.filterMap(refs, (refName) => | |
| 178 | + refName.isRemote ? Result.failVoid : Result.succeed(refName.name), | |
| 179 | + ), | |
| 176 | 180 | ); |
| 177 | 181 | |
| 178 | 182 | return refs.filter((refName) => { |