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:
2026-09-08 00:06:34 +02:00
parent 82914b56ca
commit 828dad015c
18 changed files with 238 additions and 29 deletions
+49 -2
View File
@@ -30,8 +30,16 @@ afterEach(() => {
});
/** Renders the live stores so an assertion reads what a real consumer would render. */
function Host({ enabled = true, onState }: { enabled?: boolean; onState: (s: string) => void }) {
useGlobalKeymap(enabled);
function Host({
enabled = true,
onState,
onHome = () => {},
}: {
enabled?: boolean;
onState: (s: string) => void;
onHome?: () => void;
}) {
useGlobalKeymap(enabled, onHome);
const help = useKeyboardHelpOpen();
const palette = usePaletteOpen();
useEffect(() => {
@@ -111,6 +119,45 @@ describe("useGlobalKeymap", () => {
expect(state).toBe("none");
});
// E24. The chord is the point: the Plex player binds bare letters and `w` sits on top of `s`, so a
// bare `w` would yank a viewer out of the film on a blind reach for the subtitle key.
it("jumps Home on Shift+W and ignores a bare w", () => {
const onHome = vi.fn();
render(<Host onState={() => {}} onHome={onHome} />);
key({ key: "w" });
expect(onHome).not.toHaveBeenCalled();
key({ key: "w", shiftKey: true });
expect(onHome).toHaveBeenCalledTimes(1);
});
it("leaves Shift+W to the text field it was typed into", () => {
const onHome = vi.fn();
render(<Host onState={() => {}} onHome={onHome} />);
const field = document.createElement("input");
document.body.appendChild(field);
field.focus();
act(() => {
field.dispatchEvent(
new KeyboardEvent("keydown", { key: "w", shiftKey: true, bubbles: true, cancelable: true }),
);
});
expect(onHome).not.toHaveBeenCalled();
field.remove();
});
// The handler is App's `() => setPage("home")` — a fresh closure every render. It is held in a ref
// rather than in the effect's deps, so the listener is bound once; this pins that the ref is the
// CURRENT one and not the closure captured at mount.
it("calls the latest handler after a re-render, without rebinding", () => {
const first = vi.fn();
const second = vi.fn();
const { rerender } = render(<Host onState={() => {}} onHome={first} />);
rerender(<Host onState={() => {}} onHome={second} />);
key({ key: "w", shiftKey: true });
expect(first).not.toHaveBeenCalled();
expect(second).toHaveBeenCalledTimes(1);
});
// Bound above the branch that mounts the overlays, the keys were swallowed on the loading/Welcome
// screens and the flipped store then sprang the overlay open by itself once the shell mounted.
it("binds nothing, and clears a stale store, while disabled", () => {