diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-16 16:07:18 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-16 16:07:18 +0200 |
| commit | 2916aa376009283305a7acec4aafa3c96544499e (patch) | |
| tree | bc19d010e8bebf39c175271564b3be01d1c06aa0 /packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js | |
| parent | 25f62e169e049f0757ef611d5b409e7523958dee (diff) | |
| download | meshbay-2916aa376009283305a7acec4aafa3c96544499e.tar.gz | |
playlists: make the writes actually leave the browser
Reported from a phone: signing in with the same account showed no
playlists. syncWith was called from exactly one place in the interface,
so creating a playlist, deleting one, removing a track and saving the
queue all wrote to IndexedDB and stopped there. The store pushes itself
now, coalesced, so a new mutation cannot forget to.
Silence was the real defect. The node audited only successes, so a
refusal left no trace and user_blob_list none at all; the background
push swallowed its reason; the interface said nothing. All three report
now, and "Sync now" says what happened either way.
An unreadable blob on a node was treated as a fetch failure and returned
before the push — permanent, once the node held anything. It is an
absence: the client is the authority, and it gets overwritten.
A sign-in reconciles whatever this browser already holds, a pending push
is flushed when the page goes away, and a push that did not land is
retried once.
no_key is spelled out: a client that signs in with its remembered device
key only ever has a bundle key persisted before the playlist subkey
existed, and an AES handle is non-extractable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js | 20 |
1 files changed, 19 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js b/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js index 8d85b68..e2fb3f9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js @@ -183,7 +183,25 @@ function PlaylistMenuButton({ userId, lists, reload, onPlayQueue, onSync, cached onSelect: async () => { const r = await onSync(); await reload(); - say(r && r.ok ? t('playlists.synced') : t('playlists.sync_failed')); + // The reason, not just "it failed": a sync that quietly does nothing + // is the one failure mode nobody can act on, and the reason is what + // separates an offline node from a wedged one. + // On success, say how much moved; on failure, say why. Either way + // the reader learns something they can act on — the one thing this + // never did. + const last = P.lastSync(); + const why = (r && r.reason) || last.reason || '?'; + // `no_key` is not a fault the reader can do anything about unless it + // is spelled out. This browser signed in with its remembered device + // key, so the only bundle key it has is one persisted before the + // playlist subkey existed — and an AES handle is non-extractable, so + // there is nothing to derive it from. One sign-in with the passphrase + // fixes it for good; a code on screen does not say that. + say(r && r.ok + ? `${t('playlists.synced')} (${r.pushed || 0}↑ ${r.pulled || 0}↓)` + : (why === 'no_key' + ? t('playlists.err_no_key') + : `${t('playlists.sync_failed')}: ${why}`)); }, }, ]); |