GET /events sends every event to every listener and lets the client sort it out: static/live.js filters in the browser, and an agent holding the stream open gets the whole forge whether it asked for one app or not. The direction says an agent can subscribe too — subscribing means saying what you want, on the wire, once.
Done looks like
[ ] GET /events?source=&kind= filters on the server, using the same rule the timeline uses: source exact, kind exact or a dotted prefix (order matches order.placed, ord matches nothing). One predicate, exported and pure, used by the stream and by listEvents — not a second copy that agrees today.
[ ] Both parameters are validated at the boundary against SOURCE_RE and KIND_RE; an invalid one is a plain 400 with a named reason, never an empty stream that looks like a quiet forge.
[ ] Filtering applies to the replay too: Last-Event-ID and ?since= replay only what matches, so a reconnecting subscriber does not receive a burst of what it filtered out, and the ids it sees still increase.
[ ] Non-event.posted messages (app.registered and whatever else emit carries) are not delivered to a filtered stream at all: a subscriber that asked for one app's events did not ask for the registry's.
[ ] static/live.js passes the page's current filter on the stream URL and keeps its client-side check as a belt (the server is the rule, the client is the render), and a filtered page reconnecting with ?since= does not miss a row that matches.
[ ] docs/adopting.md gains four lines under a "following" heading: curl -N "$RELAY_URL/events?source=bazaar&kind=order", what a retry: line means, and that the hundred-event replay buffer is the limit of what a reconnect recovers.
[ ] src/live.test.ts covers: a filtered subscriber receiving only matches, the prefix rule, an invalid parameter refused, the filtered replay, a second unfiltered subscriber still receiving everything at the same time, and the unsubscribe still unsubscribing.
Risks: this edits the same file as #26, which is why it waits on it — two pulls in src/live.ts is how #22 rotted. Keep the exported surface (emit, subscribe, events) the shape #26 leaves it. The filter must be applied where the event is delivered, not where it is kept, or a filtered subscriber will silently shrink the shared buffer for everyone else.
Suggested: Scout — it is one predicate, one query string and a test that two subscribers see different things.
GET /eventssends every event to every listener and lets the client sort it out:static/live.jsfilters in the browser, and an agent holding the stream open gets the whole forge whether it asked for one app or not. The direction says an agent can subscribe too — subscribing means saying what you want, on the wire, once.Done looks like
GET /events?source=&kind=filters on the server, using the same rule the timeline uses:sourceexact,kindexact or a dotted prefix (ordermatchesorder.placed,ordmatches nothing). One predicate, exported and pure, used by the stream and bylistEvents— not a second copy that agrees today.SOURCE_REandKIND_RE; an invalid one is a plain400with a named reason, never an empty stream that looks like a quiet forge.Last-Event-IDand?since=replay only what matches, so a reconnecting subscriber does not receive a burst of what it filtered out, and the ids it sees still increase.event.postedmessages (app.registeredand whatever elseemitcarries) are not delivered to a filtered stream at all: a subscriber that asked for one app's events did not ask for the registry's.static/live.jspasses the page's current filter on the stream URL and keeps its client-side check as a belt (the server is the rule, the client is the render), and a filtered page reconnecting with?since=does not miss a row that matches.docs/adopting.mdgains four lines under a "following" heading:curl -N "$RELAY_URL/events?source=bazaar&kind=order", what aretry:line means, and that the hundred-event replay buffer is the limit of what a reconnect recovers.src/live.test.tscovers: a filtered subscriber receiving only matches, the prefix rule, an invalid parameter refused, the filtered replay, a second unfiltered subscriber still receiving everything at the same time, and the unsubscribe still unsubscribing.Files:
src/live.ts,src/live.test.ts,static/live.js,docs/adopting.mdRisks: this edits the same file as #26, which is why it waits on it — two pulls in
src/live.tsis how #22 rotted. Keep the exported surface (emit,subscribe,events) the shape #26 leaves it. The filter must be applied where the event is delivered, not where it is kept, or a filtered subscriber will silently shrink the shared buffer for everyone else.Suggested: Scout — it is one predicate, one query string and a test that two subscribers see different things.