64 KiB rejected normal 128px PNGs. 128 KiB fits any 128x128 PNG (raw <= 64 KiB, worst-case base64 ~95 KiB chars) while still bounding abuse. PNG kept for transparency. Pipeline now measures the encoded base64 against RelayConstants (was checking the wrong variable with its own 512 KiB limit) and drops dead crop code. Server policy, still v4. Trade noted: avatars ride the 1 Hz room broadcast, uplink scales with cap; hash-reference follow-up later.
100 lines
4.3 KiB
Markdown
100 lines
4.3 KiB
Markdown
# ROADMAP — sync_relay_project (canonical phase tracker)
|
||
|
||
This is the durable source of truth for phases. Chat is ephemeral;
|
||
this file + `HANDOFF.md` (root) + `PROTOCOL.md` / `ARCHITECTURE.md`
|
||
carry state across sessions. Update this file when a phase changes
|
||
state. Proto bumps still require PROTOCOL.md same-commit.
|
||
|
||
Model: direct-serve (host opens ports, clients open nothing). The
|
||
brief's old WSS host-upload tunnel is obsolete unless a VPS tier
|
||
needs remote-host upload. Server = Relic, client WS =
|
||
web_socket_channel. Protocol still v4.
|
||
|
||
## Status legend
|
||
|
||
- DONE: exit criteria met, tests green at the time.
|
||
- PARTIAL: shipped + tested, exits still open.
|
||
- TODO: not started or explicitly deferred.
|
||
|
||
## Phase 0 — Scaffold (DONE)
|
||
|
||
Monorepo, path deps, CI (`dart analyze` + `dart test` on core/server),
|
||
logging/config/errors, Flutter-free enforcement tests, relay binary
|
||
binds/logs/exits, stub ARCHITECTURE.md.
|
||
|
||
## Phase 1 — mpv HTTP contract (DONE)
|
||
|
||
Relic `resolveMediaRequest` + `mountMediaRoute`, full Range table
|
||
(200 / 206×3 shapes / 416 / 401 / 403 / 410 / 503), byte-exact tests,
|
||
real mpv traces in `mpv-http-contract.md`, `tool/range_logger.dart`
|
||
+ `tool/shaper.dart` (KB/s cap, latency, --kill-after).
|
||
|
||
## Phase 2 — Sessions + media endpoint (DONE)
|
||
|
||
Secret → 128-bit token, 401/403/410 gates, kick kills WS+media,
|
||
byte counters, `tool/fake_viewer.dart` ALL PASS live. Proto v2→v3
|
||
for `kicked`/`kick`. Media: local-file relay SHIPPED
|
||
(`FileMediaSource` + token-gated Range route); links path SHIPPED
|
||
(server distributes shared URL, viewers stream direct);
|
||
`HttpSource` (server-side URL fetch) DEFERRED to Phase 8; WSS
|
||
upload tunnel NOT BUILT (spec reserved in ARCHITECTURE.md).
|
||
|
||
## Phase 3 — Sync engine + readiness (DONE)
|
||
|
||
`RoomEngine` (injectable clock): server-owned clock, reports never
|
||
move it, stale-eventId guard, free seeks, PLAYING/HELD gate, quorum
|
||
(in-set≥2 unless ≤1 fresh), swap reset, force with actor feed.
|
||
22+ engine tests, `tool/e2e_check.dart` 15/15 sim. Syncplay audit
|
||
done (conscious deviations documented in HANDOFF §2). Proto v4.
|
||
Follow-ups landed: NaN guards, name/chat caps, stale vs leave feed,
|
||
1 Hz feed flush, silent-drop (15 s stale / 30 s drop + close).
|
||
|
||
## Phase 4 — Desktop app (PARTIAL)
|
||
|
||
Shipped: SyncClient framing, RoomDiff (eventId diff, 0.95× latch),
|
||
media_kit wiring, Space=ready, avatars, toasts+sheet, health display,
|
||
reconnect loop. Counts at last green: core 42, server 40, desktop 17.
|
||
Late 09-12 session: internet test PASSED (WAN, port 6969, 2-client);
|
||
server logging wired into app (`initLogging` in `main.dart` + hello
|
||
join/reject logs in `room_hub.dart`); Cancel aborts a pending
|
||
connect (`SyncClient.disconnect` completes the welcome future);
|
||
first-join failure shows the dialog and stops (auto-reconnect only
|
||
after an established session drops).
|
||
Open exits (manual, user-driven): bisect flags REMOVED 09-12;
|
||
2-client LAN soak + 1.4 GB real-file test user-reports-done 09-12
|
||
(details pending). Still open: cache-exhaustion long outage.
|
||
|
||
## Phase 5 — Resilience (PARTIAL)
|
||
|
||
Shipped: isClosed sweep, onError forget, stop() closes sockets,
|
||
silence-timeout + reconnect loop. Never run: shaper chaos suite
|
||
(host drop 5 s / 30 s, viewer stall, 20-seek storm), OOM / stuck-room
|
||
/ drift-recovery assertions, CI wiring.
|
||
|
||
## Phase 6 — Identity & social (PARTIAL)
|
||
|
||
Shipped: names (32 cap), avatars (128 KiB cap, 128 px PNG picker, transparency kept), kick nudge
|
||
+ reason, feed attribution. Open: real chat pane/history (schema
|
||
only), sounds swap-in (wiring + NoopSfxService ready; pick
|
||
flutter_soloud or equivalent, Kenney/ObsydianX/akx sources in
|
||
HANDOFF §7).
|
||
|
||
## Phase 7 — Deployment & updates (TODO, deferred)
|
||
|
||
Thin `bin/server.dart` compiles; systemd unit exists. Missing:
|
||
Windows zip, Linux tar/AppDir, TLS/Caddy doc, update manifest +
|
||
verifier. Needs explicit user go + code-signing decision.
|
||
|
||
## Phase 8 — Optional quality (TODO, deferred)
|
||
|
||
Playlist (`queue-add`/`queue-skip`, shape reserved in PROTOCOL.md),
|
||
sidecar subtitles, local-file fallback (viewer plays matching
|
||
size+duration file locally, still synced — saves host uplink),
|
||
`HttpSource` (host pastes a direct-stream URL, server fetches +
|
||
re-serves — makes Jellyfin usable through the whitelist today, no
|
||
native integration). No code until approved.
|
||
|
||
## Deferred backlog (needs explicit approval)
|
||
|
||
Jellyfin native, P2P/WebRTC, multi-session, transcode, Android.
|