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 settings, notification prefs, what this app is sourcing) and the
integrations (Jellyfin played/resume, AniList list). integrations (Jellyfin played/resume, AniList list).
0. Safety net and seams, no visible change. Left: Connection extracted 0. Safety net and seams, no visible change. Left: Connection extracted
from the coordinator. (Done: tests audited and tagged per step, P27 from the coordinator (one sealed connection state, hello built by a
fixed, AppEvent + Notifier seam, platform ports, protocol 16 planned.) factory, reconnect delay and silence threshold as parameters); a
1. Connection + Identity + Presence, proto 16 in full (one bump, with a `noticeFor` table test; a flaky-link test harness (pure-Dart TCP proxy:
min/max version range and a stable refusal frame so later changes are pass, blackhole, delay, reset, late frame); grace and silence through
additive): a lasting client id; a socket close no longer removes the the engine clock; an env override for client id and data folder (two
member (explicit `bye`); presence joining/in/away/gone with grace; instances on one machine); the scenario list in DEVELOPMENT "Tests".
`PresencePolicy` in core used by server and app alike; bounded D2 (Done: tests audited and tagged, P27, AppEvent + Notifier, ports,
hold; relay `sourceGone` and source resume; queue `by` lasting; protocol 16 planned.)
`members[].app`. Fixes P1 P3 P4 P6, server half of P2; D1 D2 D5 D6. R. Rename (before step 1, so dev builds install side by side with 1.2.x):
2. Status (one computed level) + Notifier (pure policy over events: app id `dev.foxyz.starsync`, binary, Windows company `foxyz`; Hive out
toasts above the controls, SoundEngine, system notifications, activity of `~/Documents` into the app's support folder, copied not moved;
log). Fixes P8 P11 P12 P21 P33, visible half of P2; D4, D1, D9 alone a settings schema version with ordered migrations; an `AppDirs` port;
policy ("alone" = no other connected member, In or Away). the 1.3.0 archive still contains `watch_together(.exe)` so 1.2.x's
3. Your server: one hub, listeners attached (loopback; LAN on Invite), self-update restart works; the old `.desktop` entry removed; the
nothing restarts; Invite as a value (link/deep link, filtered manifest kept at the old URL; VPS `ExecStart`/`update.sh` paths.
addresses, version gate). Fixes P22 P23 P24; D9. 1. Connection + Identity + Presence; proto 16 opened (version ranges on
4. Library + Media identity: L1 local grouping (pure Dart, offline: kinds both sides, refusal frame, identity, grace, presence, relay resume,
episode/movie/extra, folder hints, gaps, merge across folders) and L2 ids in the feed); later steps add fields, it is frozen at the 1.3.0
match (AniList/TVDB ids, art, names, manual pin, cache); one `MediaRef` release. A drop is not the end of the session: keep the last state
carried in swap/queue media. Fixes P34 and Files' next episode. (marked stale), the player, media and relay source; the UI reads the
5. What's next (decides which/when, never opens; continuation and connection state (no update band over a running video, no emptied
countdown as server state, shared cancel) + Watch tracking (time In, queue, no reopen); reconnect with backoff and jitter; silence 8–10 s
device history, Jellyfin now, AniList later). Fixes P7 P9; D3. with "connection unstable" from ~3 s; lost/back sounds only after a
6. Media pipeline + player host port: plan → resolve → open → ready, few seconds of drop. New feed actions mapped one-to-one onto today's
commands queued during a switch, source-drop recovery, resolver and notices, matched by member id, not name. Presence shown in the strip
source chosen by capability. Fixes P2 (client) P15 P17 P18 P28 P29; and party UI (joining, away, gone = dimmed "reconnecting"). The relay
P30 P31 P32 as a player-host sub-step; D6 client half, D11. 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). 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, over `wss://`/`https://` (`/health.tls`); apps use `wss://` when offered,
`ws://` stays allowed. `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 ## Backlog
1. **Auto-skip OP/ED** (undecided): who decides and how (shared for the 1. **Auto-skip OP/ED** (undecided): who decides and how (shared for the