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:
@@ -23,6 +23,7 @@ function createWorkspace(): WorkspaceDescriptor {
|
||||
name: "worktree",
|
||||
status: "done",
|
||||
archivingAt: null,
|
||||
statusEnteredAt: null,
|
||||
diffStat: null,
|
||||
scripts: [],
|
||||
};
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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 ?? [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user