mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Separate toolbar panel state from open details
This commit is contained in:
parent
ce0ebb67a1
commit
e2e63780f6
11 changed files with 26 additions and 42 deletions
|
|
@ -100,6 +100,7 @@ interface ChatComposerState {
|
|||
|
||||
interface ChatUiState {
|
||||
openDetails: ReadonlySet<string>;
|
||||
toolbarPanel: "history" | "chat-actions" | "status-panel" | null;
|
||||
}
|
||||
|
||||
export interface ChatState {
|
||||
|
|
@ -696,6 +697,7 @@ function initialComposerState(): ChatComposerState {
|
|||
function initialUiState(): ChatUiState {
|
||||
return {
|
||||
openDetails: new Set(),
|
||||
toolbarPanel: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -731,6 +733,7 @@ function cloneChatState(state: ChatState): ChatState {
|
|||
},
|
||||
ui: {
|
||||
openDetails: new Set(state.ui.openDetails),
|
||||
toolbarPanel: state.ui.toolbarPanel,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -740,18 +743,8 @@ function isTranscriptAction(action: ChatSliceAction): action is TranscriptAction
|
|||
}
|
||||
|
||||
function setPanelSlice(state: ChatUiState, panel: "history" | "chat-actions" | "status-panel" | null, toggle: boolean): ChatUiState {
|
||||
const currentPanel = state.openDetails.has("history")
|
||||
? "history"
|
||||
: state.openDetails.has("chat-actions")
|
||||
? "chat-actions"
|
||||
: state.openDetails.has("status-panel")
|
||||
? "status-panel"
|
||||
: null;
|
||||
const nextPanel = toggle && currentPanel === panel ? null : panel;
|
||||
const toolbarPanelKeys = new Set(["history", "chat-actions", "status-panel"]);
|
||||
return patchObject(state, {
|
||||
openDetails: nextPanel ? new Set([nextPanel]) : new Set([...state.openDetails].filter((key) => !toolbarPanelKeys.has(key))),
|
||||
});
|
||||
const nextPanel = toggle && state.toolbarPanel === panel ? null : panel;
|
||||
return patchObject(state, { toolbarPanel: nextPanel });
|
||||
}
|
||||
|
||||
function setDetailOpenSlice(state: ChatUiState, key: string, open: boolean): ChatUiState {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import type { ConnectionDiagnosticsModelInput, ToolbarThreadRow, ToolbarViewMode
|
|||
export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel {
|
||||
const { state, snapshot } = input;
|
||||
const limit = rateLimitSummary(snapshot);
|
||||
const historyOpen = state.ui.openDetails.has("history");
|
||||
const chatActionsOpen = state.ui.openDetails.has("chat-actions");
|
||||
const statusPanelOpen = state.ui.openDetails.has("status-panel");
|
||||
const historyOpen = state.ui.toolbarPanel === "history";
|
||||
const chatActionsOpen = state.ui.toolbarPanel === "chat-actions";
|
||||
const statusPanelOpen = state.ui.toolbarPanel === "status-panel";
|
||||
return {
|
||||
newChatDisabled: input.turnBusy,
|
||||
chatActionsOpen,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export function toolbarSlotSnapshot(state: ChatState, connected: boolean): ChatP
|
|||
state.runtime.requestedApprovalsReviewer,
|
||||
state.runtime.requestedModel,
|
||||
state.runtime.requestedReasoningEffort,
|
||||
openDetailsSignature(state.ui.openDetails),
|
||||
state.ui.toolbarPanel,
|
||||
state.threadList.threadsLoaded,
|
||||
threadListSignature(state.threadList.listedThreads),
|
||||
modelsSignature(state.connection.availableModels),
|
||||
|
|
@ -131,10 +131,6 @@ function stableSignature(value: unknown): string {
|
|||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function openDetailsSignature(openDetails: ReadonlySet<string>): string {
|
||||
return [...openDetails].sort().join("\n");
|
||||
}
|
||||
|
||||
function messageStreamOpenDetailsSignature(openDetails: ReadonlySet<string>): string {
|
||||
return [...openDetails].filter(isMessageStreamOpenDetailKey).sort().join("\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,11 +94,7 @@ export class ToolbarPanelController {
|
|||
}
|
||||
|
||||
private hasOpenPanel(): boolean {
|
||||
return (
|
||||
this.state.ui.openDetails.has("history") ||
|
||||
this.state.ui.openDetails.has("chat-actions") ||
|
||||
this.state.ui.openDetails.has("status-panel")
|
||||
);
|
||||
return this.state.ui.toolbarPanel !== null;
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
|
|
|
|||
|
|
@ -163,16 +163,16 @@ describe("chatReducer", () => {
|
|||
it("clones map and set backed state when updating turn diffs and open panels", () => {
|
||||
const state = createChatState();
|
||||
state.transcript.turnDiffs = new Map([["turn", "old"]]);
|
||||
state.ui.openDetails = new Set(["status-panel"]);
|
||||
state.ui.toolbarPanel = "status-panel";
|
||||
|
||||
const withDiff = chatReducer(state, { type: "transcript/turn-diff-updated", turnId: "turn", diff: "new" });
|
||||
const withHistoryPanel = chatReducer(withDiff, { type: "ui/panel-set", panel: "history" });
|
||||
|
||||
expect(withDiff.transcript.turnDiffs).not.toBe(state.transcript.turnDiffs);
|
||||
expect(withDiff.transcript.turnDiffs.get("turn")).toBe("new");
|
||||
expect(withHistoryPanel.ui.openDetails).not.toBe(withDiff.ui.openDetails);
|
||||
expect(withHistoryPanel.ui.openDetails.has("history")).toBe(true);
|
||||
expect(withHistoryPanel.ui.openDetails.has("status-panel")).toBe(false);
|
||||
expect(withHistoryPanel.ui).not.toBe(withDiff.ui);
|
||||
expect(withHistoryPanel.ui.toolbarPanel).toBe("history");
|
||||
expect(withHistoryPanel.ui.openDetails).toBe(withDiff.ui.openDetails);
|
||||
});
|
||||
|
||||
it("returns the same state reference for no-op actions", () => {
|
||||
|
|
@ -352,15 +352,13 @@ describe("chatReducer", () => {
|
|||
let state = createChatState();
|
||||
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "history" });
|
||||
expect(state.ui.openDetails.has("history")).toBe(true);
|
||||
expect(state.ui.toolbarPanel).toBe("history");
|
||||
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "chat-actions" });
|
||||
expect(state.ui.openDetails.has("history")).toBe(false);
|
||||
expect(state.ui.openDetails.has("chat-actions")).toBe(true);
|
||||
expect(state.ui.toolbarPanel).toBe("chat-actions");
|
||||
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "status-panel" });
|
||||
expect(state.ui.openDetails.has("chat-actions")).toBe(false);
|
||||
expect(state.ui.openDetails.has("status-panel")).toBe(true);
|
||||
expect(state.ui.toolbarPanel).toBe("status-panel");
|
||||
});
|
||||
|
||||
it("updates remembered details and user input drafts through typed UI actions", () => {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("ToolbarPanelController", () => {
|
|||
scheduleRender,
|
||||
});
|
||||
controller.toggleHistory();
|
||||
expect(stateStore.getState().ui.openDetails.has("history")).toBe(true);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBe("history");
|
||||
|
||||
controller.closeOnOutsidePointer({
|
||||
target: document.createElement("button"),
|
||||
|
|
@ -45,7 +45,7 @@ describe("ToolbarPanelController", () => {
|
|||
renameEditing: false,
|
||||
});
|
||||
|
||||
expect(stateStore.getState().ui.openDetails.size).toBe(0);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(scheduleRender).toHaveBeenCalledWith({ forceSlots: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe("createChatReconnectActions", () => {
|
|||
|
||||
await controller.reconnectPanel();
|
||||
|
||||
expect(stateStore.getState().ui.openDetails.size).toBe(0);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(host.invalidateConnectionWork).toHaveBeenCalledOnce();
|
||||
expect(host.invalidateResumeWork).toHaveBeenCalledOnce();
|
||||
expect(host.clearDeferredDiagnostics).toHaveBeenCalledOnce();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ describe("ThreadSelectionController", () => {
|
|||
|
||||
await controller.selectThreadFromToolbar("thread");
|
||||
|
||||
expect(stateStore.getState().ui.openDetails.size).toBe(0);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(host.closeForThreadSelection).toHaveBeenCalledOnce();
|
||||
expect(host.resumeThread).toHaveBeenCalledWith("thread");
|
||||
});
|
||||
|
|
@ -87,7 +87,7 @@ describe("ThreadSelectionController", () => {
|
|||
|
||||
await controller.selectThreadFromToolbar("other");
|
||||
|
||||
expect(stateStore.getState().ui.openDetails.has("history")).toBe(true);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBe("history");
|
||||
expect(host.addSystemMessage).not.toHaveBeenCalled();
|
||||
expect(host.closeForThreadSelection).not.toHaveBeenCalled();
|
||||
expect(host.resumeThread).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ describe("createPlanImplementationActions", () => {
|
|||
|
||||
expect(ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(stateStore.getState().runtime.selectedCollaborationMode).toBe("default");
|
||||
expect(stateStore.getState().ui.openDetails.has("status-panel")).toBe(false);
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(sendTurnText).toHaveBeenCalledWith("Please implement this plan.");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe("chat view model", () => {
|
|||
state.activeThread.id = "thread-1";
|
||||
state.threadList.listedThreads = [threadFixture("thread-1", "Active"), threadFixture("thread-2", "Other")];
|
||||
state.turn.lifecycle = { kind: "running", turnId: "turn" };
|
||||
state.ui.openDetails = new Set(["history"]);
|
||||
state.ui.toolbarPanel = "history";
|
||||
state.connection.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
|
||||
state.connection.appServerDiagnostics = createAppServerDiagnostics();
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ describe("chat view snapshots", () => {
|
|||
const state = createChatState();
|
||||
const base = messagesSlotSnapshot(state, "");
|
||||
|
||||
state.ui.openDetails = new Set(["history", "chat-actions", "status-panel", "goal:editor"]);
|
||||
state.ui.toolbarPanel = "history";
|
||||
state.ui.openDetails = new Set(["goal:editor"]);
|
||||
expect(messagesSlotSnapshot(state, "")).toBe(base);
|
||||
|
||||
state.ui.openDetails = new Set(["message:item:expanded"]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue