Roadmap: second review applied (rename before step 1, drops keep the session, pipeline before what's next, release step, AppGraph root, protocol 16 additions)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-15 19:44:17 +03:00
1 parent a1b270fd4e
commit a52133b313
1 file changed
+97 -27
+97 -27
View File
@@ -86,33 +86,72 @@ the automatic continuation and countdown, relay state), the device
settings, notification prefs, what this app is sourcing) and the
integrations (Jellyfin played/resume, AniList list).
0. Safety net and seams, no visible change. Left: Connection extracted
from the coordinator. (Done: tests audited and tagged per step, P27
fixed, AppEvent + Notifier seam, platform ports, protocol 16 planned.)
1. Connection + Identity + Presence, proto 16 in full (one bump, with a
min/max version range and a stable refusal frame so later changes are
additive): a lasting client id; a socket close no longer removes the
member (explicit `bye`); presence joining/in/away/gone with grace;
`PresencePolicy` in core used by server and app alike; bounded D2
hold; relay `sourceGone` and source resume; queue `by` lasting;
`members[].app`. Fixes P1 P3 P4 P6, server half of P2; D1 D2 D5 D6.
2. Status (one computed level) + Notifier (pure policy over events:
toasts above the controls, SoundEngine, system notifications, activity
log). Fixes P8 P11 P12 P21 P33, visible half of P2; D4, D1, D9 alone
policy ("alone" = no other connected member, In or Away).
3. Your server: one hub, listeners attached (loopback; LAN on Invite),
nothing restarts; Invite as a value (link/deep link, filtered
addresses, version gate). Fixes P22 P23 P24; D9.
4. Library + Media identity: L1 local grouping (pure Dart, offline: kinds
episode/movie/extra, folder hints, gaps, merge across folders) and L2
match (AniList/TVDB ids, art, names, manual pin, cache); one `MediaRef`
carried in swap/queue media. Fixes P34 and Files' next episode.
5. What's next (decides which/when, never opens; continuation and
countdown as server state, shared cancel) + Watch tracking (time In,
device history, Jellyfin now, AniList later). Fixes P7 P9; D3.
6. Media pipeline + player host port: plan → resolve → open → ready,
commands queued during a switch, source-drop recovery, resolver and
source chosen by capability. Fixes P2 (client) P15 P17 P18 P28 P29;
P30 P31 P32 as a player-host sub-step; D6 client half, D11.
from the coordinator (one sealed connection state, hello built by a
factory, reconnect delay and silence threshold as parameters); a
`noticeFor` table test; a flaky-link test harness (pure-Dart TCP proxy:
pass, blackhole, delay, reset, late frame); grace and silence through
the engine clock; an env override for client id and data folder (two
instances on one machine); the scenario list in DEVELOPMENT "Tests".
(Done: tests audited and tagged, P27, AppEvent + Notifier, ports,
protocol 16 planned.)
R. Rename (before step 1, so dev builds install side by side with 1.2.x):
app id `dev.foxyz.starsync`, binary, Windows company `foxyz`; Hive out
of `~/Documents` into the app's support folder, copied not moved;
a settings schema version with ordered migrations; an `AppDirs` port;
the 1.3.0 archive still contains `watch_together(.exe)` so 1.2.x's
self-update restart works; the old `.desktop` entry removed; the
manifest kept at the old URL; VPS `ExecStart`/`update.sh` paths.
1. Connection + Identity + Presence; proto 16 opened (version ranges on
both sides, refusal frame, identity, grace, presence, relay resume,
ids in the feed); later steps add fields, it is frozen at the 1.3.0
release. A drop is not the end of the session: keep the last state
(marked stale), the player, media and relay source; the UI reads the
connection state (no update band over a running video, no emptied
queue, no reopen); reconnect with backoff and jitter; silence 8–10 s
with "connection unstable" from ~3 s; lost/back sounds only after a
few seconds of drop. New feed actions mapped one-to-one onto today's
notices, matched by member id, not name. Presence shown in the strip
and party UI (joining, away, gone = dimmed "reconnecting"). The relay
source re-dials on return (client half of D6). Auto sit-out with a
`ShellPort.watchActivity` (focused/unfocused/minimised/hidden): never
when alone; while playing only minimised-for-a-while counts; the
client re-sends its intent after a non-resumed welcome. A joiner holds
only while not playing, at most ~15 s; while playing never (D2). The
app close sends `bye` and flushes it; update-and-restart does not.
`Profile` moves to identity. Fixes P1 P3 P4 P6, server half of P2;
D1 D2 D5 D6 (source).
2. Status (one computed level, incl. the server's hold reason) + Notifier
(`noticeFor(event, context)`: self presence, alone, window activity,
playing, prefs, replayed; owns its toast timer; system notifications
port; library snack bars moved in). Fixes P8 P11 P12 P21 P33, "source
left"; D4, D1 notifications, D9 alone policy ("alone" defined once in
core next to `PresencePolicy`).
3. Your server: one hub, listeners attached and detached without members
reconnecting (old isolate path kept until that's proven); Invite as a
value (filtered addresses, version gate from `/health.version`, an `fp`
field reserved for pinned certificates later); `SessionState` is
deleted here. Fixes P22 P23 P24; D9.
4. Library + Media identity: L1 local grouping (kinds, folder hints,
gaps, merge across folders) and L2 match (AniList/TVDB, art, names,
manual pin, cache); `MediaRef` in swap/queue media. Fixes P34.
5. Media pipeline + player host port: plan → resolve → open → ready,
commands queued during a switch, viewer-side recovery when a source
drops (no mpv seek loops, play what's buffered), resolver and source
chosen by capability; a Windows CI smoke job (start, open, close
within N s, no orphan process). Fixes P2 P15 P17 P18 P28 P29, P30–P32;
D11.
6. What's next (decides which/when, asks the pipeline to prepare;
continuation and countdown as server state, shared cancel) + Watch
tracking (time In from the presence history, device history, Jellyfin
now, AniList later). Fixes P7 P9; D3.
7. Release 1.3.0: publish the manifest (protocol 16 makes it a required
update for 1.2.x) and update the VPS dedicated server in the same
window; check a 1.2.x app gets a clear "update the app" message.
Composition root: an `AppGraph` built in `main.dart` only constructs and
disposes systems (no logic, no signals); systems get dependencies through
constructors, widgets through one `AppScope`; a test fails if the count
of `.instance` singletons grows (today: SessionState 94, AppSettings 51,
JellyfinAccounts 10, Profile 7).
Not in the restructure: P25 (ops).
@@ -239,6 +278,37 @@ Transport: paths unchanged. A server with TLS serves the same endpoints
over `wss://`/`https://` (`/health.tls`); apps use `wss://` when offered,
`ws://` stays allowed.
**Protocol 16 additions (second review):**
- Client range too: `hello.protoMin`/`proto`; the server picks the
highest common version; read `proto` before strict hello parsing; 16
apps still send `hello.media:{}` so a 15 server answers "proto
mismatch"; a 16 app shows a 15 refusal as "the host runs an older
Starsync".
- Ordered takeover: `hello.seq` (monotonic per client), older hellos get
`err stale`; `replaced` is terminal on the client ("opened in another
window"); `welcome.instance` per server boot (grace expired vs server
restarted).
- Error codes `stale`, `forbidden`, `rate`, `full`, plus `err.for`; close
codes listed in one place. `/health.version` (no member names).
- Silence: no frame for ~15 s closes the socket (member `gone`); the
`gone` feed line only after ~10 s gone (a quick return emits nothing);
`presence.reason` manual/idle/minimised/bye; away → in while playing
goes through `joining`; `joining` ends on a report error or timeout; a
second `/source` socket for the same token replaces the first.
- Feed: `actorId`/`targetId`; `feed.seq` with `hello.feedAfter` and a
bounded replay in `welcome` (marked `replayed`: logged, not toasted).
- `state.hold {reason ready|buffer|joining|source|start, members, until}`;
`members[].caps`.
- Relay: `sourceGoneSince`; "same info" = size, duration, tracks, name.
- Queue: `auto` items carry `for: <mediaId>`, added only by the current
swapper (or first in); `advance` holds while its performer or source is
gone; `cancel` drops the `auto` item too.
- `MediaRef` gains `absolute`; `season` may be null.
- `client` is redacted from every log; optionally derived per server
(HMAC of an install secret and the dialled host:port).
- wss: only with a CA-valid certificate in 1.3.0; pinned self-signed via
the Invite `fp` later.
## Backlog
1. **Auto-skip OP/ED** (undecided): who decides and how (shared for the