The old code registered every enabled hotkey through Electron globalShortcut
API, which captures system-wide. Setting M as mute meant m could not be
typed in any other app. Now the OS-level registration only happens when
binding.global === true; otherwise the existing window-keydown listener
handles it.
Two implementation plans for the 2026-05-16 specs.
- Android white-screen: 12 tasks across 7 phases. Phase 0 wires EAS Secrets,
Phase 1-2 ship the lazy env proxy + AppBootstrap boundary + global JS
error handler, Phase 3-4 validate against a real APK, Phase 5 has
conditional hypothesis-specific fixes, Phase 6-7 close out.
- Mobile encryption port: 24 tasks across 8 phases. Extends shared
CryptoBackend with pwhash + scalarMultBase (the change that lets mobile
stop loading libsodium-wrappers-sumo in Hermes), refactors desktop
derivePublicKey through the same backend, mirrors the desktop
userIdentity orchestrator and Auth flow on RN with new PinInput, setup,
unlock, and security-settings screens, updates every device-keyed call
site, and ends with a manual Android smoke list.
Each plan ships with a spec-coverage checklist and explicit out-of-scope
list. White-screen plan must land first; mobile-encryption plan depends
on AppBootstrap deferring crypto init.
Two specs from the 2026-05-16 brainstorming session:
- mobile encryption-UX port: bring apps/mobile to feature parity with
desktop v0.18.x user-key/PIN identity. Includes the shared CryptoBackend
extension (pwhash + scalarMultBase) that removes the libsodium-wrappers-sumo
Hermes blocker.
- Android white-screen RCA: five ranked hypotheses, ordered diagnostic
playbook (env-missing, newArch, module-eval crypto init, shared sodium
side-effects, asset paths), plus defense-in-depth (lazy env proxy,
AppBootstrap boundary, global JS error handler) that ships regardless
of which hypothesis confirms.
User decisions resolved at the review gate:
- EAS Secrets for EXPO_PUBLIC_* (not eas.json env block).
- newArchEnabled: false acceptable as a temporary rollback if H2 confirms.
The inner cards (Nachrichten-Ton / Klingelton / Audio-Gerät / Hotkeys /
…) looked like they had a stark white outline. The cause: --color-line
is a semi-transparent white token, and applying the `/60` alpha modifier
in `border-line/60` overrides the original alpha — so the inner border
ended up brighter than the outer Section's normal `border-line`.
Drop the border entirely. Background tint + caps heading is enough
grouping signal in a tab-pattern panel.
The previous build used IntersectionObserver to highlight whichever
section was in view. With nine sections of unequal heights and smooth-
scroll firing observer callbacks mid-scroll, the active highlight
drifted (clicking 'Konto' showed 'Soundboard' as active because the
last section never crossed the observer's 20%-30% band).
Switched to a tab pattern (macOS System Settings / Discord / GitHub
style): the sidebar selects ONE section, only that section renders.
`useState<TabId>` is the single source of truth; no observer, no
scrolling between sections, no anchor links to clash with HashRouter.
Mobile fallback (<lg) gets a `<select>` dropdown above the panel.
Drops the now-unused `id` prop from Section and removes the
IntersectionObserver effect.
App uses HashRouter, so an <a href="#profile"> changes the routing
hash and the router can't find a match — it falls back to /chats.
Replace anchors with buttons that scroll the target section via
scrollIntoView and update the active highlight optimistically.