| author | |
| committer | |
| log | 79c57170c7fb9cc1844427aed93c5f297fb110b5 |
| tree | 9566c4e17cc4887f8485ad95363ce69e0988daf5 |
| parent | 3900c45f25049ea8df359736f0ea8ad97bd3624f |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <julius@mac.lan>30 files changed, 254 insertions(+), 177 deletions(-)
apps/mobile/src/state/entities.ts+2-3| ... | @@ -12,8 +12,7 @@ import type { | ... | @@ -12,8 +12,7 @@ import type { |
| 12 | import { Atom } from "effect/unstable/reactivity"; | 12 | import { Atom } from "effect/unstable/reactivity"; |
| 13 | 13 | ||
| 14 | import { environmentProjects } from "./projects"; | 14 | import { environmentProjects } from "./projects"; |
| 15 | import { environmentServerConfigsAtom } from "./server"; | 15 | import { environmentServerConfigsAtom, serverEnvironment } from "./server"; |
| 16 | import { environmentSession } from "./session"; | ||
| 17 | import { environmentThreadShells } from "./threads"; | 16 | import { environmentThreadShells } from "./threads"; |
| 18 | 17 | ||
| 19 | const EMPTY_PROJECT_ATOM = Atom.make<EnvironmentProject | null>(null).pipe( | 18 | const EMPTY_PROJECT_ATOM = Atom.make<EnvironmentProject | null>(null).pipe( |
| ... | @@ -50,7 +49,7 @@ export function useEnvironmentServerConfig( | ... | @@ -50,7 +49,7 @@ export function useEnvironmentServerConfig( |
| 50 | return useAtomValue( | 49 | return useAtomValue( |
| 51 | environmentId === null | 50 | environmentId === null |
| 52 | ? EMPTY_SERVER_CONFIG_ATOM | 51 | ? EMPTY_SERVER_CONFIG_ATOM |
| 53 | : environmentSession.configValueAtom(environmentId), | 52 | : serverEnvironment.configValueAtom(environmentId), |
| 54 | ); | 53 | ); |
| 55 | } | 54 | } |
| 56 | 55 |
apps/mobile/src/state/presentation.ts+2-2| ... | @@ -5,12 +5,12 @@ import type { EnvironmentId } from "@t3tools/contracts"; | ... | @@ -5,12 +5,12 @@ import type { EnvironmentId } from "@t3tools/contracts"; |
| 5 | import { Atom } from "effect/unstable/reactivity"; | 5 | import { Atom } from "effect/unstable/reactivity"; |
| 6 | 6 | ||
| 7 | import { environmentCatalog } from "../connection/catalog"; | 7 | import { environmentCatalog } from "../connection/catalog"; |
| 8 | import { environmentSession } from "./session"; | 8 | import { serverEnvironment } from "./server"; |
| 9 | 9 | ||
| 10 | export const environmentPresentations = createEnvironmentPresentationAtoms({ | 10 | export const environmentPresentations = createEnvironmentPresentationAtoms({ |
| 11 | catalogValueAtom: environmentCatalog.catalogValueAtom, | 11 | catalogValueAtom: environmentCatalog.catalogValueAtom, |
| 12 | stateAtom: environmentCatalog.stateAtom, | 12 | stateAtom: environmentCatalog.stateAtom, |
| 13 | configValueAtom: environmentSession.configValueAtom, | 13 | serverConfigValueAtom: serverEnvironment.configValueAtom, |
| 14 | }); | 14 | }); |
| 15 | 15 | ||
| 16 | const EMPTY_ENVIRONMENT_PRESENTATION_ATOM = Atom.make<EnvironmentPresentation | null>(null).pipe( | 16 | const EMPTY_ENVIRONMENT_PRESENTATION_ATOM = Atom.make<EnvironmentPresentation | null>(null).pipe( |
apps/mobile/src/state/server.ts+2-2| ... | @@ -6,9 +6,9 @@ import { connectionAtomRuntime } from "../connection/runtime"; | ... | @@ -6,9 +6,9 @@ import { connectionAtomRuntime } from "../connection/runtime"; |
| 6 | import { environmentSession } from "./session"; | 6 | import { environmentSession } from "./session"; |
| 7 | 7 | ||
| 8 | export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, { | 8 | export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, { |
| 9 | initialConfigValueAtom: environmentSession.configValueAtom, | 9 | initialConfigValueAtom: environmentSession.initialConfigValueAtom, |
| 10 | }); | 10 | }); |
| 11 | export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({ | 11 | export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({ |
| 12 | catalogValueAtom: environmentCatalog.catalogValueAtom, | 12 | catalogValueAtom: environmentCatalog.catalogValueAtom, |
| 13 | configValueAtom: serverEnvironment.configValueAtom, | 13 | serverConfigValueAtom: serverEnvironment.configValueAtom, |
| 14 | }); | 14 | }); |
apps/web/src/components/ChatView.tsx+8-4| ... | @@ -138,7 +138,7 @@ import { | ... | @@ -138,7 +138,7 @@ import { |
| 138 | } from "~/projectScripts"; | 138 | } from "~/projectScripts"; |
| 139 | import { newDraftId, newMessageId, newThreadId } from "~/lib/utils"; | 139 | import { newDraftId, newMessageId, newThreadId } from "~/lib/utils"; |
| 140 | import { getProviderModelCapabilities, resolveSelectableProvider } from "../providerModels"; | 140 | import { getProviderModelCapabilities, resolveSelectableProvider } from "../providerModels"; |
| 141 | import { useSettings } from "../hooks/useSettings"; | 141 | import { useEnvironmentSettings } from "../hooks/useSettings"; |
| 142 | import { resolveAppModelSelectionForInstance } from "../modelSelection"; | 142 | import { resolveAppModelSelectionForInstance } from "../modelSelection"; |
| 143 | import { getTerminalFocusOwner } from "../lib/terminalFocus"; | 143 | import { getTerminalFocusOwner } from "../lib/terminalFocus"; |
| 144 | import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions"; | 144 | import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions"; |
| ... | @@ -1027,7 +1027,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -1027,7 +1027,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 1027 | const activeThreadLastVisitedAt = useUiStateStore((store) => | 1027 | const activeThreadLastVisitedAt = useUiStateStore((store) => |
| 1028 | routeKind === "server" ? store.threadLastVisitedAtById[routeThreadKey] : undefined, | 1028 | routeKind === "server" ? store.threadLastVisitedAtById[routeThreadKey] : undefined, |
| 1029 | ); | 1029 | ); |
| 1030 | const settings = useSettings(); | 1030 | const settings = useEnvironmentSettings(environmentId); |
| 1031 | const setStickyComposerModelSelection = useComposerDraftStore( | 1031 | const setStickyComposerModelSelection = useComposerDraftStore( |
| 1032 | (store) => store.setStickyModelSelection, | 1032 | (store) => store.setStickyModelSelection, |
| 1033 | ); | 1033 | ); |
| ... | @@ -1417,7 +1417,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -1417,7 +1417,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 1417 | }, | 1417 | }, |
| 1418 | [retryEnvironment], | 1418 | [retryEnvironment], |
| 1419 | ); | 1419 | ); |
| 1420 | const projectGroupingSettings = useSettings(selectProjectGroupingSettings); | 1420 | const projectGroupingSettings = selectProjectGroupingSettings(settings); |
| 1421 | const logicalProjectEnvironments = useMemo(() => { | 1421 | const logicalProjectEnvironments = useMemo(() => { |
| 1422 | if (!activeProject) return []; | 1422 | if (!activeProject) return []; |
| 1423 | const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings); | 1423 | const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings); |
| ... | @@ -1586,7 +1586,11 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -1586,7 +1586,11 @@ function ChatViewContent(props: ChatViewProps) { |
| 1586 | selectedProvider: selectedProviderByThreadId, | 1586 | selectedProvider: selectedProviderByThreadId, |
| 1587 | threadProvider, | 1587 | threadProvider, |
| 1588 | }); | 1588 | }); |
| 1589 | const serverConfig = activeEnvironment?.serverConfig ?? primaryEnvironment?.serverConfig ?? null; | 1589 | // Once a thread selects an environment, never substitute the primary |
| 1590 | // environment's config while the selected environment is still loading. | ||
| 1591 | const serverConfig = activeThread | ||
| 1592 | ? (activeEnvironment?.serverConfig ?? null) | ||
| 1593 | : (primaryEnvironment?.serverConfig ?? null); | ||
| 1590 | const versionMismatch = resolveServerConfigVersionMismatch(serverConfig); | 1594 | const versionMismatch = resolveServerConfigVersionMismatch(serverConfig); |
| 1591 | const versionMismatchDismissKey = | 1595 | const versionMismatchDismissKey = |
| 1592 | versionMismatch && activeThread | 1596 | versionMismatch && activeThread |
apps/web/src/components/CommandPalette.tsx+14-38| ... | @@ -45,7 +45,7 @@ import { | ... | @@ -45,7 +45,7 @@ import { |
| 45 | import { useAtomValue } from "@effect/atom-react"; | 45 | import { useAtomValue } from "@effect/atom-react"; |
| 46 | import { OpenAddProjectCommandPaletteProvider } from "../commandPaletteContext"; | 46 | import { OpenAddProjectCommandPaletteProvider } from "../commandPaletteContext"; |
| 47 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; | 47 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; |
| 48 | import { useSettings } from "../hooks/useSettings"; | 48 | import { useClientSettings } from "../hooks/useSettings"; |
| 49 | import { readLocalApi } from "../localApi"; | 49 | import { readLocalApi } from "../localApi"; |
| 50 | import { filesystemEnvironment } from "../state/filesystem"; | 50 | import { filesystemEnvironment } from "../state/filesystem"; |
| 51 | import { projectEnvironment } from "../state/projects"; | 51 | import { projectEnvironment } from "../state/projects"; |
| ... | @@ -447,7 +447,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -447,7 +447,7 @@ function OpenCommandPaletteDialog(props: { |
| 447 | const deferredQuery = useDeferredValue(query); | 447 | const deferredQuery = useDeferredValue(query); |
| 448 | const isActionsOnly = deferredQuery.startsWith(">"); | 448 | const isActionsOnly = deferredQuery.startsWith(">"); |
| 449 | const [highlightedItemValue, setHighlightedItemValue] = useState<string | null>(null); | 449 | const [highlightedItemValue, setHighlightedItemValue] = useState<string | null>(null); |
| 450 | const settings = useSettings(); | 450 | const clientSettings = useClientSettings(); |
| 451 | const createProject = useAtomCommand(projectEnvironment.create, { | 451 | const createProject = useAtomCommand(projectEnvironment.create, { |
| 452 | reportFailure: false, | 452 | reportFailure: false, |
| 453 | }); | 453 | }); |
| ... | @@ -524,16 +524,14 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -524,16 +524,14 @@ function OpenCommandPaletteDialog(props: { |
| 524 | const environment = environments.find( | 524 | const environment = environments.find( |
| 525 | (candidate) => candidate.environmentId === environmentId, | 525 | (candidate) => candidate.environmentId === environmentId, |
| 526 | ); | 526 | ); |
| 527 | const environmentSettings = | 527 | const environmentSettings = environment?.serverConfig?.settings ?? null; |
| 528 | environment?.serverConfig?.settings ?? | ||
| 529 | (environmentId === primaryEnvironmentId ? settings : null); | ||
| 530 | const baseDirectory = environmentSettings?.addProjectBaseDirectory?.trim() ?? ""; | 528 | const baseDirectory = environmentSettings?.addProjectBaseDirectory?.trim() ?? ""; |
| 531 | if (baseDirectory.length === 0) { | 529 | if (baseDirectory.length === 0) { |
| 532 | return "~/"; | 530 | return "~/"; |
| 533 | } | 531 | } |
| 534 | return ensureBrowseDirectoryPath(baseDirectory); | 532 | return ensureBrowseDirectoryPath(baseDirectory); |
| 535 | }, | 533 | }, |
| 536 | [environments, primaryEnvironmentId, settings], | 534 | [environments], |
| 537 | ); | 535 | ); |
| 538 | 536 | ||
| 539 | const projectCwdById = useMemo( | 537 | const projectCwdById = useMemo( |
| ... | @@ -589,7 +587,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -589,7 +587,7 @@ function OpenCommandPaletteDialog(props: { |
| 589 | const latestThread = getLatestThreadForProject( | 587 | const latestThread = getLatestThreadForProject( |
| 590 | threads.filter((thread) => thread.environmentId === project.environmentId), | 588 | threads.filter((thread) => thread.environmentId === project.environmentId), |
| 591 | project.id, | 589 | project.id, |
| 592 | settings.sidebarThreadSortOrder, | 590 | clientSettings.sidebarThreadSortOrder, |
| 593 | ); | 591 | ); |
| 594 | if (latestThread) { | 592 | if (latestThread) { |
| 595 | await navigate({ | 593 | await navigate({ |
| ... | @@ -601,17 +599,9 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -601,17 +599,9 @@ function OpenCommandPaletteDialog(props: { |
| 601 | return; | 599 | return; |
| 602 | } | 600 | } |
| 603 | 601 | ||
| 604 | await handleNewThread(scopeProjectRef(project.environmentId, project.id), { | 602 | await handleNewThread(scopeProjectRef(project.environmentId, project.id)); |
| 605 | envMode: settings.defaultThreadEnvMode, | ||
| 606 | }); | ||
| 607 | }, | 603 | }, |
| 608 | [ | 604 | [handleNewThread, navigate, clientSettings.sidebarThreadSortOrder, threads], |
| 609 | handleNewThread, | ||
| 610 | navigate, | ||
| 611 | settings.defaultThreadEnvMode, | ||
| 612 | settings.sidebarThreadSortOrder, | ||
| 613 | threads, | ||
| 614 | ], | ||
| 615 | ); | 605 | ); |
| 616 | 606 | ||
| 617 | const projectSearchItems = useMemo( | 607 | const projectSearchItems = useMemo( |
| ... | @@ -650,21 +640,13 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -650,21 +640,13 @@ function OpenCommandPaletteDialog(props: { |
| 650 | activeDraftThread, | 640 | activeDraftThread, |
| 651 | activeThread: activeThread ?? undefined, | 641 | activeThread: activeThread ?? undefined, |
| 652 | defaultProjectRef, | 642 | defaultProjectRef, |
| 653 | defaultThreadEnvMode: settings.defaultThreadEnvMode, | ||
| 654 | handleNewThread, | 643 | handleNewThread, |
| 655 | }, | 644 | }, |
| 656 | scopeProjectRef(project.environmentId, project.id), | 645 | scopeProjectRef(project.environmentId, project.id), |
| 657 | ); | 646 | ); |
| 658 | }, | 647 | }, |
| 659 | }), | 648 | }), |
| 660 | [ | 649 | [activeDraftThread, activeThread, defaultProjectRef, handleNewThread, projects], |
| 661 | activeDraftThread, | ||
| 662 | activeThread, | ||
| 663 | defaultProjectRef, | ||
| 664 | handleNewThread, | ||
| 665 | projects, | ||
| 666 | settings.defaultThreadEnvMode, | ||
| 667 | ], | ||
| 668 | ); | 650 | ); |
| 669 | 651 | ||
| 670 | const allThreadItems = useMemo( | 652 | const allThreadItems = useMemo( |
| ... | @@ -673,7 +655,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -673,7 +655,7 @@ function OpenCommandPaletteDialog(props: { |
| 673 | threads, | 655 | threads, |
| 674 | ...(activeThreadId ? { activeThreadId } : {}), | 656 | ...(activeThreadId ? { activeThreadId } : {}), |
| 675 | projectTitleById, | 657 | projectTitleById, |
| 676 | sortOrder: settings.sidebarThreadSortOrder, | 658 | sortOrder: clientSettings.sidebarThreadSortOrder, |
| 677 | icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />, | 659 | icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />, |
| 678 | renderLeadingContent: (thread) => <ThreadRowLeadingStatus thread={thread} />, | 660 | renderLeadingContent: (thread) => <ThreadRowLeadingStatus thread={thread} />, |
| 679 | renderTrailingContent: (thread) => <ThreadRowTrailingStatus thread={thread} />, | 661 | renderTrailingContent: (thread) => <ThreadRowTrailingStatus thread={thread} />, |
| ... | @@ -684,7 +666,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -684,7 +666,7 @@ function OpenCommandPaletteDialog(props: { |
| 684 | }); | 666 | }); |
| 685 | }, | 667 | }, |
| 686 | }), | 668 | }), |
| 687 | [activeThreadId, navigate, projectTitleById, settings.sidebarThreadSortOrder, threads], | 669 | [activeThreadId, clientSettings.sidebarThreadSortOrder, navigate, projectTitleById, threads], |
| 688 | ); | 670 | ); |
| 689 | const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); | 671 | const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); |
| 690 | 672 | ||
| ... | @@ -956,7 +938,6 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -956,7 +938,6 @@ function OpenCommandPaletteDialog(props: { |
| 956 | activeDraftThread, | 938 | activeDraftThread, |
| 957 | activeThread: activeThread ?? undefined, | 939 | activeThread: activeThread ?? undefined, |
| 958 | defaultProjectRef, | 940 | defaultProjectRef, |
| 959 | defaultThreadEnvMode: settings.defaultThreadEnvMode, | ||
| 960 | handleNewThread, | 941 | handleNewThread, |
| 961 | }); | 942 | }); |
| 962 | }, | 943 | }, |
| ... | @@ -1072,7 +1053,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1072,7 +1053,7 @@ function OpenCommandPaletteDialog(props: { |
| 1072 | const latestThread = getLatestThreadForProject( | 1053 | const latestThread = getLatestThreadForProject( |
| 1073 | threads.filter((thread) => thread.environmentId === existing.environmentId), | 1054 | threads.filter((thread) => thread.environmentId === existing.environmentId), |
| 1074 | existing.id, | 1055 | existing.id, |
| 1075 | settings.sidebarThreadSortOrder, | 1056 | clientSettings.sidebarThreadSortOrder, |
| 1076 | ); | 1057 | ); |
| 1077 | if (latestThread) { | 1058 | if (latestThread) { |
| 1078 | await navigate({ | 1059 | await navigate({ |
| ... | @@ -1083,9 +1064,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1083,9 +1064,7 @@ function OpenCommandPaletteDialog(props: { |
| 1083 | }); | 1064 | }); |
| 1084 | } else { | 1065 | } else { |
| 1085 | const navigationResult = await settlePromise(() => | 1066 | const navigationResult = await settlePromise(() => |
| 1086 | handleNewThread(scopeProjectRef(existing.environmentId, existing.id), { | 1067 | handleNewThread(scopeProjectRef(existing.environmentId, existing.id)), |
| 1087 | envMode: settings.defaultThreadEnvMode, | ||
| 1088 | }), | ||
| 1089 | ); | 1068 | ); |
| 1090 | if (navigationResult._tag === "Failure") { | 1069 | if (navigationResult._tag === "Failure") { |
| 1091 | const error = squashAtomCommandFailure(navigationResult); | 1070 | const error = squashAtomCommandFailure(navigationResult); |
| ... | @@ -1132,9 +1111,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1132,9 +1111,7 @@ function OpenCommandPaletteDialog(props: { |
| 1132 | } | 1111 | } |
| 1133 | 1112 | ||
| 1134 | const navigationResult = await settlePromise(() => | 1113 | const navigationResult = await settlePromise(() => |
| 1135 | handleNewThread(scopeProjectRef(browseEnvironmentId, projectId), { | 1114 | handleNewThread(scopeProjectRef(browseEnvironmentId, projectId)), |
| 1136 | envMode: settings.defaultThreadEnvMode, | ||
| 1137 | }), | ||
| 1138 | ); | 1115 | ); |
| 1139 | if (navigationResult._tag === "Failure") { | 1116 | if (navigationResult._tag === "Failure") { |
| 1140 | const error = squashAtomCommandFailure(navigationResult); | 1117 | const error = squashAtomCommandFailure(navigationResult); |
| ... | @@ -1158,8 +1135,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1158,8 +1135,7 @@ function OpenCommandPaletteDialog(props: { |
| 1158 | navigate, | 1135 | navigate, |
| 1159 | projects, | 1136 | projects, |
| 1160 | setOpen, | 1137 | setOpen, |
| 1161 | settings.defaultThreadEnvMode, | 1138 | clientSettings.sidebarThreadSortOrder, |
| 1162 | settings.sidebarThreadSortOrder, | ||
| 1163 | threads, | 1139 | threads, |
| 1164 | ], | 1140 | ], |
| 1165 | ); | 1141 | ); |
apps/web/src/components/DiffPanel.tsx+2-2| ... | @@ -34,7 +34,7 @@ import { | ... | @@ -34,7 +34,7 @@ import { |
| 34 | import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; | 34 | import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; |
| 35 | import { useProject, useThread } from "../state/entities"; | 35 | import { useProject, useThread } from "../state/entities"; |
| 36 | import { resolveThreadRouteRef } from "../threadRoutes"; | 36 | import { resolveThreadRouteRef } from "../threadRoutes"; |
| 37 | import { useSettings } from "../hooks/useSettings"; | 37 | import { useClientSettings } from "../hooks/useSettings"; |
| 38 | import { formatShortTimestamp } from "../timestampFormat"; | 38 | import { formatShortTimestamp } from "../timestampFormat"; |
| 39 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; | 39 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; |
| 40 | import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/AnnotatableFileDiff"; | 40 | import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/AnnotatableFileDiff"; |
| ... | @@ -183,7 +183,7 @@ export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; | ... | @@ -183,7 +183,7 @@ export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; |
| 183 | 183 | ||
| 184 | export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) { | 184 | export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) { |
| 185 | const { resolvedTheme } = useTheme(); | 185 | const { resolvedTheme } = useTheme(); |
| 186 | const settings = useSettings(); | 186 | const settings = useClientSettings(); |
| 187 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); | 187 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 188 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); | 188 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 189 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); | 189 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); |
apps/web/src/components/Sidebar.tsx+21-20| ... | @@ -41,11 +41,11 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd- | ... | @@ -41,11 +41,11 @@ import { restrictToFirstScrollableAncestor, restrictToVerticalAxis } from "@dnd- |
| 41 | import { CSS } from "@dnd-kit/utilities"; | 41 | import { CSS } from "@dnd-kit/utilities"; |
| 42 | import { | 42 | import { |
| 43 | type ContextMenuItem, | 43 | type ContextMenuItem, |
| 44 | DEFAULT_SERVER_SETTINGS, | ||
| 44 | ProjectId, | 45 | ProjectId, |
| 45 | type ScopedThreadRef, | 46 | type ScopedThreadRef, |
| 46 | type ResolvedKeybindingsConfig, | 47 | type ResolvedKeybindingsConfig, |
| 47 | type SidebarProjectGroupingMode, | 48 | type SidebarProjectGroupingMode, |
| 48 | type ThreadEnvMode, | ||
| 49 | ThreadId, | 49 | ThreadId, |
| 50 | } from "@t3tools/contracts"; | 50 | } from "@t3tools/contracts"; |
| 51 | import { | 51 | import { |
| ... | @@ -77,6 +77,7 @@ import { | ... | @@ -77,6 +77,7 @@ import { |
| 77 | readThreadShell, | 77 | readThreadShell, |
| 78 | useProject, | 78 | useProject, |
| 79 | useProjects, | 79 | useProjects, |
| 80 | useServerConfigs, | ||
| 80 | useThreadShells, | 81 | useThreadShells, |
| 81 | useThreadShellsForProjectRefs, | 82 | useThreadShellsForProjectRefs, |
| 82 | } from "../state/entities"; | 83 | } from "../state/entities"; |
| ... | @@ -199,7 +200,7 @@ import { SidebarUpdatePill } from "./sidebar/SidebarUpdatePill"; | ... | @@ -199,7 +200,7 @@ import { SidebarUpdatePill } from "./sidebar/SidebarUpdatePill"; |
| 199 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; | 200 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; |
| 200 | import { useIsMobile } from "~/hooks/useMediaQuery"; | 201 | import { useIsMobile } from "~/hooks/useMediaQuery"; |
| 201 | import { CommandDialogTrigger } from "./ui/command"; | 202 | import { CommandDialogTrigger } from "./ui/command"; |
| 202 | import { useSettings, useUpdateSettings } from "~/hooks/useSettings"; | 203 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; |
| 203 | import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server"; | 204 | import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server"; |
| 204 | import { | 205 | import { |
| 205 | derivePhysicalProjectKey, | 206 | derivePhysicalProjectKey, |
| ... | @@ -1084,19 +1085,17 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1084,19 +1085,17 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1084 | isManualProjectSorting, | 1085 | isManualProjectSorting, |
| 1085 | dragHandleProps, | 1086 | dragHandleProps, |
| 1086 | } = props; | 1087 | } = props; |
| 1087 | const threadSortOrder = useSettings<SidebarThreadSortOrder>( | 1088 | const threadSortOrder = useClientSettings<SidebarThreadSortOrder>( |
| 1088 | (settings) => settings.sidebarThreadSortOrder, | 1089 | (settings) => settings.sidebarThreadSortOrder, |
| 1089 | ); | 1090 | ); |
| 1090 | const appSettingsConfirmThreadDelete = useSettings<boolean>( | 1091 | const appSettingsConfirmThreadDelete = useClientSettings<boolean>( |
| 1091 | (settings) => settings.confirmThreadDelete, | 1092 | (settings) => settings.confirmThreadDelete, |
| 1092 | ); | 1093 | ); |
| 1093 | const appSettingsConfirmThreadArchive = useSettings<boolean>( | 1094 | const appSettingsConfirmThreadArchive = useClientSettings<boolean>( |
| 1094 | (settings) => settings.confirmThreadArchive, | 1095 | (settings) => settings.confirmThreadArchive, |
| 1095 | ); | 1096 | ); |
| 1096 | const defaultThreadEnvMode = useSettings<ThreadEnvMode>( | 1097 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 1097 | (settings) => settings.defaultThreadEnvMode, | 1098 | const serverConfigs = useServerConfigs(); |
| 1098 | ); | ||
| 1099 | const projectGroupingSettings = useSettings(selectProjectGroupingSettings); | ||
| 1100 | const deleteProject = useAtomCommand(projectEnvironment.delete, { | 1099 | const deleteProject = useAtomCommand(projectEnvironment.delete, { |
| 1101 | reportFailure: false, | 1100 | reportFailure: false, |
| 1102 | }); | 1101 | }); |
| ... | @@ -1106,8 +1105,8 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1106,8 +1105,8 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1106 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { | 1105 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { |
| 1107 | reportFailure: false, | 1106 | reportFailure: false, |
| 1108 | }); | 1107 | }); |
| 1109 | const updateSettings = useUpdateSettings(); | 1108 | const updateSettings = useUpdateClientSettings(); |
| 1110 | const sidebarThreadPreviewCount = useSettings<SidebarThreadPreviewCount>( | 1109 | const sidebarThreadPreviewCount = useClientSettings<SidebarThreadPreviewCount>( |
| 1111 | (settings) => settings.sidebarThreadPreviewCount, | 1110 | (settings) => settings.sidebarThreadPreviewCount, |
| 1112 | ); | 1111 | ); |
| 1113 | const router = useRouter(); | 1112 | const router = useRouter(); |
| ... | @@ -1840,7 +1839,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1840,7 +1839,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1840 | const seedContext = resolveSidebarNewThreadSeedContext({ | 1839 | const seedContext = resolveSidebarNewThreadSeedContext({ |
| 1841 | projectId: member.id, | 1840 | projectId: member.id, |
| 1842 | defaultEnvMode: resolveSidebarNewThreadEnvMode({ | 1841 | defaultEnvMode: resolveSidebarNewThreadEnvMode({ |
| 1843 | defaultEnvMode: defaultThreadEnvMode, | 1842 | defaultEnvMode: |
| 1843 | serverConfigs.get(member.environmentId)?.settings.defaultThreadEnvMode ?? | ||
| 1844 | DEFAULT_SERVER_SETTINGS.defaultThreadEnvMode, | ||
| 1844 | }), | 1845 | }), |
| 1845 | activeThread: | 1846 | activeThread: |
| 1846 | currentActiveThread && currentActiveThread.projectId === member.id | 1847 | currentActiveThread && currentActiveThread.projectId === member.id |
| ... | @@ -1889,7 +1890,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1889,7 +1890,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1889 | } | 1890 | } |
| 1890 | })(); | 1891 | })(); |
| 1891 | }, | 1892 | }, |
| 1892 | [defaultThreadEnvMode, handleNewThread, isMobile, router, setOpenMobile], | 1893 | [handleNewThread, isMobile, router, serverConfigs, setOpenMobile], |
| 1893 | ); | 1894 | ); |
| 1894 | 1895 | ||
| 1895 | const handleCreateThreadClick = useCallback( | 1896 | const handleCreateThreadClick = useCallback( |
| ... | @@ -2745,7 +2746,7 @@ interface SidebarProjectsContentProps { | ... | @@ -2745,7 +2746,7 @@ interface SidebarProjectsContentProps { |
| 2745 | threadSortOrder: SidebarThreadSortOrder; | 2746 | threadSortOrder: SidebarThreadSortOrder; |
| 2746 | projectGroupingMode: SidebarProjectGroupingMode; | 2747 | projectGroupingMode: SidebarProjectGroupingMode; |
| 2747 | threadPreviewCount: SidebarThreadPreviewCount; | 2748 | threadPreviewCount: SidebarThreadPreviewCount; |
| 2748 | updateSettings: ReturnType<typeof useUpdateSettings>; | 2749 | updateSettings: ReturnType<typeof useUpdateClientSettings>; |
| 2749 | openAddProject: () => void; | 2750 | openAddProject: () => void; |
| 2750 | isManualProjectSorting: boolean; | 2751 | isManualProjectSorting: boolean; |
| 2751 | projectDnDSensors: ReturnType<typeof useSensors>; | 2752 | projectDnDSensors: ReturnType<typeof useSensors>; |
| ... | @@ -3014,12 +3015,12 @@ export default function Sidebar() { | ... | @@ -3014,12 +3015,12 @@ export default function Sidebar() { |
| 3014 | const navigate = useNavigate(); | 3015 | const navigate = useNavigate(); |
| 3015 | const pathname = useLocation({ select: (loc) => loc.pathname }); | 3016 | const pathname = useLocation({ select: (loc) => loc.pathname }); |
| 3016 | const isOnSettings = pathname.startsWith("/settings"); | 3017 | const isOnSettings = pathname.startsWith("/settings"); |
| 3017 | const sidebarThreadSortOrder = useSettings((s) => s.sidebarThreadSortOrder); | 3018 | const sidebarThreadSortOrder = useClientSettings((s) => s.sidebarThreadSortOrder); |
| 3018 | const sidebarProjectSortOrder = useSettings((s) => s.sidebarProjectSortOrder); | 3019 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); |
| 3019 | const sidebarProjectGroupingMode = useSettings((s) => s.sidebarProjectGroupingMode); | 3020 | const sidebarProjectGroupingMode = useClientSettings((s) => s.sidebarProjectGroupingMode); |
| 3020 | const projectGroupingSettings = useSettings(selectProjectGroupingSettings); | 3021 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 3021 | const sidebarThreadPreviewCount = useSettings((s) => s.sidebarThreadPreviewCount); | 3022 | const sidebarThreadPreviewCount = useClientSettings((s) => s.sidebarThreadPreviewCount); |
| 3022 | const updateSettings = useUpdateSettings(); | 3023 | const updateSettings = useUpdateClientSettings(); |
| 3023 | const handleNewThread = useNewThreadHandler(); | 3024 | const handleNewThread = useNewThreadHandler(); |
| 3024 | const { archiveThread, deleteThread } = useThreadActions(); | 3025 | const { archiveThread, deleteThread } = useThreadActions(); |
| 3025 | const { isMobile, setOpenMobile } = useSidebar(); | 3026 | const { isMobile, setOpenMobile } = useSidebar(); |
apps/web/src/components/chat/ModelPickerContent.tsx+3-3| ... | @@ -19,7 +19,7 @@ import { | ... | @@ -19,7 +19,7 @@ import { |
| 19 | resolveShortcutCommand, | 19 | resolveShortcutCommand, |
| 20 | shortcutLabelForCommand, | 20 | shortcutLabelForCommand, |
| 21 | } from "../../keybindings"; | 21 | } from "../../keybindings"; |
| 22 | import { useSettings, useUpdateSettings } from "~/hooks/useSettings"; | 22 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; |
| 23 | import { cn } from "~/lib/utils"; | 23 | import { cn } from "~/lib/utils"; |
| 24 | import { TooltipProvider } from "../ui/tooltip"; | 24 | import { TooltipProvider } from "../ui/tooltip"; |
| 25 | import { | 25 | import { |
| ... | @@ -102,7 +102,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -102,7 +102,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 102 | const searchInputRef = useRef<HTMLInputElement>(null); | 102 | const searchInputRef = useRef<HTMLInputElement>(null); |
| 103 | const modelListRef = useRef<LegendListRef | null>(null); | 103 | const modelListRef = useRef<LegendListRef | null>(null); |
| 104 | const highlightedModelKeyRef = useRef<string | null>(null); | 104 | const highlightedModelKeyRef = useRef<string | null>(null); |
| 105 | const favorites = useSettings((s) => s.favorites ?? []); | 105 | const favorites = useClientSettings((s) => s.favorites ?? []); |
| 106 | const [selectedInstanceId, setSelectedInstanceId] = useState<ProviderInstanceId | "favorites">( | 106 | const [selectedInstanceId, setSelectedInstanceId] = useState<ProviderInstanceId | "favorites">( |
| 107 | () => { | 107 | () => { |
| 108 | if (props.lockedProvider !== null) { | 108 | if (props.lockedProvider !== null) { |
| ... | @@ -117,7 +117,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -117,7 +117,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 117 | () => providedKeybindings ?? [], | 117 | () => providedKeybindings ?? [], |
| 118 | [providedKeybindings], | 118 | [providedKeybindings], |
| 119 | ); | 119 | ); |
| 120 | const updateSettings = useUpdateSettings(); | 120 | const updateSettings = useUpdateClientSettings(); |
| 121 | 121 | ||
| 122 | const focusSearchInput = useCallback(() => { | 122 | const focusSearchInput = useCallback(() => { |
| 123 | searchInputRef.current?.focus({ preventScroll: true }); | 123 | searchInputRef.current?.focus({ preventScroll: true }); |
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+3-3| ... | @@ -9,7 +9,7 @@ import { | ... | @@ -9,7 +9,7 @@ import { |
| 9 | type ProviderInstanceConfig, | 9 | type ProviderInstanceConfig, |
| 10 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 11 | 11 | ||
| 12 | import { useSettings, useUpdateSettings } from "../../hooks/useSettings"; | 12 | import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings"; |
| 13 | import { cn } from "../../lib/utils"; | 13 | import { cn } from "../../lib/utils"; |
| 14 | import { normalizeProviderAccentColor } from "../../providerInstances"; | 14 | import { normalizeProviderAccentColor } from "../../providerInstances"; |
| 15 | import { Button } from "../ui/button"; | 15 | import { Button } from "../ui/button"; |
| ... | @@ -114,8 +114,8 @@ interface AddProviderInstanceDialogProps { | ... | @@ -114,8 +114,8 @@ interface AddProviderInstanceDialogProps { |
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderInstanceDialogProps) { | 116 | export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderInstanceDialogProps) { |
| 117 | const settings = useSettings(); | 117 | const settings = usePrimarySettings(); |
| 118 | const updateSettings = useUpdateSettings(); | 118 | const updateSettings = useUpdatePrimarySettings(); |
| 119 | 119 | ||
| 120 | const [wizardStep, setWizardStep] = useState(0); | 120 | const [wizardStep, setWizardStep] = useState(0); |
| 121 | const [driver, setDriver] = useState<ProviderDriverKind>(DEFAULT_DRIVER_KIND); | 121 | const [driver, setDriver] = useState<ProviderDriverKind>(DEFAULT_DRIVER_KIND); |
apps/web/src/components/settings/SettingsPanels.tsx+7-7| ... | @@ -36,7 +36,7 @@ import { TraitsPicker } from "../chat/TraitsPicker"; | ... | @@ -36,7 +36,7 @@ import { TraitsPicker } from "../chat/TraitsPicker"; |
| 36 | import { isElectron } from "../../env"; | 36 | import { isElectron } from "../../env"; |
| 37 | import { buildHostedChannelSelectionUrl, type HostedAppChannel } from "../../hostedPairing"; | 37 | import { buildHostedChannelSelectionUrl, type HostedAppChannel } from "../../hostedPairing"; |
| 38 | import { useTheme } from "../../hooks/useTheme"; | 38 | import { useTheme } from "../../hooks/useTheme"; |
| 39 | import { useSettings, useUpdateSettings } from "../../hooks/useSettings"; | 39 | import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings"; |
| 40 | import { useThreadActions } from "../../hooks/useThreadActions"; | 40 | import { useThreadActions } from "../../hooks/useThreadActions"; |
| 41 | import { useDesktopUpdateState } from "../../state/desktopUpdate"; | 41 | import { useDesktopUpdateState } from "../../state/desktopUpdate"; |
| 42 | import { | 42 | import { |
| ... | @@ -373,8 +373,8 @@ function AboutVersionSection() { | ... | @@ -373,8 +373,8 @@ function AboutVersionSection() { |
| 373 | 373 | ||
| 374 | export function useSettingsRestore(onRestored?: () => void) { | 374 | export function useSettingsRestore(onRestored?: () => void) { |
| 375 | const { theme, setTheme } = useTheme(); | 375 | const { theme, setTheme } = useTheme(); |
| 376 | const settings = useSettings(); | 376 | const settings = usePrimarySettings(); |
| 377 | const updateSettings = useUpdateSettings(); | 377 | const updateSettings = useUpdatePrimarySettings(); |
| 378 | 378 | ||
| 379 | const isGitWritingModelDirty = !Equal.equals( | 379 | const isGitWritingModelDirty = !Equal.equals( |
| 380 | settings.textGenerationModelSelection ?? null, | 380 | settings.textGenerationModelSelection ?? null, |
| ... | @@ -479,8 +479,8 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -479,8 +479,8 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 479 | 479 | ||
| 480 | export function GeneralSettingsPanel() { | 480 | export function GeneralSettingsPanel() { |
| 481 | const { theme, setTheme } = useTheme(); | 481 | const { theme, setTheme } = useTheme(); |
| 482 | const settings = useSettings(); | 482 | const settings = usePrimarySettings(); |
| 483 | const updateSettings = useUpdateSettings(); | 483 | const updateSettings = useUpdatePrimarySettings(); |
| 484 | const observability = useAtomValue(primaryServerObservabilityAtom); | 484 | const observability = useAtomValue(primaryServerObservabilityAtom); |
| 485 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | 485 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |
| 486 | const diagnosticsDescription = formatDiagnosticsDescription({ | 486 | const diagnosticsDescription = formatDiagnosticsDescription({ |
| ... | @@ -977,8 +977,8 @@ export function GeneralSettingsPanel() { | ... | @@ -977,8 +977,8 @@ export function GeneralSettingsPanel() { |
| 977 | } | 977 | } |
| 978 | 978 | ||
| 979 | export function ProviderSettingsPanel() { | 979 | export function ProviderSettingsPanel() { |
| 980 | const settings = useSettings(); | 980 | const settings = usePrimarySettings(); |
| 981 | const updateSettings = useUpdateSettings(); | 981 | const updateSettings = useUpdatePrimarySettings(); |
| 982 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | 982 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |
| 983 | const primaryEnvironment = usePrimaryEnvironment(); | 983 | const primaryEnvironment = usePrimaryEnvironment(); |
| 984 | const refreshServerProviders = useAtomCommand(serverEnvironment.refreshProviders, { | 984 | const refreshServerProviders = useAtomCommand(serverEnvironment.refreshProviders, { |
apps/web/src/components/settings/SourceControlSettings.tsx+5-3| ... | @@ -12,7 +12,7 @@ import type { | ... | @@ -12,7 +12,7 @@ import type { |
| 12 | } from "@t3tools/contracts"; | 12 | } from "@t3tools/contracts"; |
| 13 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; | 13 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; |
| 14 | 14 | ||
| 15 | import { useSettings, useUpdateSettings } from "../../hooks/useSettings"; | 15 | import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings"; |
| 16 | import { cn } from "../../lib/utils"; | 16 | import { cn } from "../../lib/utils"; |
| 17 | import { usePrimaryEnvironment } from "../../state/environments"; | 17 | import { usePrimaryEnvironment } from "../../state/environments"; |
| 18 | import { useEnvironmentQuery } from "../../state/query"; | 18 | import { useEnvironmentQuery } from "../../state/query"; |
| ... | @@ -291,8 +291,10 @@ function DiscoveryItemRow({ | ... | @@ -291,8 +291,10 @@ function DiscoveryItemRow({ |
| 291 | } | 291 | } |
| 292 | 292 | ||
| 293 | function GitFetchIntervalSettings() { | 293 | function GitFetchIntervalSettings() { |
| 294 | const automaticGitFetchInterval = useSettings((settings) => settings.automaticGitFetchInterval); | 294 | const automaticGitFetchInterval = usePrimarySettings( |
| 295 | const updateSettings = useUpdateSettings(); | 295 | (settings) => settings.automaticGitFetchInterval, |
| 296 | ); | ||
| 297 | const updateSettings = useUpdatePrimarySettings(); | ||
| 296 | const automaticGitFetchIntervalSeconds = durationToSeconds(automaticGitFetchInterval); | 298 | const automaticGitFetchIntervalSeconds = durationToSeconds(automaticGitFetchInterval); |
| 297 | const defaultAutomaticGitFetchIntervalSeconds = durationToSeconds( | 299 | const defaultAutomaticGitFetchIntervalSeconds = durationToSeconds( |
| 298 | DEFAULT_UNIFIED_SETTINGS.automaticGitFetchInterval, | 300 | DEFAULT_UNIFIED_SETTINGS.automaticGitFetchInterval, |
apps/web/src/hooks/useHandleNewThread.ts+14-10| ... | @@ -3,7 +3,11 @@ import { | ... | @@ -3,7 +3,11 @@ import { |
| 3 | scopeProjectRef, | 3 | scopeProjectRef, |
| 4 | scopeThreadRef, | 4 | scopeThreadRef, |
| 5 | } from "@t3tools/client-runtime/environment"; | 5 | } from "@t3tools/client-runtime/environment"; |
| 6 | import { DEFAULT_RUNTIME_MODE, type ScopedProjectRef } from "@t3tools/contracts"; | 6 | import { |
| 7 | DEFAULT_RUNTIME_MODE, | ||
| 8 | DEFAULT_SERVER_SETTINGS, | ||
| 9 | type ScopedProjectRef, | ||
| 10 | } from "@t3tools/contracts"; | ||
| 7 | import { useParams, useRouter } from "@tanstack/react-router"; | 11 | import { useParams, useRouter } from "@tanstack/react-router"; |
| 8 | import { useCallback, useMemo } from "react"; | 12 | import { useCallback, useMemo } from "react"; |
| 9 | import { | 13 | import { |
| ... | @@ -19,18 +23,16 @@ import { | ... | @@ -19,18 +23,16 @@ import { |
| 19 | getProjectOrderKey, | 23 | getProjectOrderKey, |
| 20 | selectProjectGroupingSettings, | 24 | selectProjectGroupingSettings, |
| 21 | } from "../logicalProject"; | 25 | } from "../logicalProject"; |
| 22 | import { readThreadShell, useProjects, useThread } from "../state/entities"; | 26 | import { readThreadShell, useProjects, useServerConfigs, useThread } from "../state/entities"; |
| 23 | import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions"; | 27 | import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions"; |
| 24 | import { resolveThreadRouteTarget } from "../threadRoutes"; | 28 | import { resolveThreadRouteTarget } from "../threadRoutes"; |
| 25 | import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; | 29 | import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; |
| 26 | import { useSettings } from "./useSettings"; | 30 | import { useClientSettings } from "./useSettings"; |
| 27 | 31 | ||
| 28 | export function useNewThreadHandler() { | 32 | export function useNewThreadHandler() { |
| 29 | const projects = useProjects(); | 33 | const projects = useProjects(); |
| 30 | const projectGroupingSettings = useSettings(selectProjectGroupingSettings); | 34 | const serverConfigs = useServerConfigs(); |
| 31 | const newWorktreesStartFromOrigin = useSettings( | 35 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 32 | (settings) => settings.newWorktreesStartFromOrigin, | ||
| 33 | ); | ||
| 34 | const router = useRouter(); | 36 | const router = useRouter(); |
| 35 | const getCurrentRouteTarget = useCallback(() => { | 37 | const getCurrentRouteTarget = useCallback(() => { |
| 36 | const currentRouteParams = router.state.matches[router.state.matches.length - 1]?.params ?? {}; | 38 | const currentRouteParams = router.state.matches[router.state.matches.length - 1]?.params ?? {}; |
| ... | @@ -61,6 +63,8 @@ export function useNewThreadHandler() { | ... | @@ -61,6 +63,8 @@ export function useNewThreadHandler() { |
| 61 | candidate.id === projectRef.projectId && | 63 | candidate.id === projectRef.projectId && |
| 62 | candidate.environmentId === projectRef.environmentId, | 64 | candidate.environmentId === projectRef.environmentId, |
| 63 | ); | 65 | ); |
| 66 | const environmentSettings = | ||
| 67 | serverConfigs.get(projectRef.environmentId)?.settings ?? DEFAULT_SERVER_SETTINGS; | ||
| 64 | const logicalProjectKey = project | 68 | const logicalProjectKey = project |
| 65 | ? deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings) | 69 | ? deriveLogicalProjectKeyFromSettings(project, projectGroupingSettings) |
| 66 | : scopedProjectKey(projectRef); | 70 | : scopedProjectKey(projectRef); |
| ... | @@ -155,7 +159,7 @@ export function useNewThreadHandler() { | ... | @@ -155,7 +159,7 @@ export function useNewThreadHandler() { |
| 155 | const draftId = newDraftId(); | 159 | const draftId = newDraftId(); |
| 156 | const threadId = newThreadId(); | 160 | const threadId = newThreadId(); |
| 157 | const createdAt = new Date().toISOString(); | 161 | const createdAt = new Date().toISOString(); |
| 158 | const initialEnvMode = options?.envMode ?? "local"; | 162 | const initialEnvMode = options?.envMode ?? environmentSettings.defaultThreadEnvMode; |
| 159 | return (async () => { | 163 | return (async () => { |
| 160 | setLogicalProjectDraftThreadId(logicalProjectKey, projectRef, draftId, { | 164 | setLogicalProjectDraftThreadId(logicalProjectKey, projectRef, draftId, { |
| 161 | threadId, | 165 | threadId, |
| ... | @@ -167,7 +171,7 @@ export function useNewThreadHandler() { | ... | @@ -167,7 +171,7 @@ export function useNewThreadHandler() { |
| 167 | options?.startFromOrigin ?? | 171 | options?.startFromOrigin ?? |
| 168 | resolveNewDraftStartFromOrigin({ | 172 | resolveNewDraftStartFromOrigin({ |
| 169 | envMode: initialEnvMode, | 173 | envMode: initialEnvMode, |
| 170 | newWorktreesStartFromOrigin, | 174 | newWorktreesStartFromOrigin: environmentSettings.newWorktreesStartFromOrigin, |
| 171 | }), | 175 | }), |
| 172 | runtimeMode: DEFAULT_RUNTIME_MODE, | 176 | runtimeMode: DEFAULT_RUNTIME_MODE, |
| 173 | }); | 177 | }); |
| ... | @@ -179,7 +183,7 @@ export function useNewThreadHandler() { | ... | @@ -179,7 +183,7 @@ export function useNewThreadHandler() { |
| 179 | }); | 183 | }); |
| 180 | })(); | 184 | })(); |
| 181 | }, | 185 | }, |
| 182 | [newWorktreesStartFromOrigin, getCurrentRouteTarget, projectGroupingSettings, router, projects], | 186 | [getCurrentRouteTarget, projectGroupingSettings, projects, router, serverConfigs], |
| 183 | ); | 187 | ); |
| 184 | } | 188 | } |
| 185 | 189 |
apps/web/src/hooks/useSettings.test.ts created+37| ... | @@ -0,0 +1,37 @@ | ||
| 1 | import { | ||
| 2 | DEFAULT_SERVER_SETTINGS, | ||
| 3 | ProviderDriverKind, | ||
| 4 | ProviderInstanceId, | ||
| 5 | } from "@t3tools/contracts"; | ||
| 6 | import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; | ||
| 7 | import { describe, expect, it } from "vite-plus/test"; | ||
| 8 | |||
| 9 | import { mergeEnvironmentSettings } from "./useSettings"; | ||
| 10 | |||
| 11 | describe("mergeEnvironmentSettings", () => { | ||
| 12 | it("combines the selected environment's server settings with client preferences", () => { | ||
| 13 | const serverSettings = { | ||
| 14 | ...DEFAULT_SERVER_SETTINGS, | ||
| 15 | providerInstances: { | ||
| 16 | [ProviderInstanceId.make("codex_remote")]: { | ||
| 17 | driver: ProviderDriverKind.make("codex"), | ||
| 18 | enabled: true, | ||
| 19 | }, | ||
| 20 | }, | ||
| 21 | }; | ||
| 22 | const clientSettings = { | ||
| 23 | ...DEFAULT_CLIENT_SETTINGS, | ||
| 24 | favorites: [ | ||
| 25 | { | ||
| 26 | provider: ProviderInstanceId.make("codex_remote"), | ||
| 27 | model: "gpt-5.4", | ||
| 28 | }, | ||
| 29 | ], | ||
| 30 | }; | ||
| 31 | |||
| 32 | const settings = mergeEnvironmentSettings(serverSettings, clientSettings); | ||
| 33 | |||
| 34 | expect(settings.providerInstances).toBe(serverSettings.providerInstances); | ||
| 35 | expect(settings.favorites).toBe(clientSettings.favorites); | ||
| 36 | }); | ||
| 37 | }); | ||
apps/web/src/hooks/useSettings.ts+72-23| ... | @@ -1,22 +1,27 @@ | ... | @@ -1,22 +1,27 @@ |
| 1 | /** | 1 | /** |
| 2 | * Unified settings hook. | 2 | * Environment-scoped settings hooks. |
| 3 | * | 3 | * |
| 4 | * Abstracts the split between server-authoritative settings (persisted in | 4 | * Abstracts the split between server-authoritative settings (persisted in |
| 5 | * `settings.json` on the server, fetched via `server.getConfig`) and | 5 | * `settings.json` on the server, fetched via `server.getConfig`) and |
| 6 | * client-only settings (persisted in localStorage). | 6 | * client-only settings (persisted in localStorage). |
| 7 | * | 7 | * |
| 8 | * Consumers use `useSettings(selector)` to read, and `useUpdateSettings()` to | 8 | * Live server settings always require an environment id. Primary-environment |
| 9 | * write. The hook transparently routes reads/writes to the correct backing | 9 | * access is intentionally named as such so environment-sensitive consumers |
| 10 | * store. | 10 | * cannot silently read the wrong server's settings. |
| 11 | */ | 11 | */ |
| 12 | import { useCallback, useMemo, useSyncExternalStore } from "react"; | 12 | import { useCallback, useMemo, useSyncExternalStore } from "react"; |
| 13 | import { useAtomValue } from "@effect/atom-react"; | 13 | import { useAtomValue } from "@effect/atom-react"; |
| 14 | import { ServerSettings, type ServerSettingsPatch } from "@t3tools/contracts"; | 14 | import { |
| 15 | DEFAULT_SERVER_SETTINGS, | ||
| 16 | type EnvironmentId, | ||
| 17 | ServerSettings, | ||
| 18 | type ServerSettingsPatch, | ||
| 19 | } from "@t3tools/contracts"; | ||
| 15 | import { | 20 | import { |
| 16 | type ClientSettingsPatch, | 21 | type ClientSettingsPatch, |
| 17 | type ClientSettings, | 22 | type ClientSettings, |
| 18 | DEFAULT_CLIENT_SETTINGS, | 23 | DEFAULT_CLIENT_SETTINGS, |
| 19 | UnifiedSettings, | 24 | type UnifiedSettings, |
| 20 | } from "@t3tools/contracts/settings"; | 25 | } from "@t3tools/contracts/settings"; |
| 21 | import { ensureLocalApi } from "~/localApi"; | 26 | import { ensureLocalApi } from "~/localApi"; |
| 22 | import * as Struct from "effect/Struct"; | 27 | import * as Struct from "effect/Struct"; |
| ... | @@ -153,11 +158,6 @@ function splitPatch(patch: Partial<UnifiedSettings>): { | ... | @@ -153,11 +158,6 @@ function splitPatch(patch: Partial<UnifiedSettings>): { |
| 153 | 158 | ||
| 154 | // ── Hooks ──────────────────────────────────────────────────────────── | 159 | // ── Hooks ──────────────────────────────────────────────────────────── |
| 155 | 160 | ||
| 156 | /** | ||
| 157 | * Read merged settings. Selector narrows the subscription so components | ||
| 158 | * only re-render when the slice they care about changes. | ||
| 159 | */ | ||
| 160 | |||
| 161 | /** | 161 | /** |
| 162 | * Non-hook accessor for the current merged client settings snapshot. | 162 | * Non-hook accessor for the current merged client settings snapshot. |
| 163 | * Used by non-React code paths (e.g. runtime services) that need the latest | 163 | * Used by non-React code paths (e.g. runtime services) that need the latest |
| ... | @@ -175,45 +175,77 @@ export function useClientSettingsHydrated(): boolean { | ... | @@ -175,45 +175,77 @@ export function useClientSettingsHydrated(): boolean { |
| 175 | ); | 175 | ); |
| 176 | } | 176 | } |
| 177 | 177 | ||
| 178 | export function useSettings<T = UnifiedSettings>(selector?: (s: UnifiedSettings) => T): T { | 178 | function useClientSettingsValue(): ClientSettings { |
| 179 | const serverSettings = useAtomValue(primaryServerSettingsAtom); | 179 | return useSyncExternalStore( |
| 180 | const clientSettings = useSyncExternalStore( | ||
| 181 | subscribeClientSettings, | 180 | subscribeClientSettings, |
| 182 | getClientSettingsSnapshot, | 181 | getClientSettingsSnapshot, |
| 183 | () => DEFAULT_CLIENT_SETTINGS, | 182 | () => DEFAULT_CLIENT_SETTINGS, |
| 184 | ); | 183 | ); |
| 184 | } | ||
| 185 | |||
| 186 | export function mergeEnvironmentSettings( | ||
| 187 | serverSettings: ServerSettings, | ||
| 188 | clientSettings: ClientSettings, | ||
| 189 | ): UnifiedSettings { | ||
| 190 | return { ...serverSettings, ...clientSettings }; | ||
| 191 | } | ||
| 192 | |||
| 193 | function useMergedSettings<T>( | ||
| 194 | serverSettings: ServerSettings, | ||
| 195 | selector: ((settings: UnifiedSettings) => T) | undefined, | ||
| 196 | ): T { | ||
| 197 | const clientSettings = useClientSettingsValue(); | ||
| 185 | 198 | ||
| 186 | const merged = useMemo<UnifiedSettings>( | 199 | const merged = useMemo<UnifiedSettings>( |
| 187 | () => ({ | 200 | () => mergeEnvironmentSettings(serverSettings, clientSettings), |
| 188 | ...serverSettings, | ||
| 189 | ...clientSettings, | ||
| 190 | }), | ||
| 191 | [clientSettings, serverSettings], | 201 | [clientSettings, serverSettings], |
| 192 | ); | 202 | ); |
| 193 | 203 | ||
| 194 | return useMemo(() => (selector ? selector(merged) : (merged as T)), [merged, selector]); | 204 | return useMemo(() => (selector ? selector(merged) : (merged as T)), [merged, selector]); |
| 195 | } | 205 | } |
| 196 | 206 | ||
| 207 | export function useClientSettings<T = ClientSettings>( | ||
| 208 | selector?: (settings: ClientSettings) => T, | ||
| 209 | ): T { | ||
| 210 | const settings = useClientSettingsValue(); | ||
| 211 | return useMemo(() => (selector ? selector(settings) : (settings as T)), [selector, settings]); | ||
| 212 | } | ||
| 213 | |||
| 214 | /** Read current settings for one environment, merged with client-local preferences. */ | ||
| 215 | export function useEnvironmentSettings<T = UnifiedSettings>( | ||
| 216 | environmentId: EnvironmentId, | ||
| 217 | selector?: (settings: UnifiedSettings) => T, | ||
| 218 | ): T { | ||
| 219 | const serverSettings = useAtomValue(serverEnvironment.settingsValueAtom(environmentId)); | ||
| 220 | return useMergedSettings(serverSettings ?? DEFAULT_SERVER_SETTINGS, selector); | ||
| 221 | } | ||
| 222 | |||
| 223 | /** Primary-only settings access for the settings UI and other explicitly global surfaces. */ | ||
| 224 | export function usePrimarySettings<T = UnifiedSettings>( | ||
| 225 | selector?: (settings: UnifiedSettings) => T, | ||
| 226 | ): T { | ||
| 227 | return useMergedSettings(useAtomValue(primaryServerSettingsAtom), selector); | ||
| 228 | } | ||
| 229 | |||
| 197 | /** | 230 | /** |
| 198 | * Returns an updater that routes each key to the correct backing store. | 231 | * Returns an updater that routes each key to the correct backing store. |
| 199 | * | 232 | * |
| 200 | * Server keys are optimistically patched in atom-backed server state, then | 233 | * Server keys are optimistically patched in atom-backed server state, then |
| 201 | * persisted via RPC. Client keys go through client persistence. | 234 | * persisted via RPC. Client keys go through client persistence. |
| 202 | */ | 235 | */ |
| 203 | export function useUpdateSettings() { | 236 | function useUpdateSettingsTarget(environmentId: EnvironmentId | null) { |
| 204 | const persistServerSettings = useAtomCommand( | 237 | const persistServerSettings = useAtomCommand( |
| 205 | serverEnvironment.updateSettings, | 238 | serverEnvironment.updateSettings, |
| 206 | "server settings update", | 239 | "server settings update", |
| 207 | ); | 240 | ); |
| 208 | const primaryEnvironment = usePrimaryEnvironment(); | ||
| 209 | const updateSettings = useCallback( | 241 | const updateSettings = useCallback( |
| 210 | (patch: Partial<UnifiedSettings>) => { | 242 | (patch: Partial<UnifiedSettings>) => { |
| 211 | const { serverPatch, clientPatch } = splitPatch(patch); | 243 | const { serverPatch, clientPatch } = splitPatch(patch); |
| 212 | 244 | ||
| 213 | if (Object.keys(serverPatch).length > 0) { | 245 | if (Object.keys(serverPatch).length > 0) { |
| 214 | if (primaryEnvironment) { | 246 | if (environmentId) { |
| 215 | void persistServerSettings({ | 247 | void persistServerSettings({ |
| 216 | environmentId: primaryEnvironment.environmentId, | 248 | environmentId, |
| 217 | input: { patch: serverPatch }, | 249 | input: { patch: serverPatch }, |
| 218 | }); | 250 | }); |
| 219 | } | 251 | } |
| ... | @@ -226,12 +258,29 @@ export function useUpdateSettings() { | ... | @@ -226,12 +258,29 @@ export function useUpdateSettings() { |
| 226 | }); | 258 | }); |
| 227 | } | 259 | } |
| 228 | }, | 260 | }, |
| 229 | [persistServerSettings, primaryEnvironment], | 261 | [environmentId, persistServerSettings], |
| 230 | ); | 262 | ); |
| 231 | 263 | ||
| 232 | return updateSettings; | 264 | return updateSettings; |
| 233 | } | 265 | } |
| 234 | 266 | ||
| 267 | export function useUpdateEnvironmentSettings(environmentId: EnvironmentId) { | ||
| 268 | return useUpdateSettingsTarget(environmentId); | ||
| 269 | } | ||
| 270 | |||
| 271 | export function useUpdatePrimarySettings() { | ||
| 272 | return useUpdateSettingsTarget(usePrimaryEnvironment()?.environmentId ?? null); | ||
| 273 | } | ||
| 274 | |||
| 275 | export function useUpdateClientSettings() { | ||
| 276 | return useCallback((patch: ClientSettingsPatch) => { | ||
| 277 | persistClientSettings({ | ||
| 278 | ...getClientSettingsSnapshot(), | ||
| 279 | ...patch, | ||
| 280 | }); | ||
| 281 | }, []); | ||
| 282 | } | ||
| 283 | |||
| 235 | export function __resetClientSettingsPersistenceForTests(): void { | 284 | export function __resetClientSettingsPersistenceForTests(): void { |
| 236 | clientSettingsHydrationGeneration += 1; | 285 | clientSettingsHydrationGeneration += 1; |
| 237 | clientSettingsSnapshot = DEFAULT_CLIENT_SETTINGS; | 286 | clientSettingsSnapshot = DEFAULT_CLIENT_SETTINGS; |
apps/web/src/hooks/useThreadActions.ts+3-3| ... | @@ -24,7 +24,7 @@ import { useTerminalUiStateStore } from "../terminalUiStateStore"; | ... | @@ -24,7 +24,7 @@ import { useTerminalUiStateStore } from "../terminalUiStateStore"; |
| 24 | import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; | 24 | import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; |
| 25 | import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; | 25 | import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; |
| 26 | import { stackedThreadToast, toastManager } from "../components/ui/toast"; | 26 | import { stackedThreadToast, toastManager } from "../components/ui/toast"; |
| 27 | import { useSettings } from "./useSettings"; | 27 | import { useClientSettings } from "./useSettings"; |
| 28 | import { useAtomCommand } from "../state/use-atom-command"; | 28 | import { useAtomCommand } from "../state/use-atom-command"; |
| 29 | 29 | ||
| 30 | export class ThreadArchiveBlockedError extends Data.TaggedError("ThreadArchiveBlockedError")<{ | 30 | export class ThreadArchiveBlockedError extends Data.TaggedError("ThreadArchiveBlockedError")<{ |
| ... | @@ -49,8 +49,8 @@ export function useThreadActions() { | ... | @@ -49,8 +49,8 @@ export function useThreadActions() { |
| 49 | const refreshVcsStatus = useAtomCommand(vcsEnvironment.refreshStatus, { | 49 | const refreshVcsStatus = useAtomCommand(vcsEnvironment.refreshStatus, { |
| 50 | reportFailure: false, | 50 | reportFailure: false, |
| 51 | }); | 51 | }); |
| 52 | const sidebarThreadSortOrder = useSettings((settings) => settings.sidebarThreadSortOrder); | 52 | const sidebarThreadSortOrder = useClientSettings((settings) => settings.sidebarThreadSortOrder); |
| 53 | const confirmThreadDelete = useSettings((settings) => settings.confirmThreadDelete); | 53 | const confirmThreadDelete = useClientSettings((settings) => settings.confirmThreadDelete); |
| 54 | const clearComposerDraftForThread = useComposerDraftStore((store) => store.clearDraftThread); | 54 | const clearComposerDraftForThread = useComposerDraftStore((store) => store.clearDraftThread); |
| 55 | const clearProjectDraftThreadById = useComposerDraftStore( | 55 | const clearProjectDraftThreadById = useComposerDraftStore( |
| 56 | (store) => store.clearProjectDraftThreadById, | 56 | (store) => store.clearProjectDraftThreadById, |
apps/web/src/lib/chatThreadActions.test.ts+2-6| ... | @@ -18,7 +18,6 @@ function createContext(overrides: Partial<ChatThreadActionContext> = {}): ChatTh | ... | @@ -18,7 +18,6 @@ function createContext(overrides: Partial<ChatThreadActionContext> = {}): ChatTh |
| 18 | activeDraftThread: null, | 18 | activeDraftThread: null, |
| 19 | activeThread: undefined, | 19 | activeThread: undefined, |
| 20 | defaultProjectRef: scopeProjectRef(ENVIRONMENT_ID, FALLBACK_PROJECT_ID), | 20 | defaultProjectRef: scopeProjectRef(ENVIRONMENT_ID, FALLBACK_PROJECT_ID), |
| 21 | defaultThreadEnvMode: "local", | ||
| 22 | handleNewThread: async () => {}, | 21 | handleNewThread: async () => {}, |
| 23 | ...overrides, | 22 | ...overrides, |
| 24 | }; | 23 | }; |
| ... | @@ -118,21 +117,18 @@ describe("chatThreadActions", () => { | ... | @@ -118,21 +117,18 @@ describe("chatThreadActions", () => { |
| 118 | }); | 117 | }); |
| 119 | }); | 118 | }); |
| 120 | 119 | ||
| 121 | it("starts a local thread with the configured default env mode", async () => { | 120 | it("delegates the target environment defaults to the new-thread handler", async () => { |
| 122 | const handleNewThread = vi.fn<ChatThreadActionContext["handleNewThread"]>(async () => {}); | 121 | const handleNewThread = vi.fn<ChatThreadActionContext["handleNewThread"]>(async () => {}); |
| 123 | 122 | ||
| 124 | const didStart = await startNewLocalThreadFromContext( | 123 | const didStart = await startNewLocalThreadFromContext( |
| 125 | createContext({ | 124 | createContext({ |
| 126 | defaultProjectRef: scopeProjectRef(ENVIRONMENT_ID, PROJECT_ID), | 125 | defaultProjectRef: scopeProjectRef(ENVIRONMENT_ID, PROJECT_ID), |
| 127 | defaultThreadEnvMode: "worktree", | ||
| 128 | handleNewThread, | 126 | handleNewThread, |
| 129 | }), | 127 | }), |
| 130 | ); | 128 | ); |
| 131 | 129 | ||
| 132 | expect(didStart).toBe(true); | 130 | expect(didStart).toBe(true); |
| 133 | expect(handleNewThread).toHaveBeenCalledWith(scopeProjectRef(ENVIRONMENT_ID, PROJECT_ID), { | 131 | expect(handleNewThread).toHaveBeenCalledWith(scopeProjectRef(ENVIRONMENT_ID, PROJECT_ID)); |
| 134 | envMode: "worktree", | ||
| 135 | }); | ||
| 136 | }); | 132 | }); |
| 137 | 133 | ||
| 138 | it("does not start a thread when there is no project context", async () => { | 134 | it("does not start a thread when there is no project context", async () => { |
apps/web/src/lib/chatThreadActions.ts+1-8| ... | @@ -32,7 +32,6 @@ export interface ChatThreadActionContext { | ... | @@ -32,7 +32,6 @@ export interface ChatThreadActionContext { |
| 32 | readonly activeDraftThread: DraftThreadContextLike | null; | 32 | readonly activeDraftThread: DraftThreadContextLike | null; |
| 33 | readonly activeThread: ThreadContextLike | undefined; | 33 | readonly activeThread: ThreadContextLike | undefined; |
| 34 | readonly defaultProjectRef: ScopedProjectRef | null; | 34 | readonly defaultProjectRef: ScopedProjectRef | null; |
| 35 | readonly defaultThreadEnvMode: DraftThreadEnvMode; | ||
| 36 | readonly handleNewThread: NewThreadHandler; | 35 | readonly handleNewThread: NewThreadHandler; |
| 37 | } | 36 | } |
| 38 | 37 | ||
| ... | @@ -72,12 +71,6 @@ function buildContextualThreadOptions(context: ChatThreadActionContext): NewThre | ... | @@ -72,12 +71,6 @@ function buildContextualThreadOptions(context: ChatThreadActionContext): NewThre |
| 72 | }; | 71 | }; |
| 73 | } | 72 | } |
| 74 | 73 | ||
| 75 | function buildDefaultThreadOptions(context: ChatThreadActionContext): NewThreadOptions { | ||
| 76 | return { | ||
| 77 | envMode: context.defaultThreadEnvMode, | ||
| 78 | }; | ||
| 79 | } | ||
| 80 | |||
| 81 | export async function startNewThreadInProjectFromContext( | 74 | export async function startNewThreadInProjectFromContext( |
| 82 | context: ChatThreadActionContext, | 75 | context: ChatThreadActionContext, |
| 83 | projectRef: ScopedProjectRef, | 76 | projectRef: ScopedProjectRef, |
| ... | @@ -105,6 +98,6 @@ export async function startNewLocalThreadFromContext( | ... | @@ -105,6 +98,6 @@ export async function startNewLocalThreadFromContext( |
| 105 | return false; | 98 | return false; |
| 106 | } | 99 | } |
| 107 | 100 | ||
| 108 | await context.handleNewThread(projectRef, buildDefaultThreadOptions(context)); | 101 | await context.handleNewThread(projectRef); |
| 109 | return true; | 102 | return true; |
| 110 | } | 103 | } |
apps/web/src/routes/__root.tsx+2-2| ... | @@ -26,7 +26,7 @@ import { | ... | @@ -26,7 +26,7 @@ import { |
| 26 | toastManager, | 26 | toastManager, |
| 27 | } from "../components/ui/toast"; | 27 | } from "../components/ui/toast"; |
| 28 | import { resolveAndPersistPreferredEditor } from "../editorPreferences"; | 28 | import { resolveAndPersistPreferredEditor } from "../editorPreferences"; |
| 29 | import { useSettings } from "../hooks/useSettings"; | 29 | import { useClientSettings } from "../hooks/useSettings"; |
| 30 | import { | 30 | import { |
| 31 | deriveLogicalProjectKeyFromSettings, | 31 | deriveLogicalProjectKeyFromSettings, |
| 32 | derivePhysicalProjectKeyFromPath, | 32 | derivePhysicalProjectKeyFromPath, |
| ... | @@ -266,7 +266,7 @@ function AuthenticatedTracingBootstrap() { | ... | @@ -266,7 +266,7 @@ function AuthenticatedTracingBootstrap() { |
| 266 | function EventRouter() { | 266 | function EventRouter() { |
| 267 | const navigate = useNavigate(); | 267 | const navigate = useNavigate(); |
| 268 | const pathname = useLocation({ select: (loc) => loc.pathname }); | 268 | const pathname = useLocation({ select: (loc) => loc.pathname }); |
| 269 | const projectGroupingSettings = useSettings(selectProjectGroupingSettings); | 269 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 270 | const primaryEnvironment = usePrimaryEnvironment(); | 270 | const primaryEnvironment = usePrimaryEnvironment(); |
| 271 | const openInEditor = useAtomCommand(shellEnvironment.openInEditor, { | 271 | const openInEditor = useAtomCommand(shellEnvironment.openInEditor, { |
| 272 | reportFailure: false, | 272 | reportFailure: false, |
apps/web/src/routes/_chat.tsx-11| ... | @@ -16,9 +16,7 @@ import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../termina | ... | @@ -16,9 +16,7 @@ import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../termina |
| 16 | import { isPreviewSupportedInRuntime } from "../previewStateStore"; | 16 | import { isPreviewSupportedInRuntime } from "../previewStateStore"; |
| 17 | import { selectActiveRightPanel, useRightPanelStore } from "../rightPanelStore"; | 17 | import { selectActiveRightPanel, useRightPanelStore } from "../rightPanelStore"; |
| 18 | import { useThreadSelectionStore } from "../threadSelectionStore"; | 18 | import { useThreadSelectionStore } from "../threadSelectionStore"; |
| 19 | import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic"; | ||
| 20 | import { stackedThreadToast, toastManager } from "~/components/ui/toast"; | 19 | import { stackedThreadToast, toastManager } from "~/components/ui/toast"; |
| 21 | import { useSettings } from "~/hooks/useSettings"; | ||
| 22 | import { primaryServerKeybindingsAtom } from "~/state/server"; | 20 | import { primaryServerKeybindingsAtom } from "~/state/server"; |
| 23 | 21 | ||
| 24 | function ChatRouteGlobalShortcuts() { | 22 | function ChatRouteGlobalShortcuts() { |
| ... | @@ -40,8 +38,6 @@ function ChatRouteGlobalShortcuts() { | ... | @@ -40,8 +38,6 @@ function ChatRouteGlobalShortcuts() { |
| 40 | ? selectActiveRightPanel(state.byThreadKey, routeThreadRef) === "preview" | 38 | ? selectActiveRightPanel(state.byThreadKey, routeThreadRef) === "preview" |
| 41 | : false, | 39 | : false, |
| 42 | ); | 40 | ); |
| 43 | const appSettings = useSettings(); | ||
| 44 | |||
| 45 | useEffect(() => { | 41 | useEffect(() => { |
| 46 | const onWindowKeyDown = (event: KeyboardEvent) => { | 42 | const onWindowKeyDown = (event: KeyboardEvent) => { |
| 47 | if (event.defaultPrevented) return; | 43 | if (event.defaultPrevented) return; |
| ... | @@ -71,9 +67,6 @@ function ChatRouteGlobalShortcuts() { | ... | @@ -71,9 +67,6 @@ function ChatRouteGlobalShortcuts() { |
| 71 | activeDraftThread, | 67 | activeDraftThread, |
| 72 | activeThread: activeThread ?? undefined, | 68 | activeThread: activeThread ?? undefined, |
| 73 | defaultProjectRef, | 69 | defaultProjectRef, |
| 74 | defaultThreadEnvMode: resolveSidebarNewThreadEnvMode({ | ||
| 75 | defaultEnvMode: appSettings.defaultThreadEnvMode, | ||
| 76 | }), | ||
| 77 | handleNewThread, | 70 | handleNewThread, |
| 78 | }); | 71 | }); |
| 79 | return; | 72 | return; |
| ... | @@ -86,9 +79,6 @@ function ChatRouteGlobalShortcuts() { | ... | @@ -86,9 +79,6 @@ function ChatRouteGlobalShortcuts() { |
| 86 | activeDraftThread, | 79 | activeDraftThread, |
| 87 | activeThread: activeThread ?? undefined, | 80 | activeThread: activeThread ?? undefined, |
| 88 | defaultProjectRef, | 81 | defaultProjectRef, |
| 89 | defaultThreadEnvMode: resolveSidebarNewThreadEnvMode({ | ||
| 90 | defaultEnvMode: appSettings.defaultThreadEnvMode, | ||
| 91 | }), | ||
| 92 | handleNewThread, | 82 | handleNewThread, |
| 93 | }); | 83 | }); |
| 94 | return; | 84 | return; |
| ... | @@ -153,7 +143,6 @@ function ChatRouteGlobalShortcuts() { | ... | @@ -153,7 +143,6 @@ function ChatRouteGlobalShortcuts() { |
| 153 | routeThreadRef, | 143 | routeThreadRef, |
| 154 | selectedThreadKeysSize, | 144 | selectedThreadKeysSize, |
| 155 | terminalOpen, | 145 | terminalOpen, |
| 156 | appSettings.defaultThreadEnvMode, | ||
| 157 | ]); | 146 | ]); |
| 158 | 147 | ||
| 159 | return null; | 148 | return null; |
apps/web/src/state/entities.ts+6| ... | @@ -12,12 +12,14 @@ import type { | ... | @@ -12,12 +12,14 @@ import type { |
| 12 | OrchestrationThreadActivity, | 12 | OrchestrationThreadActivity, |
| 13 | ScopedProjectRef, | 13 | ScopedProjectRef, |
| 14 | ScopedThreadRef, | 14 | ScopedThreadRef, |
| 15 | ServerConfig, | ||
| 15 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 16 | import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; | 17 | import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; |
| 17 | import { Atom } from "effect/unstable/reactivity"; | 18 | import { Atom } from "effect/unstable/reactivity"; |
| 18 | import { useMemo } from "react"; | 19 | import { useMemo } from "react"; |
| 19 | import { appAtomRegistry } from "../rpc/atomRegistry"; | 20 | import { appAtomRegistry } from "../rpc/atomRegistry"; |
| 20 | import { environmentProjects } from "./projects"; | 21 | import { environmentProjects } from "./projects"; |
| 22 | import { environmentServerConfigsAtom } from "./server"; | ||
| 21 | import { environmentThreadDetails, environmentThreadShells } from "./threads"; | 23 | import { environmentThreadDetails, environmentThreadShells } from "./threads"; |
| 22 | 24 | ||
| 23 | const EMPTY_PROJECT_REFS: ReadonlyArray<ScopedProjectRef> = Object.freeze([]); | 25 | const EMPTY_PROJECT_REFS: ReadonlyArray<ScopedProjectRef> = Object.freeze([]); |
| ... | @@ -103,6 +105,10 @@ export function useProjects(): ReadonlyArray<EnvironmentProject> { | ... | @@ -103,6 +105,10 @@ export function useProjects(): ReadonlyArray<EnvironmentProject> { |
| 103 | return useAtomValue(environmentProjects.projectsAtom); | 105 | return useAtomValue(environmentProjects.projectsAtom); |
| 104 | } | 106 | } |
| 105 | 107 | ||
| 108 | export function useServerConfigs(): ReadonlyMap<EnvironmentId, ServerConfig> { | ||
| 109 | return useAtomValue(environmentServerConfigsAtom); | ||
| 110 | } | ||
| 111 | |||
| 106 | export function useThreadShells(): ReadonlyArray<EnvironmentThreadShell> { | 112 | export function useThreadShells(): ReadonlyArray<EnvironmentThreadShell> { |
| 107 | return useAtomValue(environmentThreadShells.threadShellsAtom); | 113 | return useAtomValue(environmentThreadShells.threadShellsAtom); |
| 108 | } | 114 | } |
apps/web/src/state/environments.ts+1-10| ... | @@ -5,11 +5,11 @@ import { | ... | @@ -5,11 +5,11 @@ import { |
| 5 | } from "@t3tools/client-runtime/connection"; | 5 | } from "@t3tools/client-runtime/connection"; |
| 6 | import type { EnvironmentId } from "@t3tools/contracts"; | 6 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 7 | import * as Option from "effect/Option"; | 7 | import * as Option from "effect/Option"; |
| 8 | import { Atom } from "effect/unstable/reactivity"; | ||
| 9 | import { useMemo } from "react"; | 8 | import { useMemo } from "react"; |
| 10 | 9 | ||
| 11 | import { environmentCatalog } from "../connection/catalog"; | 10 | import { environmentCatalog } from "../connection/catalog"; |
| 12 | import { environmentPresentations, useEnvironmentPresentation } from "./presentation"; | 11 | import { environmentPresentations, useEnvironmentPresentation } from "./presentation"; |
| 12 | import { primaryEnvironmentIdAtom } from "./primaryEnvironment"; | ||
| 13 | import { useEnvironmentQuery } from "./query"; | 13 | import { useEnvironmentQuery } from "./query"; |
| 14 | import { relayEnvironmentDiscovery } from "./relay"; | 14 | import { relayEnvironmentDiscovery } from "./relay"; |
| 15 | import { usePreparedConnection } from "./session"; | 15 | import { usePreparedConnection } from "./session"; |
| ... | @@ -21,15 +21,6 @@ export interface EnvironmentPresentation extends BaseEnvironmentPresentation { | ... | @@ -21,15 +21,6 @@ export interface EnvironmentPresentation extends BaseEnvironmentPresentation { |
| 21 | readonly relayManaged: boolean; | 21 | readonly relayManaged: boolean; |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | export const primaryEnvironmentIdAtom = Atom.make((get) => { | ||
| 25 | for (const [environmentId, entry] of get(environmentCatalog.catalogValueAtom).entries) { | ||
| 26 | if (entry.target._tag === "PrimaryConnectionTarget") { | ||
| 27 | return environmentId; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | return null; | ||
| 31 | }).pipe(Atom.withLabel("web-primary-environment-id")); | ||
| 32 | |||
| 33 | function projectEnvironmentPresentation( | 24 | function projectEnvironmentPresentation( |
| 34 | environmentId: EnvironmentId, | 25 | environmentId: EnvironmentId, |
| 35 | presentation: BaseEnvironmentPresentation, | 26 | presentation: BaseEnvironmentPresentation, |
apps/web/src/state/presentation.ts+2-2| ... | @@ -5,12 +5,12 @@ import type { EnvironmentId } from "@t3tools/contracts"; | ... | @@ -5,12 +5,12 @@ import type { EnvironmentId } from "@t3tools/contracts"; |
| 5 | import { Atom } from "effect/unstable/reactivity"; | 5 | import { Atom } from "effect/unstable/reactivity"; |
| 6 | 6 | ||
| 7 | import { environmentCatalog } from "../connection/catalog"; | 7 | import { environmentCatalog } from "../connection/catalog"; |
| 8 | import { environmentSession } from "./session"; | 8 | import { serverEnvironment } from "./server"; |
| 9 | 9 | ||
| 10 | export const environmentPresentations = createEnvironmentPresentationAtoms({ | 10 | export const environmentPresentations = createEnvironmentPresentationAtoms({ |
| 11 | catalogValueAtom: environmentCatalog.catalogValueAtom, | 11 | catalogValueAtom: environmentCatalog.catalogValueAtom, |
| 12 | stateAtom: environmentCatalog.stateAtom, | 12 | stateAtom: environmentCatalog.stateAtom, |
| 13 | configValueAtom: environmentSession.configValueAtom, | 13 | serverConfigValueAtom: serverEnvironment.configValueAtom, |
| 14 | }); | 14 | }); |
| 15 | 15 | ||
| 16 | const EMPTY_ENVIRONMENT_PRESENTATION_ATOM = Atom.make<EnvironmentPresentation | null>(null).pipe( | 16 | const EMPTY_ENVIRONMENT_PRESENTATION_ATOM = Atom.make<EnvironmentPresentation | null>(null).pipe( |
apps/web/src/state/primaryEnvironment.ts created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | import { Atom } from "effect/unstable/reactivity"; | ||
| 2 | |||
| 3 | import { environmentCatalog } from "../connection/catalog"; | ||
| 4 | |||
| 5 | export const primaryEnvironmentIdAtom = Atom.make((get) => { | ||
| 6 | for (const [environmentId, entry] of get(environmentCatalog.catalogValueAtom).entries) { | ||
| 7 | if (entry.target._tag === "PrimaryConnectionTarget") { | ||
| 8 | return environmentId; | ||
| 9 | } | ||
| 10 | } | ||
| 11 | return null; | ||
| 12 | }).pipe(Atom.withLabel("web-primary-environment-id")); | ||
apps/web/src/state/server.ts+3-3| ... | @@ -15,15 +15,15 @@ import { AsyncResult, Atom } from "effect/unstable/reactivity"; | ... | @@ -15,15 +15,15 @@ import { AsyncResult, Atom } from "effect/unstable/reactivity"; |
| 15 | 15 | ||
| 16 | import { environmentCatalog } from "../connection/catalog"; | 16 | import { environmentCatalog } from "../connection/catalog"; |
| 17 | import { connectionAtomRuntime } from "../connection/runtime"; | 17 | import { connectionAtomRuntime } from "../connection/runtime"; |
| 18 | import { primaryEnvironmentIdAtom } from "./environments"; | 18 | import { primaryEnvironmentIdAtom } from "./primaryEnvironment"; |
| 19 | import { environmentSession } from "./session"; | 19 | import { environmentSession } from "./session"; |
| 20 | 20 | ||
| 21 | export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, { | 21 | export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, { |
| 22 | initialConfigValueAtom: environmentSession.configValueAtom, | 22 | initialConfigValueAtom: environmentSession.initialConfigValueAtom, |
| 23 | }); | 23 | }); |
| 24 | export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({ | 24 | export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({ |
| 25 | catalogValueAtom: environmentCatalog.catalogValueAtom, | 25 | catalogValueAtom: environmentCatalog.catalogValueAtom, |
| 26 | configValueAtom: serverEnvironment.configValueAtom, | 26 | serverConfigValueAtom: serverEnvironment.configValueAtom, |
| 27 | }); | 27 | }); |
| 28 | 28 | ||
| 29 | interface PrimaryServerState { | 29 | interface PrimaryServerState { |
packages/client-runtime/src/state/presentation.ts+3-2| ... | @@ -26,7 +26,8 @@ export function createEnvironmentPresentationAtoms<E>(input: { | ... | @@ -26,7 +26,8 @@ export function createEnvironmentPresentationAtoms<E>(input: { |
| 26 | readonly stateAtom: ( | 26 | readonly stateAtom: ( |
| 27 | environmentId: EnvironmentId, | 27 | environmentId: EnvironmentId, |
| 28 | ) => Atom.Atom<AsyncResult.AsyncResult<SupervisorConnectionState, E>>; | 28 | ) => Atom.Atom<AsyncResult.AsyncResult<SupervisorConnectionState, E>>; |
| 29 | readonly configValueAtom: (environmentId: EnvironmentId) => Atom.Atom<ServerConfig | null>; | 29 | /** Authoritative live server config, including streamed provider/settings updates. */ |
| 30 | readonly serverConfigValueAtom: (environmentId: EnvironmentId) => Atom.Atom<ServerConfig | null>; | ||
| 30 | }) { | 31 | }) { |
| 31 | const presentationAtom = Atom.family((environmentId: EnvironmentId) => | 32 | const presentationAtom = Atom.family((environmentId: EnvironmentId) => |
| 32 | Atom.make((get) => { | 33 | Atom.make((get) => { |
| ... | @@ -41,7 +42,7 @@ export function createEnvironmentPresentationAtoms<E>(input: { | ... | @@ -41,7 +42,7 @@ export function createEnvironmentPresentationAtoms<E>(input: { |
| 41 | return { | 42 | return { |
| 42 | entry, | 43 | entry, |
| 43 | connection: presentEnvironmentConnection(state), | 44 | connection: presentEnvironmentConnection(state), |
| 44 | serverConfig: get(input.configValueAtom(environmentId)), | 45 | serverConfig: get(input.serverConfigValueAtom(environmentId)), |
| 45 | } satisfies EnvironmentPresentation; | 46 | } satisfies EnvironmentPresentation; |
| 46 | }).pipe(Atom.withLabel(`environment-presentation:${environmentId}`)), | 47 | }).pipe(Atom.withLabel(`environment-presentation:${environmentId}`)), |
| 47 | ); | 48 | ); |
packages/client-runtime/src/state/projectGrouping.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import { scopedProjectKey, scopeProjectRef } from "../environment/scoped.ts"; | 1 | import { scopedProjectKey, scopeProjectRef } from "../environment/scoped.ts"; |
| 2 | import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts"; | 2 | import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts"; |
| 3 | import type { UnifiedSettings } from "@t3tools/contracts/settings"; | 3 | import type { ClientSettings } from "@t3tools/contracts/settings"; |
| 4 | 4 | ||
| 5 | import type { EnvironmentProject } from "./models.ts"; | 5 | import type { EnvironmentProject } from "./models.ts"; |
| 6 | import { normalizeProjectPathForComparison } from "./projects.ts"; | 6 | import { normalizeProjectPathForComparison } from "./projects.ts"; |
| ... | @@ -12,7 +12,7 @@ export interface ProjectGroupingSettings { | ... | @@ -12,7 +12,7 @@ export interface ProjectGroupingSettings { |
| 12 | 12 | ||
| 13 | export type ProjectGroupingMode = SidebarProjectGroupingMode; | 13 | export type ProjectGroupingMode = SidebarProjectGroupingMode; |
| 14 | 14 | ||
| 15 | export function selectProjectGroupingSettings(settings: UnifiedSettings): ProjectGroupingSettings { | 15 | export function selectProjectGroupingSettings(settings: ClientSettings): ProjectGroupingSettings { |
| 16 | return { | 16 | return { |
| 17 | sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, | 17 | sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, |
| 18 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, | 18 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, |
packages/client-runtime/src/state/server.ts+12| ... | @@ -118,9 +118,21 @@ export function createServerEnvironmentAtoms<R, E>( | ... | @@ -118,9 +118,21 @@ export function createServerEnvironmentAtoms<R, E>( |
| 118 | return projection?.config ?? get(options.initialConfigValueAtom(environmentId)); | 118 | return projection?.config ?? get(options.initialConfigValueAtom(environmentId)); |
| 119 | }).pipe(Atom.withLabel(`environment-data:server:config:${environmentId}`)); | 119 | }).pipe(Atom.withLabel(`environment-data:server:config:${environmentId}`)); |
| 120 | }); | 120 | }); |
| 121 | const settingsValueAtom = Atom.family((environmentId: EnvironmentId) => | ||
| 122 | Atom.make((get) => get(configValueAtom(environmentId))?.settings ?? null).pipe( | ||
| 123 | Atom.withLabel(`environment-data:server:settings:${environmentId}`), | ||
| 124 | ), | ||
| 125 | ); | ||
| 126 | const providersValueAtom = Atom.family((environmentId: EnvironmentId) => | ||
| 127 | Atom.make((get) => get(configValueAtom(environmentId))?.providers ?? null).pipe( | ||
| 128 | Atom.withLabel(`environment-data:server:providers:${environmentId}`), | ||
| 129 | ), | ||
| 130 | ); | ||
| 121 | 131 | ||
| 122 | return { | 132 | return { |
| 123 | configValueAtom, | 133 | configValueAtom, |
| 134 | settingsValueAtom, | ||
| 135 | providersValueAtom, | ||
| 124 | traceDiagnostics: createEnvironmentRpcQueryAtomFamily(runtime, { | 136 | traceDiagnostics: createEnvironmentRpcQueryAtomFamily(runtime, { |
| 125 | label: "environment-data:server:trace-diagnostics", | 137 | label: "environment-data:server:trace-diagnostics", |
| 126 | tag: WS_METHODS.serverGetTraceDiagnostics, | 138 | tag: WS_METHODS.serverGetTraceDiagnostics, |
packages/client-runtime/src/state/session.ts+10-5| ... | @@ -26,7 +26,7 @@ export function initialConfigOption<E>( | ... | @@ -26,7 +26,7 @@ export function initialConfigOption<E>( |
| 26 | export function createEnvironmentSessionAtoms<R, E>( | 26 | export function createEnvironmentSessionAtoms<R, E>( |
| 27 | runtime: Atom.AtomRuntime<EnvironmentRegistry | R, E>, | 27 | runtime: Atom.AtomRuntime<EnvironmentRegistry | R, E>, |
| 28 | ) { | 28 | ) { |
| 29 | const configAtom = Atom.family((environmentId: EnvironmentId) => | 29 | const initialConfigAtom = Atom.family((environmentId: EnvironmentId) => |
| 30 | runtime.atom( | 30 | runtime.atom( |
| 31 | followStreamInEnvironment( | 31 | followStreamInEnvironment( |
| 32 | environmentId, | 32 | environmentId, |
| ... | @@ -49,10 +49,15 @@ export function createEnvironmentSessionAtoms<R, E>( | ... | @@ -49,10 +49,15 @@ export function createEnvironmentSessionAtoms<R, E>( |
| 49 | ), | 49 | ), |
| 50 | ); | 50 | ); |
| 51 | 51 | ||
| 52 | const configValueAtom = Atom.family((environmentId: EnvironmentId) => | 52 | // This is only the bootstrap config captured when a transport session is |
| 53 | // established. Consumers that need current provider/settings state must use | ||
| 54 | // createServerEnvironmentAtoms(...).configValueAtom instead. | ||
| 55 | const initialConfigValueAtom = Atom.family((environmentId: EnvironmentId) => | ||
| 53 | Atom.make((get): ServerConfig | null => | 56 | Atom.make((get): ServerConfig | null => |
| 54 | Option.getOrNull( | 57 | Option.getOrNull( |
| 55 | Option.getOrElse(AsyncResult.value(get(configAtom(environmentId))), () => Option.none()), | 58 | Option.getOrElse(AsyncResult.value(get(initialConfigAtom(environmentId))), () => |
| 59 | Option.none(), | ||
| 60 | ), | ||
| 56 | ), | 61 | ), |
| 57 | ).pipe(Atom.withLabel(`environment-config-value:${environmentId}`)), | 62 | ).pipe(Atom.withLabel(`environment-config-value:${environmentId}`)), |
| 58 | ); | 63 | ); |
| ... | @@ -80,8 +85,8 @@ export function createEnvironmentSessionAtoms<R, E>( | ... | @@ -80,8 +85,8 @@ export function createEnvironmentSessionAtoms<R, E>( |
| 80 | ); | 85 | ); |
| 81 | 86 | ||
| 82 | return { | 87 | return { |
| 83 | configAtom, | 88 | initialConfigAtom, |
| 84 | configValueAtom, | 89 | initialConfigValueAtom, |
| 85 | preparedConnectionAtom, | 90 | preparedConnectionAtom, |
| 86 | preparedConnectionValueAtom, | 91 | preparedConnectionValueAtom, |
| 87 | }; | 92 | }; |
packages/client-runtime/src/state/shell.test.ts+1-1| ... | @@ -75,7 +75,7 @@ function makeHarness() { | ... | @@ -75,7 +75,7 @@ function makeHarness() { |
| 75 | }); | 75 | }); |
| 76 | const serverConfigsAtom = createEnvironmentServerConfigsAtom({ | 76 | const serverConfigsAtom = createEnvironmentServerConfigsAtom({ |
| 77 | catalogValueAtom, | 77 | catalogValueAtom, |
| 78 | configValueAtom: configAtoms, | 78 | serverConfigValueAtom: configAtoms, |
| 79 | }); | 79 | }); |
| 80 | 80 | ||
| 81 | return { | 81 | return { |
packages/client-runtime/src/state/shell.ts+2-2| ... | @@ -268,13 +268,13 @@ export function createEnvironmentShellSummaryAtom(input: { | ... | @@ -268,13 +268,13 @@ export function createEnvironmentShellSummaryAtom(input: { |
| 268 | 268 | ||
| 269 | export function createEnvironmentServerConfigsAtom(input: { | 269 | export function createEnvironmentServerConfigsAtom(input: { |
| 270 | readonly catalogValueAtom: Atom.Atom<EnvironmentCatalogState>; | 270 | readonly catalogValueAtom: Atom.Atom<EnvironmentCatalogState>; |
| 271 | readonly configValueAtom: (environmentId: EnvironmentId) => Atom.Atom<ServerConfig | null>; | 271 | readonly serverConfigValueAtom: (environmentId: EnvironmentId) => Atom.Atom<ServerConfig | null>; |
| 272 | }) { | 272 | }) { |
| 273 | let previousServerConfigs = EMPTY_SERVER_CONFIGS; | 273 | let previousServerConfigs = EMPTY_SERVER_CONFIGS; |
| 274 | return Atom.make((get) => { | 274 | return Atom.make((get) => { |
| 275 | const next = new Map<EnvironmentId, ServerConfig>(); | 275 | const next = new Map<EnvironmentId, ServerConfig>(); |
| 276 | for (const environmentId of get(input.catalogValueAtom).entries.keys()) { | 276 | for (const environmentId of get(input.catalogValueAtom).entries.keys()) { |
| 277 | const config = get(input.configValueAtom(environmentId)); | 277 | const config = get(input.serverConfigValueAtom(environmentId)); |
| 278 | if (config !== null) { | 278 | if (config !== null) { |
| 279 | next.set(environmentId, config); | 279 | next.set(environmentId, config); |
| 280 | } | 280 | } |