mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Sync thread list refreshes
This commit is contained in:
parent
406edc6f57
commit
94f5bd0730
6 changed files with 102 additions and 0 deletions
|
|
@ -85,6 +85,7 @@ export interface CodexChatHost {
|
|||
refreshOpenThreadLists(): void;
|
||||
refreshThreadsViewLiveState(): void;
|
||||
refreshThreadsViewThreadList(): void;
|
||||
publishThreadList(threads: Thread[]): void;
|
||||
}
|
||||
|
||||
interface RestoredThreadState {
|
||||
|
|
@ -295,6 +296,13 @@ export class CodexChatView extends ItemView {
|
|||
void this.refreshThreads();
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: Thread[]): void {
|
||||
this.state.listedThreads = threads;
|
||||
this.state.threadsLoaded = true;
|
||||
this.refreshTabHeader();
|
||||
this.render();
|
||||
}
|
||||
|
||||
openPanelSnapshot(): OpenCodexPanelSnapshot {
|
||||
return {
|
||||
viewId: this.viewId,
|
||||
|
|
@ -424,6 +432,7 @@ export class CodexChatView extends ItemView {
|
|||
if (this.isStaleConnectionGeneration(generation)) return;
|
||||
await this.session.refreshThreadList();
|
||||
if (this.isStaleConnectionGeneration(generation)) return;
|
||||
this.publishThreadListSnapshot();
|
||||
this.scheduleDeferredDiagnostics();
|
||||
this.refreshTabHeader();
|
||||
this.setStatus("Connected.");
|
||||
|
|
@ -464,6 +473,7 @@ export class CodexChatView extends ItemView {
|
|||
if (!this.client) return;
|
||||
try {
|
||||
await this.session.refreshThreadList();
|
||||
this.publishThreadListSnapshot();
|
||||
await this.session.refreshSessionMetadata();
|
||||
this.refreshTabHeader();
|
||||
this.render();
|
||||
|
|
@ -554,6 +564,10 @@ export class CodexChatView extends ItemView {
|
|||
this.requestWorkspaceLayoutSave();
|
||||
}
|
||||
|
||||
private publishThreadListSnapshot(): void {
|
||||
this.plugin.publishThreadList(this.state.listedThreads);
|
||||
}
|
||||
|
||||
private requestWorkspaceLayoutSave(): void {
|
||||
void this.app.workspace.requestSaveLayout();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export interface CodexThreadsHost {
|
|||
getOpenPanelSnapshots(): OpenCodexPanelSnapshot[];
|
||||
notifyThreadArchived(threadId: string): void;
|
||||
notifyThreadRenamed(threadId: string, name: string): void;
|
||||
publishThreadList(threads: Thread[]): void;
|
||||
cachedThreadList(): Thread[] | null;
|
||||
}
|
||||
|
||||
export class CodexThreadsView extends ItemView {
|
||||
|
|
@ -74,6 +76,10 @@ export class CodexThreadsView extends ItemView {
|
|||
}
|
||||
|
||||
override async onOpen(): Promise<void> {
|
||||
const cachedThreads = this.plugin.cachedThreadList();
|
||||
if (cachedThreads) {
|
||||
this.threads = cachedThreads;
|
||||
}
|
||||
this.render();
|
||||
void this.refresh();
|
||||
}
|
||||
|
|
@ -106,6 +112,7 @@ export class CodexThreadsView extends ItemView {
|
|||
const response = await this.client.listThreads(this.plugin.vaultPath);
|
||||
if (this.isStaleRefresh(connectionGeneration, refreshGeneration)) return;
|
||||
this.threads = response.data;
|
||||
this.plugin.publishThreadList(response.data);
|
||||
this.status = response.data.length === 0 ? "No threads" : null;
|
||||
} catch (error) {
|
||||
if (error instanceof StaleConnectionError) return;
|
||||
|
|
@ -183,6 +190,12 @@ export class CodexThreadsView extends ItemView {
|
|||
this.scheduleRender();
|
||||
}
|
||||
|
||||
applyThreadListSnapshot(threads: Thread[]): void {
|
||||
this.threads = threads;
|
||||
this.status = threads.length === 0 ? "No threads" : null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
private scheduleRefresh(): void {
|
||||
if (this.refreshTimer !== null) return;
|
||||
this.refreshTimer = this.containerEl.win.setTimeout(() => {
|
||||
|
|
|
|||
20
src/main.ts
20
src/main.ts
|
|
@ -6,6 +6,7 @@ import { CodexChatView } from "./features/chat/view";
|
|||
import { CodexChatTurnDiffView } from "./features/chat/chat-turn-diff-view";
|
||||
import type { OpenCodexPanelSnapshot } from "./features/chat/panel-snapshot";
|
||||
import { CodexThreadsView } from "./features/threads-view/view";
|
||||
import type { Thread } from "./generated/app-server/v2/Thread";
|
||||
import { DEFAULT_SETTINGS, getVaultPath, normalizeSettings, settingsMatchNormalizedData, type CodexPanelSettings } from "./settings/model";
|
||||
import { CodexPanelSettingTab } from "./settings/tab";
|
||||
import { persistedChatTurnDiffViewState, type ChatTurnDiffViewState } from "./features/chat/ui/turn-diff";
|
||||
|
|
@ -37,6 +38,7 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
vaultPath = "";
|
||||
private bootRestoredPanelLoadCancelled = false;
|
||||
private readonly bootRestoredPanelLoadTimers = new Set<number>();
|
||||
private latestThreadList: Thread[] | null = null;
|
||||
|
||||
override async onload(): Promise<void> {
|
||||
this.bootRestoredPanelLoadCancelled = false;
|
||||
|
|
@ -187,6 +189,24 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
publishThreadList(threads: Thread[]): void {
|
||||
this.latestThreadList = threads;
|
||||
for (const leaf of this.app.workspace.getLeavesOfType(VIEW_TYPE_CODEX_PANEL)) {
|
||||
if (leaf.view instanceof CodexChatView) {
|
||||
leaf.view.applyThreadListSnapshot(threads);
|
||||
}
|
||||
}
|
||||
for (const leaf of this.app.workspace.getLeavesOfType(VIEW_TYPE_CODEX_THREADS)) {
|
||||
if (leaf.view instanceof CodexThreadsView) {
|
||||
leaf.view.applyThreadListSnapshot(threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cachedThreadList(): Thread[] | null {
|
||||
return this.latestThreadList;
|
||||
}
|
||||
|
||||
refreshThreadsViewLiveState(): void {
|
||||
for (const leaf of this.app.workspace.getLeavesOfType(VIEW_TYPE_CODEX_THREADS)) {
|
||||
if (leaf.view instanceof CodexThreadsView) {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,22 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
expect(client.listThreads).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("publishes refreshed thread lists after connecting", async () => {
|
||||
const publishThreadList = vi.fn();
|
||||
const threads = [threadFixture("thread-1")];
|
||||
const client = connectedClient({
|
||||
listThreads: vi.fn().mockResolvedValue({ data: threads }),
|
||||
});
|
||||
connectionMock.state.client = client;
|
||||
const view = await chatView({
|
||||
host: chatHost({ publishThreadList }),
|
||||
});
|
||||
|
||||
await view.connect();
|
||||
|
||||
expect(publishThreadList).toHaveBeenCalledWith(threads);
|
||||
});
|
||||
|
||||
it("ignores stale connection work after the view closes", async () => {
|
||||
let resolveConfig!: (value: unknown) => void;
|
||||
const client = connectedClient({
|
||||
|
|
@ -603,6 +619,7 @@ function chatHost(overrides: Partial<CodexChatHost> = {}): CodexChatHost {
|
|||
refreshOpenThreadLists: vi.fn(),
|
||||
refreshThreadsViewLiveState: vi.fn(),
|
||||
refreshThreadsViewThreadList: vi.fn(),
|
||||
publishThreadList: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,6 +196,41 @@ describe("CodexThreadsView", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("publishes refreshed thread lists to other thread surfaces", async () => {
|
||||
const threads = [threadFixture({ id: "thread", preview: "Thread preview" })];
|
||||
connectionMock.state.client = clientFixture({
|
||||
listThreads: vi.fn().mockResolvedValue({ data: threads }),
|
||||
});
|
||||
const host = threadsHost({
|
||||
publishThreadList: vi.fn(),
|
||||
});
|
||||
const view = await threadsView(host);
|
||||
|
||||
await view.refresh();
|
||||
|
||||
expect(host.publishThreadList).toHaveBeenCalledWith(threads);
|
||||
});
|
||||
|
||||
it("renders cached thread lists before refreshing", async () => {
|
||||
connectionMock.state.client = clientFixture({
|
||||
listThreads: vi.fn(
|
||||
() =>
|
||||
new Promise(() => {
|
||||
// Keep the app-server refresh pending so the cached snapshot remains visible for this assertion.
|
||||
}),
|
||||
),
|
||||
});
|
||||
const view = await threadsView(
|
||||
threadsHost({
|
||||
cachedThreadList: vi.fn(() => [threadFixture({ id: "cached", preview: "Cached thread" })]),
|
||||
}),
|
||||
);
|
||||
|
||||
await view.onOpen();
|
||||
|
||||
expect(view.containerEl.textContent).toContain("Cached thread");
|
||||
});
|
||||
|
||||
it("notifies open panels after archiving a thread", async () => {
|
||||
const archiveThread = vi.fn().mockResolvedValue({});
|
||||
connectionMock.state.client = clientFixture({
|
||||
|
|
@ -294,6 +329,8 @@ function threadsHost(overrides: Record<string, unknown> = {}) {
|
|||
notifyThreadArchived: vi.fn(),
|
||||
notifyThreadRenamed: vi.fn(),
|
||||
refreshOpenThreadLists: vi.fn(),
|
||||
publishThreadList: vi.fn(),
|
||||
cachedThreadList: vi.fn(() => null),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) {
|
|||
refreshOpenThreadLists: vi.fn(),
|
||||
refreshThreadsViewLiveState: vi.fn(),
|
||||
refreshThreadsViewThreadList: vi.fn(),
|
||||
publishThreadList: vi.fn(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue