feat(KB-603): improve task creation UX with draft persistence and save-to-create

- Change QuickEntryBox Save button to immediately create task instead of just saving
- Add draft persistence to InlineCreateCard via localStorage for recovery
- Add comprehensive tests for InlineCreateCard draft persistence
- Update QuickEntryBox tests for new save-and-create behavior
This commit is contained in:
gsxdsm
2026-03-31 17:02:11 -07:00
parent e6ec36b922
commit a114446844
4 changed files with 129 additions and 15 deletions

View File

@@ -90,6 +90,7 @@ function chooseModel(label: "Executor Model" | "Validator Model", optionText: st
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
vi.mocked(fetchModels).mockResolvedValue(MOCK_MODELS);
vi.mocked(fetchSettings).mockResolvedValue({
modelPresets: [],
@@ -536,3 +537,87 @@ describe("InlineCreateCard Plan and Subtask buttons", () => {
// The disabled state is the primary UX protection
});
});
describe("InlineCreateCard localStorage persistence", () => {
beforeEach(() => {
// Clear localStorage before each test
localStorage.clear();
});
afterEach(() => {
localStorage.clear();
});
it("restores description from localStorage on mount", () => {
// Pre-populate localStorage
localStorage.setItem("kb-inline-create-text", "Saved draft description");
renderCard();
const textarea = screen.getByPlaceholderText("What needs to be done?");
// Should restore the saved description
expect((textarea as HTMLTextAreaElement).value).toBe("Saved draft description");
});
it("updates localStorage when typing", async () => {
renderCard();
const textarea = screen.getByPlaceholderText("What needs to be done?");
fireEvent.change(textarea, { target: { value: "Typing this task" } });
// Wait for the useEffect to run
await waitFor(() => {
expect(localStorage.getItem("kb-inline-create-text")).toBe("Typing this task");
});
});
it("clears localStorage after successful task creation", async () => {
const { props } = renderCard();
const textarea = screen.getByPlaceholderText("What needs to be done?");
// Type something to set localStorage
fireEvent.change(textarea, { target: { value: "Task to create" } });
await waitFor(() => {
expect(localStorage.getItem("kb-inline-create-text")).toBe("Task to create");
});
// Submit the task by clicking the Save button
fireEvent.click(screen.getByRole("button", { name: /Save/i }));
await waitFor(() => {
expect(props.onSubmit).toHaveBeenCalled();
});
// localStorage should be cleared
expect(localStorage.getItem("kb-inline-create-text")).toBeNull();
});
it("clears localStorage when cancelling via Escape key", async () => {
const { props } = renderCard();
const textarea = screen.getByPlaceholderText("What needs to be done?");
// Type something to set localStorage
fireEvent.change(textarea, { target: { value: "Draft to cancel" } });
await waitFor(() => {
expect(localStorage.getItem("kb-inline-create-text")).toBe("Draft to cancel");
});
// Press Escape to cancel
fireEvent.keyDown(textarea, { key: "Escape" });
// onCancel should be called
await waitFor(() => {
expect(props.onCancel).toHaveBeenCalled();
});
// localStorage should be cleared
expect(localStorage.getItem("kb-inline-create-text")).toBeNull();
});
it("starts with empty description when localStorage is empty", () => {
renderCard();
const textarea = screen.getByPlaceholderText("What needs to be done?");
expect((textarea as HTMLTextAreaElement).value).toBe("");
});
});

View File

@@ -1036,7 +1036,7 @@ describe("QuickEntryBox", () => {
});
});
it("clicking save button shows success toast", async () => {
it("clicking save button creates the task", async () => {
const { props } = renderQuickEntryBox();
const textarea = screen.getByTestId("quick-entry-input");
@@ -1046,9 +1046,14 @@ describe("QuickEntryBox", () => {
// Click the save button
fireEvent.click(screen.getByTestId("save-button"));
// Success toast should be shown
// Task should be created
await waitFor(() => {
expect(props.addToast).toHaveBeenCalledWith("Draft saved", "success");
expect(props.onCreate).toHaveBeenCalledWith(
expect.objectContaining({
description: "Task to save",
column: "triage",
}),
);
});
});
@@ -1072,7 +1077,7 @@ describe("QuickEntryBox", () => {
fireEvent.change(textarea, { target: { value: "Task to save" } });
const saveButton = screen.getByTestId("save-button");
expect(saveButton.getAttribute("title")).toBe("Save draft to browser storage");
expect(saveButton.getAttribute("title")).toBe("Create task");
});
it("save button prevents textarea blur on mousedown", () => {