feat(e24): reach the Home surface — the landing toggle, a palette command, Shift+W
S4's reachability half. Home already led the nav order; what was missing was every way IN other than the rail. - Settings → Appearance → Startup: "Start on Home", the documented escape hatch for the landing override. Deliberately still localStorage and not a server pref — `loadInitialPage` answers before /api/me lands, and a landing module decided after the first paint is a visible jump. Its two stored values are now ONE exported constant, so the writer and the boot-time reader cannot drift on a magic string. - The command palette gets "Meglepetés" / "Surprise me": it navigates to Home and rerolls the hero on arrival. No second "Mit nézzek?" row beside it — the Home module row already matches that phrase through its descriptive title. - A global Shift+W jumps to Home. A chord, not a bare `w`: the Plex player binds bare letters and `w` sits on `s`, so a blind reach for the subtitle key would have thrown a viewer out of the film. The palette's request crosses a lazy module boundary, so it is a token that waits (lib/homeIntent) rather than an event fired into an empty room. Browser-measured: taken with a compare-and-clear, because StrictMode's double-invoked mount effect otherwise rerolled twice and landed on the page two picks deep. HU/EN parity kept; UAT on localdev in both languages, including the empty shelf.
This commit is contained in:
+24
-10
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createStore } from "./store";
|
||||
|
||||
// ── The app's ONE keyboard map ────────────────────────────────────────────────────────────────
|
||||
@@ -56,6 +56,10 @@ export const SHORTCUTS = [
|
||||
// (E10 review round 1). Its handler preventDefaults, so nothing leaks to the browser either way.
|
||||
{ id: "palette", scope: "global", keys: ["Mod+k"], labelKey: "keys.palette", allowInInput: true },
|
||||
{ id: "help", scope: "global", keys: ["h"], labelKey: "keys.help" },
|
||||
// Shift+W and not a bare `w` (E24): the Plex player binds bare letters (a s d f h i k m) and `w`
|
||||
// sits directly above `s`, so a blind reach for the subtitle key would yank you out of the film
|
||||
// and onto the landing page. A chord makes it the deliberate "I'm stuck — answer the question".
|
||||
{ id: "home", scope: "global", keys: ["Shift+w"], labelKey: "keys.home" },
|
||||
// --- the row context menu (wherever useContextMenu is wired) ---------------------------------
|
||||
// Its OWN scope, not `global`: only the surfaces that actually call `useContextMenu` register it,
|
||||
// so the sheet stops advertising "Open the context menu" on the feed, Plex, Downloads and Settings
|
||||
@@ -281,11 +285,16 @@ export const usePaletteOpen = (): boolean => paletteOpen.use();
|
||||
* key belonging to the other, and `dispatch`'s exhaustiveness would stop meaning anything. App
|
||||
* calls this; the overlays are pure renderers of their store.
|
||||
*
|
||||
* `enabled` must track where those overlays are actually MOUNTED. Bound unconditionally it swallowed
|
||||
* `h` and Mod+K on the loading/Welcome/setup screens — the browser shortcut was preventDefault'ed,
|
||||
* nothing appeared, and the flipped module-level store then popped the overlay open by itself the
|
||||
* moment the signed-in shell mounted (E10 review round 1). */
|
||||
export function useGlobalKeymap(enabled = true): void {
|
||||
* `enabled` must track where those overlays (and the pages Shift+W jumps to) are actually MOUNTED.
|
||||
* Bound unconditionally it swallowed `h` and Mod+K on the loading/Welcome/setup screens — the
|
||||
* browser shortcut was preventDefault'ed, nothing appeared, and the flipped module-level store then
|
||||
* popped the overlay open by itself the moment the signed-in shell mounted (E10 review round 1). */
|
||||
export function useGlobalKeymap(enabled: boolean, onHome: () => void): void {
|
||||
// The Home jump is the one global key whose action lives OUTSIDE this file — navigation is a
|
||||
// provider and `lib/` may not import a component — so App hands it in. Kept in a ref so a fresh
|
||||
// closure each render doesn't rebind the window listener (and can't leave a stale `setPage`).
|
||||
const homeRef = useRef(onHome);
|
||||
homeRef.current = onHome;
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
// Leaving a stale `true` behind would spring the overlay open on the next mount.
|
||||
@@ -294,10 +303,11 @@ export function useGlobalKeymap(enabled = true): void {
|
||||
return;
|
||||
}
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
// Both global keys TOGGLE an overlay, and each overlay mount/unmount costs a real history
|
||||
// entry (useBackToClose). On auto-repeat that is ~30 push/back pairs a second, which Chrome
|
||||
// starts dropping while our own depth bookkeeping keeps counting them — a desynced stack whose
|
||||
// next browser Back walks out of the app. Holding a key is not a second press.
|
||||
// Two of the three global keys TOGGLE an overlay, and each overlay mount/unmount costs a real
|
||||
// history entry (useBackToClose); the third (Shift+W) pushes a page entry of its own. Either
|
||||
// way, on auto-repeat that is ~30 push/back pairs a second, which Chrome starts dropping while
|
||||
// our own depth bookkeeping keeps counting them — a desynced stack whose next browser Back
|
||||
// walks out of the app. Holding a key is not a second press.
|
||||
if (e.repeat) return;
|
||||
dispatch(e, "global", {
|
||||
help: (ev) => {
|
||||
@@ -309,6 +319,10 @@ export function useGlobalKeymap(enabled = true): void {
|
||||
ev.preventDefault();
|
||||
paletteOpen.set((v) => !v);
|
||||
},
|
||||
home: (ev) => {
|
||||
ev.preventDefault();
|
||||
homeRef.current();
|
||||
},
|
||||
});
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
|
||||
Reference in New Issue
Block a user