Skip to content
Closed
164 changes: 163 additions & 1 deletion apps/web/src/components/BranchToolbar.logic.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { EnvironmentId, type VcsRef } from "@t3tools/contracts";
import {
EnvironmentId,
ProviderDriverKind,
ProviderInstanceId,
type ServerProvider,
type VcsRef,
} from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
import { deriveProviderInstanceEntries } from "../providerInstances";
import {
dedupeRemoteBranchesWithLocalMatches,
deriveLocalBranchNameFromRemoteRef,
resolveEnvironmentOptionLabel,
buildEnvironmentOption,
resolveBranchSelectionTarget,
resolveCurrentWorkspaceLabel,
resolveDraftEnvModeAfterBranchChange,
Expand All @@ -14,6 +22,7 @@ import {
resolveBranchToolbarValue,
resolveLockedWorkspaceLabel,
resolveLocalCheckoutBranchMismatch,
resolveMobileRunContextPopupClass,
resolvePreviousWorktreeLabel,
resolvePreviousWorktreeSeed,
sanitizeNewRefName,
Expand All @@ -25,6 +34,159 @@ import {
const localEnvironmentId = EnvironmentId.make("environment-local");
const remoteEnvironmentId = EnvironmentId.make("environment-remote");

function provider(input: {
instanceId: string;
driver: "codex" | "claudeAgent";
enabled: boolean;
defaultModel: string;
displayName?: string;
modelName?: string;
modelShortName?: string;
}): ServerProvider {
return {
instanceId: ProviderInstanceId.make(input.instanceId),
driver: ProviderDriverKind.make(input.driver),
enabled: input.enabled,
installed: true,
version: null,
status: "ready",
auth: { status: "authenticated" },
checkedAt: "2026-01-01T00:00:00.000Z",
...(input.displayName ? { displayName: input.displayName } : {}),
models: [
{
slug: input.defaultModel,
name: input.modelName ?? input.defaultModel,
...(input.modelShortName ? { shortName: input.modelShortName } : {}),
isCustom: false,
isDefault: true,
capabilities: {},
},
],
slashCommands: [],
skills: [],
};
}

describe("buildEnvironmentOption", () => {
it("joins physical project defaults with the saved machine profile", () => {
const option = buildEnvironmentOption({
environmentId: remoteEnvironmentId,
projectId: "project-remote" as never,
label: "Mini PC",
isPrimary: false,
workspaceRoot: "C:/repo",
defaultModelSelection: { instanceId: "codex" as never, model: "gpt-5.4" },
profile: {
environmentId: remoteEnvironmentId,
projectId: "project-remote" as never,
branch: "feature/remote",
worktreePath: "C:/repo/.t3/worktrees/remote",
envMode: "worktree",
startFromOrigin: true,
runtimeMode: "full-access",
interactionMode: "default",
activeProvider: "codex" as never,
modelSelectionByProvider: {
["codex" as never]: { instanceId: "codex" as never, model: "o3" },
},
},
connectionStatusLabel: "Connected",
isAvailable: true,
});

expect(option).toMatchObject({
label: "Mini PC",
workspaceRoot: "C:/repo",
connection: "connected",
profile: {
branchLabel: "feature/remote",
workspaceLabel: "C:/repo/.t3/worktrees/remote",
modelLabel: "o3",
executionLabel: "Full access · Build · origin",
},
});
});

it("marks an unavailable remote without hiding its profile", () => {
expect(
buildEnvironmentOption({
environmentId: remoteEnvironmentId,
projectId: "project-remote" as never,
label: "Mini PC",
isPrimary: false,
workspaceRoot: "C:/repo",
defaultModelSelection: null,
profile: null,
connectionStatusLabel: "Reconnecting...",
isAvailable: false,
}),
).toMatchObject({
connection: "unavailable",
connectionStatusLabel: "Reconnecting...",
profile: { branchLabel: "Current checkout" },
});
});

it("does not preview a saved model from a disabled target provider", () => {
const staleInstanceId = ProviderInstanceId.make("codex_disabled");
const fallbackInstanceId = ProviderInstanceId.make("claudeAgent");
const option = buildEnvironmentOption({
environmentId: remoteEnvironmentId,
projectId: "project-remote" as never,
label: "Mini PC",
isPrimary: false,
workspaceRoot: "C:/repo",
defaultModelSelection: { instanceId: fallbackInstanceId, model: "claude-sonnet-4" },
profile: {
environmentId: remoteEnvironmentId,
projectId: "project-remote" as never,
branch: null,
worktreePath: null,
envMode: "local",
startFromOrigin: false,
runtimeMode: "full-access",
interactionMode: "default",
activeProvider: staleInstanceId,
modelSelectionByProvider: {
[staleInstanceId]: { instanceId: staleInstanceId, model: "stale-model" },
},
},
providerEntries: deriveProviderInstanceEntries([
provider({
instanceId: staleInstanceId,
driver: "codex",
enabled: false,
defaultModel: "gpt-5.4",
}),
provider({
instanceId: fallbackInstanceId,
driver: "claudeAgent",
enabled: true,
defaultModel: "claude-sonnet-4",
displayName: "Remote Claude",
modelName: "Claude Sonnet 4",
modelShortName: "Sonnet 4",
}),
]),
connectionStatusLabel: "Connected",
isAvailable: true,
});

expect(option.profile).toMatchObject({
providerLabel: "Remote Claude",
modelLabel: "Sonnet 4",
});
});
});

describe("resolveMobileRunContextPopupClass", () => {
it("uses the detail width only when machine profiles are rendered", () => {
expect(resolveMobileRunContextPopupClass(true)).toBe("w-[min(28rem,calc(100vw-2rem))]");
expect(resolveMobileRunContextPopupClass(false)).toBe("w-64");
});
});

describe("resolvePreviousWorktreeSeed", () => {
it("picks the most recently updated worktree thread", () => {
expect(
Expand Down
120 changes: 119 additions & 1 deletion apps/web/src/components/BranchToolbar.logic.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,134 @@
import type { EnvironmentId, VcsRef, ProjectId } from "@t3tools/contracts";
import type { EnvironmentId, ModelSelection, ProjectId, VcsRef } from "@t3tools/contracts";
import * as Schema from "effect/Schema";
import { toSortableTimestamp } from "../lib/threadSort";
import {
resolveSelectableProviderInstanceEntry,
type ProviderInstanceEntry,
} from "../providerInstances";
import {
resolveMachineProfileSummary,
type MachineDraftProfile,
type MachineProfileSummary,
} from "../machineDraftProfile";
import { getTriggerDisplayModelLabel } from "./chat/providerIconUtils";
export {
dedupeRemoteBranchesWithLocalMatches,
deriveLocalBranchNameFromRemoteRef,
} from "@t3tools/shared/git";

export { resolveMachineProfileSummary } from "../machineDraftProfile";
export type { MachineDraftProfile, MachineProfileSummary } from "../machineDraftProfile";

export const MACHINE_PROFILE_POPUP_CLASS = "w-[min(28rem,calc(100vw-2rem))]";

export function resolveMobileRunContextPopupClass(showMachineProfiles: boolean): string {
return showMachineProfiles ? MACHINE_PROFILE_POPUP_CLASS : "w-64";
}

export type EnvironmentConnectionState = "connected" | "unavailable";

export interface EnvironmentOption {
environmentId: EnvironmentId;
projectId: ProjectId;
label: string;
isPrimary: boolean;
workspaceRoot: string;
connection: EnvironmentConnectionState;
connectionStatusLabel: string;
profile: MachineProfileSummary;
}

function resolveEnvironmentModelPreview(input: {
defaultModelSelection: ModelSelection | null | undefined;
profile: MachineDraftProfile | null | undefined;
providerEntries: readonly ProviderInstanceEntry[];
}): { selection: ModelSelection; providerLabel: string; modelLabel: string } | null {
const profile = input.profile ?? null;
const candidateInstanceIds = [profile?.activeProvider, input.defaultModelSelection?.instanceId];
let selectedEntry: ProviderInstanceEntry | undefined;
for (const candidateInstanceId of candidateInstanceIds) {
if (!candidateInstanceId) continue;
selectedEntry = input.providerEntries.find(
(entry) => entry.instanceId === candidateInstanceId && entry.enabled && entry.isAvailable,
);
if (selectedEntry) break;
}
selectedEntry ??= resolveSelectableProviderInstanceEntry(input.providerEntries, undefined);
if (!selectedEntry) return null;

const savedSelection = profile?.modelSelectionByProvider[selectedEntry.instanceId];
let selection =
savedSelection ??
(input.defaultModelSelection?.instanceId === selectedEntry.instanceId
? input.defaultModelSelection
: null);
if (!selection) {
const defaultModel =
selectedEntry.models.find((model) => model.isDefault && !model.isCustom)?.slug ??
selectedEntry.models.find((model) => !model.isCustom)?.slug ??
selectedEntry.models[0]?.slug;
selection = defaultModel ? { instanceId: selectedEntry.instanceId, model: defaultModel } : null;
}
if (!selection) return null;

const selectedModel = selectedEntry.models.find((model) => model.slug === selection.model);
return {
selection,
providerLabel: selectedEntry.displayName,
modelLabel: selectedModel ? getTriggerDisplayModelLabel(selectedModel) : selection.model,
};
}

export function buildEnvironmentOption(input: {
environmentId: EnvironmentId;
projectId: ProjectId;
label: string;
isPrimary: boolean;
workspaceRoot: string;
defaultModelSelection: ModelSelection | null | undefined;
profile: MachineDraftProfile | null | undefined;
fallbackExecutionProfile?: Pick<
MachineDraftProfile,
"runtimeMode" | "interactionMode" | "startFromOrigin"
> | null;
providerEntries?: readonly ProviderInstanceEntry[] | null;
isAvailable: boolean;
connectionStatusLabel: string;
}): EnvironmentOption {
const modelPreview = input.providerEntries
? resolveEnvironmentModelPreview({
defaultModelSelection: input.defaultModelSelection,
profile: input.profile,
providerEntries: input.providerEntries,
})
: undefined;
const summary = resolveMachineProfileSummary({
workspaceRoot: input.workspaceRoot,
defaultModelSelection: input.defaultModelSelection,
profile: input.profile,
...(modelPreview !== undefined
? { modelSelectionOverride: modelPreview?.selection ?? null }
: {}),
...(input.fallbackExecutionProfile !== undefined
? { fallbackExecutionProfile: input.fallbackExecutionProfile }
: {}),
});
return {
environmentId: input.environmentId,
projectId: input.projectId,
label: input.label,
isPrimary: input.isPrimary,
workspaceRoot: input.workspaceRoot,
connection: input.isAvailable ? "connected" : "unavailable",
connectionStatusLabel: input.connectionStatusLabel,
profile: modelPreview
? {
...summary,
providerLabel: modelPreview.providerLabel,
modelLabel: modelPreview.modelLabel,
}
: summary,
};
}

export const EnvMode = Schema.Literals(["local", "worktree"]);
Expand Down
22 changes: 14 additions & 8 deletions apps/web/src/components/BranchToolbar.tsx
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import {
resolveEnvModeLabel,
resolveEffectiveEnvMode,
resolveLockedWorkspaceLabel,
resolveMobileRunContextPopupClass,
resolvePreviousWorktreeLabel,
resolvePreviousWorktreeSeed,
shouldShowEnvironmentIndicator,
} from "./BranchToolbar.logic";
import { BranchToolbarBranchSelector } from "./BranchToolbarBranchSelector";
import { BranchToolbarEnvironmentSelector } from "./BranchToolbarEnvironmentSelector";
import { BranchToolbarEnvModeSelector } from "./BranchToolbarEnvModeSelector";
import { MachineProfileRow } from "./MachineProfileRow";
import { Button } from "./ui/button";
import {
Menu,
Expand Down Expand Up @@ -123,6 +125,10 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
</span>
</>
);
const showMachineProfiles =
showEnvironmentPicker &&
availableEnvironments !== undefined &&
onEnvironmentChange !== undefined;

if (isLocked) {
return (
Expand All @@ -141,8 +147,12 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
{triggerContent}
<ChevronDownIcon className="size-3 shrink-0 opacity-50" />
</MenuTrigger>
<MenuPopup align="start" side="top" className="w-64">
{showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (
<MenuPopup
align="start"
side="top"
className={resolveMobileRunContextPopupClass(showMachineProfiles)}
>
{showMachineProfiles ? (
<>
<MenuGroup>
<MenuGroupLabel>Run on</MenuGroupLabel>
Expand All @@ -151,17 +161,13 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)}
>
{availableEnvironments.map((env) => {
const Icon = env.isPrimary ? MonitorIcon : CloudIcon;
return (
<MenuRadioItem
key={env.environmentId}
disabled={envLocked}
disabled={envLocked || env.connection !== "connected"}
value={env.environmentId}
>
<span className="flex min-w-0 items-center gap-1.5">
<Icon className="size-3" />
<span className="min-w-0 truncate">{env.label}</span>
</span>
<MachineProfileRow environment={env} />
Comment thread
aGamingGod1234 marked this conversation as resolved.
</MenuRadioItem>
);
})}
Expand Down
Loading
Loading