forked from Foxyz/sync_replay_project
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
|
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
|
||||||
|
|||||||
Reference in new issue
Block a user