fix: batch beta fixes — 13 issues from 2026-03-28 testing
Frontend (neode-ui): - Login double-enter: change @keyup.enter to @keydown.enter (#10) - Login loop on LAN: post-login session verify before navigation (#12) - Splash flash: reorder isReady/showSplash, add black fallback div (#7) - Skip button text: remove "skip this step" from onboarding (#8) - Password UI: import existing ChangePasswordSection in Settings (#11) - Arrow key focus trap: add tab-order fallback when spatial nav fails (#13) ISO/Boot (image-recipe): - Step counter: TOTAL_STEPS=7 → 8 to match actual step count - GRUB theme: add desktop-image-scale-method stretch, widen menu - Boot noise: add loglevel=0, rd.systemd.show_status=false to kernel - USB removal: copy reboot script to tmpfs, exec from there - Tor setup: rewrite python3 JSON generation as bash heredoc - Doctor/reconcile: copy scripts into rootfs, fix missing file errors - zstd: add to rootfs packages for initramfs compression Docs: - BETA-ISSUES-20260328.md: full issue tracker - INSTALL-SCREENS-DESIGN.md: editable TUI mockups 522 tests pass, vue-tsc clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -240,9 +240,17 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
// Up/Down: exit field, navigate spatially
|
||||
e.preventDefault()
|
||||
const dir = e.key === 'ArrowDown' ? 'down' as const : 'up' as const
|
||||
const candidates = getFocusableElements(containerRef?.value ?? document).filter(el => el !== target)
|
||||
const all = getFocusableElements(containerRef?.value ?? document)
|
||||
const candidates = all.filter(el => el !== target)
|
||||
const nearest = findNearestInDirection(target, candidates, dir)
|
||||
if (nearest) focusEl(nearest)
|
||||
if (nearest) {
|
||||
focusEl(nearest)
|
||||
} else {
|
||||
// Fallback: tab order when spatial navigation fails
|
||||
const idx = all.indexOf(target)
|
||||
const fallback = dir === 'down' ? all[idx + 1] : all[idx - 1]
|
||||
if (fallback) focusEl(fallback)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Left/Right: stay in field (cursor movement). Escape: handled below.
|
||||
@@ -353,7 +361,7 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
const next = items[nextIdx]
|
||||
if (next && next !== activeEl) {
|
||||
focusEl(next)
|
||||
// Auto-navigate sidebar links
|
||||
// Auto-navigate sidebar links (not buttons — Logout etc. require Enter)
|
||||
if (next.tagName === 'A') {
|
||||
const href = (next as HTMLAnchorElement).getAttribute('href')
|
||||
if (href?.startsWith('/')) router.push(href).catch(() => {})
|
||||
@@ -493,10 +501,14 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
|
||||
function autoFocusMain() {
|
||||
const active = document.activeElement as HTMLElement | null
|
||||
// Don't steal focus from inputs, modals, or sidebar
|
||||
if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')) return
|
||||
if (document.querySelector('[role="dialog"]')) return
|
||||
if (isInZone(active, 'sidebar')) return
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// Re-check sidebar after RAF — user may still be navigating
|
||||
if (isInZone(document.activeElement as HTMLElement, 'sidebar')) return
|
||||
const remembered = recallFocus('main')
|
||||
if (remembered) { remembered.focus({ preventScroll: true }); return }
|
||||
const containers = getContainers()
|
||||
|
||||
Reference in New Issue
Block a user