Server and core: version ranges on both sides with a coded refusal; a
client id maps to the same member and token for the server's life; a
drop is `gone` for a 10-minute grace keeping ready, presence, queue
items, relay and avatar; `bye` leaves at once; ordered takeover (seq,
replaced/stale); presence joining/in/away/gone with one PresencePolicy;
joiners never pause playback and hold a held gate at most ~15 s;
state.hold; feed ids, seq and replay; the relay lives through its
source's grace and continues on re-dial; queue `by` lasting, auto
items and cancel; MediaRef carried; client ids never logged.
App: persisted client id and seq; Connection with unstable/lost/
replaced states, backoff with jitter, no lost/back pair per flap; a drop
keeps the session (state, player, media, relay source); presence in the
strip and members card; sit out = away intent; feed matched by id;
auto sit-out (idle when not playing, minimised for a while; never when
alone) replaces auto-unready; bye on close and Leave, not on update
restart; relay source re-dials through its grace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connection owns the socket lifecycle, connect/dial with epochs,
reconnect, the clock offset, diagnostics upload and a sealed ConnState
signal; the hello is built by an injectable builder, the resume token
stays private, and the reconnect delay and silence limit are parameters.
The coordinator maps ConnState to the same health texts and keeps the
reconnect restore and drop handling (step 1 replaces them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tests: the four known failures fixed (test-side causes), one duplicate
file and four detail-pinning cases removed, every test that pins
behaviour the restructure changes tagged with its step.
- Every toast, sound and activity-log line goes through Notifier.notify
(AppEvent), mapped one-to-one to today's behaviour.
- Platform ports (shell, URLs, folders, link resolver, system
notifications) with desktop implementations; window_manager, Process
and file_picker now live only in platform/desktop/.
- ROADMAP: protocol 16 planned; app id dev.foxyz.starsync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PlayerControlsModel holds the controls' state and actions once; the
Standard skin (our own, no more media_kit controls or private-state
reach-ins) and Minimal (the former uosc design, made the app's own and
credited) only draw it. One layout spec for both: play first, then
previous/next, volume, time; subtitles, audio, settings, chat,
fullscreen last; everything else in the settings menu. Settings ->
Player -> Controls: time display, size, hide delay, optional buttons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server keeps one small relay (32 MiB ahead, 8 behind) for Up next's
first relayed item, keyed by its queue id at a URL that stays the same
after the swap: fetched by the server for server-fetch links, fed by
the adder's app for through-me links and files, by a member with an
account for member-passed Jellyfin items. Every app preloads from it;
the swap promotes it with its buffer. Dropped when the item stops
being first or its adder leaves. Measured switch: ~1.6 s cold, 20-40 ms
preloaded. ROADMAP gains the research item on server-side fetching of
member-resolved URLs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every app gets the first Up next item ready itself (own Jellyfin plan,
direct links, guests via /j when state.relayNext says the server's
login can pass it on) and opens it in a hidden, muted, paused second
player; the swap promotes that player instead of opening cold, so its
buffer counts at once. Reports go out on open and when the buffer
reaches the gate's need. SwitchTrace times each phase. Measured in the
e2e harness: 1.5-3 s before, 35-103 ms with a preload. Relayed items
still open cold for everyone but their source (ROADMAP).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Overlay: one layout for both designs (top bar with Up next as one of
its buttons; one action band above the controls for the status pill,
skips, next episode and end cards), one glass recipe (AppGlass) and
one pill (FloatingPill); hovering any of them, or the open queue,
holds the controls up.
- Up next: the first item (or the first continuation episode) is kept
resolved on the app that puts it up, refreshed before it expires;
Schedule and drop/paste can queue too.
- Jellyfin: WatchSession (core) counts real watched coverage; an
episode is watched at 80 % or at its ending after half; a played one
is re-marked after Playing (10.11 unmarks on open) and never gets a
position; a peek leaves the old resume point; part-way resume points
stay under 85 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Up next: a server-side queue anyone edits by item id (add at end or
next, move, remove, play now, skip); each item says who added it and
how it plays. At the real end the first item swaps in after the end
card's countdown, put up by whoever can (its adder for through-me links
and files, someone with access for Jellyfin, else anyone); items that
can't play are skipped with the reason. The next item's link is
resolved ahead. Greyed "Continues with" episodes below. A chip with the
controls opens it as a popover (leaf_widgets, local path dependency for
now); "Add to queue" next to Put it up everywhere.
Delivery: the server measures the source's rate, the media's bitrate
and each member's read rate; the members card shows a source line
(who passes it on, through what, keeping up or not, source left) and
each member's route and buffer. A slow source is told once per media.
A failed server fetch is announced to everyone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>