- Add debounced aria-live region that announces visible terminal text
to screen readers (500ms throttle, non-empty trimmed rows only)
- Add role=application, aria-label, aria-roledescription to terminal
- Add prefers-reduced-motion listener: disables cursor blink animation
and sets --terminal-reduce-motion CSS variable
- Add prefers-contrast: more media query with Canvas/CanvasText system
color fallback
- Add visually hidden .terminal-screen-reader CSS utility
- Mark P7 complete in roadmap
Implement IME composition support and migrate regular character input to
a hidden textarea, matching the xterm.js approach.
Key changes:
- Add a hidden, absolutely-positioned `<textarea>` inside `.terminal-screen`
that follows the terminal cursor cell. It receives focus on click, connect,
and resize so the OS/browser IME candidate window appears at the cursor.
- `on:input` now sends regular printable characters; `on:keydown` only handles
special keys (arrows, Enter, Esc, Backspace, F-keys, Ctrl combos).
- `on:compositionstart/end` track IME sessions and send the final composed
string at the end, avoiding partial pinyin/jamo/kana leaks.
- Move `on:paste` and `on:keydown` from `.terminal-screen` to the textarea so
focus stays on the IME target.
- Replace `.terminal-screen:focus` with `:focus-within` since the screen div
no longer holds focus.
- Add `is_regular_text_input` helper and unit test.
Web-sys features extended with `InputEvent` and `CompositionEvent`.
Tests: 24/24 pass, clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drag to select terminal text and copy it with Ctrl-Shift-C. Built as a
custom selection model (xterm.js-style) rather than native selection,
because the virtual scroll only keeps ~60 rows in the DOM and rebuilds
them every frame — native selection would be destroyed and couldn't span
scrolled-out rows.
core.rs:
- selection_text(start, end) extracts a (row,col)→(row,col) range straight
from history_rows + screen_cache, so copy is correct across the whole
buffer regardless of what is currently rendered
- TerminalRow::text_in_cols slices a row by grid columns (using the
segment `cols` widths), trimming trailing whitespace
component.rs:
- Selection { anchor, head } in logical buffer coords; mousedown/move/up
drag updates it; point_to_cell maps pixels→cell (padding + scrollTop)
- selection_rects computes per-row highlight rects clipped to the rendered
window; rendered as an overlay inside the translated rows layer so the
highlight follows scrolling without splitting text segments
- cell_size signal kept in sync with viewport measurement
- Ctrl-Shift-C copies via navigator.clipboard.write_text; Ctrl-C is left
untouched (still SIGINT); typing clears the selection
style: .terminal-selection overlay, user-select:none on the grid, rows
above the highlight via z-index.
web-sys: add DomRect, MouseEvent, Navigator, Clipboard.
Adds point_to_cell / selection_rects / selection_text unit tests (16
terminal tests total). Marks P0 done in TERMINAL_ROADMAP.md.
Known follow-up: no auto-scroll when dragging past the viewport edge
(scroll first, then select); wide+combining column slicing is approximate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the spacer-estimate scrollback model with an explicit history
row buffer owned by TerminalCore, rendered with absolute-positioned
virtual scrolling so huge outputs (cat of 50k-line files) stay smooth.
core.rs:
- TerminalCore owns history_rows + a cached screen, captured after each
process() via a bounded "conveyor belt" read of vt100 scrollback
- feed bytes in newline-bounded safe slices so a capture never reads
deeper than viewport_rows (works around vt100 0.15 visible_rows
subtract-overflow panic)
- handle clear (ESC[3J) by wiping our own history buffer
- TerminalSegment carries grid column count for exact-width rendering
- collect_window() clones only the visible slice
component.rs:
- TerminalCore lives in an RwSignal; render driven by an rAF-coalesced
render_tick so packet bursts collapse into one frame
- virtual_window pins to the bottom in live mode, follows scroll_top in
history mode; absolute-positioned rows in a fixed-height sizer keep
scroll geometry constant (fixes the "keeps drifting up" bounce)
- position-based programmatic-scroll detection replaces the racy
ignore-next-scroll flag
- alt-screen wheel events translate to arrow keys (vim/less paging)
- try_* signal access guards against disposal during hydration
style: lock row height to 18px, segment width to Nch (exact grid so
fallback-font glyphs like btm braille can't push the row sideways),
absolute-position the virtual rows layer.
Adds 12 unit tests (capture, clear, alt-screen, window slicing, wheel
mapping) and TERMINAL_ROADMAP.md tracking xterm.js parity work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>