mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 19:20:07 +02:00
fix(runtimes): always show machine header in picker; pre-fill rename with shared name only
Testing + review follow-ups on #5087: 1. The create-agent picker hid the machine group header when there was only one machine (e.g. after a search narrowed to one), collapsing to a flat list. Always render the machine header so grouping stays consistent. 2. (Elon) RenameMachineDialog pre-filled from the first non-empty custom_name on the machine, but the machine title only uses a name when ALL runtimes share one (sharedCustomName). A lone per-runtime name would thus pre-fill as if it were the machine name — the same runtime-vs-machine confusion this PR set out to remove. Export sharedCustomName and use it for the pre-fill; otherwise pre-fill empty. Added direct unit tests. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -72,7 +72,6 @@ export function RuntimePicker({
|
||||
}, [filteredRuntimes, search, currentUserId]);
|
||||
|
||||
const showSearch = runtimes.length > SEARCH_THRESHOLD;
|
||||
const showGroupHeaders = machines.length > 1;
|
||||
|
||||
const selectedRuntime =
|
||||
runtimes.find((d) => d.id === selectedRuntimeId) ?? null;
|
||||
@@ -210,17 +209,19 @@ export function RuntimePicker({
|
||||
) : (
|
||||
machines.map((machine) => (
|
||||
<div key={machine.id}>
|
||||
{showGroupHeaders && (
|
||||
<div className="flex items-center justify-between gap-2 px-2 pb-0.5 pt-2 text-[11px] font-medium text-muted-foreground">
|
||||
<span className="truncate">{machine.title}</span>
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{t(($) => $.create_dialog.runtime_group_online, {
|
||||
online: machine.onlineCount,
|
||||
total: machine.runtimes.length,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Always show the machine header — even when a search or a
|
||||
single-machine workspace narrows it to one group — so the
|
||||
grouping stays consistent instead of collapsing to a flat
|
||||
list. */}
|
||||
<div className="flex items-center justify-between gap-2 px-2 pb-0.5 pt-2 text-[11px] font-medium text-muted-foreground">
|
||||
<span className="truncate">{machine.title}</span>
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{t(($) => $.create_dialog.runtime_group_online, {
|
||||
online: machine.onlineCount,
|
||||
total: machine.runtimes.length,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{machine.runtimes.map((device) => {
|
||||
const ownerMember = getOwnerMember(device.owner_id);
|
||||
const disabled = !isRuntimeUsableForUser(
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
buildRuntimeMachines,
|
||||
filterRuntimeMachines,
|
||||
runtimeMachineCounts,
|
||||
sharedCustomName,
|
||||
splitRuntimeName,
|
||||
} from "./runtime-machines";
|
||||
|
||||
@@ -349,3 +350,33 @@ describe("splitRuntimeName", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sharedCustomName", () => {
|
||||
it("returns the name when every runtime shares one non-empty custom_name", () => {
|
||||
expect(
|
||||
sharedCustomName([
|
||||
makeRuntime({ id: "a", custom_name: "Bohan's MacBook" }),
|
||||
makeRuntime({ id: "b", custom_name: "Bohan's MacBook" }),
|
||||
]),
|
||||
).toBe("Bohan's MacBook");
|
||||
});
|
||||
|
||||
it("returns null when only some runtimes are named (a lone per-runtime name is not the machine name)", () => {
|
||||
expect(
|
||||
sharedCustomName([
|
||||
makeRuntime({ id: "a", custom_name: "just this one" }),
|
||||
makeRuntime({ id: "b", custom_name: null }),
|
||||
]),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when the names disagree, or the set is empty", () => {
|
||||
expect(
|
||||
sharedCustomName([
|
||||
makeRuntime({ id: "a", custom_name: "Air" }),
|
||||
makeRuntime({ id: "b", custom_name: "Pro" }),
|
||||
]),
|
||||
).toBeNull();
|
||||
expect(sharedCustomName([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,7 +274,7 @@ function runtimeDeviceName(runtime: AgentRuntime): string | null {
|
||||
// daemon, so a name shared by all of them is the machine's label. A one-off
|
||||
// per-runtime rename (not shared) is deliberately ignored here so it can't
|
||||
// masquerade as the whole machine's name.
|
||||
function sharedCustomName(runtimes: AgentRuntime[]): string | null {
|
||||
export function sharedCustomName(runtimes: AgentRuntime[]): string | null {
|
||||
if (runtimes.length === 0) return null;
|
||||
const names = runtimes.map((r) => r.custom_name?.trim() ?? "");
|
||||
const first = names[0];
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
buildRuntimeMachines,
|
||||
filterRuntimeMachines,
|
||||
runtimeMachineCounts,
|
||||
sharedCustomName,
|
||||
type RuntimeMachine,
|
||||
type RuntimeMachineFilter,
|
||||
} from "./runtime-machines";
|
||||
@@ -254,8 +255,10 @@ export function RuntimesPage({
|
||||
? m.runtimes[0]
|
||||
: m.runtimes.find((r) => r.owner_id === currentUserId);
|
||||
if (!editable) return null;
|
||||
const currentName =
|
||||
m.runtimes.find((r) => r.custom_name?.trim())?.custom_name?.trim() ?? "";
|
||||
// Pre-fill only a real machine name — the same rule the machine title uses
|
||||
// (every runtime shares one non-empty custom_name). A lone per-runtime
|
||||
// custom name must NOT masquerade as the machine's current name.
|
||||
const currentName = sharedCustomName(m.runtimes) ?? "";
|
||||
return { runtimeId: editable.id, currentName };
|
||||
}, [selectedMachine, canManageProfiles, currentUserId]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user