Updater v1 --upgrade + roadmap as flat backlog

Updater: watch-together --upgrade checks updates/manifest.json,
downloads the tarball, verifies sha256, applies over the live
install (extract-temp + delete-first move-over; rename-safe on
Linux while running). Zero Flutter imports, 7 tests incl.
stub-server flows. Version stamped via --dart-define=APP_VERSION
in package script. Warns (never silently jumps) on proto mismatch.
Roadmap: phases retired as tracker (history in git log), now flat
Built/Backlog/Epics/Deferred; Jellyfin next feature, transcode
never; updater expansion stays an epic. Endpoint publishing
(manifest + release tarball) is user-owned.
This commit is contained in:
foxyz committed 2026-09-12 18:12:28 +03:00
1 parent b2173e82c8
commit d77b0e804d
9 files changed
+505 -103

No files matched your search

+51 -85
View File
@@ -1,99 +1,65 @@
# ROADMAP — sync_relay_project (canonical phase tracker)
# ROADMAP — sync_relay_project (backlog 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.
This is the durable source of truth for what is built and what is next.
Chat is ephemeral; this file + `HANDOFF.md` (root) + `PROTOCOL.md` /
`ARCHITECTURE.md` carry state across sessions. Check an item off when it
lands, same commit as the code. 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
## Built (phases 0–4 + hardening — done, details in git history)
- DONE: exit criteria met, tests green at the time.
- PARTIAL: shipped + tested, exits still open.
- TODO: not started or explicitly deferred.
Scaffold + CI, mpv Range contract, sessions/tokens/media endpoint,
sync engine + quorum, desktop shell (SyncClient, RoomDiff, media_kit,
avatars, toasts+sheet, reconnect), presence hardening, frameless
chrome, chat/idle fixes, chat double-row fix, avatar 256 KiB cap,
Linux tarball packager + Discord upload card, updater v1
(`--upgrade`: manifest check, download, sha256, apply-over).
WAN-tested 2-client. Counts at last green: core 42, server 41,
desktop 17 + 7 updater.
## Phase 0 — Scaffold (DONE)
## Backlog (priority order, each one sitting-sized)
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.
1. Update endpoint live: publish `updates/manifest.json` + first
release tarball on Forgejo (user-owned step; schema in
`updates/manifest.example.json`). Until then `--upgrade` reports
up-to-date against a missing manifest.
2. Jumpy seek: live repro with timeline log first, then fix the
seek-land retry vs deadband/debounce fight.
3. Sounds swap-in: replace `NoopSfxService` (flutter_soloud preload,
Kenney/ObsydianX/akx sources in HANDOFF §7); keep seek/pause/
resume cues muted or near-silent.
4. `/health` auth + stop broadcasting the full media URL (api_key
leaks onto the wire).
5. File-join wart: welcome mediaUrl advertises 127.0.0.1, remote file
joiners fail-open then self-heal on first room broadcast.
6. Cache-exhaustion long outage via `tool/shaper.dart --kill-after`.
7. Phase 5 chaos suite (host drop 5 s / 30 s, viewer stall, 20-seek
storm) + OOM / stuck-room / drift-recovery assertions.
8. Windows zip in `tool/package_linux.sh`'s sibling + Discord card.
9. TLS/Caddy doc.
10. Chat history pane (schema exists, Activity log only for now).
11. Backfill LAN soak + 1.4 GB test details (record-only, user ran
both 09-12).
## Phase 1 — mpv HTTP contract (DONE)
## Epics (multi-sitting, scheduled whole — not before the backlog
friction items above, except Jellyfin which is next feature)
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).
- File relay (original brief, second half): links shipped, relay
unbuilt. Split into sub-tasks when scheduled.
- Jellyfin native — NEXT feature.
- Updater expansion: in-app prompt, Windows helper (locked-exe
dance), auto-restart after apply.
- Playlist (`queue-add`/`queue-skip`, shape reserved in PROTOCOL.md),
sidecar subtitles, local-file fallback (viewer plays matching
size+duration file locally, still synced), `HttpSource` (host
pastes a direct-stream URL, server fetches + re-serves).
## Phase 2 — Sessions + media endpoint (DONE)
## Deferred (needs explicit approval; transcode is never)
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 (256 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.
P2P/WebRTC, multi-session, Android. Transcode: NEVER (user decision
09-12 — not the age for it anymore).