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>
- Add optional "Include settings" checkbox (unchecked by default) to both
Save Vault Locally and Export Vault flows — exports 56 DataStore prefs
(keyboard, display, QuickBar customization, hardware actions, etc.)
- Import automatically restores settings when present in vault file
- EXPORTABLE_*_KEYS lists in TerminalPrefsKeys define which prefs are backed up
- Fix misleading "Jump Host" pro upgrade message — now says "Jump host chaining"
so free users understand single jump hosts work, only chaining is pro-gated
- Gate vault import for free users: can only import local vault saves (MODE_LOCAL),
not pro-exported vaults (MODE_PASSWORD/MODE_QR)
- All strings in EN/ES/SV/FR/DE
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>
- AppColors centralized palette in ui/theme/Theme.kt — single source of truth for all colors
- All 15 screen files migrated from hardcoded Color() to AppColors references
- TopAppBar terminal style on all screens: teal Space Grotesk uppercase, #10141A bg, divider
- Settings section headers: teal, uppercase, Space Grotesk, 13sp, letter-spaced
- Settings cards: #181C22 bg, explicit #DFE2EB/#BDC9C8 text colors
- Settings About section: cropped pro icon, centered branding, version in mono
- Settings: session navigation moved above scrollback lines
- LanguageScreen: full-screen language selector with flag emojis, accent bars, fixed info card
- French and German translations (complete ~590 strings each)
- Spanish marked as Español (España) for region clarity
- Drawer bar: AnimatedContent label transition, hamburger icon with teal border/bg
- Drawer kebab menu anchored to icon (was anchored to row)
- Top bar preview: tab switching animation
- Drawer preview: hamburger glow on tap
- Wider horizontal padding (24dp) on screens >400dp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Telnet connections can now tunnel through SSH jump hosts via direct-tcpip channel
- TelnetSession.connectVia() accepts pre-opened streams for tunneled connections
- Jump host picker shown for both SSH and Telnet in EditConnectionScreen
- SFTP option hidden for telnet/local in tab bar overflow and connection list
- Duplicate session fixed for telnet (username=null early return) and local shell
- Session badge shows correct type label (Telnet/Local/SSH) with protocol color
- Label numbering only counts live sessions, not disconnected ones
- Cycle-causing jump hosts shown in red and disabled in dropdown
- Save/Save&Connect disabled when jump host cycle detected
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>
Theme picker:
- EditConnection now shows "Default (<global theme>)" using the actual
global theme from TerminalPreferences, not the hardcoded "Default Dark"
- Dropdown adds a Default entry at the top that clears the per-connection
override (empty string)
- SessionTabBar theme picker replaced hardcoded 7-theme list with
TerminalTheme.builtInThemes (all 20 themes), scrollable column, and a
Default entry that clears the per-session override
- MainViewModel exposes globalThemeName StateFlow; threaded through
SessionTabBar and SessionDrawerContent
- Fix setSessionTheme: blank name clears the override (was wrongly
treating the literal "Default Dark" as "clear")
Drawer back button:
- Back in terminal pane closes the drawer first if drawer mode is active
and the drawer is open; otherwise falls back to terminal → nav host
- Drawer state/scope declarations moved above BackHandler
Session drawer mode (already in progress pre-session):
- SessionDrawerBar (hamburger + active label + kebab) and
SessionDrawerContent (session list with type-colored rows) composables
- New strings: switch_to_top_bar, switch_to_drawer, open_drawer,
sessions_header (EN, ES, SV)
Mini numpad:
- widthPercent 10 → 15 in all three layout JSONs (EN, ES, SV)
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>
QuickBarView:
- Font/icon scaling uses constraining dimension: rect.width() for vertical,
rect.height() for horizontal. Clamped to 6sp-22sp.
- W icon keeps square aspect ratio (no stretching)
- onMeasure uses parent height from Compose (not fixed config height)
AQB Settings:
- Live preview with real QuickBarView (non-interactive, edge-to-edge)
- Vertical positions: Row layout (QB sidebar + settings), full height
- Horizontal: Column layout (QB preview on top)
- Preview updates live on size/color/orientation changes
- Unsaved changes dialog (Save/Cancel/Discard) on BACK
- Reset to defaults button
- "Above keyboard" added to AQB position options
- "Hidden" hides preview
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>
SFTP is now fully standalone — each SFTP tab creates its own SSH
connection (tunnelOnly=true) instead of reusing a terminal session's.
SFTP lives independently: disconnecting terminal SSH does not affect
SFTP sessions, and SFTP can be opened from the connection list without
any existing terminal session.
- SftpSessionManager owns SSH+SFTP lifecycle per session
- TerminalService.openSftpSession builds auth, TOFU, connects standalone
- Connection list "New SFTP Session" shows unconditionally (no gate)
- Session picker shows SFTP tabs (amber dots) with labels, scrollbar
- Per-type session counts on connection cards (green/amber badges)
- Connected-since time uses earliest active session, not Room overwrite
- Failed SFTP keeps tab with error instead of auto-closing
- Duplicate SFTP fixed (was passing sessionId instead of connectionId)
- Session observer preserves SFTP tab labels (retainAll includes SFTP keys)
- SFTP cleanup in onDestroy (closeAll)
- Pane switching only when on terminal screen (not from connection list)
- Disconnect button for SFTP sessions in session picker
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>
Add menu key system to Quick Bar — tap a menu key to show a popup strip
with common combos. Each item fires the right byte sequence on tap.
- MenuItem model + menuItems on KeyDefinition, parsed from JSON
- LongPressPopupView: menu mode with auto-sized cells, gaps, borders
- QuickBarView: onMenuKeyTap callback routes menu keys
- TerminalKeyboard: full-screen overlay captures tap on popup item
- New "menu" style in all 4 keyboard themes (purple accent)
- vim: :w, :q, :wq, :q!, :%s/, ciw, >>, <<, redo
- nano: save, exit, find, repl, cut, paste, help, goto
- tmux: new, nxt, prv, det, sp│, sp─, scr, zoom, kill
- screen: new, nxt, prv, det, sp─, sp│, cpy, kill, list
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>
- Add status bar insets to SessionTabBar and navigation bar insets to terminal
Column for proper edge-to-edge rendering on Samsung S23 (Android 14+)
- Redesign quick bar: ESC, TAB, :, ~, |, arrows, shift-tab (remove Ctrl+C)
- Defer haptic in infinite scroll mode until tap is confirmed (no haptic on scroll)
- Manage focus explicitly when switching sessions — clear focus on invisible
views, request focus + restartInput on visible to fix Samsung IME routing
- Wire space long-press gear to KeyboardSettingsDialog from terminal pane
- Add diagnostic logging for writeInput drops and null session entries
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>
Import can now read QR from a saved image (Pick image button) in
addition to camera scanning. Register ACTION_SEND image/* intent
filter so QR images shared from WhatsApp/Telegram auto-open the
import flow. Export screen enforces strong passwords (12+ chars,
upper/lower/digit/special), gates QR export on save/share, and
shows confirmation dialog before exporting with QR mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>