chore(orbit): manual approval flow for guest suggestions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-24 17:36:44 +02:00
parent e6d15bf9ce
commit 17bcac7155
9 changed files with 347 additions and 27 deletions
+35
View File
@@ -511,6 +511,41 @@ export async function suggestOrbitTrack(trackId: string): Promise<void> {
useOrbitStore.getState().addPendingSuggestion(trackId);
}
/**
* Stable per-suggestion key across reshuffles — `addedBy`, `addedAt` and
* `trackId` are all immutable once the host sweep has written them.
* Shared between the host tick and the manual-approval UI.
*/
export const suggestionKey = (q: OrbitQueueItem): string =>
`${q.addedBy}:${q.addedAt}:${q.trackId}`;
/**
* Host: accept a guest suggestion and route it into the live play queue.
* No-op outside host role. Uses the shared `mergedSuggestionKeys` store
* slot so the tick doesn't re-process the same item.
*/
export async function approveOrbitSuggestion(q: OrbitQueueItem): Promise<void> {
const store = useOrbitStore.getState();
if (store.role !== 'host' || !store.state) return;
try {
const song = await getSong(q.trackId);
if (!song) return;
const track = songToTrack(song);
usePlayerStore.getState().enqueue([track]);
store.addMergedSuggestion(suggestionKey(q));
} catch { /* silent */ }
}
/**
* Host: reject a guest suggestion. It stays in `OrbitState.queue` as
* history but is filtered out of the approval UI and the merge tick.
*/
export function declineOrbitSuggestion(q: OrbitQueueItem): void {
const store = useOrbitStore.getState();
if (store.role !== 'host') return;
store.addDeclinedSuggestion(suggestionKey(q));
}
/**
* Host: add a track to the active Orbit session directly, skipping the
* outbox/approval loop guests go through. The track lands in the host's