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>
- Add "Settings" item to tab bar and drawer bar kebab menus — opens full
SettingsScreen from the terminal view via NavHost navigation
- Add Settings row + compact about footer (app icon, version, developer)
to drawer content panel
- Scrollback truncation: when user lowers scrollback value with active
SSH/Telnet/Local sessions, confirmation dialog warns about history loss.
ScreenBuffer.truncateHistory() trims oldest lines and clamps scrollOffset.
- TerminalService.truncateAllScrollback() iterates all session buffers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New ActionsScreen (Settings → Terminal → Hardware Key Actions): collapsible accordion cards mapping Volume Up/Down + Shake to terminal actions
- HardwareActionHandler.kt extracted from MainActivity (~284 lines): owns volume key state, double-press detection, shake sensor lifecycle, action execution
- Actions: disabled, close session, font up/down, next/prev session, scroll up/down, custom key sequence
- Double press for volume keys with configurable 200-500ms delay (default 300ms)
- Single-disabled + double-enabled = volume still works, replayed via AudioManager after delay
- Custom key sequences parsed via QuickBar's textToMenuItemAction (supports [Ctrl]x, [Alt]x, [Esc], [F1]-[F12], 0xHH)
- Layered overlapping icons for double press cards
- Accordion: only one card open at a time
- SessionEntry callbacks (onFontSizeRequest, onScrollRequest) for external view updates from Compose tree
- Shake sensor only registers when enabled and Activity resumed (zero battery cost when disabled)
- Settings: section headers in teal Space Grotesk uppercase 13sp, About section with cropped pro launcher icon
- Language moved from General to Display section, value subtitles use mono muted style
- Translated in EN/ES/SV/FR/DE
- FUTURE.md: detect remote OS, automatic session logging, opt-in diagnostic data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auth flow rewritten to be fully server-driven: no pre-connect password
dialog. TCP + KEX + host key verification happen first; password prompt
only appears when the server actually asks via keyboard-interactive or
the new prompted-password fallback (for servers that only accept the
"password" SSH method). Up to 3 password attempts per connection,
matching OpenSSH's NumberOfPasswordPrompts default.
"Remember password" checkbox now functional: AuthPromptResult threads
the remember flag through the callback chain; SSHSession stashes the
typed password in pendingRememberPassword; TerminalService persists it
to CredentialStore only after session.connect() succeeds — wrong
passwords are never saved.
Removed dead pre-connect dialog code: PasswordDialog composable,
PasswordResult, TerminalDialogRequest.PasswordPrompt, and
passwordPromptHandler.
Added docs/LEGACY_TEST_LAB.md: self-contained 2100-line guide for
building a dedicated server with 56 historical/modern Unix systems
for terminal parser conformance testing (Docker, QEMU/KVM, SIMH,
polarhome, IBM PowerVS). Includes all Dockerfiles, compose.yml,
SIMH configs, systemd units, and helper scripts inline.
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>
- Dev flavor: .dev applicationId suffix (coexists with prod), yellow icon,
all pro features via flavor ProFeaturesModule, no FLAG_SECURE
- Prod flavor: subscription-gated ProFeaturesModule, teal/gold icons
- FLAG_SECURE: three granular settings (Full App / Vault / Terminal),
biometric-gated, all default OFF, replaces single toggle
- Keys & Vault screen: combines SSH Keys, Save Vault Locally, Export/Import
- Local vault (mode 0x03): device-bound backup with DeviceFingerprint,
password-only, verified on import via ANDROID_ID + brand + model
- Free users see "Import Local Vault", pro users can import both types
- Connection list: kebab menu replaced with direct Settings gear icon
- singleTask launch mode fixes Home→icon returning to connection list
- QR scanner locked to portrait orientation
- Deploy script deletes older APK versions on duero before uploading
- Test scripts updated for .dev package name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major architecture change: single APK with Google Play Billing
subscriptions (monthly/yearly/lifetime) replacing free/pro build
flavors. Runtime feature gating via SubscriptionProFeatures with
grandfathering support. Dev/prod flavor split isolates all debug
infrastructure (ADB receiver, test profiles, log icons) from
production builds via DevConfig with const val dead code elimination.
- billing/: BillingManager, SubscriptionRepository, SubscriptionStore, SubscriptionState
- SubscriptionProFeatures replaces FreeProFeatures/FullProFeatures
- SubscriptionScreen for in-app purchase UI
- Activity-alias icon switching (teal free / gold pro)
- Pro APK migration detection
- Vault import: fix credential/key/jumpHost re-linking (id=0 bug)
- DB reset to v1 (new package, no migrations needed)
- Default font 10sp (was 14sp in library), global pref applied to new sessions
- Log download no longer clears the log (only Clear does)
- Active session timer always visible (not gated behind sessionTracking)
- Build timestamp shows version number (dev only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- enableEdgeToEdge() before super.onCreate() for proper inset handling
- SFTP rendered at Box level (outside terminal Column) so Scaffold
bottomBar with navigationBarsPadding() reaches the nav bar edge
- SFTP only composed when active tab (prevents invisible touch blocking)
- Status bar + 41dp top padding on SFTP overlay for tab bar visibility
- Removed windowInsetsPadding from terminal Column (edge-to-edge handles it)
- Cleaned unused imports and debug parameters
- docs: number row modes in KEYBOARD.md, SFTP folder reconnect done in FUTURE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- QB re-attaches when keyboard is recreated (number row mode change)
- LaunchedEffect keys include keyboard for settings re-apply
- Page indicators applied synchronously in update block (no flash)
- SFTP: compact header (Row instead of TopAppBar), tight breadcrumbs
- SFTP: system back button navigates up one folder (BackHandler)
- SFTP: bottomBar footer, reconnect remembers last folder
- SFTP: --ez sftp true launch param opens SFTP-only (no terminal)
- Slider: commit-on-drag-end prevents accidental changes while scrolling
- Hidden number hints: top-right corner position, light blue (#99CCFF)
- hintColor field on KeyDefinition for per-key hint color override
- Glossary: back button = OS nav bar back button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Number row setting: top (legacy), left/right (mini numpad), hidden (long-press)
- Mini numpad: JSON-defined side section (3x3+1 grid), separate KeyboardPageView
with touch handling, LinearLayout split in AndroidView, clipChildren fix for
SurfaceView rendering
- Font scaling: proportional 45%/25% of tallest key height, 10sp/7sp floor
(was 13sp/8sp clamping to theme minimum, too big on small keyboards)
- Tablet defaults: sameSizeBoth=false, landscape height min 20% (phones 30%)
- Disconnect notification tap navigates to session, red badge on connection cards
- CKB hide/show via tab bar kebab menu
- KeyboardDisplaySettings Eagerly shared to avoid stale initial values
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
QuickBar Customizer:
- Keys tab: view-swap pattern (Your Keys ↔ Available Keys full-screen)
- 55 keys in 4 sections (Modifiers/Navigation/Symbols/F-Keys) with OutlinedButton style
- App Shortcuts: list with undo-on-delete snackbar, tap to open editor
- App Shortcut Editor: editable name, maxCols stepper (2-6), key map drag-reorder
- Action format: sequential parser with [Ctrl]x, [Alt]x, [Esc], [F1]-[F12], 0xHH, \n, \t
- Validate/OK two-step flow, modifier insert buttons, auto-close tokens, case normalization
- Reset dialog with "also reset key order" checkbox
Connection list & tabs:
- Duplicate connection appears below original with 10s blue flash highlight
- Tab labels show end of name (start-ellipsis) for long names
- Duplicate nickname validation in EditConnectionScreen
- Save button in EditConnection title bar, Save & Connect full width
Naming & quality:
- "Quick Bar" → "QuickBar" (one word) across all UI, docs, locales
- All hardcoded strings moved to strings.xml (EN/ES/SV)
- StateFlow atomic .update{} for thread safety (20 sites in MainViewModel)
- Removed dead code: unused imports, variables, parameters
- Fixed localized string truncation (.take(60) → TextOverflow.Ellipsis)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace up/down arrows with drag handles (Modifier.draggable) for reordering
in both Keys and App Shortcuts tabs. Replace ✕ with trashcan icon positioned
left of drag handle. Available keys now shown in a compact 4-column grid
instead of a vertical list. App shortcuts auto-collapse when drag starts.
Menu items within expanded apps also use drag-and-drop reorder.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
QB positioning:
- All 5 positions work: top, above_keyboard, vertical_left, vertical_right, none (hidden)
- Compose layout restructured: Row for vertical, Column for horizontal
- Orientation set in update block (not LaunchedEffect) for reliable timing
- Popup direction: RIGHT for top/left, LEFT for right, UP for bottom
- Popup item order reverses based on direction so vim stays closest to W
- Minimum 56dp width for vertical mode
- Scroll over W/snippets buttons no longer triggers them
QB color:
- Custom colors applied via setCustomColors(keyBg, keyText) on QuickBarView
- Reads CQB or AQB color prefs based on keyboard mode
CTRL modifier:
- AKB input intercepted: armed CTRL → Ctrl+key (a-z → 0x01-0x1A), ALT → ESC+key
- getActiveModifiers() exposed from TerminalKeyboard
Tab bar:
- + button replaced with kebab menu (New Session + KB/QB Settings)
- Session label duplicate numbering: SSHTest, SSHTest (2), SSHTest (3)
- Long labels show suffix: "…kap (2)" instead of "Nordka..."
Other:
- Connect debounce (1s) prevents rapid multi-tap opening multiple sessions
- Quick-connect independent: no saved connection lookup, shows user@host as label
- W glossary entry added
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quick Bar:
- App shortcuts button (W icon, far-left) opens two-level popup:
vim/nano/tmux/scr/F1-12 → 3-column key map grid
- AppShortcutsPopupView: Canvas-based, direction-aware, tap-to-select
- PopupWindow replaced with DecorView overlay + onBackPressedDispatcher
+ layout listener for AKB close detection (one BACK closes both)
- W button toggles popup; toggle_mod actions keep popup open
- Canvas-drawn key icons (tab, backtab, arrows, pgup, pgdn) via labelIcon
- Bounded scroll mode (infiniteScroll=false default)
- CTRL, HOME, END, PGUP, PGDN added to QB scrollable keys
- Menu keys (vim/nano/tmux/screen) removed from scrollable area
Tab bar:
- + button replaced with kebab menu (New Session + KB/QB Settings)
- KB Settings label adapts to keyboard mode (CKB vs AKB)
Bug fixes:
- Alt buffer reflow: main screen content preserved during resize when
vim/htop is active (was lost on navigate away + return)
- Quick-connect: no saved connection lookup for connectionId=0, tab
label shows user@host instead of saved connection nickname
- ADB broadcast: enter fires independently from text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SFTP standalone sessions now use shared buildJumpChain for ProxyJump support,
start keepalive monitor for zombie detection, and have full disconnect/reconnect
UI with SSH state monitoring. Tab overflow menu adds "Connect to Terminal".
Fling scroll removes hard distance cap (flingMaxRows) so momentum decays
naturally. Selection toolbar Google button wired. Mouse reporting gated by
pro feature. Dead code (onSaveSnippet) removed. Write logging for paste debug.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- ensureStarted() counteracts previous stopSelf() before creating any session,
preventing service destruction when Activity unbinds (screen off, Home, task switch)
- onTaskRemoved() re-starts service to survive swipe-from-recents
- Auto-reconnect no longer removes session from map (prevents empty-map window)
- Auto-reconnect clones password CharArray before disconnect zeroes the original
- checkStopSelf() now includes SFTP sessions in the empty check
ProxyJump fix:
- onConnect callback now passes savedConnectionId directly instead of re-looking
up by host/port/username (LIMIT 1 picked wrong duplicate connection)
- Threaded through ConnectionListScreen → NavGraph → MainActivity → MainViewModel
UX improvements:
- Quick-connect history (pro): stores 100 entries with timestamps, shows 20 in
dropdown with relative dates, X to remove, dismiss on focus loss
- Disconnect snackbar suppressed for active session (user already sees the bar)
- DisconnectedBar: muted dark background with teal/grey buttons instead of
red/green/blue Christmas lights
- Session auto-switch works for Idle state (ProxyJump sessions start in Idle
during jump chain build)
Refactoring:
- SshConnectionHelper extracted: shared auth lookup, TOFU, session factory
(eliminates duplication between connectSSH, buildJumpChain, openSftpSession)
- Remove redundant stopForeground call from updateNotification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tab colors preserve type identity in all states: teal (SSH), amber (SFTP),
violet (Telnet). Disconnected tabs show dimmed type color + red dot instead
of uniform red, so you can tell session types apart at a glance.
- SFTP tabs auto-close when parent SSH disconnects (fixes frozen SFTP tab
after overnight disconnect).
- SFTP tab labels use connection alias without "SFTP" suffix (amber color
already identifies type), with host/username fallback to avoid "Session N".
- New SFTP Session option in connection list context menu and session picker
bottom sheet (amber folder icon, requires connected SSH session).
- i18n: new_sftp_session string in EN/ES/SV.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fix: SFTPClient.close() kills the parent SSH transport by
sending a channel EOF. Removed the close() call — just null out the
reference, SFTP subsystem cleans up when SSH connection closes.
Other SFTP fixes:
- Single SFTP session open (was opening twice — race corrupted transport)
- SftpSessionManager.open() is now suspend (no 500ms guesswork delay)
- SftpScreen always composed (preserves SAF launchers across picker)
- Keyboard/QuickBar hidden on SFTP tabs (tab type check)
- Disconnect notifications suppressed when app in foreground
- Title shows connection name (titleSmall style)
New SFTP features:
- File type icons (32dp): APK, image, video, audio, archive, PDF, code,
shell, keys, text — color-coded per type
- Multi-select: tap icon to select, batch download/delete bar
- Hidden files hidden by default, toggle in overflow menu
- i18n strings for hidden files toggle (EN/ES/SV)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CKB: temporary hide via ckbHidden state (not pref change), tap terminal to reshow
- CKB/QB input now scrolls terminal to bottom (was IME-only)
- Keyboard hints: all keys get hints (removed label length filter), smart
positioning (above by default, flips below if off-screen, clamped to edges)
- Long-press popup: auto-shrinks cells to fit screen width
- QB: hints on all keys (onKeyDown on ACTION_DOWN for all, not just repeatable),
scroll cancels key repeat
- PICK_HOST Back returns to terminal when sessions are open
- Copy Log ZIP now includes logcat.txt (last 5000 lines)
- TerminalKeyboard.attachTo() safe for re-attachment
- SFTP: long-press context menu, settings detail expansion, i18n additions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix foreground service lifecycle: keep service alive while disconnected sessions exist,
preventing Android from killing sessions when app is backgrounded during WiFi drops
- Fix cleanupDisconnectedSession: keep sessions in _activeSessions map so UI shows
disconnect bar and tab indicator instead of silently removing them
- FileLogger: append mode (persists across app opens), fallback to app-private dir,
ZIP export with .bin recordings, comprehensive trace logging across all components
- SessionNotifier: batched disconnect notifications (5s window), auto-cancel on reconnect,
smart handling of partial reconnects (5 disconnect, 3 reconnect → shows 2 remaining)
- Save Output: export scrollback + screen to Download/<alias>_<timestamp>.txt
- Disconnect bar: styled pill buttons, timestamps on all disconnect/reconnect messages
- Session tab bar: red accent + dark red background for disconnected tabs
- In-app snackbar: brief "Duero disconnected" alert when session drops while in app
- Pinch-zoom in mouse mode: scale detector fed before mouse intercept, multi-finger
gestures bypass mouse reporting so htop/vim resize works
- Snippet manager: duplicate action, list refreshes after edit/delete/duplicate
- Notify on disconnect setting (default on), build timestamp on connection list
- Deploy script fix, SessionRecorder rolling reset at 1MB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement VT52 mode as a standalone delegate class (Vt52Parser), activated
via CSI ?2l (DECANM reset) and exited via ESC <. Supports all VT52 escape
sequences: cursor movement (A-D), direct addressing (ESC Y row col), erase
(J/K), reverse index (I), home (H), and DECID response (ESC /Z).
Graphics character mode (ESC F/G) maps ASCII 0x5F-0x7E to DEC Special
Graphics glyphs (box-drawing, math symbols) — same charset as VT100's G0 '0'.
37 unit tests, all vttest tests 1-11 now pass including test 7 (VT52 mode).
New docs/TERMINAL_PARSER.md with full parser architecture, state machine,
DECCOLM quirks, VT52 implementation details, and graphics charset table.
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>
Three-tier zoom persistence:
- View: live fontSizeSp on TerminalSurfaceView (view lifetime)
- Service: SessionEntry.fontSizeSp (survives Activity death)
- Room: SavedConnection.fontSizeSp (survives app restart)
Rules:
- Zoom always saves to SessionEntry
- If only 1 session for the host: also saves to Room (permanent)
- If 2+ sessions for same host: SessionEntry only (per-session)
- New connection: reads default from Room, seeds SessionEntry
- Duplicate: copies fontSizeSp from source SessionEntry
- View recreation: factory reads from SessionEntry before surface fires
Added Room migration 10→11 (fontSizeSp REAL column).
Documented three-tier model in TECHNICAL.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lib-ssh — SftpSession:
- Wraps SSHJ SFTPClient with coroutines API on single-threaded dispatcher
- listDirectory (folders first, sort by name/size/date), downloadFile,
uploadFile (64KB streaming with progress), createDirectory, rename,
delete, canonicalize
- SftpEntry data class with Unix permissions string formatting
- Reuses existing SSHClient via newSFTPClient() — no second TCP connection
app — SftpViewModel:
- Directory browsing with currentPath/entries/isLoading/error StateFlows
- Transfer tracking: TransferState with ACTIVE/COMPLETE/FAILED/CANCELLED
- Large file confirmation (≥5MB) via confirmLargeDownload SharedFlow
- Upload, download, mkdir, rename, delete, sort order, download URI prefs
app — SftpActivity (replaces stub):
- Dark terminal aesthetic, programmatic layout
- Path breadcrumb bar with tappable segments
- File list: folder/file icons, name, permissions, size, date
- Tap folder to navigate, tap file to download, long-press context menu
- Bottom action bar: Upload (SAF multi-file) + Download folder (SAF tree)
- Transfer progress cards with direction badges, cancel, retry, auto-dismiss
- SAF integration: persistable URI permissions, stale URI recovery
app — TerminalService SFTP session management:
- openSftpSession/getSftpSession/closeSftpSession methods
- Auto-cleanup on parent SSH session disconnect
22 new unit tests (SftpSession, SftpViewModel, SftpEntrySort)
25 new SFTP string resources in EN/ES/SV
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dual-mode session navigation for TerminalActivity:
- Top bar mode: 36dp tab strip with scrollable session tabs (teal accent),
SFTP tab (amber), + popup menu (New session / Open SFTP / Switch to drawer)
- Drawer mode: 260dp left DrawerLayout with session list (teal dot indicators),
SFTP section, hamburger button on quick bar, footer to switch back
- In-session switching via popup/drawer footer; global default in Settings
- Session switching rebinds TerminalSurfaceView.screenBuffer to target session
New files:
- TerminalSessionNav.kt — tab bar, drawer, session switching, hamburger button
- SftpActivity.kt — stub ("SFTP — coming soon"), registered in manifest
- SessionNavTest.kt — 8 unit tests for NavMode enum and label truncation
Settings:
- Session navigation preference (Top bar / Drawer) in Appearance section
- Show session tab bar toggle (when top bar mode selected)
- sessionNavStyle + showSessionTabBar in TerminalPreferences + SettingsViewModel
Also: rewrote docs/SOCKS5_TEST.md to use adb forward from dev machine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Password auth: server-driven flow (try connect first, prompt on auth failure),
IME Done key triggers Connect, masked by default, keyboard focus/show
- Credential fixes: deletePassword when cleared in edit screen, no auto-save
of password in autoSaveConnection (only explicit "Remember" saves)
- Dialog icons: moved from centered body to title bar (setIcon) across all dialogs
- Keyboard: defer terminalView focus until Connected state, show AKB explicitly
after auth retry succeeds
- Password dialog: ADJUST_PAN soft input mode, requestFocus + showSoftInput
- Firebase Analytics + Crashlytics integration (BoM, plugins, .gitignore)
- ADB testing: broadcast receiver enhancements, test script, docs/TESTING.md
- Quick bar: double-tap word jump, CTRL modifier for system keyboard,
key repeat on quick bar, green highlight for active modifiers
- Snippet hint colors lightened (#BBBBBB)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port Forwarding:
- PortForward Room entity + DAO + DB v10 migration
- Full-screen add/edit dialog with type selector (Local/Remote/Dynamic)
- Validation: local port ≥ 1024 hard error, 0.0.0.0 warning
- EADDRINUSE shows red error in terminal, no crash
- Runtime: LOCAL and REMOTE via SSHJ after shell starts
- WiFi lock forced when port forwards active
- All forwards closed on disconnect, notification shows count
- 13 unit tests for entity fields and validation
Keyboard Languages:
- Swedish layout (layout_qwerty_sv.json): å after p, ö ä after l
- Spanish layout (layout_qwerty_es.json): ñ after l
- Swedish language pack (lang_sv.json): å ä ö accents, Swedish symbols
- Language selector in keyboard settings dialog
- Per-connection language saved, rebuilds keyboard on change
- Language loaded synchronously before keyboard setup
Strings:
- All EditConnectionScreen hardcoded strings → stringResource
- Port forwarding strings in EN/ES/SV
- startup_commands, one_command_per_line added
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>