fix: keep current worker visible in collapsed sidebar

This commit is contained in:
2026-09-24 19:58:09 +09:00
parent 1649e1ad89
commit 0b0669b77b
3 changed files with 84 additions and 4 deletions
@@ -11,7 +11,12 @@
workspaceWorkersStore,
type SidebarWorker,
} from './worker-subscription';
import { canShowWorkerInSidebar, sidebarWorkerActivity } from './workers';
import {
canShowWorkerInSidebar,
sidebarWorkerActivity,
visibleWorkersForSidebar,
workerOwnsSidebarPath,
} from './workers';
import { sidebarWorkdirMeta } from './worker-workdir-meta';
const COLLAPSED_WORKER_COUNT = 6;
@@ -32,7 +37,12 @@
let menuTrigger = $state<HTMLButtonElement | null>(null);
let busyAction = $state<{ workerKey: string; kind: WorkerActionKind } | null>(null);
let visibleWorkers = $derived(
expanded ? workers : workers.slice(0, COLLAPSED_WORKER_COUNT),
visibleWorkersForSidebar(workers, {
workspaceId,
currentPath,
expanded,
limit: COLLAPSED_WORKER_COUNT,
}),
);
let hiddenWorkerCount = $derived(
Math.max(0, workers.length - COLLAPSED_WORKER_COUNT),
@@ -173,13 +183,14 @@
{@const activity = sidebarWorkerActivity(worker)}
{@const key = workerKey(worker)}
{@const label = worker.display_name || worker.label}
{@const active = workerOwnsSidebarPath(workspaceId, worker, currentPath)}
{@const workdir = sidebarWorkdirMeta(worker.workdir_attachments)}
<li class="worker-nav-item" data-worker-actions={key}>
<a
href={href}
class="worker-nav-link"
class:active={currentPath === href}
aria-current={currentPath === href ? 'page' : undefined}
class:active
aria-current={active ? 'page' : undefined}
>
<span class="worker-status-indicator">
{#if activity === 'worker-running'}
@@ -3,6 +3,8 @@ import {
canShowWorkerInSidebar,
compareWorkersForSidebar,
sidebarWorkerActivity,
visibleWorkersForSidebar,
workerOwnsSidebarPath,
} from "./workers.ts";
import type { Worker } from "./types.ts";
@@ -79,6 +81,39 @@ Deno.test("sidebar workers sort running then idle then stopped", () => {
assertEquals(workers.map((candidate) => candidate.worker_id).join(","), "2,1,4,3");
});
Deno.test("collapsed sidebar keeps the current Worker visible", () => {
const workers = Array.from({ length: 8 }, (_, index) =>
worker({
worker_id: String(index + 1),
resource_key: `W-${index + 1}`,
display_name: `Worker ${index + 1}`,
}));
const currentPath = "/w/workspace-1/workers/W-8-worker-8/console";
assertEquals(
workerOwnsSidebarPath("workspace-1", workers[7], currentPath),
true,
);
assertEquals(
visibleWorkersForSidebar(workers, {
workspaceId: "workspace-1",
currentPath,
expanded: false,
limit: 6,
}).map((candidate) => candidate.resource_key).join(","),
"W-1,W-2,W-3,W-4,W-5,W-8",
);
assertEquals(
visibleWorkersForSidebar(workers, {
workspaceId: "workspace-1",
currentPath,
expanded: true,
limit: 6,
}).length,
8,
);
});
Deno.test("fatal child stop clears the sidebar SubWorker spinner activity", () => {
const parent = { state: "idle", has_running_internal_workers: true };
assertEquals(sidebarWorkerActivity(parent), "subworker-running");
@@ -1,3 +1,5 @@
import { workerHref } from '../resource-links';
import { ownsRoutePath } from './route-ownership';
import type { Worker } from './types';
export type SidebarWorkerActivity =
@@ -31,6 +33,38 @@ export function canOpenWorkerConsole(
return canShowWorkerInSidebar(worker);
}
type SidebarWorkerLink = Pick<Worker, 'resource_key' | 'display_name'>;
export function workerOwnsSidebarPath(
workspaceId: string,
worker: SidebarWorkerLink,
currentPath: string,
): boolean {
return ownsRoutePath(workerHref(workspaceId, worker), currentPath);
}
export function visibleWorkersForSidebar<T extends SidebarWorkerLink>(
workers: readonly T[],
options: {
workspaceId: string;
currentPath: string;
expanded: boolean;
limit: number;
},
): readonly T[] {
if (options.expanded || workers.length <= options.limit) return workers;
if (options.limit <= 0) return [];
const visible = workers.slice(0, options.limit);
const currentIndex = workers.findIndex((worker) =>
workerOwnsSidebarPath(options.workspaceId, worker, options.currentPath)
);
if (currentIndex < options.limit) return visible;
visible[visible.length - 1] = workers[currentIndex];
return visible;
}
type SortableWorker = Pick<
Worker,
'state' | 'display_name' | 'runtime_id' | 'worker_id'