Commit Graph

17 Commits

Author SHA1 Message Date
Psychotoxical 7c379c2111 fix(orbit): pending counter ignored merged/declined items
The "X / Y pending" counter in the queue head and the guest-side
gate-check both used `state.queue.filter(non-host).length`, which is the
*history* count — items the host has already approved or declined still
sit in `state.queue` for attribution lookup, so the counter never
decreased. Reported as "3 / 4 pending" with no actual rows in the
approval list.

The merged / declined sets only exist in the host's local store, so the
guest can't filter them out itself. Solution: the host writes an
authoritative `pendingApprovalCount` into the state blob each tick;
guests (and the host's own UI) read it directly, with a fallback to the
old over-counting behaviour for any older client that doesn't write the
field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:30:57 +02:00
Psychotoxical cfb7e7f6c1 feat(orbit): per-guest suggestion mute + global pending-cap setting
Two anti-spam knobs the host can dial during a live session:

1. Per-guest suggestion mute — Mic / MicOff toggle next to the
   kick/ban buttons in the participants popover. Symmetric (re-enable
   later). State lives in OrbitState.suggestionBlocked: string[]; the
   guest reads it and disables its own Suggest controls so the user
   sees a clear "muted" state instead of silent failures. Host-side
   sweep also drops their outbox entries as a safety net.

2. Max pending approvals cap — number input in the session-settings
   popover, default 0 (= unlimited so existing sessions are unaffected).
   When set, the host sweep stops folding new outbox entries into the
   approval list once the cap is reached. The OrbitQueueHead surfaces
   "X / Y pending" so guests can see when they're getting close.

State changes are additive on the wire — both fields are optional, with
parseOrbitState defaulting them, so older clients keep working.

evaluateOrbitSuggestGate() centralises the guest-side allow/block check
shared between useOrbitSongRowBehavior and the ContextMenu Add-to-Session
items, plus suggestOrbitTrack as a defensive last line.

i18n: en + de + fr + nl + zh + nb + ru + es.

Also fixes an earlier dedupe-key collision: the (user, trackId) cache
keys were missing their separator (NULL byte slipped in during the
previous patch), so two tracks could share a key and one of them
silently overwrite the other. Restored the space separator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:21:51 +02:00
Psychotoxical 2b1124d14a fix(orbit): dedupe guest outbox entries per host sweep
Guest outboxes are append-only from the host's POV — every sweep reads
the same playlist. `applyOutboxSnapshotsToState` was unconditionally
pushing every trackId in every snapshot into `state.queue`, so the
pending-approval list grew by one duplicate per tick for every unhandled
suggestion (visible as 4+ identical rows after ~10 s).

Dedupe against `(user, trackId)` already present in `state.queue` or
`state.currentTrack` before appending. A guest re-suggesting the same
track after it lands/plays is a rare enough case to live with for now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:10:40 +02:00
Psychotoxical 0d18d5dfa9 refactor(orbit): switch share link to psysonic2- magic string format
Aligns orbit invites with the existing magic-string family (psysonic1- for
server invites, psysonic2- for library shares) by folding orbit into the
psysonic2- payload as a new k:'orbit' variant. The JSON body is
intentionally extendable so future layers (passwords, permissions,
invite expiry) can be added without a format migration.

- SharePayloadV1 split into EntitySharePayloadV1 + OrbitSharePayloadV1
- decodeSharePayloadFromText filters orbit out; orbit has its own decoder
- applySharePastePayload param narrowed to EntitySharePayloadV1
- buildOrbitShareLink / parseOrbitShareLink kept as thin wrappers
- slug parameter dropped (magic string is opaque, so the slug was
  cosmetic-only in the old URL form); slugifyOrbitName removed as dead code
- joinModalLinkPlaceholder updated in all 8 locales

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:59:37 +02:00
Psychotoxical f2e4c5b684 fix(orbit): tear down session on server switch + raise cleanup TTL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:16:30 +02:00
Psychotoxical 17bcac7155 chore(orbit): manual approval flow for guest suggestions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:36:44 +02:00
Psychotoxical e6d15bf9ce chore(orbit): pending-suggestion strip in guest queue
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:22:17 +02:00
Psychotoxical 089774dc3e chore(orbit): configurable shuffle interval
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:13:32 +02:00
Psychotoxical 67385c7cef chore(orbit): hide and auto-reap technical session playlists
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 12:23:36 +02:00
Psychotoxical f8bcd57ed4 chore(orbit): session-start polish
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:49:13 +02:00
Psychotoxical 11dddf6290 exp(orbit): iterative refinements
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:56:28 +02:00
Psychotoxical 2b97a7b01e exp(orbit): host merge, guest queue view, settings popover, i18n
Guest suggestions now auto-merge into the host's play queue at random
positions inside the upcoming range, so they actually surface alongside
host-picked tracks instead of piling up at the end. Per-item dedupe via
addedBy:addedAt:trackId keys survives reshuffles.

Guests get a read-only mirror of the session in place of their queue
panel — live-card for the host's current track, suggestion list with
submitter attribution, and a footer reminding them the host owns
playback. Track metadata is resolved lazily and cached locally.

New host-only settings popover (gear in the bar) with auto-approve and
auto-shuffle toggles plus a "Shuffle now" action. Settings live in the
OrbitState blob and push immediately to Navidrome; missing fields on
older blobs default to "both on".

15-min shuffle now touches the real playerStore queue via a new
shuffleUpcomingQueue action — previous behaviour only reshuffled the
guest-facing suggestion history. A manual shuffle is available from the
settings popover so the interval can be verified without waiting.

Start-modal reworked into a single step: share-link is visible and
copy-able from the start, auto-copied on Start if the host hasn't
already hit Copy. Link carries a live name-derived slug (parser strips
it, SID is authoritative). LAN/remote-server hint above the facts.
Random session-name suggester pulls from three pattern pools for ~10k
unique combinations.

All user-visible Orbit strings moved to a dedicated orbit i18n
namespace (en + de); other locales fall back to en via i18next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 02:49:42 +02:00
Psychotoxical dc82e49bd1 exp(orbit): participants list, kick flow, exit modals
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:13:00 +02:00
Psychotoxical cebf0e238d exp(orbit): periodic shuffle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:03:18 +02:00
Psychotoxical 60e0bbfa2a exp(orbit): track pipeline and participants sweep
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:59:59 +02:00
Psychotoxical 87c51e3b11 exp(orbit): guest join/leave + tick hook
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:54:58 +02:00
Psychotoxical a45943d078 exp(orbit): host lifecycle and tick hook
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:47:53 +02:00