Skip to content

Commit 37eefa9

Browse files
committed
feat: rename an environment so accounts on one machine are distinguishable
Environment names come from the machine name, so two user accounts on one Mac show the same name. Add a per-environment custom name (server setting environmentLabel), a Rename… action in Settings → Connections, and resolve the displayed name in the shared client presentation so web and mobile show it live. Fixes #25505
1 parent d6fb62a commit 37eefa9

30 files changed

Lines changed: 331 additions & 55 deletions

‎apps/mobile/src/features/connection/ConnectionDiagnosisDetails.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function useConnectionDiagnosis(
3838
const relayOnly =
3939
connectionRoutes(presentation.entry).length === 1 && hasRelayRoute(presentation.entry);
4040
const diagnosis = presentConnectionDiagnosis({
41-
hostLabel: presentation.entry.target.label,
41+
hostLabel: presentation.label,
4242
state,
4343
via: relayOnly ? "relay" : "direct",
4444
clientVersion: Constants.expoConfig?.version,

‎apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,18 @@ export function ConnectionEnvironmentRow(props: {
4949
updates: { readonly label: string; readonly displayUrl: string },
5050
) => Promise<AtomCommandResult<unknown, unknown>>;
5151
}) {
52-
const [label, setLabel] = useState(props.environment.environmentLabel);
52+
const presentation = useAtomValue(
53+
environmentPresentations.presentationAtom(props.environment.environmentId),
54+
);
55+
// Edit the saved label; the displayed one may be the environment's own custom name.
56+
const [label, setLabel] = useState(
57+
presentation?.entry.target.label ?? props.environment.environmentLabel,
58+
);
5359
const [url, setUrl] = useState(props.environment.displayUrl);
5460
const serverConfig = useAtomValue(
5561
serverEnvironment.configValueAtom(props.environment.environmentId),
5662
);
5763
const unsupported = props.environment.connectionState === "unsupported";
58-
const presentation = useAtomValue(
59-
environmentPresentations.presentationAtom(props.environment.environmentId),
60-
);
6164
const canRetry = presentation !== null && hasSavedBearerRoute(presentation.entry);
6265
const { diagnosis, state: connectionState } = useConnectionDiagnosis(
6366
props.environment.environmentId,

‎apps/mobile/src/features/connection/PullRequestRoutingSettings.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Pressable, View } from "react-native";
1313
import { AppText as Text } from "../../components/ui/AppText";
1414
import { SymbolView } from "../../components/ui/AppSymbol";
1515
import { environmentCatalog } from "../../connection/catalog";
16+
import { environmentPresentations } from "../../state/presentation";
1617
import { useAtomCommand } from "../../state/use-atom-command";
1718
import { SettingsSection } from "../settings/components/SettingsSection";
1819

@@ -38,6 +39,7 @@ const options: ReadonlyArray<{
3839
export function PullRequestRoutingSettings() {
3940
const catalog = useAtomValue(environmentCatalog.catalogValueAtom);
4041
const permissions = useAtomValue(environmentCatalog.pullRequestRoutingPermissionsValueAtom);
42+
const presentations = useAtomValue(environmentPresentations.presentationsAtom);
4143
const update = useAtomCommand(environmentCatalog.setPullRequestRoutingPermission);
4244
const [expanded, setExpanded] = useState<string | null>(null);
4345
const [saving, setSaving] = useState(false);
@@ -48,22 +50,21 @@ export function PullRequestRoutingSettings() {
4850
<SettingsSection title="Pull request routing">
4951
{[...catalog.entries.values()].map((entry) => {
5052
const environmentId = entry.target.environmentId;
53+
const label = presentations.get(environmentId)?.label ?? entry.target.label;
5154
const selected = pullRequestRoutingPermissionFor(entry, permissions);
5255
const disabled =
5356
!catalog.isReady || saving || pullRequestRoutingConnectionKey(entry) === null;
5457
return (
5558
<View key={environmentId}>
5659
<Pressable
5760
accessibilityRole="button"
58-
accessibilityLabel={`${entry.target.label} pull request routing`}
61+
accessibilityLabel={`${label} pull request routing`}
5962
accessibilityState={{ expanded: expanded === environmentId }}
6063
className="flex-row items-center gap-3 p-4"
6164
onPress={() => setExpanded(expanded === environmentId ? null : environmentId)}
6265
>
6366
<View className="min-w-0 flex-1 gap-0.5">
64-
<Text className="text-base font-t3-bold text-foreground">
65-
{entry.target.label}
66-
</Text>
67+
<Text className="text-base font-t3-bold text-foreground">{label}</Text>
6768
<Text className="text-xs text-foreground-muted" numberOfLines={1}>
6869
{connectionCatalogDisplayUrl(entry) ?? "T3 Connect"}
6970
</Text>

‎apps/mobile/src/features/files/WorkspaceFilePreviewError.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function WorkspaceFilePreviewError(props: {
2626
return (
2727
<View className="flex-1 bg-sheet">
2828
<EnvironmentConnectionNotice
29-
environmentLabel={environment.presentation?.entry.target.label ?? "Environment"}
29+
environmentLabel={environment.presentation?.label ?? "Environment"}
3030
connection={
3131
environment.presentation?.connection ?? {
3232
phase: "available",

‎apps/mobile/src/features/review/ReviewSheet.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
695695
{showConnectionNotice ? (
696696
<View className="flex-1" style={{ paddingTop: topContentInset }}>
697697
<EnvironmentConnectionNotice
698-
environmentLabel={environment.presentation?.entry.target.label ?? "Environment"}
698+
environmentLabel={environment.presentation?.label ?? "Environment"}
699699
connection={
700700
environment.presentation?.connection ?? {
701701
phase: "available",

‎apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ function ThreadTerminal(props: {
890890
{!isEnvironmentReady ? (
891891
<EnvironmentConnectionNotice
892892
environmentLabel={
893-
environment.presentation?.entry.target.label ??
893+
environment.presentation?.label ??
894894
selectedEnvironmentConnection?.environmentLabel ??
895895
"Environment"
896896
}

‎apps/mobile/src/features/usage/UsageLimitsSection.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export function useRefreshLimits(
340340
);
341341
const failedLabels = selected
342342
.filter(([environmentId]) => failedIds.has(environmentId))
343-
.map(([, presentation]) => presentation.entry.target.label);
343+
.map(([, presentation]) => presentation.label);
344344

345345
const refresh = (afterPending: boolean) =>
346346
Promise.all(

‎apps/mobile/src/state/environments.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { useEnvironmentQuery } from "./query";
1313

1414
export interface EnvironmentPresentation extends BaseEnvironmentPresentation {
1515
readonly environmentId: EnvironmentId;
16-
readonly label: string;
1716
readonly displayUrl: string | null;
1817
readonly relayManaged: boolean;
1918
}
@@ -25,7 +24,6 @@ export function projectEnvironmentPresentation(
2524
return {
2625
...presentation,
2726
environmentId,
28-
label: presentation.entry.target.label,
2927
displayUrl: connectionCatalogDisplayUrl(presentation.entry),
3028
relayManaged: hasRelayRoute(presentation.entry),
3129
};

‎apps/mobile/src/state/remote-environment-projections.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ function presentation(
2828
endpoint: string = environmentId,
2929
serverConfig: ServerConfig | null = null,
3030
): EnvironmentPresentation {
31+
const entry = { target: target(environmentId, endpoint), profile: Option.none(), enabled: true };
3132
return {
32-
entry: { target: target(environmentId, endpoint), profile: Option.none(), enabled: true },
33+
entry,
34+
label: entry.target.label,
3335
connection: { phase: "connected", error: null, traceId: null },
3436
serverConfig,
3537
};

‎apps/mobile/src/state/remote-environment-projections.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export function createRemoteEnvironmentProjectionAtoms(input: {
2121
}) {
2222
const savedConnectionAtom = Atom.family((environmentId: EnvironmentId) => {
2323
let previousEntry: EnvironmentPresentation["entry"] | null = null;
24+
let previousLabel: string | null = null;
2425
let previousPrepared: PreparedConnection | null = null;
2526
let previous: SavedRemoteConnection | null = null;
2627

2728
return Atom.make((get) => {
2829
const presentation = get(input.presentationAtom(environmentId));
2930
if (presentation === null) {
3031
previousEntry = null;
32+
previousLabel = null;
3133
previousPrepared = null;
3234
previous = null;
3335
return null;
@@ -37,6 +39,7 @@ export function createRemoteEnvironmentProjectionAtoms(input: {
3739
if (
3840
previous !== null &&
3941
presentation.entry === previousEntry &&
42+
presentation.label === previousLabel &&
4043
prepared === previousPrepared
4144
) {
4245
return previous;
@@ -58,10 +61,11 @@ export function createRemoteEnvironmentProjectionAtoms(input: {
5861
: presentation.entry.target._tag === "RelayConnectionTarget";
5962

6063
previousEntry = presentation.entry;
64+
previousLabel = presentation.label;
6165
previousPrepared = prepared;
6266
previous = {
6367
environmentId,
64-
environmentLabel: presentation.entry.target.label,
68+
environmentLabel: presentation.label,
6569
pairingUrl: displayUrl,
6670
displayUrl,
6771
httpBaseUrl,

0 commit comments

Comments
 (0)