feat: advance and highlight next AI suggestion on accept/deny/copy-to-note, remove copied suggestion from inline list, and add native Linux .deb packaging

This commit is contained in:
Schwitzz
2026-10-30 13:01:40 -05:00
parent 951aa81055
commit 6bc681df5f
7 changed files with 497 additions and 6 deletions
+41 -5
View File
@@ -910,6 +910,32 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
setFocusedIndex(0);
};
const advanceAndHighlightNext = (
removedId: string,
updatedAll: AISuggestion[],
) => {
const prevIdx = pendingSuggestions.findIndex((item) => item.id === removedId);
const nextPending = updatedAll.filter((item) => item.status === "pending");
if (nextPending.length === 0) {
setFocusedIndex(0);
setHighlightedLine(null);
setHighlightedText(null);
return;
}
const nextIdx = Math.min(
prevIdx >= 0 ? prevIdx : focusedIndex,
nextPending.length - 1,
);
const nextSg = nextPending[nextIdx]!;
setFocusedIndex(nextIdx);
if (nextSg.originalText) {
setHighlightedText(nextSg.originalText);
}
if (nextSg.lineNumber != null) {
setHighlightedLine(nextSg.lineNumber);
}
};
const handleAcceptSuggestion = async (sg: AISuggestion) => {
if (!aiStore || !activeSessionId) return;
const replacement = (draftEdits[sg.id] ?? sg.newText ?? sg.text).trim();
@@ -950,6 +976,7 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
const nextList = await aiStore.listSuggestions(activeSessionId);
setSuggestions(nextList);
advanceAndHighlightNext(sg.id, nextList);
if (applied) {
toast.success(`Accepted granular edit: "${replacement.slice(0, 36)}${replacement.length > 36 ? "…" : ""}"`);
@@ -968,10 +995,7 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
}
const nextList = await aiStore.listSuggestions(activeSessionId);
setSuggestions(nextList);
if (highlightedText === sg.originalText || highlightedLine === sg.lineNumber) {
setHighlightedLine(null);
setHighlightedText(null);
}
advanceAndHighlightNext(sg.id, nextList);
toast.info(`Denied suggestion: ${sg.title}`);
};
@@ -995,7 +1019,19 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
status: "draft",
});
setNotes(await aiStore.listNotes(activeSessionId));
toast.success(`Copied "${sg.title}" to Notes`);
// Remove the suggestion from the inline edits list and advance to the next suggestion
setSuggestions((prev) => prev.filter((item) => item.id !== sg.id));
if (typeof aiStore.deleteSuggestion === "function") {
await aiStore.deleteSuggestion(activeSessionId, sg.id);
} else {
await aiStore.resolveSuggestion(activeSessionId, sg.id, false);
}
const nextList = await aiStore.listSuggestions(activeSessionId);
setSuggestions(nextList);
advanceAndHighlightNext(sg.id, nextList);
toast.success(`Moved "${sg.title}" to Notes`);
};
const handleAddNote = async () => {
@@ -139,6 +139,11 @@ describe("AISidebar + WritingView granular text-pegged suggestion flow", () => {
await new Promise((r) => setTimeout(r, 120));
});
// After accepting Suggestion #1, the next suggestion (#2) must be automatically highlighted
expect(useProjectStore.getState().highlightedText).toContain(
"the light had turned the sea the color of a bruised plum",
);
// Accept Suggestion #2 (clause 2 of the SAME paragraph 2)
const acceptBtn2 = Array.from(container.querySelectorAll("button")).find((b) =>
b.textContent?.includes("Accept"),
@@ -150,6 +155,25 @@ describe("AISidebar + WritingView granular text-pegged suggestion flow", () => {
await new Promise((r) => setTimeout(r, 120));
});
// After accepting Suggestion #2, Suggestion #3 (on the next paragraph) must be automatically highlighted
expect(useProjectStore.getState().highlightedText).toContain(
"She pressed her palm against the cold window",
);
expect(container.querySelector(".ps-editor")?.getAttribute("data-highlight-line")).toBe("3");
// Clicking "Copy to Note" on Suggestion #3 removes it from the inline suggestions list
const beforeCards = container.querySelectorAll(".ps-ai-suggestion-card").length;
const copyToNoteBtn = Array.from(container.querySelectorAll("button")).find((b) =>
b.textContent?.includes("Copy to Note"),
);
expect(copyToNoteBtn).toBeDefined();
await act(async () => {
copyToNoteBtn!.click();
await new Promise((r) => setTimeout(r, 120));
});
const afterCards = container.querySelectorAll(".ps-ai-suggestion-card").length;
expect(afterCards).toBe(beforeCards - 1);
// Verify BOTH granular clauses in paragraph 2 were updated AND the rest of the paragraph was NOT deleted!
const pmText = container.querySelector(".ProseMirror")?.textContent ?? "";
expect(pmText).toContain("The ferry that carried Mara Vell to the Glass Harbor cleared the mainland before dawn");