Hide app-server query observer details

This commit is contained in:
murashit 2026-06-22 20:35:16 +09:00
parent f474ff49c7
commit 2ff5c89e2b
15 changed files with 130 additions and 126 deletions

View file

@ -7,6 +7,7 @@ import { readRateLimitMetadataProbe, readSkillMetadataProbe } from "./metadata-p
import { runtimeConfigSnapshotFromAppServerConfig } from "../protocol/runtime-config";
import { listThreads } from "../threads";
import type { ModelMetadata } from "../../domain/catalog/metadata";
import type { ObservedDataListener, ObservedDataResult } from "../../domain/observed-data";
import { createServerDiagnostics, diagnosticProbeError, diagnosticProbeOk, diagnosticsWithProbe } from "../../domain/server/diagnostics";
import type { SharedServerMetadata } from "../../domain/server/metadata";
import type { Thread } from "../../domain/threads/model";
@ -34,11 +35,6 @@ export interface AppServerQueryClientRunner {
): Promise<T>;
}
export type AppServerObservedQueryResult<T> = Omit<QueryObserverResult<T>, "data" | "error"> & {
readonly data: T | null;
readonly error: Error | null;
};
interface AppServerQueryOptions<T> {
readonly queryKey: readonly unknown[];
readonly queryFn: () => Promise<T>;
@ -94,7 +90,7 @@ export class AppServerQueryCache {
observeActiveThreadsResult(
context: AppServerQueryContext,
listener: (result: AppServerObservedQueryResult<readonly Thread[]>) => void,
listener: ObservedDataListener<readonly Thread[]>,
options: { emitCurrent?: boolean } = {},
): () => void {
return this.observeQueryResult(this.threadListQueryOptions(context, "active"), cloneThreads, listener, options);
@ -102,7 +98,7 @@ export class AppServerQueryCache {
observeArchivedThreadsResult(
context: AppServerQueryContext,
listener: (result: AppServerObservedQueryResult<readonly Thread[]>) => void,
listener: ObservedDataListener<readonly Thread[]>,
options: { emitCurrent?: boolean } = {},
): () => void {
return this.observeQueryResult(this.threadListQueryOptions(context, "archived"), cloneThreads, listener, options);
@ -187,7 +183,7 @@ export class AppServerQueryCache {
observeAppServerMetadataResult(
context: AppServerQueryContext,
listener: (result: AppServerObservedQueryResult<SharedServerMetadata>) => void,
listener: ObservedDataListener<SharedServerMetadata>,
options: { emitCurrent?: boolean } = {},
): () => void {
return this.observeQueryResult(this.appServerMetadataQueryOptions(context), cloneSharedServerMetadata, listener, options);
@ -241,7 +237,7 @@ export class AppServerQueryCache {
observeModelsResult(
context: AppServerQueryContext,
listener: (result: AppServerObservedQueryResult<readonly ModelMetadata[]>) => void,
listener: ObservedDataListener<readonly ModelMetadata[]>,
options: { emitCurrent?: boolean } = {},
): () => void {
return this.observeQueryResult(this.modelsQueryOptions(context), cloneModelMetadata, listener, options);
@ -427,7 +423,7 @@ export class AppServerQueryCache {
private observeQueryResult<T>(
queryOptions: AppServerQueryOptions<T>,
clone: (value: T) => T,
listener: (result: AppServerObservedQueryResult<T>) => void,
listener: ObservedDataListener<T>,
options: { emitCurrent?: boolean },
): () => void {
const observer = new QueryObserver<T>(this.client, {
@ -442,11 +438,11 @@ export class AppServerQueryCache {
return unsubscribe;
}
private cloneObservedResult<T>(result: QueryObserverResult<T>, clone: (value: T) => T): AppServerObservedQueryResult<T> {
private cloneObservedResult<T>(result: QueryObserverResult<T>, clone: (value: T) => T): ObservedDataResult<T> {
return {
...result,
data: result.data === undefined ? null : clone(result.data),
error: result.error instanceof Error ? result.error : null,
isFetching: result.isFetching,
};
}

View file

@ -1,13 +0,0 @@
import type { AppServerObservedQueryResult } from "./cache";
export function observedQueryData<T>(result: AppServerObservedQueryResult<T>): T | null {
return result.data;
}
export function observedQueryInitialLoading<T>(result: AppServerObservedQueryResult<T>, currentData: T | null | undefined): boolean {
return currentData == null && result.isFetching;
}
export function observedQueryInitialError<T>(result: AppServerObservedQueryResult<T>, currentData: T | null | undefined): Error | null {
return currentData == null ? result.error : null;
}

View file

@ -1,7 +1,8 @@
import type { ModelMetadata } from "../../domain/catalog/metadata";
import type { ObservedDataListener } from "../../domain/observed-data";
import type { SharedServerMetadata } from "../../domain/server/metadata";
import type { Thread } from "../../domain/threads/model";
import type { AppServerObservedQueryResult, AppServerQueryCache } from "./cache";
import type { AppServerQueryCache } from "./cache";
import {
appServerQueryContextMatches,
appServerQueryContextRawEquals,
@ -70,10 +71,7 @@ export class AppServerSharedQueries {
return this.options.cache.updateArchivedThreads(this.context(), updater);
}
observeActiveThreadsResult(
listener: (result: AppServerObservedQueryResult<readonly Thread[]>) => void,
options?: { emitCurrent?: boolean },
): () => void {
observeActiveThreadsResult(listener: ObservedDataListener<readonly Thread[]>, options?: { emitCurrent?: boolean }): () => void {
return this.observeCurrentContext(
(context, contextListener, observeOptions) => this.options.cache.observeActiveThreadsResult(context, contextListener, observeOptions),
listener,
@ -81,10 +79,7 @@ export class AppServerSharedQueries {
);
}
observeArchivedThreadsResult(
listener: (result: AppServerObservedQueryResult<readonly Thread[]>) => void,
options?: { emitCurrent?: boolean },
): () => void {
observeArchivedThreadsResult(listener: ObservedDataListener<readonly Thread[]>, options?: { emitCurrent?: boolean }): () => void {
return this.observeCurrentContext(
(context, contextListener, observeOptions) =>
this.options.cache.observeArchivedThreadsResult(context, contextListener, observeOptions),
@ -105,10 +100,7 @@ export class AppServerSharedQueries {
return this.runForCurrentContext((context) => this.options.cache.refreshAppServerMetadata(context, options));
}
observeAppServerMetadataResult(
listener: (result: AppServerObservedQueryResult<SharedServerMetadata>) => void,
options?: { emitCurrent?: boolean },
): () => void {
observeAppServerMetadataResult(listener: ObservedDataListener<SharedServerMetadata>, options?: { emitCurrent?: boolean }): () => void {
return this.observeCurrentContext(
(context, contextListener, observeOptions) =>
this.options.cache.observeAppServerMetadataResult(context, contextListener, observeOptions),
@ -129,10 +121,7 @@ export class AppServerSharedQueries {
return this.runForCurrentContext((context) => this.options.cache.refreshModels(context));
}
observeModelsResult(
listener: (result: AppServerObservedQueryResult<readonly ModelMetadata[]>) => void,
options?: { emitCurrent?: boolean },
): () => void {
observeModelsResult(listener: ObservedDataListener<readonly ModelMetadata[]>, options?: { emitCurrent?: boolean }): () => void {
return this.observeCurrentContext(
(context, contextListener, observeOptions) => this.options.cache.observeModelsResult(context, contextListener, observeOptions),
listener,

View file

@ -0,0 +1,19 @@
export interface ObservedDataResult<T> {
readonly data: T | null;
readonly error: Error | null;
readonly isFetching: boolean;
}
export type ObservedDataListener<T> = (result: ObservedDataResult<T>) => void;
export function observedData<T>(result: ObservedDataResult<T>): T | null {
return result.data;
}
export function observedInitialLoading<T>(result: ObservedDataResult<T>, currentData: T | null | undefined): boolean {
return currentData == null && result.isFetching;
}
export function observedInitialError<T>(result: ObservedDataResult<T>, currentData: T | null | undefined): Error | null {
return currentData == null ? result.error : null;
}

View file

@ -1,6 +1,8 @@
import type { App, Component, EventRef } from "obsidian";
import type { AppServerSharedQueries } from "../../../app-server/query/shared-queries";
import type { ModelMetadata } from "../../../domain/catalog/metadata";
import type { ObservedDataListener } from "../../../domain/observed-data";
import type { SharedServerMetadata } from "../../../domain/server/metadata";
import type { ArchiveExportAdapter } from "../../../domain/threads/archive-markdown";
import type { CodexPanelSettings } from "../../../settings/model";
import type { ThreadCatalogActiveReader, ThreadCatalogChatEvents } from "../../../workspace/thread-catalog";
@ -27,17 +29,16 @@ interface WorkspacePanels {
type ChatThreadCatalog = ThreadCatalogActiveReader & ThreadCatalogChatEvents;
type ChatAppServerData = Pick<
AppServerSharedQueries,
| "updateAppServerMetadata"
| "appServerMetadataSnapshot"
| "refreshAppServerMetadata"
| "modelsSnapshot"
| "fetchModels"
| "refreshModels"
| "observeAppServerMetadataResult"
| "observeModelsResult"
>;
interface ChatAppServerData {
updateAppServerMetadata(updater: (metadata: SharedServerMetadata | null) => SharedServerMetadata | null): SharedServerMetadata | null;
appServerMetadataSnapshot(): SharedServerMetadata | null;
refreshAppServerMetadata(options?: { forceSkills?: boolean }): Promise<SharedServerMetadata | null>;
observeAppServerMetadataResult(listener: ObservedDataListener<SharedServerMetadata>, options?: { emitCurrent?: boolean }): () => void;
modelsSnapshot(): readonly ModelMetadata[] | null;
fetchModels(): Promise<readonly ModelMetadata[]>;
refreshModels(): Promise<readonly ModelMetadata[]>;
observeModelsResult(listener: ObservedDataListener<readonly ModelMetadata[]>, options?: { emitCurrent?: boolean }): () => void;
}
export interface ChatPanelEnvironment {
obsidian: {

View file

@ -2,8 +2,8 @@ import { Notice } from "obsidian";
import { ConnectionManager } from "../../../app-server/connection/connection-manager";
import type { AppServerClientAccess } from "../../../app-server/connection/client-access";
import type { AppServerObservedQueryResult } from "../../../app-server/query/cache";
import { observedQueryData } from "../../../app-server/query/observed-result";
import type { ObservedDataResult } from "../../../domain/observed-data";
import { observedData } from "../../../domain/observed-data";
import { isStaleAppServerSharedQueryContextError } from "../../../app-server/query/shared-queries";
import type { ModelMetadata } from "../../../domain/catalog/metadata";
import type { MessageStreamNoticeSection } from "../domain/message-stream/items";
@ -404,22 +404,22 @@ function createChatPanelSharedStateBinding(
serverActions.threads.applyThreadList(threads);
refreshTabHeader(host);
};
const receiveThreadResult = (result: AppServerObservedQueryResult<readonly Thread[]>): void => {
const data = observedQueryData(result);
const receiveThreadResult = (result: ObservedDataResult<readonly Thread[]>): void => {
const data = observedData(result);
if (data) receiveThreads(data);
};
const receiveAppServerMetadata = (metadata: SharedServerMetadata): void => {
serverActions.metadata.applyAppServerMetadata(metadata);
};
const receiveAppServerMetadataResult = (result: AppServerObservedQueryResult<SharedServerMetadata>): void => {
const data = observedQueryData(result);
const receiveAppServerMetadataResult = (result: ObservedDataResult<SharedServerMetadata>): void => {
const data = observedData(result);
if (data) receiveAppServerMetadata(data);
};
const receiveModels = (models: readonly ModelMetadata[]): void => {
dispatch(host.stateStore, { type: "connection/metadata-applied", availableModels: models });
};
const receiveModelsResult = (result: AppServerObservedQueryResult<readonly ModelMetadata[]>): void => {
const data = observedQueryData(result);
const receiveModelsResult = (result: ObservedDataResult<readonly ModelMetadata[]>): void => {
const data = observedData(result);
if (data) receiveModels(data);
};
const unsubscribe = (): void => {

View file

@ -1,10 +1,10 @@
import { Notice } from "obsidian";
import type { AppServerClientAccess } from "../../app-server/connection/client-access";
import type { AppServerObservedQueryResult } from "../../app-server/query/cache";
import { observedQueryData, observedQueryInitialError, observedQueryInitialLoading } from "../../app-server/query/observed-result";
import { isStaleAppServerSharedQueryContextError } from "../../app-server/query/shared-queries";
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import type { ObservedDataResult } from "../../domain/observed-data";
import { observedData, observedInitialError, observedInitialLoading } from "../../domain/observed-data";
import type { Thread } from "../../domain/threads/model";
import type { OpenCodexPanelSnapshot } from "../../workspace/panel-coordinator";
import type { ThreadCatalogActiveReader, ThreadCatalogThreadManagementEvents } from "../../workspace/thread-catalog";
@ -158,19 +158,19 @@ export class CodexThreadsSession {
this.render();
}
private receiveObservedThreadsResult(result: AppServerObservedQueryResult<readonly Thread[]>): void {
const data = observedQueryData(result);
private receiveObservedThreadsResult(result: ObservedDataResult<readonly Thread[]>): void {
const data = observedData(result);
if (data) {
this.receiveObservedThreads(data);
return;
}
const currentData = this.currentThreadsData();
if (observedQueryInitialLoading(result, currentData)) {
if (observedInitialLoading(result, currentData)) {
this.status = { kind: "loading", message: "Loading threads..." };
this.render();
return;
}
const initialError = observedQueryInitialError(result, currentData);
const initialError = observedInitialError(result, currentData);
if (initialError) {
this.status = { kind: "error", message: initialError.message };
this.render();

View file

@ -1,11 +1,11 @@
import type { AppServerClient } from "../app-server/connection/client";
import type { AppServerObservedQueryResult } from "../app-server/query/cache";
import { observedQueryData } from "../app-server/query/observed-result";
import { isStaleAppServerSharedQueryContextError } from "../app-server/query/shared-queries";
import { setHookItemEnabled, trustHookItem } from "../app-server/catalog";
import { restoreArchivedThread as restoreArchivedThreadOnAppServer } from "../app-server/threads";
import type { HookItem, ModelMetadata, ReasoningEffort } from "../domain/catalog/metadata";
import { findModelMetadataByIdOrName, sortedModelMetadata, supportedEffortsForModelMetadata } from "../domain/catalog/metadata";
import type { ObservedDataResult } from "../domain/observed-data";
import { observedData } from "../domain/observed-data";
import type { Thread } from "../domain/threads/model";
import { threadArchiveDisplayTitle } from "../domain/threads/title";
import { errorMessage } from "../utils";
@ -124,15 +124,15 @@ export class SettingsDynamicDataController {
this.unsubscribeArchivedThreads = null;
}
private receiveObservedModelsResult(result: AppServerObservedQueryResult<readonly ModelMetadata[]>): void {
const data = observedQueryData(result);
private receiveObservedModelsResult(result: ObservedDataResult<readonly ModelMetadata[]>): void {
const data = observedData(result);
if (!data) return;
this.models = [...data];
this.callbacks.display("helper");
}
private receiveObservedArchivedThreadsResult(result: AppServerObservedQueryResult<readonly Thread[]>): void {
const data = observedQueryData(result);
private receiveObservedArchivedThreadsResult(result: ObservedDataResult<readonly Thread[]>): void {
const data = observedData(result);
if (!data) return;
this.archivedThreads = [...data];
this.archivedThreadsLoaded = true;

View file

@ -1,16 +1,22 @@
import type { AppServerSharedQueries } from "../app-server/query/shared-queries";
import type { AppServerClientAccess } from "../app-server/connection/client-access";
import type { ModelMetadata } from "../domain/catalog/metadata";
import type { ObservedDataListener } from "../domain/observed-data";
import type { CodexPanelSettings } from "./model";
import type { ThreadCatalogArchivedReader, ThreadCatalogThreadDeletes, ThreadCatalogThreadRestores } from "../workspace/thread-catalog";
interface SettingsAppServerData {
modelsSnapshot(): readonly ModelMetadata[] | null;
observeModelsResult(listener: ObservedDataListener<readonly ModelMetadata[]>, options?: { emitCurrent?: boolean }): () => void;
fetchModels(): Promise<readonly ModelMetadata[]>;
refreshModels(): Promise<readonly ModelMetadata[]>;
notifyContextChanged(): void;
}
export interface SettingsDynamicDataHost {
settings: CodexPanelSettings;
vaultPath: string;
clientAccess: AppServerClientAccess;
appServerData: Pick<
AppServerSharedQueries,
"modelsSnapshot" | "observeModelsResult" | "fetchModels" | "refreshModels" | "notifyContextChanged"
>;
appServerData: SettingsAppServerData;
threadCatalog: ThreadCatalogArchivedReader & ThreadCatalogThreadDeletes & ThreadCatalogThreadRestores;
}

View file

@ -1,8 +1,22 @@
import type { AppServerObservedQueryResult } from "../app-server/query/cache";
import type { AppServerSharedQueries } from "../app-server/query/shared-queries";
import type { ObservedDataListener } from "../domain/observed-data";
import type { Thread } from "../domain/threads/model";
type ThreadListObserver = (result: AppServerObservedQueryResult<readonly Thread[]>) => void;
type ThreadListObserver = ObservedDataListener<readonly Thread[]>;
interface ThreadCatalogQuerySource {
activeThreadsSnapshot(): readonly Thread[] | null;
archivedThreadsSnapshot(): readonly Thread[] | null;
fetchActiveThreads(): Promise<readonly Thread[]>;
fetchArchivedThreads(): Promise<readonly Thread[]>;
refreshActiveThreads(): Promise<readonly Thread[]>;
refreshArchivedThreads(): Promise<readonly Thread[]>;
observeActiveThreadsResult(observer: ThreadListObserver, options?: { emitCurrent?: boolean }): () => void;
observeArchivedThreadsResult(observer: ThreadListObserver, options?: { emitCurrent?: boolean }): () => void;
setActiveThreads(threads: readonly Thread[]): void;
setArchivedThreads(threads: readonly Thread[]): void;
updateActiveThreads(updater: (threads: readonly Thread[] | null) => readonly Thread[] | null): readonly Thread[] | null;
updateArchivedThreads(updater: (threads: readonly Thread[] | null) => readonly Thread[] | null): readonly Thread[] | null;
}
interface ThreadSurfaceActions {
applyThreadArchived(threadId: string, options?: { closeOpenPanels?: boolean }): void;
@ -10,7 +24,7 @@ interface ThreadSurfaceActions {
}
export interface ThreadCatalogOptions {
queries: AppServerSharedQueries;
queries: ThreadCatalogQuerySource;
surfaces: ThreadSurfaceActions;
}
@ -126,7 +140,7 @@ export function createThreadCatalog(options: ThreadCatalogOptions): ThreadCatalo
};
}
function recordActiveThread(queries: AppServerSharedQueries, thread: Thread): void {
function recordActiveThread(queries: ThreadCatalogQuerySource, thread: Thread): void {
queries.updateActiveThreads((current) => promoteThreadInList(current ?? [], thread));
}
@ -135,7 +149,7 @@ function promoteThreadInList(threads: readonly Thread[], thread: Thread): readon
return [thread, ...withoutThread];
}
function refreshArchivedThreadsAfterUnknownArchive(queries: AppServerSharedQueries): void {
function refreshArchivedThreadsAfterUnknownArchive(queries: ThreadCatalogQuerySource): void {
// A force refresh can join an older in-flight archived request. Run one more
// refresh afterward so an archive recorded during that request is not lost.
void queries

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import type { AppServerObservedQueryResult } from "../../src/app-server/query/cache";
import { observedQueryData, observedQueryInitialError, observedQueryInitialLoading } from "../../src/app-server/query/observed-result";
import type { ObservedDataResult } from "../../src/domain/observed-data";
import { observedData, observedInitialError, observedInitialLoading } from "../../src/domain/observed-data";
describe("observed query result helpers", () => {
it("treats successful empty arrays as current data", () => {
@ -9,29 +9,27 @@ describe("observed query result helpers", () => {
const error = new Error("boom");
const failed = observedResult<readonly string[]>({ data: null, error });
expect(observedQueryInitialLoading(loading, [])).toBe(false);
expect(observedQueryInitialError(failed, [])).toBeNull();
expect(observedInitialLoading(loading, [])).toBe(false);
expect(observedInitialError(failed, [])).toBeNull();
});
it("returns initial loading and error only before current data exists", () => {
const error = new Error("boom");
expect(observedQueryInitialLoading(observedResult({ data: null, isFetching: true }), null)).toBe(true);
expect(observedQueryInitialError(observedResult({ data: null, error }), null)).toBe(error);
expect(observedInitialLoading(observedResult({ data: null, isFetching: true }), null)).toBe(true);
expect(observedInitialError(observedResult({ data: null, error }), null)).toBe(error);
});
it("projects nullable observed data without reinterpreting empty values", () => {
expect(observedQueryData(observedResult({ data: [] as readonly string[] }))).toEqual([]);
expect(observedQueryData(observedResult({ data: null }))).toBeNull();
expect(observedData(observedResult({ data: [] as readonly string[] }))).toEqual([]);
expect(observedData(observedResult({ data: null }))).toBeNull();
});
});
function observedResult<T>(
overrides: Partial<AppServerObservedQueryResult<T>> & Pick<AppServerObservedQueryResult<T>, "data">,
): AppServerObservedQueryResult<T> {
function observedResult<T>(overrides: Partial<ObservedDataResult<T>> & Pick<ObservedDataResult<T>, "data">): ObservedDataResult<T> {
return {
error: null,
isFetching: false,
...overrides,
} as AppServerObservedQueryResult<T>;
};
}

View file

@ -1,7 +1,8 @@
import { describe, expect, it, vi } from "vitest";
import type { AppServerObservedQueryResult, AppServerQueryCache } from "../../src/app-server/query/cache";
import type { AppServerQueryCache } from "../../src/app-server/query/cache";
import { AppServerSharedQueries, StaleAppServerSharedQueryContextError } from "../../src/app-server/query/shared-queries";
import type { ObservedDataResult } from "../../src/domain/observed-data";
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
import { createServerDiagnostics, diagnosticProbeOk, diagnosticsWithProbe } from "../../src/domain/server/diagnostics";
import type { SharedServerMetadata } from "../../src/domain/server/metadata";
@ -72,7 +73,7 @@ describe("AppServerSharedQueries", () => {
}),
context: () => context,
});
let observedThreadListener!: (result: AppServerObservedQueryResult<readonly Thread[]>) => void;
let observedThreadListener!: (result: ObservedDataResult<readonly Thread[]>) => void;
queries.observeActiveThreadsResult(listener);
context.codexPath = "codex-b";
@ -83,7 +84,7 @@ describe("AppServerSharedQueries", () => {
it("resubscribes observers when the app-server query context changes", () => {
const context = { codexPath: "codex-a", vaultPath: "/vault" };
const listeners = new Map<string, (result: AppServerObservedQueryResult<readonly Thread[]>) => void>();
const listeners = new Map<string, (result: ObservedDataResult<readonly Thread[]>) => void>();
const queries = new AppServerSharedQueries({
cache: cacheWith({
observeActiveThreadsResult: (queryContext, listener) => {
@ -124,8 +125,8 @@ describe("AppServerSharedQueries", () => {
}),
context: () => ({ codexPath: "codex", vaultPath: "/vault" }),
});
let metadataObserver!: (result: AppServerObservedQueryResult<SharedServerMetadata>) => void;
let modelObserver!: (result: AppServerObservedQueryResult<readonly ModelMetadata[]>) => void;
let metadataObserver!: (result: ObservedDataResult<SharedServerMetadata>) => void;
let modelObserver!: (result: ObservedDataResult<readonly ModelMetadata[]>) => void;
queries.observeAppServerMetadataResult(metadataListener);
queries.observeModelsResult(modelListener);
@ -160,8 +161,8 @@ function cacheWith(overrides: Partial<AppServerQueryCache>): AppServerQueryCache
} as unknown as AppServerQueryCache;
}
function observedResult<T>(data: T): AppServerObservedQueryResult<T> {
return { data, error: null } as AppServerObservedQueryResult<T>;
function observedResult<T>(data: T): ObservedDataResult<T> {
return { data, error: null, isFetching: false };
}
function thread(id: string): Thread {

View file

@ -4,9 +4,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
import type { CodexChatHost } from "../../../src/features/chat/host/runtime";
import type { AppServerObservedQueryResult } from "../../../src/app-server/query/cache";
import { StaleAppServerSharedQueryContextError } from "../../../src/app-server/query/shared-queries";
import { modelMetadataFromCatalogModels } from "../../../src/app-server/protocol/catalog";
import type { ObservedDataResult } from "../../../src/domain/observed-data";
import { createServerDiagnostics } from "../../../src/domain/server/diagnostics";
import type { Thread } from "../../../src/domain/threads/model";
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
@ -1358,9 +1358,9 @@ function chatHost(overrides: ChatHostFixtureOverrides = {}): TestCodexChatHost {
let activeThreads = overrides.activeSnapshot?.() ?? null;
let metadata = overrides.appServerMetadataSnapshot?.() ?? null;
const models = overrides.modelsSnapshot?.() ?? null;
const activeThreadResultListeners = new Set<(result: AppServerObservedQueryResult<readonly Thread[]>) => void>();
const metadataResultListeners = new Set<(result: AppServerObservedQueryResult<SharedServerMetadata>) => void>();
const modelResultListeners = new Set<(result: AppServerObservedQueryResult<readonly ModelMetadata[]>) => void>();
const activeThreadResultListeners = new Set<(result: ObservedDataResult<readonly Thread[]>) => void>();
const metadataResultListeners = new Set<(result: ObservedDataResult<SharedServerMetadata>) => void>();
const modelResultListeners = new Set<(result: ObservedDataResult<readonly ModelMetadata[]>) => void>();
const settings = {
...DEFAULT_SETTINGS,
codexPath: "codex",
@ -1516,19 +1516,12 @@ function chatHost(overrides: ChatHostFixtureOverrides = {}): TestCodexChatHost {
};
}
function queryResult<T>(data: T | null): AppServerObservedQueryResult<T> {
function queryResult<T>(data: T | null): ObservedDataResult<T> {
return {
data,
error: null,
isFetching: false,
isLoading: false,
isPending: data === null,
isSuccess: data !== null,
isError: false,
isStale: false,
status: data === null ? "pending" : "success",
fetchStatus: "idle",
} as AppServerObservedQueryResult<T>;
};
}
async function chatView(

View file

@ -3,7 +3,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
import type { AppServerObservedQueryResult } from "../../../src/app-server/query/cache";
import type { ObservedDataResult } from "../../../src/domain/observed-data";
import type { TurnRecord } from "../../../src/app-server/protocol/turn";
import type { Thread } from "../../../src/domain/threads/model";
import type * as ThreadTitleGeneratorModule from "../../../src/app-server/services/thread-title-generation";
@ -270,7 +270,7 @@ describe("CodexThreadsView", () => {
});
it("keeps successful empty thread lists as last-known-good observed data", async () => {
let observedThreads!: (result: AppServerObservedQueryResult<readonly Thread[]>) => void;
let observedThreads!: (result: ObservedDataResult<readonly Thread[]>) => void;
const view = await threadsView(
threadsHost({
threadCatalog: {
@ -280,7 +280,7 @@ describe("CodexThreadsView", () => {
// Keep the initial refresh pending; this test drives observed query results directly.
}),
),
observeActive: vi.fn((listener: (result: AppServerObservedQueryResult<readonly Thread[]>) => void) => {
observeActive: vi.fn((listener: (result: ObservedDataResult<readonly Thread[]>) => void) => {
observedThreads = listener;
return () => undefined;
}),
@ -587,12 +587,12 @@ function threadFromRecord(record: Record<string, unknown>): Thread {
};
}
function queryResult<T>(data: T | null, error: Error | null = null): AppServerObservedQueryResult<T> {
function queryResult<T>(data: T | null, error: Error | null = null): ObservedDataResult<T> {
return {
data,
error,
isFetching: false,
} as AppServerObservedQueryResult<T>;
};
}
function threadFixture(overrides: Record<string, unknown> = {}): Record<string, unknown> {

View file

@ -3,7 +3,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { CatalogHookMetadata, CatalogModel } from "../../src/app-server/protocol/catalog";
import type { AppServerObservedQueryResult } from "../../src/app-server/query/cache";
import type { ObservedDataResult } from "../../src/domain/observed-data";
import type { AppServerClientAccessOptions } from "../../src/app-server/connection/client-access";
import type { ThreadRecord } from "../../src/app-server/protocol/thread";
import type { ModelMetadata, ReasoningEffort } from "../../src/domain/catalog/metadata";
@ -324,7 +324,7 @@ describe("settings tab", () => {
settingsTabHost({
observeArchived: (listener) => {
emitArchived = (threads) => {
listener({ data: threads, error: null } as unknown as AppServerObservedQueryResult<readonly Thread[]>);
listener({ data: threads, error: null, isFetching: false } satisfies ObservedDataResult<readonly Thread[]>);
};
return () => undefined;
},
@ -563,7 +563,7 @@ describe("settings tab", () => {
archivedThreads: [panelThread({ id: "thread-old", preview: "Old archived", archived: true })],
observeArchived: (listener) => {
emitArchived = (threads) => {
listener({ data: threads, error: null } as unknown as AppServerObservedQueryResult<readonly Thread[]>);
listener({ data: threads, error: null, isFetching: false } satisfies ObservedDataResult<readonly Thread[]>);
};
return () => undefined;
},
@ -610,7 +610,7 @@ describe("settings tab", () => {
archivedThreads: [panelThread({ id: "thread-old", preview: "Old archived", archived: true })],
observeArchived: (listener) => {
emitArchived = (threads) => {
listener({ data: threads, error: null } as unknown as AppServerObservedQueryResult<readonly Thread[]>);
listener({ data: threads, error: null, isFetching: false } satisfies ObservedDataResult<readonly Thread[]>);
};
return () => undefined;
},