feat(app): clear all events from the Event Center (red trash icon)

Adds Cmd::ClearEvents + Evt::EventsCleared: the daemon drops the persistent
event log (keeping next_id monotonic), persists, and broadcasts so every
client empties its list. A red trash icon next to 'Mark all read' triggers it;
disabled when the list is empty. Threaded through proto, the daemon handler,
the Tauri bridge, and socketBridge. Includes an EventLog::clear test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 13:38:35 +07:00
parent bcc88b6be7
commit f9a565a712
8 changed files with 53 additions and 5 deletions
+4 -1
View File
@@ -9,7 +9,7 @@ import { Settings } from "./Settings";
import { EventCenter } from "./EventCenter";
import { maybeNotify } from "./notify";
import { COLORS, applyTheme, resolvePalette } from "./theme";
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface, getEventLog, markEventsRead, getHealth, closeWorkspaceCmd, getConfig } from "./socketBridge";
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface, getEventLog, markEventsRead, clearEvents, getHealth, closeWorkspaceCmd, getConfig } from "./socketBridge";
import type { EventRecord, DaemonHealth, ConfigView } from "./socketBridge";
import { leafIds } from "./layoutTypes";
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
@@ -95,6 +95,8 @@ export function App() {
} else if (evt.evt === "events_read") {
const ids = new Set(evt.data.ids);
setEvents((es) => es.map((e) => (ids.has(e.id) ? { ...e, read: true } : e)));
} else if (evt.evt === "events_cleared") {
setEvents([]);
} else if (evt.evt === "state") {
setStates((m) => ({ ...m, [evt.data.surface_id]: evt.data.state }));
void refresh();
@@ -177,6 +179,7 @@ export function App() {
<EventCenter
events={events}
onMarkAllRead={() => { void markEventsRead({ target: "all" }); }}
onClear={() => { void clearEvents(); }}
onSelect={(sid, id) => { void focusSurface(sid); void markEventsRead({ target: "ids", value: [id] }); }}
/>
)}
+11 -3
View File
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Check, Hourglass, X, Power, Send, MessageSquare } from "lucide-react";
import { Check, Hourglass, X, Power, Send, MessageSquare, Trash2 } from "lucide-react";
import { COLORS, FONT } from "./theme";
import type { EventRecord } from "./socketBridge";
@@ -26,10 +26,11 @@ function rel(ts: number): string {
}
export function EventCenter({
events, onMarkAllRead, onSelect,
events, onMarkAllRead, onClear, onSelect,
}: {
events: EventRecord[];
onMarkAllRead: () => void;
onClear: () => void;
onSelect: (surfaceId: string, id: number) => void;
}) {
const [tab, setTab] = useState<Tab>("all");
@@ -41,7 +42,14 @@ export function EventCenter({
<div style={{ display: "flex", flexDirection: "column", width: 300, flex: "0 0 300px", background: COLORS.bgSidebar, height: "100%", padding: 14, boxSizing: "border-box", borderLeft: `1px solid ${COLORS.borderSubtle}` }}>
<div style={{ display: "flex", alignItems: "center", marginBottom: 12 }}>
<span style={{ fontFamily: FONT.ui, fontSize: 13, fontWeight: 700, color: COLORS.textPrimary, flex: 1 }}>Event Center</span>
<span onClick={onMarkAllRead} style={{ fontFamily: FONT.ui, fontSize: 11, color: COLORS.accent, cursor: "pointer" }}>Mark all read</span>
<span onClick={onMarkAllRead} style={{ fontFamily: FONT.ui, fontSize: 11, color: COLORS.accent, cursor: "pointer", marginRight: 10 }}>Mark all read</span>
<span
title="Clear all events"
onClick={() => { if (events.length) onClear(); }}
style={{ display: "flex", cursor: events.length ? "pointer" : "default", opacity: events.length ? 1 : 0.4 }}
>
<Trash2 size={14} color={COLORS.stError} aria-label="Clear all" />
</span>
</div>
<div style={{ display: "flex", gap: 6, marginBottom: 12 }}>
+6 -1
View File
@@ -86,6 +86,10 @@ export async function markEventsRead(target: MarkReadTarget): Promise<void> {
await invoke("mark_read", { target });
}
export async function clearEvents(): Promise<void> {
await invoke("clear_events");
}
export type DaemonEvt =
| { evt: "exit"; data: { surface_id: string; code: number } }
| { evt: "surface_created"; data: { surface_id: string; workspace_id: string } }
@@ -96,7 +100,8 @@ export type DaemonEvt =
| { evt: "groups_changed"; data: unknown }
| { evt: "config_changed"; data: { config: ConfigView } }
| { evt: "event"; data: { record: EventRecord } }
| { evt: "events_read"; data: { ids: number[] } };
| { evt: "events_read"; data: { ids: number[] } }
| { evt: "events_cleared"; data: unknown };
export function onDaemonEvent(handler: (evt: DaemonEvt) => void): Promise<() => void> {
return listen<DaemonEvt>("spacesh:evt", (e) => handler(e.payload));