Edits across all 13 EGGs to remove implementer guesswork. After this an
implementer can build a listener and speaker without reading our source.
README:
- Conventions section: hex casing rule (lowercase, 64 chars, no 0x),
NIP-01 foundations, `a`-tag form, created_at tie-break, JSON / time.
- Joining sequence: numbered end-to-end walkthrough from `kind:30312`
to first audio frame, referencing each EGG.
EGG-01 (room event):
- `relays` tag is one tag with multiple values (not multi-tag).
- `service` URL trailing-slash normalization.
- `private` status: gate is implementation-defined, not "render as open".
- `d`-tag charset locked to [A-Za-z0-9._-] so it interpolates safely
into the moq-auth namespace.
- Relay-discovery rule: publish to `relays` tag ∪ NIP-65 outbox.
- Tie-break on identical created_at via smallest event id.
EGG-02 (auth):
- JWT signing pinned: ES256 over P-256, JWKS at /.well-known/jwks.json,
5-minute relay cache.
- NIP-98 tags pinned: u / method / payload, base64 RFC 4648 standard
(not base64url).
- Error taxonomy: full HTTP status + `error` slug table for /auth, plus
WebTransport CONNECT 200/401/403/404 table.
EGG-03 (audio):
- Pin moq-lite Lite-03 to kixelated/moq-rs `rs/moq-lite/src/lite/`.
- One Opus packet per moq Frame (no container, no timestamp).
- Pubkey hex casing reaffirmed at the suffix / broadcast slots.
- AnnouncePlease prefix="" for speaker discovery.
- Mute = stop publishing (not silence frames, not Announce Ended).
- Mid-stream join: discard pre-skip per RFC 7845.
EGG-04 (presence):
- Heartbeat jitter ±5 s required (anti-thundering-herd).
- "0"/"1" are strings, not booleans.
- Single-room rule via replaceable-event semantics.
EGG-05 (chat): 8 KB suggested, 64 KB hard cap, 3 msg/s render rate.
EGG-06 (reactions): 30s window measured against created_at, drop-on-arrival
if already stale.
EGG-07 (moderation): replay protection — dedupe kicks by id within 120 s.
EGG-08 (scheduling): planned rooms MUST 403 at the auth sidecar.
EGG-10 (theming): bg image caps (1 MB / 4096 px soft, 8 MB / 8192 px hard).
Deferred (per review): EGG-00 (Conventions as a standalone spec), EGG-13
(capability advertisement), EGG-14 (discovery), test vectors corpus.
The "Conventions" section in README covers EGG-00's most urgent content
inline.
https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
3.2 KiB
EGG-06: Reactions (kind:7)
status: draft
requires: EGG-01
category: optional
Summary
Audience-side ephemeral feedback (👏 ❤️ 🎉 ...) is carried on standard
NIP-25 kind:7 reaction events, scoped to the room via an a-tag.
Clients render these as a short-lived floating overlay over the
participant grid.
A reaction is NOT durable state. Clients SHOULD NOT page through historical reactions; only the recent window matters.
Wire format
{
"kind": 7,
"pubkey": "<reactor pubkey hex>",
"tags": [
["a", "30312:<host pubkey hex>:<room d>", "<relay hint>"],
["p", "<target pubkey hex>"]?,
["emoji", "<shortcode>", "<image url>"]?
],
"content": "<emoji glyph or :shortcode:>",
...
}
content carries the reaction itself. Standard Unicode emoji glyphs are
used directly. Custom emoji follow NIP-30: content = ":shortcode:" with
a sibling ["emoji", shortcode, url] tag.
A reaction MAY include a ["p", target] tag scoping the reaction to a
specific speaker (renders over their avatar). With no p tag, the reaction
is room-wide (renders centered on the room canvas).
Behavior
- Reactors MUST emit
kind:7events with at least onea-tag matching EGG-01 and a non-emptycontentfield. - Receivers MUST apply a 30-second sliding window measured against the
reaction's
created_at: a reaction is rendered from the moment it is observed untilcreated_at + 30 s(wall-clock unix seconds), then dropped from the floating overlay. A reaction that arrives already older than 30 s MUST be dropped on receipt without ever being shown. - Receivers SHOULD throttle their reaction-overlay updates to at most 4 Hz (250 ms minimum between repaints). At ~30 reactions per second a naive recomposition can starve the audio decoder.
- Receivers MUST tolerate unknown
contentvalues (e.g. a Unicode glyph without a font fallback) by falling back to a generic indicator rather than dropping the reaction. - Receivers MUST NOT auto-fetch the
["emoji", _, url]image at the moment of receipt — that opens an animated-emoji-driven DDoS vector. Image fetch SHOULD be batched and rate-limited. - A reactor MAY include the same
a-tagged reaction more than once (e.g. spamming claps). Receivers MUST NOT de-duplicate by content alone; events with different ids are always distinct.
Example
A clap reaction targeted at a speaker:
{
"kind": 7,
"pubkey": "ghi...audience",
"created_at": 1714003220,
"tags": [
["a", "30312:abchost:office-hours-2026-04"],
["p", "def...co"]
],
"content": "👏",
"id": "...",
"sig": "..."
}
A custom-emoji reaction (NIP-30):
{
"kind": 7,
"pubkey": "ghi...audience",
"created_at": 1714003221,
"tags": [
["a", "30312:abchost:office-hours-2026-04"],
["emoji", "amethyst", "https://example/amethyst.png"]
],
"content": ":amethyst:",
"id": "...",
"sig": "..."
}
Compatibility
EGG-06 is independent from EGG-05 (chat). A peer MAY implement either, both, or neither without affecting interop on EGG-01 through EGG-04.
Future EGGs MAY introduce paid reactions (kind:9735 zap receipts at the
same a-tag). Implementers SHOULD be prepared to source the reaction
overlay from both kind:7 and kind:9735 streams.