Skip to content
Closed
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
17 changes: 13 additions & 4 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import { flushSync } from "react-dom";

import { useComposerDraftStore, type DraftId } from "../composerDraftStore";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "./EnvironmentBadgeIcon";
import { useProject, useThreadShellsForProjectRefs } from "../state/entities";
import {
ADD_TO_MACHINE_LABEL_CLASS,
Expand Down Expand Up @@ -244,7 +244,8 @@ const RunContextSelector = memo(function RunContextSelector({
aria-hidden="true"
/>
) : (
<EnvironmentMachineIcon
<EnvironmentBadgeIcon
environmentId={environmentId}
kind={activeEnvironment?.machine ?? "server"}
// Wide machine glyphs need a little more box to match the square row icons.
className={
Expand Down Expand Up @@ -373,7 +374,11 @@ const RunContextSelector = memo(function RunContextSelector({
closeOnClick
>
<span className="flex min-w-0 items-center gap-1.5">
<EnvironmentMachineIcon kind={env.machine} className="size-3" />
<EnvironmentBadgeIcon
environmentId={env.environmentId}
kind={env.machine}
className="size-3"
/>
<span className="min-w-0 truncate">{env.label}</span>
</span>
</MenuRadioItem>
Expand All @@ -394,7 +399,11 @@ const RunContextSelector = memo(function RunContextSelector({
onClick={() => onAddToMachine(target.environmentId)}
>
<span className="flex min-w-0 items-center gap-1.5">
<EnvironmentMachineIcon kind={target.machine} className="size-3" />
<EnvironmentBadgeIcon
environmentId={target.environmentId}
kind={target.machine}
className="size-3"
/>
<span className="min-w-0 truncate">{target.label}</span>
</span>
</MenuItem>
Expand Down
14 changes: 10 additions & 4 deletions apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { PlusIcon, ScaleIcon } from "lucide-react";
import { memo } from "react";

import { ADD_TO_MACHINE_LABEL_CLASS, type EnvironmentOption } from "./BranchToolbar.logic";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "./EnvironmentBadgeIcon";
import { useComposerMenuProps } from "./chat/composerEventScope";
import {
Select,
Expand Down Expand Up @@ -71,7 +71,8 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
<EnvironmentMachineIcon
<EnvironmentBadgeIcon
environmentId={environmentId}
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
Expand Down Expand Up @@ -116,7 +117,8 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
{autoEnvironmentLabel ? (
<ScaleIcon className="size-3 shrink-0" aria-hidden="true" />
) : (
<EnvironmentMachineIcon
<EnvironmentBadgeIcon
environmentId={environmentId}
kind={activeEnvironment?.machine ?? "server"}
className="size-3 shrink-0"
/>
Expand Down Expand Up @@ -153,7 +155,11 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
disabled={onEnvironmentChange === undefined && env.environmentId !== environmentId}
>
<span className="inline-flex items-center gap-1.5">
<EnvironmentMachineIcon kind={env.machine} className="size-3" />
<EnvironmentBadgeIcon
environmentId={env.environmentId}
kind={env.machine}
className="size-3"
/>
{env.label}
</span>
</SelectItem>
Expand Down
10 changes: 8 additions & 2 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ import {
CommandPaletteVirtualizedResults,
scrollCommandPaletteRowIntoView,
} from "./CommandPaletteResults";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "./EnvironmentBadgeIcon";
import { Checkbox } from "./ui/checkbox";
import { ProjectFilePicker } from "./files/ProjectFilePicker";
import { openLinkPullRequestDialog } from "./pullRequest/LinkPullRequestDialog";
Expand Down Expand Up @@ -1049,7 +1049,13 @@ function OpenCommandPaletteDialog(props: {
: option.environmentId
: option.status,
disabled: !option.isConnected,
icon: <EnvironmentMachineIcon kind={option.machine} className={ITEM_ICON_CLASS} />,
icon: (
<EnvironmentBadgeIcon
environmentId={option.environmentId}
kind={option.machine}
className={ITEM_ICON_CLASS}
/>
),
keepOpen: true,
run: async () => {
run(option.environmentId);
Expand Down
32 changes: 32 additions & 0 deletions apps/web/src/components/EnvironmentBadgeIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { EnvironmentId, EnvironmentMachineKind } from "@t3tools/contracts";

import { useEnvironmentGlyphStore } from "../environmentGlyphStore";
import { cn } from "~/lib/utils";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";

/** A device-local glyph overrides the machine icon without changing server settings. */
export function EnvironmentBadgeIcon({
environmentId,
kind,
className,
}: {
readonly environmentId: EnvironmentId;
readonly kind: EnvironmentMachineKind;
readonly className?: string;
}) {
const glyph = useEnvironmentGlyphStore((state) => state.byId[environmentId]);

return glyph ? (
<span
aria-hidden
className={cn(
"inline-flex shrink-0 items-center justify-center text-xs leading-none font-medium select-none",
className,
)}
>
{glyph}
</span>
) : (
<EnvironmentMachineIcon aria-hidden kind={kind} className={className} />
);
}
6 changes: 3 additions & 3 deletions apps/web/src/components/ProjectEnvironmentBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { EnvironmentId, EnvironmentMachineKind } from "@t3tools/contracts";

import type { SidebarProjectSnapshot } from "~/sidebarProjectGrouping";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "./EnvironmentBadgeIcon";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

export function ProjectEnvironmentBadge(props: {
Expand Down Expand Up @@ -32,8 +32,8 @@ export function ProjectEnvironmentBadge(props: {
/>
}
>
<EnvironmentMachineIcon
aria-hidden
<EnvironmentBadgeIcon
environmentId={first.environmentId}
kind={props.machineByEnvironmentId.get(first.environmentId) ?? "server"}
className="size-3.5"
/>
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/ThreadStatusIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { Atom } from "effect/reactivity";
import { FolderGit2Icon, TerminalIcon } from "lucide-react";
import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments";
import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "./EnvironmentBadgeIcon";
import { useProject } from "../state/entities";
import {
pullRequestListLines,
Expand Down Expand Up @@ -1002,7 +1002,8 @@ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSumma
/>
}
>
<EnvironmentMachineIcon
<EnvironmentBadgeIcon
environmentId={thread.environmentId}
kind={remoteMachine}
className="size-3 text-muted-foreground/60"
/>
Expand Down
18 changes: 15 additions & 3 deletions apps/web/src/components/settings/ConnectionsSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import { Switch } from "../ui/switch";
import { stackedThreadToast, toastManager } from "../ui/toast";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { Button } from "../ui/button";
import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";
import { EnvironmentBadgeIcon } from "../EnvironmentBadgeIcon";
import { EnvironmentGlyphInput } from "./EnvironmentGlyphInput";
import {
revokeOtherServerClientSessions,
revokeServerClientSession,
Expand Down Expand Up @@ -1036,8 +1037,8 @@ export function ConnectionsSettings({ view }: { readonly view: "environments" |
primaryEnvironment?.label ?? (desktopBridge ? "This machine" : "Primary environment")
}
icon={
<EnvironmentMachineIcon
aria-hidden
<EnvironmentBadgeIcon
environmentId={primaryEnvironmentId}
kind={
primaryServerConfig
? resolveEnvironmentMachineKind(primaryServerConfig)
Expand Down Expand Up @@ -1071,6 +1072,17 @@ export function ConnectionsSettings({ view }: { readonly view: "environments" |
) : null
}
>
<SettingsRow
title="Badge glyph"
description="One character or emoji, shown on this device. Clear it to use the machine icon."
control={
<EnvironmentGlyphInput
key={primaryEnvironmentId}
environmentId={primaryEnvironmentId}
label={primaryEnvironment?.label ?? "this environment"}
/>
}
/>
{canManageLocalBackend ? (
<SettingsRow
title="Version"
Expand Down
61 changes: 61 additions & 0 deletions apps/web/src/components/settings/EnvironmentGlyphInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { EnvironmentId } from "@t3tools/contracts";
import { useRef, useState } from "react";

import { normalizeEnvironmentGlyph, useEnvironmentGlyphStore } from "~/environmentGlyphStore";
import { Input } from "../ui/input";

/** Edit the local glyph without changing the server-backed machine icon. */
export function EnvironmentGlyphInput({
environmentId,
label,
}: {
readonly environmentId: EnvironmentId;
readonly label: string;
}) {
const [draft, setDraft] = useState(
() => useEnvironmentGlyphStore.getState().byId[environmentId] ?? "",
);
const composing = useRef(false);
const setGlyph = (value: string) => {
useEnvironmentGlyphStore.getState().setGlyph(environmentId, value);
};

const finish = (value: string) => {
composing.current = false;
const normalized = normalizeEnvironmentGlyph(value) ?? "";
setDraft(normalized);
setGlyph(normalized);
};

return (
<div className="w-12 shrink-0 text-center">
<Input
aria-label={`Badge glyph for ${label}`}
title="One character or emoji (this device only). Clear to use the machine icon."
value={draft}
onChange={(event) => {
const value = event.currentTarget.value;
setDraft(value);
if (!composing.current) setGlyph(value);
}}
onCompositionStart={() => {
composing.current = true;
}}
onCompositionEnd={(event) => {
composing.current = false;
const value = event.currentTarget.value;
setDraft(value);
setGlyph(value);
}}
onBlur={(event) => finish(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !composing.current && !event.nativeEvent.isComposing) {
event.currentTarget.blur();
}
}}
placeholder="α"
size="sm"
/>
</div>
);
}
27 changes: 21 additions & 6 deletions apps/web/src/components/settings/EnvironmentRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import {
connectionRouteLabel,
connectionRoutes,
} from "@t3tools/client-runtime/connection";
import type { DesktopSshEnvironmentTarget, EnvironmentMachineKind } from "@t3tools/contracts";
import type {
DesktopSshEnvironmentTarget,
EnvironmentId,
EnvironmentMachineKind,
} from "@t3tools/contracts";
import * as Option from "effect/Option";
import type { ReactNode } from "react";

import { cn } from "~/lib/utils";
import type { EnvironmentPresentation } from "~/state/environments";
import { isDesktopLocalConnectionTarget } from "~/connection/desktopLocal";
import { EnvironmentBadgeIcon } from "../EnvironmentBadgeIcon";
import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";

export function formatDesktopSshTarget(target: DesktopSshEnvironmentTarget): string {
Expand Down Expand Up @@ -49,6 +54,7 @@ export function environmentTransportLabel(
}

export function EnvironmentRow({
environmentId,
kind,
label,
subtitle,
Expand All @@ -58,6 +64,7 @@ export function EnvironmentRow({
className,
children,
}: {
readonly environmentId?: EnvironmentId;
readonly kind: EnvironmentMachineKind;
readonly label: string;
readonly subtitle: ReactNode;
Expand All @@ -75,11 +82,19 @@ export function EnvironmentRow({
)}
>
{/* Dim only identity so switched-off controls remain readable and distinct from disabled controls. */}
<EnvironmentMachineIcon
aria-hidden
kind={kind}
className={cn("size-4 text-muted-foreground", dimmed && "opacity-60")}
/>
{environmentId ? (
<EnvironmentBadgeIcon
environmentId={environmentId}
kind={kind}
className={cn("size-4 text-muted-foreground", dimmed && "opacity-60")}
/>
) : (
<EnvironmentMachineIcon
aria-hidden
kind={kind}
className={cn("size-4 text-muted-foreground", dimmed && "opacity-60")}
/>
)}
<div className={cn("min-w-0", dimmed && "opacity-60")}>
<p className="truncate text-sm font-medium text-foreground">{label}</p>
<div className="truncate text-xs text-muted-foreground">{subtitle}</div>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/settings/SavedEnvironmentRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { cn } from "../../lib/utils";
import { SettingsRow } from "./settingsLayout";
import { searchableSetting } from "./settingsSearch";
import { EnvironmentIconMenu } from "./EnvironmentIconPicker";
import { EnvironmentGlyphInput } from "./EnvironmentGlyphInput";
import { EnvironmentRoutesList } from "./EnvironmentRoutesList";
import {
ConnectionDiagnosisPanel,
Expand Down Expand Up @@ -238,6 +239,7 @@ export function SavedBackendListRow({

return (
<EnvironmentRow
environmentId={environmentId}
kind={machineKind}
label={environment.label}
dimmed={!enabled}
Expand Down Expand Up @@ -355,6 +357,11 @@ export function SavedBackendListRow({
</>
}
>
<EnvironmentGlyphInput
key={environmentId}
environmentId={environmentId}
label={environment.label}
/>
{unsupported &&
environment.entry.serverUpdateRequired === true &&
serverUpdateState.status !== "running" ? (
Expand Down
Loading
Loading