feat: BIP-39 master seed for unified key derivation
Replace fragmented random key generation with a single 24-word BIP-39 mnemonic that deterministically derives all node keys: Ed25519 (DID), secp256k1 (Nostr/Bitcoin), BIP-84 xprv (Bitcoin Core), and LND aezeed entropy. New onboarding flow: seed generate → word verification → identity naming. Restore path enabled via 24-word entry. Includes seed RPC handlers, mock backend support, LND/Bitcoin Core wallet-from-seed integration, and UI polish across settings and discover views. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5da9e217e6
commit
19dcfd4f31
@@ -84,9 +84,13 @@ function getNavBarItems(): HTMLElement[] {
|
||||
|
||||
function isNavBarItem(el: HTMLElement | null): boolean {
|
||||
if (!el) return false
|
||||
return isInZone(el, 'main') &&
|
||||
!el.hasAttribute('data-controller-container') &&
|
||||
!el.closest('[data-controller-container]')
|
||||
if (!isInZone(el, 'main')) return false
|
||||
if (el.hasAttribute('data-controller-container') || el.closest('[data-controller-container]')) return false
|
||||
// On container-free pages (e.g. Settings), don't classify elements as nav bar items —
|
||||
// let them fall through to the main zone handler which supports linear up/down/right nav.
|
||||
const zone = document.querySelector('[data-controller-zone="main"]')
|
||||
if (zone && !zone.querySelector('[data-controller-container]')) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** Inner focusables within a container (buttons, links — not the container itself) */
|
||||
@@ -425,6 +429,13 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
if (dest) {
|
||||
focusEl(dest)
|
||||
} else {
|
||||
// Check if this is a container-free page (e.g. Settings) — focus first button immediately
|
||||
const zone = document.querySelector('[data-controller-zone="main"]') as HTMLElement | null
|
||||
const hasAnyContainers = zone?.querySelector('[data-controller-container]')
|
||||
if (!hasAnyContainers && zone) {
|
||||
const focusable = getFocusableElements(zone)
|
||||
if (focusable[0]) { focusEl(focusable[0]); return }
|
||||
}
|
||||
// Containers not rendered yet (route transition / animation in progress)
|
||||
// Poll until they appear, up to 1s
|
||||
let attempts = 0
|
||||
@@ -436,9 +447,8 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
focusEl(retryContainers[0])
|
||||
} else if (attempts >= 10) {
|
||||
clearInterval(poll)
|
||||
// No containers on this page (e.g. Settings) — focus first focusable element
|
||||
const z = document.querySelector('[data-controller-zone="main"]') as HTMLElement | null
|
||||
if (z) { const f = getFocusableElements(z); if (f[0]) focusEl(f[0]) }
|
||||
// Last resort: focus first focusable element
|
||||
if (zone) { const f = getFocusableElements(zone); if (f[0]) focusEl(f[0]) }
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
@@ -477,31 +487,20 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dir === 'down') {
|
||||
// Down from nav bar → jump to containers (remember tab for Up return)
|
||||
rememberFocus('navBar', activeEl)
|
||||
const containers = getContainers()
|
||||
const nearest = findNearestInDirection(activeEl, containers, 'down')
|
||||
if (nearest) { rememberFocus('main', nearest); focusEl(nearest); return }
|
||||
// Fallback: just focus first container
|
||||
if (containers[0]) { rememberFocus('main', containers[0]); focusEl(containers[0]); return }
|
||||
// Containers not rendered yet — poll until they appear
|
||||
let attempts = 0
|
||||
const poll = setInterval(() => {
|
||||
attempts++
|
||||
const retryContainers = getContainers()
|
||||
if (retryContainers[0]) {
|
||||
clearInterval(poll)
|
||||
rememberFocus('main', retryContainers[0])
|
||||
focusEl(retryContainers[0])
|
||||
} else if (attempts >= 10) {
|
||||
clearInterval(poll)
|
||||
}
|
||||
}, 100)
|
||||
return
|
||||
if (dir === 'down' || dir === 'up') {
|
||||
// Up/Down from standalone element → find nearest focusable (container or button) in direction.
|
||||
// Searches containers + standalone elements together so mixed pages (Settings) don't jump.
|
||||
if (dir === 'down') rememberFocus('navBar', activeEl)
|
||||
const zone = document.querySelector('[data-controller-zone="main"]') as HTMLElement | null
|
||||
if (zone) {
|
||||
const allFocusable = getFocusableElements(zone).filter(el =>
|
||||
el.hasAttribute('data-controller-container') ||
|
||||
!el.closest('[data-controller-container]')
|
||||
)
|
||||
const target = findNearestInDirection(activeEl, allFocusable, dir)
|
||||
if (target) { focusEl(target); return }
|
||||
}
|
||||
}
|
||||
|
||||
// Up from nav bar → nothing (use Escape to go to sidebar)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -509,8 +508,14 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
if (isInZone(activeEl, 'main')) {
|
||||
const containers = getContainers()
|
||||
|
||||
// Try spatial nav to another container
|
||||
const next = findNearestInDirection(activeEl, containers, dir)
|
||||
// Try spatial nav to containers + standalone focusables (not inner buttons).
|
||||
// This handles mixed pages (e.g. Settings) where containers and buttons coexist.
|
||||
const zone = document.querySelector('[data-controller-zone="main"]') as HTMLElement | null
|
||||
const navTargets = zone ? getFocusableElements(zone).filter(el =>
|
||||
el.hasAttribute('data-controller-container') ||
|
||||
!el.closest('[data-controller-container]')
|
||||
) : containers
|
||||
const next = findNearestInDirection(activeEl, navTargets, dir)
|
||||
if (next) {
|
||||
rememberFocus('main', next)
|
||||
focusEl(next)
|
||||
|
||||
Reference in New Issue
Block a user