diff --git a/internal/httpapi/sse.go b/internal/httpapi/sse.go index 4a5a398..9cd928f 100644 --- a/internal/httpapi/sse.go +++ b/internal/httpapi/sse.go @@ -209,12 +209,22 @@ func sqlcEventToGen(ev sqlcgen.Event) gen.Event { // writeSSE writes a single Event as an SSE message. Returns false if the // write failed (client disconnected). flusher may be nil (io.Pipe path, // which has no separate flush step). +// +// We deliberately DO NOT set the SSE `event:` name field, even though every +// event has a type. A named SSE event is only delivered to a matching +// addEventListener(type) handler, NOT to EventSource.onmessage — and the whole +// frontend (stores/events.ts and every page that reads liveEvents) consumes the +// stream via onmessage, reading the type from the JSON payload's `type` field. +// Emitting `event: ` silently routed every event away from onmessage, so +// the live stream delivered nothing to the UI. Leaving the name off sends all +// events to onmessage; the type is already in `data`, and new event types need +// zero client changes. `id:` is kept for Last-Event-ID reconnection. func writeSSE(w ioWriter, flusher http.Flusher, ev sqlcgen.Event) bool { data, err := json.Marshal(sqlcEventToGen(ev)) if err != nil { return true // skip un-serializable events } - _, err = fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", ev.ID, ev.Type, data) + _, err = fmt.Fprintf(w, "id: %d\ndata: %s\n\n", ev.ID, data) if err != nil { return false } diff --git a/web/src/App.svelte b/web/src/App.svelte index f3cceeb..e7378c1 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -1,6 +1,6 @@ - -
-

Sessions

-
- {#each $sessions as session (session.id)} -
- - -
- {:else} -
No sessions yet. Start chatting with Nomos.
- {/each} -
-
- - diff --git a/web/src/pages/Tasks.svelte b/web/src/pages/Tasks.svelte new file mode 100644 index 0000000..397bcf0 --- /dev/null +++ b/web/src/pages/Tasks.svelte @@ -0,0 +1,213 @@ + + +
+
+
+

Tasks

+

Every task is a goal Nomos works to completion.

+
+ +
+ + +
+ {#each FILTERS as f} + + {/each} +
+ +
+ {#if visible.length === 0} +
+

+ {filter === 'all' + ? 'No tasks yet. Start one and Nomos will plan it, execute it, and report the outcome.' + : `No ${FILTERS.find((f) => f.id === filter)?.label.toLowerCase()} tasks.`} +

+ {#if filter === 'all'} + + {/if} +
+ {:else} +
+ {#each visible as s (s.id)} + {@const st = statusStyle(s)} +
+ + +
+ {/each} +
+ {/if} +
+