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>
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>
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>
- 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>
replaceEngine now updates the old parser's screen reference so remaining
bytes in a chunk after DECCOLM are written to the new buffer instead of
the discarded one. Also bumps MAX_PARAM_LEN 10→16 so vttest's 11-digit
leading-zero parameters parse correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Handle CSI?3h (132-col) and CSI?3l (80-col) in setDecPrivateMode:
clear screen, reset scroll margins, home cursor, fire listener callback
- Add onColumnModeChange callback to TerminalListener interface
- TerminalService handles DECCOLM by reflowing ScreenBuffer to requested
column count and sending PTY resize to the SSH server
- Add onBufferReplaced callback so TerminalSurfaceView picks up the new
ScreenBuffer immediately (not just on next Compose recomposition)
- Add DECCOLM parity tests verifying match with Termux behavior
- vttest test 1 now renders correctly on terminals wider than 80 columns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add --es profile "Name" launch intent to auto-connect by connection name/nickname
- Add --ez clearLog true intent extra to clear debug log on launch
- Add --ez dump true ADB broadcast to dump full screen buffer to log file
- Fix PTY allocation to use actual terminal dimensions instead of hardcoded 80x24:
SSHSession.resize() now stores dimensions even while Connecting, and PTY
allocation picks up the latest values instead of the initial config
- Add connectByProfile() to MainViewModel for profile-based connections
- Add VttestParityTest: replays recorded vttest session through both our engine
and Termux at 80x24 and 102x38, verifying cell-by-cell parity (both pass)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zoom: defer commit to ACTION_UP so ScaleGestureDetector min-span restarts
don't interrupt continuous zoom-out. Suppress tap after pinch to prevent
AKB appearing on finger lift. Thread-safe font metrics (@Volatile).
Tabs: telnet sessions show violet accent, detect session type (SSH/Telnet/
Local) via TabType enum. Connection list now sorts by last-used (call
updateLastConnected on connect).
SFTP: simplify large download confirmation (inline threshold check),
AndroidViewModel for string resource access, proper error on null stream.
Render: consume dirty after lockCanvas, re-request on lockCanvas failure,
extra render after zoom settle. Default font size 10sp. Dismiss soft
keyboard when navigating to NavHost. Paste preview color matches keyboard
hint (#888888).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SFTP is now rendered as an independent tab (amber accent) in the terminal
layer instead of a NavHost route. Tabs support duplicate, rename, close.
Fix dual-keyboard bug where system keyboard appeared on top of custom
keyboard: set softInputEnabled=false on TerminalSurfaceView in CKB mode
so tapping the terminal no longer triggers the IME.
Add diagnostic logging for keyboard show/hide transitions, softInputEnabled
changes, and DataStore keyboard type persistence to debug field reports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove ExtraKeysBar entirely — quick bar from custom keyboard now serves
both keyboard modes, always visible
- System keyboard mode: quick bar always shown, custom keyboard container
hidden, no longer forced visible on Connected state re-observation
- Selection handle snap-to-content on drop: end handle snaps backward to
last non-whitespace, start handle snaps forward to next content line
- Suppress IME suggestions with VISIBLE_PASSWORD input type
- Context menu: show "New Session" (green) instead of "Connect" when host
has active sessions
- Fix APK date: resolve date at task execution time, not configuration
time, so it's never stale from Gradle config cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Preserve terminal screen buffer across reconnect for both SSH and Telnet
sessions (was only working for SSH triggerReconnect path)
- Add network awareness to auto-reconnect: wait for connectivity before
counting attempts, reset counter when network returns
- Add ACCESS_NETWORK_STATE permission for ConnectivityManager callbacks
- Fix launcher icon reopening: detect MAIN/LAUNCHER intent in
ConnectionManagerActivity and finish() to reveal existing TerminalActivity
- Fix spurious blank line on resume: only write separator on
Connecting→Connected transition, not on re-observation
- Guard performEditorAction to only send Enter for explicit IME actions
- Add reconnecting_in and waiting_for_network strings (EN/ES/SV)
- Strengthen CLAUDE.md strings.xml rule
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snippets:
- Inline create form in snippet picker (no separate dialog)
- Name optional, command required
- Global/per-host checkbox (remembered, defaults to current host)
- List auto-refreshes after save
- Full-screen snippet picker
- Snippet tap sends command first, enter after 150ms delay
Selection:
- Wrapped word copy no longer inserts newline between wrapped rows
- Paste-only toolbar: tap anywhere triggers paste (not just button)
Settings:
- Remove font size slider (pinch-to-zoom is sufficient)
- Softer haptic feedback (CLOCK_TICK instead of KEYBOARD_TAP)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SelectionMagnifier: zoomed chars around handle, green cursor highlight
always visible (even on spaces), smooth velocity, no flickering
- Paste-only toolbar: clipboard text preview below Paste button
- Google search replaces snippet button in selection toolbar
- Toolbar follows visible teardrop handle dynamically
- Scrollbar only grabbable when visible from active scrolling
- Long-press host always shows full context menu (Connect, Disconnect
All, Edit, Duplicate, Delete) regardless of active sessions
- Delete disconnects all sessions first
- Fix port field: remove select-all-on-recompose bug
- Add DefaultConfig timing log in SSHSession
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Selection & toolbar:
- Extract SelectionToolbar class from TerminalSurfaceView
- Multi-line word selection (follows wrapped lines)
- Long-press empty space = paste-only at cursor, space between text = select line
- Google search button replaces snippet in toolbar
- Toolbar follows visible teardrop handle dynamically
- Block scroll/scrollbar during active selection
- Scrollbar only grabbable when already visible from scrolling
- Compact toolbar: smaller text/padding
Settings & per-host:
- Auto-reconnect per-connection (DB v9), global as default
- Theme picker dialog with live terminal preview (ls -l --color)
- Force dark theme everywhere (XML + Compose), dark splash screen
- Keyboard settings: fix scroll in BottomSheet, non-draggable
- Unsaved changes prompt (Save/Discard/Cancel) in KB settings + edit connection
- All hardcoded strings → strings.xml (EN/ES/SV)
- Biometric lock warns when no device lock configured
- QB position "Hidden" option, remove drag handle
- Port field selects-all on focus
- Keyboard height loaded upfront (no resize flash)
- Global defaults properly applied on connect
- Vertical QB insets terminal view, not keyboard
Fixes:
- Remove "No ScreenBuffer" debug text
- Bottom sheets: skipPartiallyExpanded, dark containerColor
- Dead code: hapticFeedback from KB settings, outputTextView clears
- Snippet tap = execute (with enter), long-tap for insert/edit/delete
- Default Dark blue lightened (#6E7EFF / #9EA8FF)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WiFi lock: per-connection wifiLock field (Room migration v7), global
wifiLockDefault in DataStore, WIFI_MODE_FULL_HIGH_PERF acquired on
connect / released on disconnect+error+onDestroy. Hidden for local
shell, visible for SSH and Telnet. Info dialog on label tap.
Zoom overlay: dimension display now shows rows×cols (stty order)
instead of cols×rows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add debug-only byte stream recorder (SessionRecorder) with auto-recording
per session, MediaScanner integration, and replay test infrastructure.
Add CSI_GT/CSI_LT parser states for \e[> and \e[< private sequences —
DA2 response, kitty keyboard, modifyOtherKeys handled without affecting
general CSI parsing. Fixes \e[<u being dispatched as restoreCursor.
Fix OSC title leak: 0x9C (8-bit ST) was terminating OSC mid-UTF-8
sequence (✳ = E2 9C B3), causing " Claude Code" to print as visible
text. Removed 0x9C check — only BEL and ESC\ terminate OSC.
105 tests pass, 1883/1891 parity (99.6%), 2 session replay goldens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert treating > as a general CSI flag (broke line wrap at right edge).
Re-apply as targeted fix: CSI > sequences (kitty keyboard, modifyOtherKeys)
are consumed fully and silently ignored without affecting CSI dispatch.
Add session recovery via DataStore, SSH/Telnet reconnect logic,
disconnected-mode UI, DSR diagnostic logging, thread-safety annotations.
Add 8 regression tests (line wrap, CSI > handling) — 1883/1891 parity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an OverScroller instance to TerminalGestureHandler and calls
forceFinished(true) on touch down alongside the existing custom fling
stop logic. This is the standard Android pattern for immediately
halting scroll momentum when the user touches the screen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Product flavors:
- Free (com.roundingmobile.sshworkbench) and Pro (com.roundingmobile.sshworkbench.pro)
- ProFeatures interface with Hilt DI — all branching via interface, no BuildConfig checks
- Free stubs: biometric lock, language packs, startup commands, session tracking,
save buffer, mouse reporting, keyboard customization gated with upgrade prompt
- Pro icon uses gold background to distinguish from free (green)
- ProGuard fix for EdDSA sun.security.x509 reference
Keyboard improvements:
- Key press hint stays visible while finger is down (no 500ms auto-hide)
- First row keys show hint below key (was clipped above container)
- Hint respects Shift state (shows uppercase when shifted)
- Page swiping disabled during key press to prevent accidental page switches
- Quick bar arrows match main keyboard glyphs (filled triangles)
- Page indicator dots: colored per page, tappable for direct navigation, larger spacing
Terminal improvements:
- Dimension overlay (cols x rows) shown during pinch-to-zoom and resize
- Selection auto-scroll acceleration based on drag distance past edge
- Copy toolbar consumes all touch events to prevent re-triggering selection
- Reconnect fallback uses saved connection when session entry is cleaned up
- Mouse reporting gated via TerminalSurfaceView.mouseReportingEnabled flag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add TerminalUrlDetector: detect and underline URLs in terminal output
- Tap URL shows Open/Copy dialog instead of auto-opening
- Long press selects word (min 3-char span on whitespace)
- Double tap selects entire row
- Prevent selection handles from crossing each other
- Fix double-tap char read when scrolled back in history
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-scroll viewport when dragging selection handle to top/bottom edge,
allowing selection across entire scrollback buffer without lifting finger
- Save session dialog with editable filename using connection nickname/name
instead of raw IP address (e.g. Hello_20260325_143022.txt)
- Larger copy/paste toolbar buttons and density-aware selection handles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Store session start/end/duration in DB (migration v2→v3), display formatted
times in connection list with live timer for active sessions
- Detect 12/24h device setting for all time formatting
- Replace all AlertDialog.Builder dialogs with Material 3 styled TerminalDialogs
(host key, auth prompt, connect, clean exit, connection actions)
- Upgrade app theme to Theme.Material3.DayNight.NoActionBar with terminal colors
- Add auto-reconnect setting (default off) in Settings → Terminal
- Disconnected "Stay" mode: hide keyboard, show compact bottom bar with
reconnect button and save-to-file button, terminal stays interactive for
scroll/select/copy. Buffer saved to Downloads/SshWorkbench/
- Fix session lifecycle: clean up dead sessions from activeSessions map,
properly clean up on back press, reset state flags on reconnect
- Larger selection handles (density-aware 14dp) and toolbar buttons (min 14dp
text, more padding, 12dp corners)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix render race condition: pass onScreenUpdated callback into connectSSH/
startLocalShell so it's set before the output collection job starts
- Remove LAYER_TYPE_SOFTWARE on SurfaceView that created an opaque layer
hiding the Surface canvas
- Add proportional scrollbar on right edge (visible when scrollback > 0)
- Add jump-to-top/bottom overlay buttons that fade after 3s of inactivity
- Boost fling velocity 2.5x, reduce friction, increase max fling distance
- Add "Tap to reconnect" on clean session exit (was null click listener)
- Add SSH key management, connection manager UI, credential store,
keyboard improvements, and database schema v2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>