Repository Turn Document
AI Alert Copilot UX
Upgraded the desktop Copilot result surface so generated analysis now reads like Islandflow content instead of raw terminal output. The alert panel can reuse session results by action, expose Regenerate after completion, show live running/cancel states, and request task cancellation through the desktop bridge.
Issueislandflow-xtg
Primary SurfaceAlert Copilot
Validation272 tests passed
Relevant Diff Snippets
Patch-style snippets prepared for Diffs-style review. Reference: Diffs rendering docs.
Task result surface
@@ apps/web/app/desktop-ai-panels.tsx
+import ReactMarkdown from "react-markdown";
+
+export const getCopilotTaskSurfaceState = (...) => {
+ if (!task) return "empty";
+ if (RUNNING_TASK_STATUSES.includes(task.status)) return "running";
+ if (task.status === "completed") return "completed";
+ if (task.status === "failed") return "failed";
+ return "cancelled";
+};
+
+{surfaceState === "running" ? (
+ <div className="copilot-task-running-row">
+ <span className="copilot-spinner" aria-hidden="true" />
+ <p className="copilot-note">Generating analysis as deltas arrive.</p>
+ <button className="terminal-button" onClick={() => onCancel(task.taskId)}>Cancel</button>
+ </div>
+) : null}
+{task.text ? (
+ <div className="copilot-markdown">
+ <ReactMarkdown>{task.text}</ReactMarkdown>
+ </div>
+) : null}
Desktop cancellation bridge
@@ apps/desktop/src/desktop-ai-ipc.ts
+export const DESKTOP_AI_CANCEL_TASK = "islandflow:desktop-ai:cancel-task";
@@ apps/desktop/src/main.ts
+ipcMain.handle(DESKTOP_AI_CANCEL_TASK, async (event, taskId) => {
+ guard(event);
+ await service.cancelTask(String(taskId));
+});
@@ apps/desktop/src/desktop-ai.ts
+async cancelTask(taskId: string): Promise<void> {
+ const task = this.state.tasks.find((candidate) => candidate.taskId === taskId);
+ if (!task || (task.status !== "queued" && task.status !== "running")) return;
+ this.locallyCancelledTaskIds.add(taskId);
+ this.patchTask(taskId, { status: "cancelled", error: null });
+ if (task.threadId) {
+ this.activeTasksByThreadId.delete(task.threadId);
+ await this.client.request("turn/cancel", { threadId: task.threadId, turnId: task.turnId ?? undefined }).catch(() => undefined);
+ }
+}