Group and clear workspace status in the sidebar (#1317)

* Add status grouping to the workspace sidebar

* Mark workspaces as read from the sidebar

* Clean package builds before release checks

* Fix sidebar status follow-up checks
This commit is contained in:
Mohamed Boudra
2026-06-04 01:20:20 +08:00
committed by GitHub
parent 71ce96b913
commit 31ae545289
65 changed files with 3758 additions and 290 deletions
@@ -23,6 +23,7 @@ function createWorkspace(): WorkspaceDescriptor {
name: "worktree",
status: "done",
archivingAt: null,
statusEnteredAt: null,
diffStat: null,
scripts: [],
};
+1
View File
@@ -44,6 +44,7 @@ function workspace(input: {
name: input.id,
status: "done",
archivingAt: null,
statusEnteredAt: null,
diffStat: null,
scripts: [],
gitRuntime: {
@@ -35,7 +35,7 @@ describe("deriveSidebarStateBucket", () => {
).toBe("attention");
});
it("treats initializing agents as done", () => {
it("treats initializing agents as running", () => {
expect(
deriveSidebarStateBucket({
status: "initializing",
@@ -43,6 +43,6 @@ describe("deriveSidebarStateBucket", () => {
requiresAttention: false,
attentionReason: null,
}),
).toBe("done");
).toBe("running");
});
});
@@ -25,6 +25,7 @@ function workspace(overrides: Partial<SidebarWorkspaceEntry> = {}): SidebarWorks
archiveUnpushedCommitCount: null,
scripts: [],
hasRunningScripts: false,
statusEnteredAt: null,
...overrides,
archivingAt: overrides.archivingAt ?? null,
};
@@ -4,7 +4,11 @@ import type {
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import { buildSidebarShortcutModel, getRelativeSidebarShortcutTarget } from "./sidebar-shortcuts";
import {
buildSidebarShortcutModel,
buildStatusSidebarShortcutModel,
getRelativeSidebarShortcutTarget,
} from "./sidebar-shortcuts";
function workspace(input: {
serverId: string;
@@ -12,6 +16,8 @@ function workspace(input: {
workspaceDirectory: string;
name: string;
projectKey?: string;
statusBucket?: SidebarWorkspaceEntry["statusBucket"];
statusEnteredAt?: Date | null;
}): SidebarWorkspaceEntry {
return {
workspaceKey: `${input.serverId}:${input.workspaceId}`,
@@ -22,8 +28,9 @@ function workspace(input: {
projectKind: "git",
workspaceKind: "checkout",
name: input.name,
statusBucket: "done",
statusBucket: input.statusBucket ?? "done",
archivingAt: null,
statusEnteredAt: input.statusEnteredAt ?? null,
diffStat: null,
prHint: null,
archiveHasUncommittedChanges: null,
@@ -132,6 +139,68 @@ describe("buildSidebarShortcutModel", () => {
});
});
describe("buildStatusSidebarShortcutModel", () => {
it("builds shortcut targets in status visual order", () => {
const workspaces = [
workspace({
serverId: "s1",
workspaceId: "done-old",
workspaceDirectory: "/repo/done-old",
name: "done old",
projectKey: "p1",
statusBucket: "done",
statusEnteredAt: new Date("2026-01-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "running-new",
workspaceDirectory: "/repo/running-new",
name: "running new",
projectKey: "p2",
statusBucket: "running",
statusEnteredAt: new Date("2026-03-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "needs-input",
workspaceDirectory: "/repo/needs-input",
name: "needs input",
projectKey: "p1",
statusBucket: "needs_input",
statusEnteredAt: new Date("2026-02-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "running-old",
workspaceDirectory: "/repo/running-old",
name: "running old",
projectKey: "p2",
statusBucket: "running",
statusEnteredAt: new Date("2026-01-15T00:00:00.000Z"),
}),
];
const model = buildStatusSidebarShortcutModel({
workspaces,
projectNamesByKey: new Map([
["p1", "Project 1"],
["p2", "Project 2"],
]),
});
expect(model.shortcutTargets).toEqual([
{ serverId: "s1", workspaceId: "needs-input" },
{ serverId: "s1", workspaceId: "running-new" },
{ serverId: "s1", workspaceId: "running-old" },
{ serverId: "s1", workspaceId: "done-old" },
]);
expect(model.shortcutIndexByWorkspaceKey.get("s1:needs-input")).toBe(1);
expect(model.shortcutIndexByWorkspaceKey.get("s1:running-new")).toBe(2);
expect(model.shortcutIndexByWorkspaceKey.get("s1:running-old")).toBe(3);
expect(model.shortcutIndexByWorkspaceKey.get("s1:done-old")).toBe(4);
});
});
describe("getRelativeSidebarShortcutTarget", () => {
const targets = [
{ serverId: "s1", workspaceId: "ws-1" },
+27 -1
View File
@@ -2,6 +2,7 @@ import type {
SidebarProjectEntry,
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import { buildStatusGroups } from "@/hooks/sidebar-status-view-model";
import { isSidebarProjectFlattened } from "./sidebar-project-row-model";
export interface SidebarShortcutWorkspaceTarget {
@@ -37,7 +38,32 @@ export function buildSidebarShortcutModel(input: {
for (const workspace of project.workspaces) {
if (shortcutTargets.length >= maxShortcuts) {
continue;
break;
}
const shortcutNumber = shortcutTargets.length + 1;
shortcutTargets.push(createShortcutTarget(workspace));
shortcutIndexByWorkspaceKey.set(workspace.workspaceKey, shortcutNumber);
}
}
return { shortcutTargets, shortcutIndexByWorkspaceKey };
}
export function buildStatusSidebarShortcutModel(input: {
workspaces: SidebarWorkspaceEntry[];
projectNamesByKey: Map<string, string>;
shortcutLimit?: number;
}): SidebarShortcutModel {
const maxShortcuts = Math.max(0, Math.floor(input.shortcutLimit ?? 9));
const groups = buildStatusGroups(input.workspaces, input.projectNamesByKey);
const shortcutTargets: SidebarShortcutWorkspaceTarget[] = [];
const shortcutIndexByWorkspaceKey = new Map<string, number>();
for (const group of groups) {
for (const workspace of group.rows) {
if (shortcutTargets.length >= maxShortcuts) {
break;
}
const shortcutNumber = shortcutTargets.length + 1;
@@ -21,6 +21,7 @@ function workspace(
name: input.name ?? input.id,
status: input.status ?? "done",
archivingAt: input.archivingAt ?? null,
statusEnteredAt: null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
};
@@ -22,6 +22,7 @@ function createWorkspace(
name: input.name ?? "main",
status: input.status ?? "running",
archivingAt: input.archivingAt ?? null,
statusEnteredAt: null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
};