* feat(terminal): add reconnection buffer support for persistent sessions
Fix two related issues:
1. Reconnecting to persistent sessions shows blank screen - server now
automatically sends historical buffer on reconnection via SessionState
machine with pending_buffer, eliminating the need for client-initiated
buffer requests.
2. Terminal output before view ready causes NaN errors - buffer output
chunks on client side until terminal view has valid dimensions, then
flush in order on first valid resize.
Rust side:
- Introduce SessionState enum (Closed/Active) replacing bool is_opened
- Auto-attach pending buffer on reconnection in handle_open()
- Always drain output channel in read_outputs() to prevent overflow
- Increase channel buffer from 100 to 500
- Optimize get_recent() to collect whole chunks (avoids ANSI truncation)
- Extract create_terminal_data_response() helper (DRY)
- Add reconnected flag to TerminalOpened protobuf message
Flutter side:
- Buffer output chunks until terminal view has valid dimensions
- Flush buffered output on first valid resize via _markViewReady()
- Clear terminal on reconnection to avoid duplicate output from buffer replay
- Fix max_bytes type (u32) to match protobuf definition
- Pass reconnected field through FlutterHandler event
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(terminal): add two-phase SIGWINCH for TUI app redraw and session remap on reconnection
Fix TUI apps (top, htop) not redrawing after reconnection. A single
resize-then-restore is too fast for ncurses to detect a size change,
so split across two read_outputs() polling cycles (~30ms apart) to
force a full redraw.
Also fix reconnection failure when client terminal_id doesn't match
any surviving server-side session ID by remapping the lowest surviving
session to the requested ID.
Rust side:
- Add two-phase SIGWINCH state machine (SigwinchPhase: TempResize →
Restore → Idle) with retry logic (max 3 attempts per phase)
- Add do_sigwinch_resize() for cross-platform PTY resize (direct PTY
and Windows helper mode)
- Add session remap logic for non-contiguous terminal_id reconnection
- Extract try_send_output() helper with rate-limited drop logging (DRY)
- Add 3-byte limit to UTF-8 continuation byte skipping in get_recent()
to prevent runaway on non-UTF-8 binary data
- Remove reconnected flag from flutter.rs (unused on client side)
Flutter side:
- Add reconnection screen clear and deferred flush logic
- Filter self from persistent_sessions restore list
- Add comments for web-related changes
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(terminal): ensure tab close is resilient to session cleanup failures
- Wrap _closeTerminalSessionIfNeeded in isolated try/catch so that
tabController.closeBy always executes even if FFI calls throw
- Add clarifying comment in handleWindowCloseButton for single-tab
audit dialog flow
* fix(terminal): fix session reconnect ID mismatch and tab close race condition
Remap surviving persistent sessions to client-requested terminal IDs on
reconnect, preventing new shell creation when IDs are non-contiguous.
Snapshot peerTabCount before async operations in _closeTab to avoid race
with concurrent _closeAllTabs clearing the tab controller.
Remove debug log statements.
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
- Fix new tab not auto-focusing: add FocusNode to TerminalView and
request focus when tab is selected via tab state listener
- Fix NaN error when data arrives before terminal view layout: buffer
output data until terminal view has valid dimensions, flush on first
valid resize callback
Signed-off-by: fufesou <linlong1266@gmail.com>
Terminal tab keys use the format "peerId_terminalId". The previous code
used split('_')[0] or startsWith('$peerId_') to extract the peerId,
which breaks when the peerId itself contains underscores.
This can happen in two scenarios:
- Hostname-based ID: when OPTION_ALLOW_HOSTNAME_AS_ID is enabled, the
peerId is derived from the system hostname, which commonly contains
underscores (e.g. "my_dev_machine").
- Custom ID: the validation regex ^[a-zA-Z][\w-]{5,15}$ allows
underscores since \w matches [a-zA-Z0-9_], so IDs like "my_dev_01"
are valid.
Fix all three parsing sites in terminal_tab_page.dart to use
lastIndexOf('_'), which is safe because terminalId is always a plain
integer with no underscores.