Represent restored panel presence in threads view

This commit is contained in:
murashit 2026-06-22 15:03:03 +09:00
parent 1639c37ac3
commit 2d490c6fca
5 changed files with 45 additions and 22 deletions

View file

@ -11,7 +11,7 @@ import {
type ThreadRenameLifecycleState as SharedThreadRenameLifecycleState,
} from "../threads/rename-lifecycle";
type ThreadsLiveStatus = "pending" | "running" | "draft" | "offline" | "open";
type ThreadsLiveStatus = "pending" | "running" | "open";
interface ThreadsLiveState {
status: ThreadsLiveStatus;
@ -33,10 +33,8 @@ export type ThreadsRenameLifecycleEvent =
| { type: "auto-name-finished"; generatingState: ThreadsGeneratingRenameState };
const STATUS_PRIORITY: Record<ThreadsLiveStatus, number> = {
pending: 4,
running: 3,
draft: 2,
offline: 1,
pending: 2,
running: 1,
open: 0,
};
@ -127,7 +125,5 @@ function snapshotsForThreads(snapshots: OpenCodexPanelSnapshot[]): Map<string, O
function snapshotStatus(snapshot: OpenCodexPanelSnapshot): ThreadsLiveStatus {
if (hasPendingRequests(pendingRequestCounts(snapshot))) return "pending";
if (snapshot.turnLifecycle.kind !== "idle") return "running";
if (snapshot.hasComposerDraft) return "draft";
if (!snapshot.connected) return "offline";
return "open";
}

View file

@ -78,22 +78,12 @@
--codex-panel-threads-gutter-color: color-mix(in srgb, var(--codex-panel-color-accent) 68%, transparent);
}
.codex-panel-threads__row--draft {
--codex-panel-threads-gutter-color: color-mix(in srgb, var(--codex-panel-color-accent) 36%, transparent);
}
.codex-panel-threads__row--offline {
--codex-panel-threads-gutter-color: var(--text-muted);
}
.codex-panel-threads__row--open {
--codex-panel-threads-gutter-color: var(--codex-panel-border-muted-color);
}
.codex-panel-threads__row--pending::before,
.codex-panel-threads__row--running::before,
.codex-panel-threads__row--draft::before,
.codex-panel-threads__row--offline::before,
.codex-panel-threads__row--open::before {
background: var(--codex-panel-threads-gutter-color);
}

View file

@ -129,9 +129,12 @@ export class WorkspacePanelCoordinator {
getOpenPanelSnapshots(): OpenCodexPanelSnapshot[] {
const leaves = this.panelLeaves();
this.ensureInitialFocusedPanel(leaves);
return leaves.flatMap((leaf) =>
leaf.view instanceof CodexChatView ? [this.openPanelSnapshotWithFocus(workspacePanelSurface(leaf.view).openPanelSnapshot())] : [],
);
return leaves.flatMap((leaf, index) => {
if (leaf.view instanceof CodexChatView)
return [this.openPanelSnapshotWithFocus(workspacePanelSurface(leaf.view).openPanelSnapshot())];
const restoredSnapshot = restoredPanelSnapshot(leaf, index);
return restoredSnapshot ? [restoredSnapshot] : [];
});
}
async focusOpenPanel(viewId: string, threadId: string | null = null): Promise<boolean> {
@ -438,3 +441,19 @@ function restoredThreadId(leaf: WorkspaceLeaf): string | null {
const threadId = (state as { threadId?: unknown }).threadId;
return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
}
function restoredPanelSnapshot(leaf: WorkspaceLeaf, index: number): OpenCodexPanelSnapshot | null {
const threadId = restoredThreadId(leaf);
if (!threadId) return null;
return {
viewId: `restored:${String(index)}:${threadId}`,
threadId,
turnLifecycle: { kind: "idle" },
pendingApprovals: 0,
pendingUserInputs: 0,
pendingMcpElicitations: 0,
hasComposerDraft: false,
connected: false,
lastFocused: false,
};
}

View file

@ -104,7 +104,7 @@ describe("threads view renderer decisions", () => {
new Map(),
)[0]?.live,
).toMatchObject({
status: "draft",
status: "open",
});
expect(
threadRows(
@ -113,7 +113,7 @@ describe("threads view renderer decisions", () => {
new Map(),
)[0]?.live,
).toMatchObject({
status: "offline",
status: "open",
});
expect(
threadRows(

View file

@ -99,6 +99,24 @@ describe("CodexPanelPlugin workspace panel reconciliation", () => {
expect(restoredLeaf.view).toBeInstanceOf(CodexChatView);
});
it("includes deferred restored panels in open panel snapshots", async () => {
const restoredLeaf = leaf({ state: { threadId: "thread-1", threadTitle: "Restored thread" } });
const plugin = await pluginWithLeaves([restoredLeaf]);
expect(panels(plugin).getOpenPanelSnapshots()).toMatchObject([
{
threadId: "thread-1",
turnLifecycle: { kind: "idle" },
pendingApprovals: 0,
pendingUserInputs: 0,
pendingMcpElicitations: 0,
hasComposerDraft: false,
connected: false,
lastFocused: false,
},
]);
});
it("focuses an already open thread before reusing an empty panel", async () => {
const { CodexChatView } = await import("../src/features/chat/host/view");
const openLeaf = leaf();