* fix(orbit): harden the guest outbox — recover lost suggestions, avoid a duplicate outbox
Two outbox robustness issues on the poll-based, non-atomic playlist
transport:
- Lost-update: a track a guest appends to its outbox can be wiped by a
concurrent host sweep-clear before the host recorded it, leaving the
suggestion lost AND stuck on "waiting on host" forever (it only clears
once it reaches the host's play queue). The playlist read-modify-write
can't be made atomic, so recover: re-send a pending suggestion the host
hasn't recorded (absent from state.queue) past a grace window — the host
dedupes by (user, trackId), so it's idempotent — and give up + toast past
a 45s window so the row stops hanging. New pendingResend planner module
with unit tests; suggest + tick share ensureTrackInOutbox.
- Duplicate outbox: a transient getPlaylists failure at join was swallowed
to [], so the existing-outbox check missed and a second outbox was
created. Distinguish "lookup failed" from "genuinely absent" and retry
once before falling back to create.
New i18n key for the give-up toast across all locales.
* fix(orbit): reject non-http(s) schemes in share invites
normalizeShareServerUrl only prepends http:// when the server string doesn't
already start with "http", so an invite carrying a scheme like httpx:// or
https-phish:// slips through unchanged. Reject anything whose parsed protocol
isn't http(s) at parse time, before the known-server match. Add round-trip
tests covering the accepted and rejected schemes.
* fix(orbit): read exit-modal role fresh to avoid a stale keydown closure
The exit modal's Enter/Escape handler binds once per open (deps [isOpen]) and
closed over the role prop, so a role change while the modal was up would act
on a stale value. Read role from the store inside the action instead, and drop
the now-unused reactive subscription.
* docs(changelog): consolidate Orbit fixes into one block, add 1159
Each src/locales/<lang>.ts (~1800 LOC) becomes a folder src/locales/<lang>/
with one module per i18n namespace (44 each) plus an index.ts barrel that
reassembles <lang>Translation in the original key order.
Mechanical, script-driven split with a JSON round-trip check: every
locale object is byte-identical to its pre-split form. i18n.ts is
unchanged — './locales/<lang>' now resolves to the folder index.
The per-namespace settings.ts files land ~440-460 LOC; a single i18n
namespace is the natural, non-arbitrary split unit for a flat string
table, so they are intentionally left whole.