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:
1 parent
a1b270fd4e
commit
a52133b313
1 file changed
+97
-27
+97
-27
@@ -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
|
||||
|
||||
Reference in new issue
Block a user