Commit5882af3unknown_key
feat(ux): j/k keyboard list navigation + shortcuts page grouping
feat(ux): j/k keyboard list navigation + shortcuts page grouping - j/k keys move focus through list rows on PR, issue, notification, and explore pages; Enter follows the focused link; x toggles selection - .is-kbd-focus and .is-kbd-selected CSS classes highlight the active row - /shortcuts page now groups shortcuts by section (Global / Lists) and documents the new j/k/Enter/x bindings https://claude.ai/code/session_01ACsT2Pc8GRoZwZRF8SK68Y
2 files changed+67−165882af3ec0e9cdc45dcd0f2e758f1e62026d6921
2 changed files+67−16
Modifiedsrc/routes/search.tsx+28−16View fileUnifiedSplit
@@ -941,31 +941,43 @@ search.get("/search", async (c) => {
941941search.get("/shortcuts", async (c) => {
942942 const user = c.get("user");
943943 const unread = user ? await getUnreadCount(user.id) : 0;
944 const shortcuts: Array<{ keys: string; desc: string }> = [
945 { keys: "/", desc: "Focus global search" },
946 { keys: "Cmd/Ctrl + K", desc: "Open AI assistant" },
944 const shortcuts: Array<{ keys: string; desc: string; section?: string }> = [
945 { keys: "/", desc: "Focus global search", section: "Global" },
946 { keys: "Cmd/Ctrl + K", desc: "Open command palette / AI assistant" },
947 { keys: "?", desc: "Show keyboard shortcuts" },
948 { keys: "n", desc: "New repository" },
947949 { keys: "g d", desc: "Go to dashboard" },
948950 { keys: "g n", desc: "Go to notifications" },
949951 { keys: "g e", desc: "Go to explore" },
950 { keys: "n", desc: "New repository" },
951 { keys: "?", desc: "Show this help" },
952 { keys: "g a", desc: "Go to AI ask" },
953 { keys: "j", desc: "Move selection down on list pages", section: "Lists" },
954 { keys: "k", desc: "Move selection up on list pages" },
955 { keys: "Enter", desc: "Open selected item" },
956 { keys: "x", desc: "Toggle select on focused item" },
952957 ];
953958
959 const sections = [...new Set(shortcuts.map((s) => s.section ?? "Global"))];
960
954961 return c.html(
955962 <Layout title="Keyboard shortcuts" user={user} notificationCount={unread}>
956963 <h2 style="margin-bottom: 16px">Keyboard shortcuts</h2>
957 <div class="panel">
958 {shortcuts.map((s) => (
959 <div class="panel-item" style="justify-content: space-between">
960 <span>{s.desc}</span>
961 <kbd
962 style="font-family: var(--font-mono); background: var(--bg-tertiary); padding: 2px 8px; border: 1px solid var(--border); border-radius: 4px; font-size: 12px"
963 >
964 {s.keys}
965 </kbd>
964 {sections.map((section) => (
965 <>
966 <h3 style="color: var(--text-muted); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; margin: 20px 0 8px">{section}</h3>
967 <div class="panel">
968 {shortcuts.filter((s) => (s.section ?? "Global") === section).map((s) => (
969 <div class="panel-item" style="justify-content: space-between">
970 <span>{s.desc}</span>
971 <kbd
972 style="font-family: var(--font-mono); background: var(--bg-tertiary); padding: 2px 8px; border: 1px solid var(--border); border-radius: 4px; font-size: 12px"
973 >
974 {s.keys}
975 </kbd>
976 </div>
977 ))}
966978 </div>
967 ))}
968 </div>
979 </>
980 ))}
969981 </Layout>
970982 );
971983});
Modifiedsrc/views/layout.tsx+39−0View fileUnifiedSplit
@@ -900,6 +900,33 @@ const navScript = `
900900 else if (e.key === 'a') { e.preventDefault(); window.location.href = '/ask'; }
901901 chord = null;
902902 }
903 // j/k list navigation — move through .prs-row, .issue-row, .notif-item rows
904 if (e.key === 'j' || e.key === 'k') {
905 var selectors = '.prs-row, .issue-row, .issue-list-item, .notif-item, .repo-item, .exp-repo-card, .disc-row';
906 var items = Array.from(document.querySelectorAll(selectors));
907 if (items.length === 0) return;
908 e.preventDefault();
909 var cur = items.findIndex(function(el){ return el.classList.contains('is-kbd-focus'); });
910 var next = e.key === 'j' ? (cur < 0 ? 0 : Math.min(items.length - 1, cur + 1)) : (cur < 0 ? items.length - 1 : Math.max(0, cur - 1));
911 items.forEach(function(el){ el.classList.remove('is-kbd-focus'); });
912 items[next].classList.add('is-kbd-focus');
913 items[next].scrollIntoView({ block: 'nearest' });
914 return;
915 }
916 if (e.key === 'Enter') {
917 var focused = document.querySelector('.is-kbd-focus');
918 if (focused) {
919 e.preventDefault();
920 var link = focused.tagName === 'A' ? focused : focused.querySelector('a');
921 if (link && link.href) { window.location.href = link.href; }
922 return;
923 }
924 }
925 if (e.key === 'x') {
926 // 'x' selects/deselects focused item (future: bulk actions)
927 var sel = document.querySelector('.is-kbd-focus');
928 if (sel) { e.preventDefault(); sel.classList.toggle('is-kbd-selected'); return; }
929 }
903930 });
904931 })();
905932`;
@@ -2861,6 +2888,18 @@ const css = `
28612888 }
28622889 .panel-item:last-child { border-bottom: none; }
28632890 .panel-item:hover { background: var(--bg-hover); }
2891
2892 /* ─── j/k keyboard list navigation ─── */
2893 .is-kbd-focus {
2894 outline: 2px solid rgba(140,109,255,0.6) !important;
2895 outline-offset: -2px;
2896 background: rgba(140,109,255,0.06) !important;
2897 border-radius: 4px;
2898 }
2899 .is-kbd-selected {
2900 outline: 2px solid rgba(52,211,153,0.6) !important;
2901 background: rgba(52,211,153,0.06) !important;
2902 }
28642903 .panel-empty {
28652904 padding: 24px;
28662905 text-align: center;
28672906