fix(web): reset new-conversation draft when project chat defaults are saved (#223)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-08-06 20:10:43 +08:00
committed by GitHub
parent c98749fbff
commit 75a8aa12c8
6 changed files with 249 additions and 4 deletions
+48
View File
@@ -11,6 +11,7 @@
import { describe, expect, it } from "vitest";
import {
clearDraft,
clearDraftChatDefaults,
clearDraftModelRef,
draftKey,
loadDraft,
@@ -221,6 +222,53 @@ describe("load / save / clear (key isolation, errors silenced)", () => {
});
});
it("clearDraftChatDefaults strips only the [default_chat]-seeded selections, preserving user content and the model pin", () => {
// Called by the project-settings save when the block changed: the next /chat/new must
// reseed Agent / Workspace / approval mode from the fresh defaults, while typed text,
// staged skills, the handoff/switch chips and the switch-becomes-default model
// carry-over (released only by clearDraftModelRef) all survive.
const s = memStorage();
saveDraft(
draftKey("user-a1", "project-a"),
{
text: "typed but unsent",
agentId: "old_default_agent",
workspace: "/srv/old-default",
approvalMode: "read-only",
modelRef: { provider: "deepseek", modelId: "deepseek-v4-pro" },
handoffAgentId: "agent_helper",
switchModelRef: { provider: "openai", modelId: "gpt-5" },
skills: ["agent-creation"],
},
s,
);
clearDraftChatDefaults("user-a1", "project-a", s);
expect(loadDraft(draftKey("user-a1", "project-a"), s)).toEqual({
text: "typed but unsent",
modelRef: { provider: "deepseek", modelId: "deepseek-v4-pro" },
handoffAgentId: "agent_helper",
switchModelRef: { provider: "openai", modelId: "gpt-5" },
skills: ["agent-creation"],
});
});
it('clearDraftChatDefaults strips any subset of the three fields (a cached "" workspace counts) and is otherwise a no-op', () => {
const s = memStorage();
// A single seeded field is enough to rewrite; "" workspace is an explicit "auto temp"
// pin and must be stripped like any other value (undefined-check, not truthiness).
saveDraft(draftKey("user-a1", "project-a"), { text: "t", workspace: "" }, s);
clearDraftChatDefaults("user-a1", "project-a", s);
expect(loadDraft(draftKey("user-a1", "project-a"), s)).toEqual({ text: "t" });
// Nothing seeded left (or no draft at all): a no-op, never an errant write.
clearDraftChatDefaults("user-a1", "project-a", s);
clearDraftChatDefaults("user-b2", "project-a", s);
expect(s.map.has(draftKey("user-b2", "project-a"))).toBe(false);
// Scoped by user × Project: another user's pins survive.
saveDraft(draftKey("user-b2", "project-a"), { agentId: "default_agent" }, s);
clearDraftChatDefaults("user-a1", "project-a", s);
expect(loadDraft(draftKey("user-b2", "project-a"), s)).toEqual({ agentId: "default_agent" });
});
it("storage throwing (quota/private mode): save does not throw, load yields an empty draft", () => {
const broken: DraftStorage = {
getItem: () => {