feat(e10): a jump-to palette on Ctrl/Cmd+K
Reaching a channel three modules away cost a rail, a page and a scroll. Now it costs a keystroke and half a word: Mod+K searches modules, channels, playlists, the saved views applicable where you stand, and the app-wide actions. Scope agreed with the product owner: navigation + actions, but only the reversible local ones (the shortcuts sheet, the theme). "Sync from YouTube" is deliberately absent — it is a long-running job against a quota, it wants the progress and error surface the Channels page already gives it, and one mistyped Enter is a worse failure than the walk it would have saved. Matching is word-wise and accent-folded, because this is a bilingual app and nobody reaches for the accent keys in a hurry: "pod thevr" finds TheVR Podcast and "penzugy" finds pénzügy. The arrows walk one flat result list so crossing a group heading is invisible, and the field carries the combobox/listbox aria tree that virtual selection needs. ui/CommandPalette is domain-free (items are a prop, per the ui/ import rule); AppCommandPalette supplies them. The shell is Modal again, with a new `top` placement so a search-first dialog grows downwards instead of jumping as results change. Also fixes a bug the palette hit on its first run, and that PlayerModal had already hit once: closing a dialog reclaims its Back-to-close history entry with a programmatic history.back(), which undoes any pushState made before it lands — so a command that navigates only closed the palette. The rule is now closeThenNavigate() in history.ts with a test, and PlayerModal's inline copy is gone. The global `h` moves out of KeyboardHelp into useGlobalKeymap beside Mod+K, so one listener owns the whole `global` scope and dispatch's exhaustiveness keeps meaning what it says.
This commit is contained in:
@@ -45,7 +45,7 @@ import {
|
||||
import { renderDescription } from "../lib/descriptionLinks";
|
||||
import { fullscreenElement } from "../lib/fullscreen";
|
||||
import { dispatch, isActivationTarget, useKeyScope } from "../lib/keymap";
|
||||
import { useBackToClose } from "../lib/history";
|
||||
import { closeThenNavigate, useBackToClose } from "../lib/history";
|
||||
import {
|
||||
useNativeControlsYield,
|
||||
useYtBigView,
|
||||
@@ -756,16 +756,11 @@ export default function PlayerModal({
|
||||
onClick={() => {
|
||||
const cid = active.channel_id;
|
||||
const cname = active.channel_title ?? undefined;
|
||||
// Closing the player pops its own history entry (useBackToClose → history.back()).
|
||||
// Open the channel only AFTER that pop's popstate — pushing the channel entry
|
||||
// before it would let the back remove it again (the bug: clicking the name only
|
||||
// closed the player). One-shot listener fires after the popstate handler.
|
||||
const open = () => {
|
||||
window.removeEventListener("popstate", open);
|
||||
openChannel(cid, cname);
|
||||
};
|
||||
window.addEventListener("popstate", open);
|
||||
onClose();
|
||||
// Close first, open the channel only once this player's own Back-to-close
|
||||
// entry has been reclaimed — otherwise that reclaim undoes the push and
|
||||
// clicking the name merely closes the player. The rule lives in history.ts
|
||||
// now; the command palette needs exactly the same one.
|
||||
closeThenNavigate(onClose, () => openChannel(cid, cname));
|
||||
}}
|
||||
className="font-medium hover:text-accent truncate text-left"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user