Refresh lint boundaries and validation cleanup

This commit is contained in:
murashit 2026-06-12 17:29:04 +09:00
parent 552e53681b
commit 7ebe802e0b
38 changed files with 180 additions and 187 deletions

View file

@ -29,7 +29,7 @@ The source tree is organized by responsibility rather than by the original singl
- `src/domain/threads/` contains thread title, reference, transcript, naming, and archive export helpers shared outside the chat feature. Some helpers still read app-server turn/item types directly when they are pure protocol interpreters, but new shared UI/workspace/settings-facing behavior should prefer panel-owned models or small adapters at the app-server boundary.
- `src/styles/` contains the authored CSS modules and `order.json` concatenation order. `scripts/build-styles.mjs` concatenates them into the ignored root `styles.css`, which remains the Obsidian release asset.
Keep new code near the state or API it owns. Feature code should not import from another feature directly; move shared behavior to `src/shared/`, `src/domain/`, or `src/app-server/` when more than one feature needs it.
Keep new code near the state or API it owns. Feature-to-feature imports are allowed when the imported feature owns a concrete capability or integration point, such as thread export, thread title generation, or an Obsidian view. Do not use another feature as a generic utility bucket; move truly feature-neutral behavior to `src/shared/`, `src/domain/`, or `src/app-server/`. Lower-level modules in `src/app-server/`, `src/domain/`, and `src/shared/` must remain independent from `src/features/`; ESLint enforces that dependency direction.
Codex Panel's runtime UI is Preact-owned through the direct Preact APIs. Keep chat panel UI, the Threads view, and the selection rewrite popover in Preact components. The chat panel shell is a single Preact tree; toolbar, goal, message stream, and composer content should be composed as Preact nodes rather than mounted through HTMLElement region renderers. Use `preact/hooks` for hooks, `preact` for component types and fragments, and the shared root adapter for mounting and unmounting Preact subtrees. Imperative DOM writes are limited to explicit bridge modules or Obsidian-owned API boundaries such as `MarkdownRenderer`, TanStack Virtual measurement/lifecycle glue, diff rendering, icon rendering, `SuggestModal`, and the Obsidian `Setting`-based settings tab. ESLint enforces this boundary with allowlisted bridge files; add a new allowlist entry only when the code is genuinely bridging an external DOM API.

View file

@ -21,24 +21,9 @@ const removedChatStateEscapeHatchRestrictions = [
message: "Use a named ChatAction instead of reintroducing the generic state patch escape hatch.",
},
];
const generatedAppServerSourceImportPatterns = [
"src/generated/app-server/**",
"../generated/app-server/**",
"../../generated/app-server/**",
"../../../generated/app-server/**",
"../../../../generated/app-server/**",
"../../../../../generated/app-server/**",
"../../../../../../generated/app-server/**",
];
const generatedAppServerTestImportPatterns = [
"src/generated/app-server/**",
"../src/generated/app-server/**",
"../../src/generated/app-server/**",
"../../../src/generated/app-server/**",
"../../../../src/generated/app-server/**",
"../../../../../src/generated/app-server/**",
"../../../../../../src/generated/app-server/**",
];
const generatedAppServerSourceImportPatterns = importBoundaryPatterns("generated/app-server", "src/generated/app-server", 6);
const generatedAppServerTestImportPatterns = importBoundaryPatterns("src/generated/app-server", "src/generated/app-server", 6);
const lowerLevelFeatureImportPatterns = importBoundaryPatterns("features", "src/features", 6);
const generatedAppServerThreadImportRestrictions = [
{
selector:
@ -99,17 +84,16 @@ const pureChatModelRestrictions = [
];
const chatExternalDomBridgeFiles = [
"src/features/chat/ui/message-stream/markdown-renderer.ts",
"src/features/chat/ui/message-stream/rendered-markdown-links.ts",
"src/features/chat/ui/message-virtualizer.ts",
"src/features/chat/ui/message-stream/virtualizer.ts",
];
const chatPreactDomBridgeFiles = [
"src/features/chat/ui/goal.tsx",
"src/features/chat/ui/message-stream/text-item-actions.tsx",
"src/features/chat/ui/message-stream/text-item.tsx",
"src/features/chat/ui/message-stream/render.tsx",
"src/features/chat/ui/message-stream/tool-result.tsx",
"src/features/chat/ui/message-stream/viewport.tsx",
"src/features/chat/ui/composer.tsx",
"src/features/chat/ui/goal-banner.tsx",
"src/features/chat/ui/shell.tsx",
"src/features/chat/ui/tool-result.tsx",
"src/features/chat/turn-diff/render.tsx",
];
const chatImperativeDomBridgeFiles = [...chatExternalDomBridgeFiles, ...chatPreactDomBridgeFiles];
@ -125,6 +109,16 @@ const nonChatImperativeDomBridgeFiles = [
"src/shared/ui/ui-root.tsx",
];
const nonUiEventListenerFiles = ["src/shared/lifecycle/abortable.ts"];
const baseSourceSyntaxRestrictions = [
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...preactFormRestrictions,
];
const sourceSyntaxRestrictions = [...baseSourceSyntaxRestrictions, ...imperativeDomRestrictions];
const domBridgeSyntaxRestrictions = baseSourceSyntaxRestrictions;
const eventBridgeSyntaxRestrictions = [...baseSourceSyntaxRestrictions, ...imperativeDomWriteRestrictions];
const pureChatModelSyntaxRestrictions = [...sourceSyntaxRestrictions, ...pureChatModelRestrictions];
const codexPanelEslintPlugin = {
rules: {
"no-self-referential-initializer-callback": {
@ -179,6 +173,17 @@ const codexPanelEslintPlugin = {
},
};
function importBoundaryPatterns(relativeTarget, absoluteTarget, maxParentDepth) {
const targets = [absoluteTarget, ...Array.from({ length: maxParentDepth }, (_, index) => `${"../".repeat(index + 1)}${relativeTarget}`)];
return targets.flatMap((target) => [target, `${target}/**`]);
}
function restrictedSyntaxRule(restrictions) {
return {
"no-restricted-syntax": ["error", ...restrictions],
};
}
function isChatStateMember(node) {
if (!isMemberExpression(node)) return false;
@ -328,7 +333,7 @@ export default defineConfig([
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
"@typescript-eslint/require-await": "off",
"no-restricted-syntax": ["error", ...generatedAppServerThreadImportRestrictions],
...restrictedSyntaxRule(generatedAppServerThreadImportRestrictions),
},
},
{
@ -351,107 +356,48 @@ export default defineConfig([
{
files: ["src/**/*.{ts,tsx}"],
ignores: ["src/features/chat/**/*.{ts,tsx}", ...nonChatImperativeDomBridgeFiles, ...nonUiEventListenerFiles],
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...imperativeDomRestrictions,
...preactFormRestrictions,
],
},
rules: restrictedSyntaxRule(sourceSyntaxRestrictions),
},
{
files: ["src/features/chat/**/*.{ts,tsx}"],
ignores: chatImperativeDomBridgeFiles,
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...imperativeDomRestrictions,
...preactFormRestrictions,
],
...restrictedSyntaxRule(sourceSyntaxRestrictions),
"codex-panel/no-chat-state-direct-mutation": "error",
},
},
{
files: chatImperativeDomBridgeFiles,
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...preactFormRestrictions,
],
...restrictedSyntaxRule(domBridgeSyntaxRestrictions),
"codex-panel/no-chat-state-direct-mutation": "error",
},
},
{
files: nonChatImperativeDomBridgeFiles,
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...preactFormRestrictions,
],
},
rules: restrictedSyntaxRule(domBridgeSyntaxRestrictions),
},
{
files: nonUiEventListenerFiles,
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...imperativeDomWriteRestrictions,
...preactFormRestrictions,
],
},
rules: restrictedSyntaxRule(eventBridgeSyntaxRestrictions),
},
{
files: ["src/features/chat/state/reducer.ts", "src/features/chat/display/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": [
"error",
...removedChatStateEscapeHatchRestrictions,
...generatedAppServerThreadImportRestrictions,
...unsafeIteratorRestrictions,
...imperativeDomRestrictions,
...preactFormRestrictions,
...pureChatModelRestrictions,
],
...restrictedSyntaxRule(pureChatModelSyntaxRestrictions),
"codex-panel/no-chat-state-direct-mutation": "error",
},
},
{
files: ["src/app-server/**/*.{ts,tsx}", "src/domain/**/*.{ts,tsx}", "src/runtime/**/*.{ts,tsx}", "src/shared/**/*.{ts,tsx}"],
files: ["src/app-server/**/*.{ts,tsx}", "src/domain/**/*.{ts,tsx}", "src/shared/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: [
"src/features",
"src/features/**",
"../features",
"../features/**",
"../../features",
"../../features/**",
"../../../features",
"../../../features/**",
"../../../../features",
"../../../../features/**",
],
message:
"Lower-level modules must not import feature modules. Move shared behavior to shared, domain, runtime, or app-server.",
group: lowerLevelFeatureImportPatterns,
message: "Lower-level modules must not import feature modules. Move shared behavior to shared, domain, or app-server.",
},
],
},

View file

@ -1,6 +0,0 @@
{
"protocolAdapterFiles": [
"src/features/chat/protocol/display-items.ts",
"src/features/chat/protocol/inbound/notification-plan.ts"
]
}

View file

@ -39,7 +39,14 @@ import type { TurnSteerResponse } from "../../generated/app-server/v2/TurnSteerR
import type { UserInput } from "../../generated/app-server/v2/UserInput";
import { CLIENT_VERSION } from "../../constants";
import { StdioAppServerTransport, type AppServerTransport, type AppServerTransportHandlers } from "./transport";
import type { ClientRequestMethod, ClientRequestParams, PendingRequest, RpcError, RpcInboundMessage, RpcOutboundMessage } from "./rpc-messages";
import type {
ClientRequestMethod,
ClientRequestParams,
PendingRequest,
RpcError,
RpcInboundMessage,
RpcOutboundMessage,
} from "./rpc-messages";
import type { ServerNotification } from "../../generated/app-server/ServerNotification";
import type { ServerRequest } from "../../generated/app-server/ServerRequest";
import type { JsonValue } from "../../generated/app-server/serde_json/JsonValue";

View file

@ -12,7 +12,7 @@ export const DIAGNOSTIC_PROBE_METHODS = [
export type DiagnosticProbeMethod = (typeof DIAGNOSTIC_PROBE_METHODS)[number];
type DiagnosticProbeStatus = "unknown" | "ok" | "failed";
export type McpAuthStatus = "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth";
type McpAuthStatus = "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth";
export type McpServerStartupStatus = "starting" | "ready" | "failed" | "cancelled";
export interface McpServerStatus {

View file

@ -121,7 +121,7 @@ function asRecordOrNull(value: unknown): Record<string, unknown> | null {
}
function asConfigRecord(value: unknown): ConfigProjectionRecord {
return asRecord(value) as ConfigProjectionRecord;
return asRecord(value);
}
function selectedConfigProfile(layers: ConfigReadResult["layers"]): string | null {

View file

@ -41,7 +41,7 @@ export interface TokenUsageBreakdown {
reasoningOutputTokens: number;
}
export function rateLimitSnapshotFromAppServerSnapshot(snapshot: AppServerRateLimitSnapshot): RateLimitSnapshot {
function rateLimitSnapshotFromAppServerSnapshot(snapshot: AppServerRateLimitSnapshot): RateLimitSnapshot {
return {
limitId: snapshot.limitId,
limitName: snapshot.limitName,

View file

@ -21,7 +21,7 @@ export function transcriptEntriesFromTurnRecords(turns: readonly TurnRecord[]):
return turns.flatMap(transcriptEntriesFromTurnRecord);
}
export function conversationSummaryFromTurnRecord(turn: TurnRecord): ThreadConversationSummary {
function conversationSummaryFromTurnRecord(turn: TurnRecord): ThreadConversationSummary {
return conversationSummaryFromTranscriptEntries(transcriptEntriesFromTurnRecord(turn));
}
@ -42,7 +42,7 @@ export function completedConversationSummariesFromTurnRecords(turns: readonly Tu
});
}
export function conversationSummariesFromTurnRecords(turns: readonly TurnRecord[]): ThreadConversationSummary[] {
function conversationSummariesFromTurnRecords(turns: readonly TurnRecord[]): ThreadConversationSummary[] {
return nonEmptyConversationSummaries(turns.map(conversationSummaryFromTurnRecord));
}

View file

@ -73,11 +73,7 @@ async function refreshDiagnosticProbes(
return `${String(count)} skills`;
},
),
probeDiagnostic(
"account/rateLimits/read",
() => client.readAccountRateLimits(),
accountRateLimitsSummaryFromResponse,
),
probeDiagnostic("account/rateLimits/read", () => client.readAccountRateLimits(), accountRateLimitsSummaryFromResponse),
);
}
@ -148,10 +144,7 @@ async function mcpStatusLines(host: ChatServerDiagnosticsActionsHost): Promise<s
try {
const state = host.stateStore.getState();
const response = await client.listMcpServerStatus(mcpServerStatusParams(state.activeThread.id));
return buildMcpStatusLines(
mcpServerStatusSummariesFromStatuses(response.data),
state.connection.appServerDiagnostics.mcpServers,
);
return buildMcpStatusLines(mcpServerStatusSummariesFromStatuses(response.data), state.connection.appServerDiagnostics.mcpServers);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return ["MCP servers", `Could not load MCP servers: ${message}`];

View file

@ -15,15 +15,15 @@ import {
} from "../../protocol/server-requests/user-input";
export type PendingRequestId = RequestId;
export type PendingRequestApprovalAction = ApprovalAction;
type PendingRequestApprovalAction = ApprovalAction;
export interface PendingRequestApprovalOption {
interface PendingRequestApprovalOption {
label: string;
className: string;
action: PendingRequestApprovalAction;
}
export interface PendingRequestDetailRow {
interface PendingRequestDetailRow {
key: string;
value: string;
}
@ -36,7 +36,7 @@ export interface PendingApprovalViewModel {
actions: PendingRequestApprovalOption[];
}
export interface PendingUserInputOptionViewModel {
interface PendingUserInputOptionViewModel {
label: string;
description?: string | null;
}
@ -96,4 +96,3 @@ export function pendingUserInputViewModel(input: PendingUserInput): PendingUserI
})),
};
}

View file

@ -47,11 +47,7 @@ export function rollbackCandidateFromItems(items: readonly DisplayItem[]): Rollb
export function isRollbackCandidateItem(item: DisplayItem, candidate: RollbackCandidate | null): boolean {
return Boolean(
candidate &&
item.kind === "message" &&
item.role === "user" &&
item.id === candidate.displayItemId &&
item.turnId === candidate.turnId,
candidate && item.kind === "message" && item.role === "user" && item.id === candidate.displayItemId && item.turnId === candidate.turnId,
);
}

View file

@ -62,7 +62,7 @@ export function agentDisplayItem(item: DisplayCollabAgentToolCall, turnId?: stri
};
}
export function agentActivitySummaryLabel(tool: string): string {
function agentActivitySummaryLabel(tool: string): string {
if (tool === "spawnAgent") return "Spawn agent";
if (tool === "sendInput") return "Send input to agent";
if (tool === "resumeAgent") return "Resume agent";

View file

@ -9,7 +9,7 @@ const TASK_STATES = {
completed: "completed",
} as const satisfies ExecutionStateByStatus;
export type TaskStepStatus = "pending" | "inProgress" | "completed";
type TaskStepStatus = "pending" | "inProgress" | "completed";
export interface TaskPlanStep {
step: string;

View file

@ -31,9 +31,7 @@ type DynamicToolCallItem = Extract<TurnItem, { type: "dynamicToolCall" }>;
type WebSearchItem = Extract<TurnItem, { type: "webSearch" }>;
type ImageViewItem = Extract<TurnItem, { type: "imageView" }>;
type ImageGenerationItem = Extract<TurnItem, { type: "imageGeneration" }>;
type ReviewModeItem =
| Extract<TurnItem, { type: "enteredReviewMode" }>
| Extract<TurnItem, { type: "exitedReviewMode" }>;
type ReviewModeItem = Extract<TurnItem, { type: "enteredReviewMode" }> | Extract<TurnItem, { type: "exitedReviewMode" }>;
type ContextCompactionItem = Extract<TurnItem, { type: "contextCompaction" }>;
type DisplayExecutionState = Exclude<ExecutionState, null>;
type ExecutionStateByStatus = Readonly<Record<string, DisplayExecutionState>>;
@ -693,7 +691,7 @@ export function dynamicToolCallExecutionState(status: string, success?: boolean
return success === true ? "completed" : null;
}
export function imageGenerationExecutionState(status: string): ExecutionState {
function imageGenerationExecutionState(status: string): ExecutionState {
return standardToolCallExecutionState(status);
}

View file

@ -28,7 +28,7 @@ interface ChatPanelGoalActions {
setEditingOpen: (open: boolean) => void;
}
export interface ChatPanelStatePort {
interface ChatPanelStatePort {
state: {
chat: () => ChatState;
};

View file

@ -111,7 +111,10 @@ export class ChatInboundController {
}
addStructuredSystemMessage(text: string, details: DisplayDetailSection[]): void {
this.dispatch({ type: "message-stream/system-item-added", item: createStructuredSystemItem(this.localItemId("system"), text, details) });
this.dispatch({
type: "message-stream/system-item-added",
item: createStructuredSystemItem(this.localItemId("system"), text, details),
});
}
addDedupedSystemMessage(text: string): void {

View file

@ -21,7 +21,12 @@ import {
completeReasoningItems,
upsertDisplayItem,
} from "../../state/message-stream-updates";
import { displayItemFromTurnItem, displayItemsFromTurns, normalizeFileChanges, shouldSuppressLifecycleItem } from "../../display/turn-items";
import {
displayItemFromTurnItem,
displayItemsFromTurns,
normalizeFileChanges,
shouldSuppressLifecycleItem,
} from "../../display/turn-items";
import { taskProgressDisplayItem } from "../../display/items/task-progress";
import { createSystemItem } from "../../display/items/system";
import type { DisplayItem, DisplayKind, MessageDisplayItem } from "../../display/types";

View file

@ -1,4 +1,8 @@
import { cloneRuntimeConfigSnapshot, emptyRuntimeConfigSnapshot, type RuntimeConfigSnapshot } from "../../../app-server/protocol/runtime-config";
import {
cloneRuntimeConfigSnapshot,
emptyRuntimeConfigSnapshot,
type RuntimeConfigSnapshot,
} from "../../../app-server/protocol/runtime-config";
import { findModelMetadataByIdOrName, type ModelMetadata } from "../../../domain/catalog/metadata";
import type { ApprovalPolicy, ApprovalsReviewer } from "../../../app-server/protocol/runtime-policy";
import { supportedEffortsForModelMetadata, type ReasoningEffort } from "../../../domain/catalog/metadata";

View file

@ -1,4 +1,8 @@
import { applyThreadSettingsValue, appServerCollaborationMode, type ThreadSettingsUpdate } from "../../../app-server/protocol/thread-settings";
import {
applyThreadSettingsValue,
appServerCollaborationMode,
type ThreadSettingsUpdate,
} from "../../../app-server/protocol/thread-settings";
import type { ServiceTierRequest } from "../../../app-server/protocol/thread-settings";
import { currentModel, currentReasoningEffort, fastServiceTierRequestValue } from "./effective";
import type { RuntimeConfigSnapshot } from "../../../app-server/protocol/runtime-config";

View file

@ -30,7 +30,9 @@ function mergeChanges(previous: DisplayItem, next: DisplayItem): DisplayFileChan
}
export function appendAssistantDelta(items: readonly DisplayItem[], sourceItemId: string, turnId: string, delta: string): DisplayItem[] {
const index = items.findIndex((item) => item.sourceItemId === sourceItemId && item.kind === "message" && item.messageKind === "assistantResponse");
const index = items.findIndex(
(item) => item.sourceItemId === sourceItemId && item.kind === "message" && item.messageKind === "assistantResponse",
);
if (index !== -1) {
return items.map((item, itemIndex) =>
itemIndex === index && item.kind === "message" && item.messageKind === "assistantResponse"

View file

@ -46,7 +46,12 @@ import type {
TurnStartFailedAction,
UserInputDraftSetAction,
} from "./actions";
import { initialChatMessageStreamState, reduceMessageStreamSlice, type ChatMessageStreamState, type MessageStreamAction } from "./message-stream";
import {
initialChatMessageStreamState,
reduceMessageStreamSlice,
type ChatMessageStreamState,
type MessageStreamAction,
} from "./message-stream";
import {
initialChatRequestState,
reduceRequestSlice,

View file

@ -1,5 +1,12 @@
import { activeTurnId as selectActiveTurnId, chatTurnBusy, pendingTurnStart } from "./reducer";
import type { ChatActiveThreadState, ChatRuntimeState, ChatState, ChatMessageStreamState, ChatTurnState, PendingTurnStart } from "./reducer";
import type {
ChatActiveThreadState,
ChatRuntimeState,
ChatState,
ChatMessageStreamState,
ChatTurnState,
PendingTurnStart,
} from "./reducer";
import type { Thread } from "../../../domain/threads/model";
import type { DisplayItem } from "../display/types";

View file

@ -49,12 +49,7 @@ async function syncThreadGoal(host: GoalActionsHost, threadId: string): Promise<
}
}
async function setObjective(
host: GoalActionsHost,
threadId: string,
objective: string,
tokenBudget: number | null,
): Promise<boolean> {
async function setObjective(host: GoalActionsHost, threadId: string, objective: string, tokenBudget: number | null): Promise<boolean> {
const trimmed = objective.trim();
if (!trimmed) {
host.addSystemMessage("Goal objective cannot be empty.");
@ -104,12 +99,7 @@ async function setGoal(host: GoalActionsHost, threadId: string, params: ThreadGo
}
}
function applyGoalIfActive(
host: GoalActionsHost,
threadId: string,
goal: ThreadGoal | null,
options: { reportChange: boolean },
): boolean {
function applyGoalIfActive(host: GoalActionsHost, threadId: string, goal: ThreadGoal | null, options: { reportChange: boolean }): boolean {
const state = host.stateStore.getState();
if (state.activeThread.id !== threadId) return false;
const item = options.reportChange ? goalChangeItem(goalEventId(), state.activeThread.goal, goal) : null;

View file

@ -58,7 +58,11 @@ export class HistoryController {
if (this.state.activeThread.id !== threadId) return false;
this.host.setThreadTurnPresence(response.data.length > 0);
this.host.showLatestPageAtBottom();
this.dispatch({ type: "message-stream/items-replaced", items: displayItemsFromTurns(response.data), historyCursor: response.nextCursor });
this.dispatch({
type: "message-stream/items-replaced",
items: displayItemsFromTurns(response.data),
historyCursor: response.nextCursor,
});
this.host.render();
return true;
}

View file

@ -4,11 +4,7 @@ import { getThreadTitle } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import type { CodexPanelSettings } from "../../../settings/model";
import { generateThreadTitleWithCodex } from "../../thread-title/generation";
import {
findThreadTitleContext,
THREAD_TITLE_CONTEXT_UNAVAILABLE_MESSAGE,
type ThreadTitleContext,
} from "../../thread-title/model";
import { findThreadTitleContext, THREAD_TITLE_CONTEXT_UNAVAILABLE_MESSAGE, type ThreadTitleContext } from "../../thread-title/model";
import type { ChatState, ChatStateStore } from "../state/reducer";
import { renameConnectedThread } from "./rename-actions";
import { firstThreadTitleContextFromDisplayItems } from "./title-context";

View file

@ -2,7 +2,10 @@ import { parseServiceTier } from "../../../app-server/protocol/runtime-policy";
import { upsertThread } from "../../../domain/threads/model";
import { normalizeReasoningEffort } from "../../../domain/catalog/metadata";
import type { Thread } from "../../../domain/threads/model";
import { threadActivationSnapshotFromAppServerResponse, type ThreadActivationSnapshot } from "../../../app-server/services/thread-activation";
import {
threadActivationSnapshotFromAppServerResponse,
type ThreadActivationSnapshot,
} from "../../../app-server/services/thread-activation";
import type { ChatRuntimeState } from "../runtime/state";
import type { DisplayItem } from "../display/types";
import type { ActiveThreadResumedAction } from "../state/actions";

View file

@ -52,7 +52,7 @@ export function bindRenderedWikiLinks(parent: HTMLElement, sourcePath: string, c
});
}
export function bindRenderedMarkdownFileLinks(parent: HTMLElement, sourcePath: string, context: RenderedMarkdownLinkContext): void {
function bindRenderedMarkdownFileLinks(parent: HTMLElement, sourcePath: string, context: RenderedMarkdownLinkContext): void {
parent.querySelectorAll<HTMLAnchorElement>("a[href]:not(.internal-link)").forEach((link) => {
const href = link.getAttribute("href") ?? "";
const target = vaultFileLinkTarget(context.app, context.vaultPath, href);

View file

@ -93,7 +93,13 @@ function CollapsibleTextItemContent({ item, context }: { item: TextDisplayItem;
.filter(Boolean)
.join(" ")}
>
<TextContent key={textItemContentKey(item)} item={item} context={context} contentRef={contentRef} collapsed={overflows && !expanded} />
<TextContent
key={textItemContentKey(item)}
item={item}
context={context}
contentRef={contentRef}
collapsed={overflows && !expanded}
/>
<details
className="codex-panel__message-collapse-details"
hidden={!overflows || expanded}

View file

@ -1,7 +1,12 @@
import type { ComponentChild as UiNode } from "preact";
import { useLayoutEffect, useState } from "preact/hooks";
import { activeAgentRunSummary, agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel } from "../../display/stream/agent-summary";
import {
activeAgentRunSummary,
agentActivityMetaLabel,
agentMessagePreview,
agentRunSummaryLabel,
} from "../../display/stream/agent-summary";
import type {
AgentDisplayItem,
AgentRunSummary,

View file

@ -27,7 +27,7 @@ interface ToolbarDiagnosticRow {
level?: "normal" | "warning" | "error";
}
export interface ToolbarDiagnosticSection {
interface ToolbarDiagnosticSection {
title: string;
rows: ToolbarDiagnosticRow[];
}

View file

@ -1,7 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AppServerClient } from "../../src/app-server/connection/client";
import type { AppServerRpcError, AppServerStartStructuredTurnOptions, AppServerStartTurnOptions } from "../../src/app-server/connection/client";
import type {
AppServerRpcError,
AppServerStartStructuredTurnOptions,
AppServerStartTurnOptions,
} from "../../src/app-server/connection/client";
import type { AppServerTransport, AppServerTransportHandlers } from "../../src/app-server/connection/transport";
import type { RpcOutboundMessage } from "../../src/app-server/connection/rpc-messages";
import type { InitializeResponse } from "../../src/generated/app-server/InitializeResponse";

View file

@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { appServerCollaborationMode, applyThreadSettingsValue, type ThreadSettingsUpdate } from "../../src/app-server/protocol/thread-settings";
import {
appServerCollaborationMode,
applyThreadSettingsValue,
type ThreadSettingsUpdate,
} from "../../src/app-server/protocol/thread-settings";
import { appServerApprovalsReviewerOrNull, parseServiceTier } from "../../src/app-server/protocol/runtime-policy";
describe("app-server thread settings", () => {

View file

@ -11,14 +11,15 @@ import {
appendToolOutput,
upsertDisplayItem,
} from "../../../../src/features/chat/state/message-stream-updates";
import {
taskProgressDisplayItem,
taskProgressExecutionState,
} from "../../../../src/features/chat/display/items/task-progress";
import { taskProgressDisplayItem, taskProgressExecutionState } from "../../../../src/features/chat/display/items/task-progress";
import { normalizeProposedPlanMarkdown } from "../../../../src/features/chat/display/items/proposed-plan";
import { pathRelativeToRoot } from "../../../../src/features/chat/display/details/path-labels";
import { permissionRows } from "../../../../src/features/chat/display/details/permission-rows";
import { autoReviewExecutionState, createAutoReviewResultItem, createReviewResultItem } from "../../../../src/features/chat/display/items/review-result";
import {
autoReviewExecutionState,
createAutoReviewResultItem,
createReviewResultItem,
} from "../../../../src/features/chat/display/items/review-result";
import {
commandExecutionState,
dynamicToolCallExecutionState,

View file

@ -1231,7 +1231,9 @@ describe("ChatInboundController", () => {
expect(fallbackStateWithoutClientId.messageStream.displayItems).toEqual([
expect.objectContaining({ id: "server-u1", text: "fallback text" }),
]);
expect(fallbackStateWithoutClientId.messageStream.displayItems.some((item) => item.id === "local-user-without-client-id")).toBe(false);
expect(fallbackStateWithoutClientId.messageStream.displayItems.some((item) => item.id === "local-user-without-client-id")).toBe(
false,
);
});
it("keeps the observed steer message order when completed turns reconcile by client id", () => {
@ -1483,7 +1485,11 @@ describe("ChatInboundController", () => {
method: "thread/goal/updated",
params: { threadId: "thread-active", turnId: null, goal: updatedGoal },
} satisfies Extract<ServerNotification, { method: "thread/goal/updated" }>);
expect(state.messageStream.displayItems.at(-1)).toMatchObject({ kind: "goal", text: "updated: Finish well", objective: "Finish well" });
expect(state.messageStream.displayItems.at(-1)).toMatchObject({
kind: "goal",
text: "updated: Finish well",
objective: "Finish well",
});
const pausedGoal = { ...updatedGoal, status: "paused", updatedAt: 3 } satisfies Extract<
ServerNotification,
@ -1493,7 +1499,11 @@ describe("ChatInboundController", () => {
method: "thread/goal/updated",
params: { threadId: "thread-active", turnId: null, goal: pausedGoal },
} satisfies Extract<ServerNotification, { method: "thread/goal/updated" }>);
expect(state.messageStream.displayItems.at(-1)).toMatchObject({ kind: "goal", text: "paused: Finish well", objective: "Finish well" });
expect(state.messageStream.displayItems.at(-1)).toMatchObject({
kind: "goal",
text: "paused: Finish well",
objective: "Finish well",
});
const resumedGoal = { ...pausedGoal, status: "active", updatedAt: 4 } satisfies Extract<
ServerNotification,
@ -1503,7 +1513,11 @@ describe("ChatInboundController", () => {
method: "thread/goal/updated",
params: { threadId: "thread-active", turnId: null, goal: resumedGoal },
} satisfies Extract<ServerNotification, { method: "thread/goal/updated" }>);
expect(state.messageStream.displayItems.at(-1)).toMatchObject({ kind: "goal", text: "resumed: Finish well", objective: "Finish well" });
expect(state.messageStream.displayItems.at(-1)).toMatchObject({
kind: "goal",
text: "resumed: Finish well",
objective: "Finish well",
});
const messageCount = state.messageStream.displayItems.length;
controller.handleNotification({
@ -1518,7 +1532,11 @@ describe("ChatInboundController", () => {
} satisfies Extract<ServerNotification, { method: "thread/goal/cleared" }>);
expect(state.activeThread.goal).toBeNull();
expect(state.messageStream.displayItems.at(-1)).toMatchObject({ kind: "goal", text: "cleared: Finish well", objective: "Finish well" });
expect(state.messageStream.displayItems.at(-1)).toMatchObject({
kind: "goal",
text: "cleared: Finish well",
objective: "Finish well",
});
});
it("adds a goal event when a goal completes", () => {

View file

@ -1,10 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../src/features/chat/state/reducer";
import {
createSelectionActions,
type SelectionActionsHost,
} from "../../../../src/features/chat/threads/selection-actions";
import { createSelectionActions, type SelectionActionsHost } from "../../../../src/features/chat/threads/selection-actions";
function resumeThreadState(stateStore: ChatStateStore, threadId: string): void {
stateStore.dispatch({

View file

@ -25,7 +25,11 @@ import {
type SelectionRewriteClient,
type SelectionRewriteClientFactory,
} from "../../../src/features/selection-rewrite/runner";
import type { AppServerClient, AppServerClientHandlers, AppServerStartStructuredTurnOptions } from "../../../src/app-server/connection/client";
import type {
AppServerClient,
AppServerClientHandlers,
AppServerStartStructuredTurnOptions,
} from "../../../src/app-server/connection/client";
import type { AppServerInitialization } from "../../../src/app-server/protocol/initialization";
import type { RequestId, ServerNotification } from "../../../src/app-server/connection/rpc-messages";
import type { TurnItem, TurnRecord } from "../../../src/app-server/protocol/turn";

View file

@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import type { AppServerClient, AppServerClientHandlers, AppServerStartStructuredTurnOptions } from "../../../src/app-server/connection/client";
import type {
AppServerClient,
AppServerClientHandlers,
AppServerStartStructuredTurnOptions,
} from "../../../src/app-server/connection/client";
import { modelMetadataFromCatalogModels, type CatalogModel } from "../../../src/app-server/protocol/catalog";
import type { AppServerInitialization } from "../../../src/app-server/protocol/initialization";
import type { TurnItem, TurnRecord } from "../../../src/app-server/protocol/turn";

View file

@ -12,10 +12,7 @@ import {
modelOverrideMessage,
reasoningEffortOverrideMessage,
} from "../../src/features/chat/runtime/settings-copy";
import {
parseModelOverride,
parseReasoningEffortOverride,
} from "../../src/features/chat/conversation/turns/runtime-setting-commands";
import { parseModelOverride, parseReasoningEffortOverride } from "../../src/features/chat/conversation/turns/runtime-setting-commands";
import {
autoReviewActive,
currentApprovalsReviewer,
@ -764,10 +761,7 @@ function snapshotConfig(snapshot: RuntimeSnapshot): RuntimeConfigSnapshot {
return runtimeConfigOrDefault(snapshot.runtimeConfig);
}
function runtimeConfigFixture(
config: Record<string, unknown>,
layers: ConfigReadResult["layers"] = null,
): RuntimeConfigSnapshot {
function runtimeConfigFixture(config: Record<string, unknown>, layers: ConfigReadResult["layers"] = null): RuntimeConfigSnapshot {
return runtimeConfigSnapshotFromAppServerConfig({
config: config as ConfigReadResult["config"],
origins: {},