Fifth full codebase audit across all five modules (lib-ssh, lib-terminal-view,
lib-terminal-keyboard, lib-vault-crypto, app).
Security:
- Added cppFlags to lib-vault-crypto build — vault_crypto.cpp JNI bridge was
missing all compiler hardening flags (-fstack-protector-strong, -D_FORTIFY_SOURCE=2)
Bugs fixed:
- SessionNotifier crash: first{} → firstOrNull to prevent NoSuchElementException
- Keyboard modifiers not consumed on SwitchPage/ToggleNumBlock — armed CTRL/ALT
would persist and incorrectly modify the next key press
- KeyManagerViewModel silent exception swallow — now logs errors via FileLogger
- TelnetSession.sendTerminalType() variable shadowing fix
Dead code removed:
- Vt100Parser empty class (Vt220Parser now extends BaseTermParser directly)
- XtermParser.sendPrimaryDA() redundant override (identical to parent)
- TerminalKeyboard dead fields: menuPopupActive, menuPopupItems, miniContainer
- SpecialAction.SETTINGS_OPENED never emitted
- Deprecated 3-arg saveHostKeyFingerprint overload (no callers)
Code quality:
- Color(0xFF6E7979) → AppColors.Muted in ConnectionListScreen
- Hardcoded "v1.0.0" → BuildConfig.VERSION_NAME in SettingsScreen
- SubscriptionScreen back button contentDescription for accessibility
- TAG → companion const val in StartupCommandRunner, PortForwardManager, SftpSessionManager
- TerminalRenderer swapped KDoc comments fixed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ViewPager2 rotation phantom (Google Issue 175796502):
- KeyboardView.onSizeChanged rebuilds buildPages() on any width
transition including initial 0→actual layout, not just on resize.
The first buildPages() from setup() runs at width=0 (view still
detached), VP2's internal RecyclerView can't reliably re-anchor to
the real dimensions that arrive later, leaving a fractional scroll
offset that leaks the neighbor page on the right edge. A second
buildPages() once the real size is known produces a clean result.
- KeyboardView.lastModifierStates caches CTRL/ALT/SHIFT state so it
survives the rebuild.
- Works around an unfixed VP2 1.1.0 bug for non-FragmentStateAdapter
usage. Fresh launches and all rotation paths confirmed clean on S23.
NumBlok toggle on mini numpad:
- KeyDefinition gains numLabel/numAction optional fields
- KeyAction gains ToggleNumBlock data object
- LayoutParser understands "numLabel"/"numAction" JSON and the
"toggle_numblock" action type
- KeyboardPageView.numBlockActive swaps label rendering; toggle key
gets LOCKED-style amber glow while active
- TerminalKeyboard.numBlockActive state; handleKeyAction swaps
action→numAction; attachMiniTo carries state across rotation rebuilds
- Mini last row: Num 0 \ (base) → Num Ins ~ (numblock)
- Rows 1-3 map to PC-keypad nav: Home/↑/PgUp, ←/Esc/→, End/↓/PgDn
- QuickBarCustomizer serializer/deserializer learned toggle_numblock
Hardware keyboard auto-hide:
- MainActivity derives hasHwKeyboard from LocalConfiguration.current.
keyboard != KEYBOARD_NOKEYS && hardKeyboardHidden != HARDKEYBOARDHIDDEN_YES
- LaunchedEffect(hasHwKeyboard) { ckbHidden = hasHwKeyboard } seeds
the toggle on every HW kb connect/disconnect
- In HW kb mode the kebab Show/Hide controls both CKB and QuickBar as
a pair: val qbShownByUser = if (hasHwKeyboard) !ckbHidden else
quickBarVisible
- Normal mode unchanged (QB follows its own quickBarVisible pref)
Number row dropdown:
- KeyboardSettingsDialog now shows all four options (top/left/right/
hidden) in both orientations. Previously left/right were filtered
out in portrait, preventing the user from setting the mini mode
without first rotating into landscape.
- Portrait-override on the effective numberRowMode stays in place
(mini is landscape-only by design — documented in CLAUDE.md and
auto-memory).
Audit fixes:
- KeyboardPageView: removed key.hint!! by capturing into a local
hintText val so smart-cast flows through the render branches
- SSHKeyLoader.loadEd25519FromPkcs8: require(seed.size == 32) so a
malformed PKCS#8 blob fails fast with a clear message instead of
crashing deep inside EdDSAPrivateKeySpec
- vault_crypto.cpp nativeEncrypt: secure_zero(ptPtr, ptLen) before
ReleaseByteArrayElements on all three paths (success + two error
paths), matching the existing key-handling pattern for plaintext
defence-in-depth through the JNI boundary
Docs:
- TECHNICAL.md: NumBlok, VP2 rebuild workaround, HW keyboard auto-hide,
portrait mini override notes
- CLAUDE.md: lib-terminal-keyboard module description updated with
the same invariants
- TODO.md: recently-completed items through 2026-04-06
- Audit.md: investigation log including false positives for future
reference
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New lib-vault-crypto module with Argon2id + AES-256-GCM in C via JNI.
Export screen (full Scaffold) with item selection, password mode (min 12
chars, upper/lower/digit/special), and QR mode (background generation,
save/share gating, confirmation dialog). Import via bottom sheet with
SAF file picker, password/QR decryption, and conflict resolution.
Kebab menu replaces settings icon in ConnectionListScreen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>