Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ import {
connectPairingUrl as connectPairingUrlAtom,
updateBearerConnection,
} from "../../connection/onboarding";
import { useEnvironments } from "../../state/environments";
import { useWorkspaceEnvironments } from "../../state/workspace";
import { relayEnvironmentDiscovery } from "../../state/relay";
import { useAtomCommand } from "../../state/use-atom-command";
import { projectWorkspaceEnvironment, type WorkspaceEnvironment } from "../../state/workspaceModel";
import { relayManagedEnvironmentIds } from "./environmentSections";

export interface RelayEnvironmentView {
Expand All @@ -31,7 +30,7 @@ export interface RelayEnvironmentView {
}

export function useConnectionController() {
const { environments } = useEnvironments();
const connectedEnvironments = useWorkspaceEnvironments();
const discovery = useAtomValue(relayEnvironmentDiscovery.stateValueAtom);
const connectPairingUrlMutation = useAtomCommand(connectPairingUrlAtom, {
reportFailure: false,
Expand All @@ -49,10 +48,6 @@ export function useConnectionController() {
"relay environment refresh",
);

const connectedEnvironments = useMemo<ReadonlyArray<WorkspaceEnvironment>>(
() => environments.map(projectWorkspaceEnvironment),
[environments],
);
const registeredIds = useMemo(
() => relayManagedEnvironmentIds(connectedEnvironments),
[connectedEnvironments],
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/src/features/keyboard/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
import { T3KeyboardCommands } from "../../native/T3KeyboardCommands";
import { useProjects, useThreadShell, useThreadShells } from "../../state/entities";
import { useThreadSearch } from "../../state/queries";
import { useWorkspaceState } from "../../state/workspace";
import { useWorkspaceEnvironments } from "../../state/workspace";
import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
import { ThreadSearchMatchExcerpt } from "../threads/thread-search-match";
Expand Down Expand Up @@ -123,7 +123,7 @@ export function CommandPalette(props: {
const threads = useThreadShells();
const activeThreadRef = useMemo(() => parseActiveThreadPath(props.pathname), [props.pathname]);
const activeThread = useThreadShell(activeThreadRef);
const { environments } = useWorkspaceState();
const environments = useWorkspaceEnvironments();
const { savedConnectionsById } = useSavedRemoteConnections();
const [query, setQuery] = useState("");
const [selection, setSelection] = useState<string | null>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { useEnvironmentPresentation } from "../../state/presentation";
import { terminalEnvironment } from "../../state/terminal";
import { useAtomCommand } from "../../state/use-atom-command";
import { useServerConfigs } from "../../state/entities";
import { useWorkspaceState } from "../../state/workspace";
import { useConnectionsReady } from "../../state/workspace";
import {
MAX_TERMINAL_FONT_SIZE,
MIN_TERMINAL_FONT_SIZE,
Expand Down Expand Up @@ -166,7 +166,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
const closeTerminal = useAtomCommand(terminalEnvironment.close, "terminal close");
const openTerminal = useAtomCommand(terminalEnvironment.open, "terminal open");
const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry");
const { state: workspaceState } = useWorkspaceState();
const connectionsReady = useConnectionsReady();
const { layout, panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout();
const params = props.route.params;
const { selectedThread, selectedThreadProject, selectedEnvironmentConnection } =
Expand Down Expand Up @@ -1114,7 +1114,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps)
}, [retryEnvironment, routeEnvironmentId]);

if (!selectedThread) {
if (workspaceState.isLoadingConnections) {
if (!connectionsReady) {
return <LoadingScreen message="Opening terminal…" />;
}

Expand Down
12 changes: 6 additions & 6 deletions apps/mobile/src/features/threads/ThreadRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {
} from "@t3tools/shared/projectScripts";
import { Alert, Platform, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useWorkspaceState } from "../../state/workspace";
import { useEnvironmentShellState } from "../../state/shell";
import { useConnectionsReady } from "../../state/workspace";
import { useEnvironmentShellReadiness } from "../../state/shell";
import { restoredNewTaskDraftKey } from "../../state/new-task-draft-key";
import { clearPendingThreadCreationOutcome } from "../../state/pending-thread-creation";
import { recoverFailedThreadDraft } from "../../state/recover-failed-thread-draft";
Expand Down Expand Up @@ -148,15 +148,15 @@ function ThreadUnavailableScreen(props: {
}

export function ThreadRouteScreen(props: ThreadRouteScreenProps) {
const { state: workspaceState } = useWorkspaceState();
const connectionsReady = useConnectionsReady();
const { connectionState } = useRemoteConnectionStatus();
const { selectedThread } = useThreadSelection();
const params = props.route.params;
const environmentIdRaw = firstRouteParam(params.environmentId);
const threadIdRaw = firstRouteParam(params.threadId);
const environmentId = environmentIdRaw ? EnvironmentId.make(environmentIdRaw) : null;
const routeEnvironmentRuntime = useRemoteEnvironmentRuntime(environmentId);
const routeEnvironmentShellState = useEnvironmentShellState(environmentId);
const routeEnvironmentShellState = useEnvironmentShellReadiness(environmentId);
const { onReconnectEnvironment } = useRemoteConnections();
const navigation = useNavigation();
const routeConnectionState =
Expand Down Expand Up @@ -185,10 +185,10 @@ export function ThreadRouteScreen(props: ThreadRouteScreenProps) {
}

const stillHydrating = threadRouteIsHydrating({
isLoadingConnections: workspaceState.isLoadingConnections,
isLoadingConnections: !connectionsReady,
connectionState: routeConnectionState,
shellStatus: routeEnvironmentShellState.status,
shellHasError: Option.isSome(routeEnvironmentShellState.error),
shellHasError: routeEnvironmentShellState.hasError,
detailStatus: selectedThreadDetailState.status,
detailHasError: Option.isSome(selectedThreadDetailState.error),
});
Expand Down
35 changes: 17 additions & 18 deletions apps/mobile/src/state/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { useAtomValue } from "@effect/atom-react";
import type { EnvironmentId } from "@t3tools/contracts";
import * as Option from "effect/Option";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { Atom } from "effect/unstable/reactivity";

import { environmentCatalog } from "../connection/catalog";
import { connectionAtomRuntime } from "../connection/runtime";
Expand All @@ -21,24 +21,23 @@ export const environmentShellSummaryAtom = createEnvironmentShellSummaryAtom({
shellStateValueAtom: environmentShell.stateValueAtom,
});

const EMPTY_ENVIRONMENT_SHELL_STATE_ATOM = Atom.make(
AsyncResult.success<EnvironmentShellState>({
snapshot: Option.none(),
status: "empty",
error: Option.none(),
}),
).pipe(Atom.withLabel("mobile-environment-shell:empty"));
const EMPTY_ENVIRONMENT_SHELL_STATE_ATOM = Atom.make<EnvironmentShellState>({
snapshot: Option.none(),
status: "empty",
error: Option.none(),
});

const shellStatus = (state: EnvironmentShellState) => state.status;
const shellHasError = (state: EnvironmentShellState) => Option.isSome(state.error);

/** Reads one environment's shell projection without waiting on other environments. */
export function useEnvironmentShellState(environmentId: EnvironmentId | null) {
const result = useAtomValue(
/** Snapshot contents do not affect whether the route is still hydrating. */
export function useEnvironmentShellReadiness(environmentId: EnvironmentId | null) {
const atom =
environmentId === null
? EMPTY_ENVIRONMENT_SHELL_STATE_ATOM
: environmentShell.stateAtom(environmentId),
);
return Option.getOrElse(AsyncResult.value(result), () => ({
snapshot: Option.none(),
status: "empty" as const,
error: Option.none(),
}));
: environmentShell.stateValueAtom(environmentId);
return {
status: useAtomValue(atom, shellStatus),
hasError: useAtomValue(atom, shellHasError),
};
}
30 changes: 9 additions & 21 deletions apps/mobile/src/state/use-remote-environment-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useAtomValue } from "@effect/atom-react";
import type { EnvironmentId } from "@t3tools/contracts";
import * as Cause from "effect/Cause";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { useCallback, useMemo } from "react";
import { useCallback } from "react";
import { Alert } from "react-native";

import { useConnectionController } from "../features/connection/useConnectionController";
import { environmentPresentations } from "./presentation";
import { useWorkspaceState } from "../state/workspace";
import { useWorkspaceConnectionState, useWorkspaceEnvironments } from "./workspace";
import type { SavedRemoteConnection } from "../lib/connection";
import { appAtomRegistry } from "./atom-registry";
import type { ConnectedEnvironmentSummary, EnvironmentRuntimeState } from "./remote-runtime-types";
Expand Down Expand Up @@ -45,9 +45,9 @@ const EMPTY_RUNTIME_STATE_ATOM = Atom.make<EnvironmentRuntimeState | null>(null)
);

const savedConnectionsByIdAtom = Atom.make((get) => {
const presentationById = get(environmentPresentations.presentationsAtom);
const catalog = get(environmentCatalog.catalogValueAtom);
return Object.fromEntries(
[...presentationById.keys()].flatMap((environmentId) => {
[...catalog.entries.keys()].flatMap((environmentId) => {
const connection = get(remoteEnvironmentProjections.savedConnectionAtom(environmentId));
return connection === null ? [] : [[environmentId, connection]];
}),
Expand Down Expand Up @@ -85,27 +85,15 @@ export function useRemoteEnvironmentRuntime(
}

export function useRemoteConnectionStatus() {
const workspace = useWorkspaceState();
const state = useWorkspaceConnectionState();
const connectedEnvironments: ReadonlyArray<ConnectedEnvironmentSummary> =
useWorkspaceEnvironments();
const pendingConnectionError = useAtomValue(pendingConnectionErrorAtom);
const connectedEnvironments = useMemo<ReadonlyArray<ConnectedEnvironmentSummary>>(
() =>
workspace.environments.map((environment) => ({
environmentId: environment.environmentId,
environmentLabel: environment.environmentLabel,
displayUrl: environment.displayUrl,
isRelayManaged: environment.isRelayManaged,
isEnabled: environment.isEnabled,
connectionState: environment.connectionState,
connectionError: environment.connectionError,
connectionErrorTraceId: environment.connectionErrorTraceId,
})),
[workspace.environments],
);

return {
connectedEnvironments,
connectionState: workspace.state.connectionState,
connectionError: pendingConnectionError ?? workspace.state.connectionError,
connectionState: state.connectionState,
connectionError: pendingConnectionError ?? state.connectionError,
};
}

Expand Down
Loading
Loading