diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b521d83..152ef2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,12 +4,15 @@ on: push: pull_request: +permissions: + contents: read + jobs: test-and-build: runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v7 - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm diff --git a/src/App.tsx b/src/App.tsx index 483b1df..2f9f355 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { createHistoryStore } from './core/historyStore'; import type { PlaylistSummary } from './core/playlists'; import { createApi } from './spotify/api'; import { createAuth } from './spotify/auth'; +import { describeError } from './spotify/errors'; import { createWebPlayer, type WebPlayer } from './spotify/player'; const CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID ?? ''; @@ -23,7 +24,7 @@ const auth = createAuth({ const api = createApi({ getAccessToken: () => auth.getAccessToken(), - forceRefresh: () => auth.forceRefresh(), + forceRefresh: (rejectedToken) => auth.forceRefresh(rejectedToken), fetch: (input, init) => window.fetch(input, init), }); @@ -108,7 +109,15 @@ export function App() { ); case 'connect': - return void auth.login()} />; + return ( + + auth.login().catch((e: unknown) => setScreen({ name: 'connect', error: describeError(e) })) + } + /> + ); case 'picker': return (

No duplicates in {playlist.name}

Every song appears once.

- diff --git a/src/components/HistoryPanel.tsx b/src/components/HistoryPanel.tsx index cabdda6..79f97d5 100644 --- a/src/components/HistoryPanel.tsx +++ b/src/components/HistoryPanel.tsx @@ -1,3 +1,4 @@ +import { useEffect, useRef } from 'react'; import type { RemovedEntry } from '../core/historyStore'; interface Props { @@ -10,8 +11,27 @@ interface Props { } export function HistoryPanel({ entries, sessionId, note, onRestore, onClose }: Props) { + const panelRef = useRef(null); + const onCloseRef = useRef(onClose); + onCloseRef.current = onClose; + + useEffect(() => { + // The panel can cover the card: move focus into it (the panel itself, not a button, + // so Space still toggles playback), let Escape close it, and give focus back after. + const previous = document.activeElement as HTMLElement | null; + panelRef.current?.focus(); + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onCloseRef.current(); + }; + window.addEventListener('keydown', onKey); + return () => { + window.removeEventListener('keydown', onKey); + previous?.focus?.(); + }; + }, []); + return ( -