mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Fix React island cleanup and rename click guard
This commit is contained in:
parent
45c471a331
commit
b5ad9ea0fd
6 changed files with 45 additions and 2 deletions
|
|
@ -123,6 +123,12 @@ export class ChatComposerController {
|
|||
this.composer?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
unmountReactRoot(this.suggestionsEl);
|
||||
this.composer = null;
|
||||
this.suggestionsEl = null;
|
||||
}
|
||||
|
||||
syncControls(parent: HTMLElement | null): void {
|
||||
syncComposerControls(parent, this.composer, this.options.state.busy, this.options.canInterrupt(), this.options.composerPlaceholder());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
|||
import { isAbsoluteFileHref, vaultFileLinkTarget, vaultRelativeFileLinkTarget } from "./markdown-file-links";
|
||||
import { isRollbackCandidateItem, rollbackCandidateFromItems } from "./rollback";
|
||||
import type { ChatState } from "./chat-state";
|
||||
import { unmountReactRoot } from "../../shared/ui/react-root";
|
||||
|
||||
export interface ChatMessageRendererOptions {
|
||||
app: App;
|
||||
|
|
@ -30,6 +31,7 @@ export type ChatMessageScrollIntent = "auto" | "force-bottom" | "preserve";
|
|||
|
||||
export class ChatMessageRenderer {
|
||||
private renderGeneration = 0;
|
||||
private messagesEl: HTMLElement | null = null;
|
||||
|
||||
constructor(private readonly options: ChatMessageRendererOptions) {}
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ export class ChatMessageRenderer {
|
|||
const generation = ++this.renderGeneration;
|
||||
const { state } = this.options;
|
||||
const messagesEl = parent.querySelector<HTMLElement>(".codex-panel__messages") ?? parent.createDiv({ cls: "codex-panel__messages" });
|
||||
this.messagesEl = messagesEl;
|
||||
messagesEl.onscroll = () => {
|
||||
state.messagesPinnedToBottom = isNearScrollBottom(messagesEl);
|
||||
};
|
||||
|
|
@ -103,6 +106,16 @@ export class ChatMessageRenderer {
|
|||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderGeneration += 1;
|
||||
if (this.messagesEl) {
|
||||
this.messagesEl.onscroll = null;
|
||||
unmountReactRoot(this.messagesEl);
|
||||
}
|
||||
this.messagesEl = null;
|
||||
this.options.blockSignatures.clear();
|
||||
}
|
||||
|
||||
private async copyMessageText(text: string): Promise<void> {
|
||||
await copyTextWithNotice(text, "Copied message.", "Could not copy message.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,8 @@ export class CodexChatView extends ItemView {
|
|||
}
|
||||
this.clearDeferredDiagnostics();
|
||||
unmountReactRoot(this.toolbarEl);
|
||||
this.messageRenderer.dispose();
|
||||
this.composerController.dispose();
|
||||
unmountReactRoot(this.messagesSlotEl);
|
||||
unmountReactRoot(this.composerSlotEl);
|
||||
this.connection.disconnect();
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function ThreadRow({ row, actions }: { row: ThreadsRowModel; actions: ThreadsVie
|
|||
.join(" ");
|
||||
|
||||
const open = () => {
|
||||
if (archiveConfirm.active) return;
|
||||
if (row.rename.active || archiveConfirm.active) return;
|
||||
actions.openThread(row.thread.id);
|
||||
};
|
||||
useLayoutEffect(() => {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,24 @@ describe("ChatMessageRenderer scroll pinning", () => {
|
|||
expect(scrollIntoView).not.toHaveBeenCalled();
|
||||
expect(state.messagesPinnedToBottom).toBe(false);
|
||||
});
|
||||
|
||||
it("unmounts the React message stream root on dispose", () => {
|
||||
const state = createChatState();
|
||||
state.activeThreadId = "thread";
|
||||
state.displayItems = [{ id: "message", kind: "message", role: "assistant", text: "Rendered message", turnId: "turn" }];
|
||||
const parent = document.createElement("div");
|
||||
const blockSignatures = new Map<string, string>();
|
||||
const renderer = chatMessageRenderer(state, vi.fn(), "/vault", [], blockSignatures);
|
||||
|
||||
renderer.render(parent);
|
||||
expect(parent.querySelector('[data-codex-panel-block-key="item:message"]')).not.toBeNull();
|
||||
expect(blockSignatures.size).toBeGreaterThan(0);
|
||||
|
||||
renderer.dispose();
|
||||
|
||||
expect(parent.querySelector('[data-codex-panel-block-key="item:message"]')).toBeNull();
|
||||
expect(blockSignatures.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
function chatMessageRenderer(
|
||||
|
|
@ -192,6 +210,7 @@ function chatMessageRenderer(
|
|||
openLinkText = vi.fn(),
|
||||
vaultPath = "/vault",
|
||||
vaultFiles: string[] = [],
|
||||
blockSignatures = new Map<string, string>(),
|
||||
): ChatMessageRenderer {
|
||||
const files = new Map(vaultFiles.map((path) => [path, tFile(path)]));
|
||||
return new ChatMessageRenderer({
|
||||
|
|
@ -208,7 +227,7 @@ function chatMessageRenderer(
|
|||
owner: {} as never,
|
||||
state,
|
||||
vaultPath,
|
||||
blockSignatures: new Map(),
|
||||
blockSignatures,
|
||||
consumeScrollIntent: () => "auto",
|
||||
loadOlderTurns: vi.fn(),
|
||||
rollbackThread: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -2184,6 +2184,9 @@ describe("threads view renderer decisions", () => {
|
|||
|
||||
expect(actions.updateRename).toHaveBeenCalledWith("thread", "New name");
|
||||
expect(actions.saveRename).toHaveBeenCalledWith("thread", "New name");
|
||||
|
||||
expectPresent(parent.querySelector<HTMLElement>(".codex-panel-threads__row")).click();
|
||||
expect(actions.openThread).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders threads view rename actions inline with auto-name", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue