mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Remove toolbar runtime controls
This commit is contained in:
parent
75ee797ed9
commit
fa0f3c30c5
16 changed files with 43 additions and 289 deletions
|
|
@ -78,7 +78,6 @@ export interface ChatState {
|
|||
historyCursor: string | null;
|
||||
loadingHistory: boolean;
|
||||
composerDraft: string;
|
||||
runtimePicker: "model" | "effort" | null;
|
||||
availableModels: readonly Model[];
|
||||
availableSkills: readonly SkillMetadata[];
|
||||
reportedLogs: ReadonlySet<string>;
|
||||
|
|
@ -159,7 +158,7 @@ export type ChatAction =
|
|||
}
|
||||
| {
|
||||
type: "ui/panel-set";
|
||||
panel: "history" | "status-panel" | "model" | "effort" | null;
|
||||
panel: "history" | "status-panel" | null;
|
||||
toggle?: boolean;
|
||||
}
|
||||
| { type: "ui/messages-pinned-set"; pinned: boolean }
|
||||
|
|
@ -562,7 +561,6 @@ export function clearConnectionScopedState(state: ChatState): ChatState {
|
|||
availableModels: [],
|
||||
availableSkills: [],
|
||||
appServerDiagnostics: createAppServerDiagnostics(),
|
||||
runtimePicker: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -635,9 +633,8 @@ function initialComposerState(): Pick<
|
|||
};
|
||||
}
|
||||
|
||||
function initialPanelUiState(): Pick<ChatState, "runtimePicker" | "openDetails"> {
|
||||
function initialPanelUiState(): Pick<ChatState, "openDetails"> {
|
||||
return {
|
||||
runtimePicker: null,
|
||||
openDetails: new Set(),
|
||||
};
|
||||
}
|
||||
|
|
@ -723,19 +720,14 @@ function updatedTurnDiffs(turnDiffs: ReadonlyMap<string, string>, turnId: string
|
|||
return next;
|
||||
}
|
||||
|
||||
function setPanelState(state: ChatState, panel: "history" | "status-panel" | "model" | "effort" | null, toggle: boolean): ChatState {
|
||||
const currentPanel = state.openDetails.has("history")
|
||||
? "history"
|
||||
: state.openDetails.has("status-panel")
|
||||
? "status-panel"
|
||||
: state.runtimePicker;
|
||||
function setPanelState(state: ChatState, panel: "history" | "status-panel" | null, toggle: boolean): ChatState {
|
||||
const currentPanel = state.openDetails.has("history") ? "history" : state.openDetails.has("status-panel") ? "status-panel" : null;
|
||||
const nextPanel = toggle && currentPanel === panel ? null : panel;
|
||||
return patchChatState(state, {
|
||||
openDetails:
|
||||
nextPanel === "history" || nextPanel === "status-panel"
|
||||
? new Set([nextPanel])
|
||||
: new Set([...state.openDetails].filter((key) => key !== "history" && key !== "status-panel")),
|
||||
runtimePicker: nextPanel === "model" || nextPanel === "effort" ? nextPanel : null,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/view";
|
||||
|
||||
export type ToolbarPanelKind = "history" | "status" | "runtime";
|
||||
|
||||
export interface ToolbarChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
export type ToolbarPanelKind = "history" | "status";
|
||||
|
||||
export interface ToolbarThreadRow {
|
||||
title: string;
|
||||
|
|
@ -38,16 +30,10 @@ export interface ToolbarViewModel {
|
|||
newChatDisabled: boolean;
|
||||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
runtimeOpen: boolean;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: EffectiveConfigSection[];
|
||||
openPanel: ToolbarPanelKind | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
modelChoices: ToolbarChoice[];
|
||||
effortChoices: ToolbarChoice[];
|
||||
connectLabel: string;
|
||||
diagnostics: ToolbarDiagnosticSection[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,6 @@ export class ToolbarPanelController {
|
|||
this.host.scheduleRender();
|
||||
}
|
||||
|
||||
toggleRuntime(picker: NonNullable<ChatState["runtimePicker"]>): void {
|
||||
this.dispatch({ type: "ui/panel-set", panel: picker, toggle: true });
|
||||
this.host.scheduleRender();
|
||||
}
|
||||
|
||||
closeForThreadSelection(): void {
|
||||
this.archiveConfirmThreadId = null;
|
||||
}
|
||||
|
|
@ -90,7 +85,7 @@ export class ToolbarPanelController {
|
|||
}
|
||||
|
||||
private hasOpenPanel(): boolean {
|
||||
return this.state.openDetails.has("history") || this.state.openDetails.has("status-panel") || this.state.runtimePicker !== null;
|
||||
return this.state.openDetails.has("history") || this.state.openDetails.has("status-panel");
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import type { ButtonHTMLAttributes, ComponentChild as UiNode, Ref } from "preact
|
|||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import type { ComposerSuggestion } from "../composer/suggestions";
|
||||
import type { ComposerMetaViewModel } from "../view-model";
|
||||
import type { ToolbarChoice } from "../toolbar-model";
|
||||
import type { ComposerMetaViewModel, RuntimeChoice } from "../view-model";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
import { renderUiRoot } from "../../../shared/ui/ui-root";
|
||||
import { syncTextareaHeight } from "../../../shared/ui/textarea-autogrow";
|
||||
|
|
@ -421,7 +420,7 @@ function ComposerMetaChoicePopover({
|
|||
onClose,
|
||||
}: {
|
||||
kind: ComposerMetaPickerKind;
|
||||
choices: ToolbarChoice[];
|
||||
choices: RuntimeChoice[];
|
||||
left: number;
|
||||
onClose: () => void;
|
||||
}): UiNode {
|
||||
|
|
@ -437,7 +436,7 @@ function ComposerMetaChoicePopover({
|
|||
);
|
||||
}
|
||||
|
||||
function ComposerMetaChoice({ choice, onClose }: { choice: ToolbarChoice; onClose: () => void }): UiNode {
|
||||
function ComposerMetaChoice({ choice, onClose }: { choice: RuntimeChoice; onClose: () => void }): UiNode {
|
||||
const onSelect = () => {
|
||||
if (choice.disabled) return;
|
||||
choice.onClick();
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ type ButtonProps = ButtonHTMLAttributes & {
|
|||
export interface ToolbarActions {
|
||||
startNewThread: () => void;
|
||||
toggleHistory: () => void;
|
||||
toggleAutoReview: () => void;
|
||||
toggleStatusPanel: () => void;
|
||||
togglePlan: () => void;
|
||||
toggleFast: () => void;
|
||||
toggleRuntime: () => void;
|
||||
connect: () => void;
|
||||
refreshStatus: () => void;
|
||||
resumeThread: (threadId: string) => void;
|
||||
|
|
@ -53,7 +49,6 @@ function Toolbar({ model, actions }: { model: ToolbarViewModel; actions: Toolbar
|
|||
disabled={model.newChatDisabled}
|
||||
onClick={actions.startNewThread}
|
||||
/>
|
||||
<RuntimeButtons model={model} actions={actions} />
|
||||
<StatusButton model={model} actions={actions} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -82,60 +77,6 @@ function ToolbarIconButton({
|
|||
);
|
||||
}
|
||||
|
||||
function RuntimeButtons({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
return (
|
||||
<>
|
||||
<RuntimeIcon
|
||||
icon="list-todo"
|
||||
label="Toggle plan mode"
|
||||
className="codex-panel__plan-toggle"
|
||||
active={model.planActive}
|
||||
onClick={actions.togglePlan}
|
||||
/>
|
||||
<RuntimeIcon
|
||||
icon="shield"
|
||||
label="Toggle auto-review"
|
||||
className="codex-panel__auto-review-toggle"
|
||||
active={model.autoReviewActive}
|
||||
onClick={actions.toggleAutoReview}
|
||||
/>
|
||||
<RuntimeIcon icon="zap" label="Toggle fast mode" active={model.fastActive} onClick={actions.toggleFast} />
|
||||
<ToolbarIconButton
|
||||
icon="bot"
|
||||
label="Change model and reasoning effort"
|
||||
className={["codex-panel__runtime-model", model.runtimeOpen ? "is-active" : ""].filter(Boolean).join(" ")}
|
||||
aria-label="Change model and reasoning effort"
|
||||
aria-expanded={model.runtimeOpen ? "true" : "false"}
|
||||
onClick={actions.toggleRuntime}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RuntimeIcon({
|
||||
icon,
|
||||
label,
|
||||
className,
|
||||
active,
|
||||
onClick,
|
||||
}: {
|
||||
icon: string;
|
||||
label: string;
|
||||
className?: string;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
}): UiNode {
|
||||
return (
|
||||
<ToolbarIconButton
|
||||
icon={icon}
|
||||
label={label}
|
||||
className={`codex-panel__runtime-icon ${className ?? ""} ${active ? "is-active" : ""}`}
|
||||
aria-pressed={active ? "true" : "false"}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusButton({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
return (
|
||||
<ToolbarIconButton
|
||||
|
|
@ -153,7 +94,6 @@ function ToolbarPanel({ model, actions }: { model: ToolbarViewModel; actions: To
|
|||
return (
|
||||
<div className={`codex-panel__toolbar-panel codex-panel__toolbar-panel--${model.openPanel}`}>
|
||||
{model.openPanel === "history" ? <ThreadList threads={model.threads} actions={actions} /> : null}
|
||||
{model.openPanel === "runtime" ? <RuntimePicker model={model} /> : null}
|
||||
{model.openPanel === "status" ? <StatusPanel model={model} actions={actions} /> : null}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -267,39 +207,6 @@ function FragmentedConfigSection({ section }: { section: EffectiveConfigSection
|
|||
);
|
||||
}
|
||||
|
||||
function RuntimePicker({ model }: { model: ToolbarViewModel }): UiNode {
|
||||
return (
|
||||
<div className="codex-panel__runtime-picker" role="listbox">
|
||||
<div className="codex-panel__runtime-picker-label">Reasoning effort</div>
|
||||
{model.effortChoices.map((choice) => (
|
||||
<ToolbarPanelItem
|
||||
key={`effort:${choice.label}`}
|
||||
label={choice.label}
|
||||
selected={choice.selected}
|
||||
disabled={choice.disabled}
|
||||
meta={choice.meta}
|
||||
onClick={choice.onClick}
|
||||
className="codex-panel__runtime-choice"
|
||||
role="option"
|
||||
/>
|
||||
))}
|
||||
<div className="codex-panel__runtime-picker-label">Model</div>
|
||||
{model.modelChoices.map((choice) => (
|
||||
<ToolbarPanelItem
|
||||
key={`model:${choice.label}`}
|
||||
label={choice.label}
|
||||
selected={choice.selected}
|
||||
disabled={choice.disabled}
|
||||
meta={choice.meta}
|
||||
onClick={choice.onClick}
|
||||
className="codex-panel__runtime-choice"
|
||||
role="option"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThreadList({ threads, actions }: { threads: ToolbarThreadRow[]; actions: ToolbarActions }): UiNode {
|
||||
if (threads.length === 0) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { codexPanelDisplayTitle, explicitThreadName, getThreadTitle } from "../.
|
|||
import { connectionDiagnosticSections } from "./diagnostics";
|
||||
import type { ChatState } from "./chat-state";
|
||||
import { statusValue, usageLimitStatusLines } from "./status-lines";
|
||||
import type { ToolbarChoice, ToolbarThreadRow, ToolbarViewModel } from "./toolbar-model";
|
||||
import type { ToolbarThreadRow, ToolbarViewModel } from "./toolbar-model";
|
||||
|
||||
export interface RuntimeSnapshotInput {
|
||||
state: ChatState;
|
||||
|
|
@ -33,8 +33,16 @@ export interface ComposerMetaViewModel {
|
|||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
modelChoices?: ToolbarChoice[];
|
||||
effortChoices?: ToolbarChoice[];
|
||||
modelChoices?: RuntimeChoice[];
|
||||
effortChoices?: RuntimeChoice[];
|
||||
}
|
||||
|
||||
export interface RuntimeChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterCellViewModel {
|
||||
|
|
@ -56,8 +64,6 @@ export interface ToolbarViewModelInput {
|
|||
configuredCommand: string;
|
||||
archiveConfirmThreadId: string | null;
|
||||
archiveExportEnabled: boolean;
|
||||
modelChoices: ToolbarChoice[];
|
||||
effortChoices: ToolbarChoice[];
|
||||
renameState: (threadId: string) => ToolbarThreadRow["rename"];
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +73,7 @@ export interface ConnectionDiagnosticsModelInput {
|
|||
configuredCommand: string;
|
||||
}
|
||||
|
||||
export interface RuntimeToolbarChoicesInput {
|
||||
export interface RuntimeComposerChoicesInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
setRequestedModel: (model: string | null) => void;
|
||||
|
|
@ -103,11 +109,14 @@ export function runtimeSnapshotForChatState({ state }: RuntimeSnapshotInput): Ru
|
|||
};
|
||||
}
|
||||
|
||||
export function runtimeToolbarChoices(input: RuntimeToolbarChoicesInput): Pick<ToolbarViewModel, "modelChoices" | "effortChoices"> {
|
||||
export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
||||
modelChoices: RuntimeChoice[];
|
||||
effortChoices: RuntimeChoice[];
|
||||
} {
|
||||
const config = readRuntimeConfig(input.state.effectiveConfig);
|
||||
const activeModel = currentModel(input.snapshot, config);
|
||||
const models = sortedAvailableModels(input.state.availableModels);
|
||||
const modelChoices: ToolbarChoice[] = models.slice(0, 12).map((model) => ({
|
||||
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => {
|
||||
|
|
@ -123,7 +132,7 @@ export function runtimeToolbarChoices(input: RuntimeToolbarChoicesInput): Pick<T
|
|||
}
|
||||
|
||||
const activeEffort = currentReasoningEffort(input.snapshot, config);
|
||||
const effortChoices: ToolbarChoice[] = supportedReasoningEfforts(input.snapshot).map((effort) => ({
|
||||
const effortChoices: RuntimeChoice[] = supportedReasoningEfforts(input.snapshot).map((effort) => ({
|
||||
label: effort,
|
||||
selected: activeEffort === effort,
|
||||
onClick: () => {
|
||||
|
|
@ -225,22 +234,16 @@ function onOffLabel(active: boolean): string {
|
|||
|
||||
export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel {
|
||||
const { state, snapshot } = input;
|
||||
const config = readRuntimeConfig(state.effectiveConfig);
|
||||
const limit = rateLimitSummary(snapshot);
|
||||
const historyOpen = state.openDetails.has("history");
|
||||
const statusPanelOpen = state.openDetails.has("status-panel");
|
||||
const runtimeOpen = state.runtimePicker !== null;
|
||||
return {
|
||||
newChatDisabled: input.turnBusy,
|
||||
historyOpen,
|
||||
statusPanelOpen,
|
||||
runtimeOpen,
|
||||
planActive: state.selectedCollaborationMode === "plan",
|
||||
autoReviewActive: autoReviewActive(snapshot, config),
|
||||
fastActive: fastModeActive(snapshot, config),
|
||||
rateLimit: limit,
|
||||
configSections: effectiveConfigSections(snapshot, input.vaultPath),
|
||||
openPanel: historyOpen ? "history" : runtimeOpen ? "runtime" : statusPanelOpen ? "status" : null,
|
||||
openPanel: historyOpen ? "history" : statusPanelOpen ? "status" : null,
|
||||
threads: toolbarThreadRows({
|
||||
threads: state.listedThreads,
|
||||
activeThreadId: state.activeThreadId,
|
||||
|
|
@ -249,8 +252,6 @@ export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel
|
|||
archiveExportEnabled: input.archiveExportEnabled,
|
||||
renameState: input.renameState,
|
||||
}),
|
||||
modelChoices: input.modelChoices,
|
||||
effortChoices: input.effortChoices,
|
||||
connectLabel: input.connected ? "Reconnect" : "Connect",
|
||||
diagnostics: connectionDiagnosticsModel({
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export function toolbarSlotSnapshot(state: ChatState, connected: boolean): ChatP
|
|||
state.requestedApprovalsReviewer,
|
||||
state.requestedModel,
|
||||
state.requestedReasoningEffort,
|
||||
state.runtimePicker,
|
||||
openDetailsSignature(state.openDetails),
|
||||
state.threadsLoaded,
|
||||
threadListSignature(state.listedThreads),
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import {
|
|||
composerPlaceholder as buildComposerPlaceholder,
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
runtimeToolbarChoices,
|
||||
runtimeComposerChoices,
|
||||
runtimeSnapshotForChatState,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
toolbarViewModel as buildToolbarViewModel,
|
||||
|
|
@ -558,7 +558,7 @@ export class CodexChatView extends ItemView {
|
|||
private composerMetaViewModel() {
|
||||
return {
|
||||
...buildComposerMetaViewModel(this.state, this.runtimeSnapshot()),
|
||||
...runtimeToolbarChoices({
|
||||
...runtimeComposerChoices({
|
||||
state: this.state,
|
||||
snapshot: this.runtimeSnapshot(),
|
||||
setRequestedModel: (model) => void this.setRequestedModelFromUi(model),
|
||||
|
|
@ -587,15 +587,9 @@ export class CodexChatView extends ItemView {
|
|||
toggleHistory: () => {
|
||||
this.toolbarPanels.toggleHistory();
|
||||
},
|
||||
toggleAutoReview: () => void this.runtimeSettings.toggleAutoReview(),
|
||||
toggleStatusPanel: () => {
|
||||
this.toolbarPanels.toggleStatus();
|
||||
},
|
||||
togglePlan: () => void this.runtimeSettings.toggleCollaborationMode(),
|
||||
toggleFast: () => void this.runtimeSettings.toggleFastMode(),
|
||||
toggleRuntime: () => {
|
||||
this.toolbarPanels.toggleRuntime("model");
|
||||
},
|
||||
connect: () => void this.reconnectActions.reconnectFromToolbar(),
|
||||
refreshStatus: () => void this.refreshStatusPanel(),
|
||||
resumeThread: (threadId) => void this.threadSelection.selectThreadFromToolbar(threadId),
|
||||
|
|
@ -627,12 +621,6 @@ export class CodexChatView extends ItemView {
|
|||
configuredCommand: this.plugin.settings.codexPath,
|
||||
archiveConfirmThreadId: this.toolbarPanels.archiveConfirmId(),
|
||||
archiveExportEnabled: this.plugin.settings.archiveExportEnabled,
|
||||
...runtimeToolbarChoices({
|
||||
state: this.state,
|
||||
snapshot: this.runtimeSnapshot(),
|
||||
setRequestedModel: (model) => void this.setRequestedModelFromUi(model),
|
||||
setRequestedReasoningEffort: (effort) => void this.setRequestedReasoningEffortFromUi(effort),
|
||||
}),
|
||||
renameState: (threadId) => this.threadRename.editState(threadId),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,16 +16,6 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.codex-panel__runtime-icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.codex-panel__runtime-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nav-item-margin-bottom, var(--codex-panel-panel-gap));
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -76,10 +66,3 @@
|
|||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.codex-panel__runtime-picker-label {
|
||||
padding: var(--size-4-1) var(--size-4-2) var(--size-2-1);
|
||||
color: var(--text-faint);
|
||||
font-size: var(--codex-panel-toolbar-meta-size);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,13 +290,11 @@ describe("chatReducer", () => {
|
|||
let state = createChatState();
|
||||
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "history" });
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "model" });
|
||||
expect(state.openDetails.size).toBe(0);
|
||||
expect(state.runtimePicker).toBe("model");
|
||||
expect(state.openDetails.has("history")).toBe(true);
|
||||
|
||||
state = chatReducer(state, { type: "ui/panel-set", panel: "status-panel" });
|
||||
expect(state.openDetails.has("history")).toBe(false);
|
||||
expect(state.openDetails.has("status-panel")).toBe(true);
|
||||
expect(state.runtimePicker).toBeNull();
|
||||
});
|
||||
|
||||
it("updates remembered details and user input drafts through typed UI actions", () => {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ describe("ChatConnectionController", () => {
|
|||
threadsLoaded: false,
|
||||
availableModels: [],
|
||||
availableSkills: [],
|
||||
runtimePicker: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -70,13 +70,13 @@ describe("PlanImplementationController", () => {
|
|||
const { controller, ensureConnected, sendTurnText, stateStore } = createController();
|
||||
const plan = planItem("plan");
|
||||
resumeThread(stateStore, [plan]);
|
||||
stateStore.dispatch({ type: "ui/panel-set", panel: "model" });
|
||||
stateStore.dispatch({ type: "ui/panel-set", panel: "status-panel" });
|
||||
|
||||
await controller.implement(plan);
|
||||
|
||||
expect(ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(stateStore.getState().selectedCollaborationMode).toBe("default");
|
||||
expect(stateStore.getState().runtimePicker).toBeNull();
|
||||
expect(stateStore.getState().openDetails.has("status-panel")).toBe(false);
|
||||
expect(sendTurnText).toHaveBeenCalledWith("Please implement this plan.");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ describe("toolbar renderer decisions", () => {
|
|||
const parent = document.createElement("div");
|
||||
const startNewThread = vi.fn();
|
||||
const toggleHistory = vi.fn();
|
||||
const toggleAutoReview = vi.fn();
|
||||
const baseModel = toolbarModel();
|
||||
|
||||
renderToolbar(parent, baseModel, toolbarActions({ startNewThread, toggleHistory, toggleAutoReview }));
|
||||
renderToolbar(parent, baseModel, toolbarActions({ startNewThread, toggleHistory }));
|
||||
|
||||
const navHeader = parent.querySelector(".codex-panel__toolbar-primary");
|
||||
expect(navHeader?.classList.contains("nav-header")).toBe(true);
|
||||
|
|
@ -32,12 +31,11 @@ describe("toolbar renderer decisions", () => {
|
|||
expect([...expectPresent(navButtons).children].map((button) => button.getAttribute("aria-label"))).toEqual([
|
||||
"Show thread list",
|
||||
"Start new chat",
|
||||
"Toggle plan mode",
|
||||
"Toggle auto-review",
|
||||
"Toggle fast mode",
|
||||
"Change model and reasoning effort",
|
||||
"Show panel menu",
|
||||
]);
|
||||
expect(parent.querySelector(".codex-panel__plan-toggle")).toBeNull();
|
||||
expect(parent.querySelector(".codex-panel__auto-review-toggle")).toBeNull();
|
||||
expect(parent.querySelector(".codex-panel__runtime-model")).toBeNull();
|
||||
const newChatButton = parent.querySelector<HTMLButtonElement>(".codex-panel__new-chat");
|
||||
expect(newChatButton?.getAttribute("aria-label")).toBe("Start new chat");
|
||||
expect(newChatButton?.dataset["icon"]).toBe("messages-square");
|
||||
|
|
@ -59,27 +57,6 @@ describe("toolbar renderer decisions", () => {
|
|||
expect(historyButton?.classList.contains("clickable-icon")).toBe(true);
|
||||
historyButton?.click();
|
||||
expect(toggleHistory).toHaveBeenCalled();
|
||||
const planButton = parent.querySelector<HTMLButtonElement>(".codex-panel__plan-toggle");
|
||||
expect(planButton?.getAttribute("aria-label")).toBe("Toggle plan mode");
|
||||
expect(planButton?.dataset["icon"]).toBe("list-todo");
|
||||
expect(planButton?.getAttribute("aria-pressed")).toBe("false");
|
||||
expect(planButton?.classList.contains("codex-panel__runtime-icon")).toBe(true);
|
||||
const autoReviewButton = parent.querySelector<HTMLButtonElement>(".codex-panel__auto-review-toggle");
|
||||
expect(autoReviewButton?.getAttribute("aria-label")).toBe("Toggle auto-review");
|
||||
expect(autoReviewButton?.getAttribute("aria-pressed")).toBe("false");
|
||||
expect(autoReviewButton?.classList.contains("codex-panel__runtime-icon")).toBe(true);
|
||||
expect(autoReviewButton?.classList.contains("nav-action-button")).toBe(true);
|
||||
expect(autoReviewButton?.classList.contains("codex-panel-ui__toolbar-action")).toBe(true);
|
||||
expect(autoReviewButton?.classList.contains("codex-panel-ui__toolbar-control")).toBe(false);
|
||||
expect(autoReviewButton?.classList.contains("clickable-icon")).toBe(true);
|
||||
autoReviewButton?.click();
|
||||
expect(toggleAutoReview).toHaveBeenCalled();
|
||||
|
||||
parent.empty();
|
||||
renderToolbar(parent, toolbarModel({ autoReviewActive: true }), toolbarActions());
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.getAttribute("aria-label")).toBe("Show panel menu");
|
||||
expect(parent.querySelector(".codex-panel__auto-review-toggle")?.getAttribute("aria-pressed")).toBe("true");
|
||||
|
||||
parent.empty();
|
||||
renderToolbar(parent, toolbarModel({ newChatDisabled: true }), toolbarActions());
|
||||
expect(parent.querySelector<HTMLButtonElement>(".codex-panel__new-chat")?.disabled).toBe(true);
|
||||
|
|
@ -90,36 +67,6 @@ describe("toolbar renderer decisions", () => {
|
|||
expect(parent.querySelector(".codex-panel__history-toggle")?.classList.contains("is-active")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.getAttribute("aria-label")).toBe("Hide panel menu");
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.classList.contains("is-active")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__runtime-model")?.getAttribute("aria-label")).toBe("Change model and reasoning effort");
|
||||
expect(parent.querySelector<HTMLElement>(".codex-panel__runtime-model")?.dataset["icon"]).toBe("bot");
|
||||
expect(parent.querySelector(".codex-panel__runtime-model")?.classList.contains("is-active")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__runtime-model")?.classList.contains("nav-action-button")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps frequently changed effort choices first inside the runtime menu", () => {
|
||||
const parent = document.createElement("div");
|
||||
|
||||
renderToolbar(
|
||||
parent,
|
||||
toolbarModel({
|
||||
modelChoices: [{ label: "gpt-5.5", selected: true, onClick: vi.fn() }],
|
||||
effortChoices: [{ label: "high", selected: true, onClick: vi.fn() }],
|
||||
}),
|
||||
toolbarActions(),
|
||||
);
|
||||
|
||||
expect([...parent.querySelectorAll(".codex-panel__runtime-picker-label")].map((label) => label.textContent)).toEqual([
|
||||
"Reasoning effort",
|
||||
"Model",
|
||||
]);
|
||||
expect([...parent.querySelectorAll(".codex-panel__runtime-choice")].map((choice) => choice.textContent)).toEqual(["high", "gpt-5.5"]);
|
||||
for (const choice of parent.querySelectorAll(".codex-panel__runtime-choice")) {
|
||||
expect(choice.getAttribute("role")).toBe("option");
|
||||
expect(choice.getAttribute("aria-selected")).toBe("true");
|
||||
expect(choice.querySelector<HTMLElement>(".codex-panel__toolbar-panel-check")).toBeNull();
|
||||
expect(choice.classList.contains("selected")).toBe(false);
|
||||
expect(choice.classList.contains("is-selected")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps context out of the toolbar and Codex limits inside the status menu", () => {
|
||||
|
|
@ -381,16 +328,10 @@ function toolbarModel(overrides: Partial<ToolbarViewModel> = {}): ToolbarViewMod
|
|||
newChatDisabled: false,
|
||||
historyOpen: false,
|
||||
statusPanelOpen: false,
|
||||
runtimeOpen: true,
|
||||
planActive: false,
|
||||
autoReviewActive: false,
|
||||
fastActive: false,
|
||||
rateLimit: null,
|
||||
configSections: [],
|
||||
openPanel: "runtime",
|
||||
openPanel: null,
|
||||
threads: [{ title: "Thread", threadId: "thread", selected: true, disabled: false, canArchive: true, rename: null }],
|
||||
modelChoices: [{ label: "Default", selected: true, onClick: vi.fn() }],
|
||||
effortChoices: [{ label: "Default", selected: true, onClick: vi.fn() }],
|
||||
connectLabel: "Reconnect",
|
||||
diagnostics: [{ title: "Process", rows: [{ label: "Codex App Server", value: "codex-cli/test" }] }],
|
||||
...overrides,
|
||||
|
|
@ -401,11 +342,7 @@ function toolbarActions(overrides: Partial<Parameters<typeof renderToolbar>[2]>
|
|||
return {
|
||||
toggleHistory: vi.fn(),
|
||||
startNewThread: vi.fn(),
|
||||
toggleAutoReview: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
togglePlan: vi.fn(),
|
||||
toggleFast: vi.fn(),
|
||||
toggleRuntime: vi.fn(),
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
resumeThread: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ describe("ChatPanelShell", () => {
|
|||
|
||||
await act(async () => {
|
||||
store.dispatch({ type: "status/set", status: "Working" });
|
||||
store.dispatch({ type: "ui/panel-set", panel: "model" });
|
||||
store.dispatch({ type: "ui/panel-set", panel: "status-panel" });
|
||||
store.dispatch({ type: "system/message-added", item: { id: "system-1", kind: "system", role: "system", text: "Model set." } });
|
||||
await settleShellEffects();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
composerMetaViewModel,
|
||||
composerPlaceholder,
|
||||
effortStatusLines,
|
||||
runtimeToolbarChoices,
|
||||
runtimeComposerChoices,
|
||||
modelStatusLines,
|
||||
runtimeSnapshotForChatState,
|
||||
statusSummaryLines,
|
||||
|
|
@ -38,8 +38,6 @@ describe("chat view model", () => {
|
|||
configuredCommand: "codex",
|
||||
archiveConfirmThreadId: "thread-2",
|
||||
archiveExportEnabled: true,
|
||||
modelChoices: [{ label: "gpt-5.5", selected: true, onClick: () => undefined }],
|
||||
effortChoices: [{ label: "high", selected: true, onClick: () => undefined }],
|
||||
renameState: (threadId) => (threadId === "thread-1" ? { draft: "Active", generating: false } : null),
|
||||
});
|
||||
|
||||
|
|
@ -49,32 +47,6 @@ describe("chat view model", () => {
|
|||
{ threadId: "thread-1", title: "Active", selected: true, disabled: false, rename: { draft: "Active" } },
|
||||
{ threadId: "thread-2", title: "Other", selected: false, disabled: true, archiveConfirm: { active: true } },
|
||||
]);
|
||||
expect(model.modelChoices).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("marks fast active for the catalog Fast service tier id", () => {
|
||||
const state = createChatState();
|
||||
state.activeModel = "gpt-5.5";
|
||||
state.activeServiceTier = "priority";
|
||||
state.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
|
||||
state.availableModels = [modelFixture("gpt-5.5", "priority")];
|
||||
|
||||
const model = toolbarViewModel({
|
||||
state,
|
||||
snapshot: runtimeSnapshotForChatState({ state }),
|
||||
connected: true,
|
||||
turnBusy: false,
|
||||
vaultPath: "/vault",
|
||||
configuredCommand: "codex",
|
||||
archiveConfirmThreadId: null,
|
||||
archiveExportEnabled: true,
|
||||
modelChoices: [],
|
||||
effortChoices: [],
|
||||
renameState: () => null,
|
||||
});
|
||||
|
||||
expect(model.fastActive).toBe(true);
|
||||
expect(model.newChatDisabled).toBe(false);
|
||||
});
|
||||
|
||||
it("builds composer meta from context and runtime state", () => {
|
||||
|
|
@ -211,14 +183,14 @@ describe("chat view model", () => {
|
|||
expect(effortStatusLines(state, snapshot)).toContain("Supported: high");
|
||||
});
|
||||
|
||||
it("builds runtime toolbar choices from immutable chat state snapshots", () => {
|
||||
it("builds runtime composer choices from immutable chat state snapshots", () => {
|
||||
const state = createChatState();
|
||||
state.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
|
||||
state.availableModels = [modelFixture("gpt-5.5"), modelFixture("gpt-5-mini")];
|
||||
const selectedModels: (string | null)[] = [];
|
||||
const selectedEfforts: string[] = [];
|
||||
|
||||
const choices = runtimeToolbarChoices({
|
||||
const choices = runtimeComposerChoices({
|
||||
state,
|
||||
snapshot: runtimeSnapshotForChatState({ state }),
|
||||
setRequestedModel: (model) => {
|
||||
|
|
|
|||
|
|
@ -98,11 +98,9 @@ describe("chat toolbar CSS", () => {
|
|||
it("keeps chat thread row actions inset from the row edge", () => {
|
||||
const threadList = /\.codex-panel__threads \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const navRow = /\.codex-panel-ui__nav-row \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const runtimePicker = /\.codex-panel__runtime-picker \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const statusPanelItems = /\.codex-panel__status-panel-items \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
|
||||
expect(threadList).toContain("gap: var(--nav-item-margin-bottom, var(--codex-panel-panel-gap))");
|
||||
expect(runtimePicker).toContain("gap: var(--nav-item-margin-bottom, var(--codex-panel-panel-gap))");
|
||||
expect(statusPanelItems).toContain("gap: var(--nav-item-margin-bottom, var(--codex-panel-panel-gap))");
|
||||
expect(navRow).toContain("--codex-panel-nav-item-background-hover: transparent");
|
||||
expect(navRow).toContain("padding-inline-end: var(--size-4-2)");
|
||||
|
|
|
|||
Loading…
Reference in a new issue