Addresses review feedback: the explanatory block comments in
FullscreenProvider (source-of-truth rationale, CoreTransport typing
note) were noise — the same context already lives in the PR
description and commit history. Copyright headers on the four new
files were carried over from the template at 2023; bumped to 2026.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The merge of development into this branch combined two incompatible
changes: development's new GamepadNavigation files (#882) imported
useFullscreen from the old 'stremio/common/useFullscreen' path, while
this branch removed that file in b7f7a3d2e and moved the hook to
'stremio/common/Fullscreen'. Webpack failed to resolve the module on
CI even though git auto-merged without conflict markers.
Update the single stale import in useHorizontalNavGamepadNavigation.tsx
to point at the new module. The new index.ts exports useFullscreen as
default, so the import shape is unchanged.
Verified locally with `pnpm build` (passes with only pre-existing
bundle-size warnings).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolves a conflict in src/App/App.js between the new GamepadProvider
(landed via #882 on development) and FullscreenProvider on this branch.
Both wrap parts of the app tree at the same point.
Resolution: nest as <GamepadProvider> > <ShortcutsProvider> >
<FullscreenProvider>. FullscreenProvider stays innermost so it remains
inside ShortcutsProvider — required for the onShortcut('fullscreen', ...)
subscription added in 35b100767. Both ShortcutsModal and GamepadModal
render as siblings inside FullscreenProvider. NavBar conflict
auto-merged cleanly (kept useFullscreen import, added gamepad-nav hook).
Lint and types clean on App.js, HorizontalNavBar.js, FullscreenProvider.tsx.
1. Player.js — Added optional chaining (gamepad?.on/gamepad?.off) to prevent null crash
2. GamepadProvider.tsx — Toast messages now use t('GAMEPAD_CONNECTED') / t('GAMEPAD_DISCONNECTED') via i18next, added keys to en-US.json
3. useVerticalNavGamepadNavigation.tsx — Fixed event.nativeEvent?.spatialNavigationPrevented → event.spatialNavigationPrevented (native DOM events don't have nativeEvent)
4. GamepadProvider.tsx — Changed connectedGamepads from useState to useRef to avoid rAF effect restart cycle, removed it from effect deps, simplified the enabled guard
shortcuts.json already declares fullscreen -> F, but the provider was
listening for KeyF on its own keydown handler in parallel — both fired
on every F press, with the canonical shortcuts dispatch going nowhere.
Subscribe via onShortcut('fullscreen', toggleFullscreen, ...) and drop
the KeyF branch (plus the now-unused inputFocused check). Escape stays
local because its action is gated on the escExitFullscreen profile
setting; F11 stays local because it's shell-only and not in
shortcuts.json.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous NewState listener fired on every change to the ctx model —
notifications, search history, library sync, streaming-server URL — and
each fire triggered a getState('ctx') round-trip to the worker just to
re-read escExitFullscreen.
Switch to the CoreEvent / SettingsUpdated channel (same pattern App.js
uses for interfaceLanguage/quitOnClose), reading the new value straight
from the event payload. Initial seed still uses getState('ctx') once
on mount.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
useServices is already typed via src/services/ServicesContext/useServices.d.ts,
so the @ts-expect-error suppression on the import was unnecessary and
masked two real type holes that surfaced once it was removed:
- core.transport.getState('ctx') returns Promise<object>; cast to the
ambient Ctx type so escExitFullscreen is read through a typed path.
- CoreTransport.on/off types listeners as () => void, but the 'NewState'
event actually emits a string[]. Use a (...args: unknown[]) wrapper +
Array.isArray narrowing so the call site stays type-safe without
weakening the ambient transport signature.
No behavior change.
Made-with: Cursor
The C shortcut tracked enabled/disabled via a ref that was never reset
on stream change and was not updated when the user picked tracks via
the menu, so the toggle could become inverted or no-op until pressed
twice. Read selectedSubtitlesTrackId / selectedExtraSubtitlesTrackId
directly instead.