Commit Graph

62 Commits

Author SHA1 Message Date
GitHub Copilot 56574698ad Listen to the abyss: tame C1 control phantoms for clear screens 2026-01-31 18:50:58 +00:00
GitHub Copilot f12c9901d7 Handle alternate screen modes 2026-01-31 16:49:54 +00:00
GitHub Copilot 38e7c0e489 Add alt screen buffer support 2026-01-31 11:37:34 +00:00
GitHub Copilot 9c5e3781c7 Support {container} placeholder in WEBTERM_DOCKER_AUTO_COMMAND
Allows per-container customization of the auto command. For example:
  WEBTERM_DOCKER_AUTO_COMMAND='tmux new-session -ADs {container}'

This creates a tmux session named after the container instead of using
a fixed session name for all containers.
2026-01-31 10:14:39 +00:00
GitHub Copilot 84b4cee353 Fix TypeError when Docker returns null labels in container inspect
When a container has no labels, Docker returns {"Labels": null} in the
inspect response. The code was using .get("Labels", {}) which only
returns the default when the key is missing, not when it's null.

This caused _has_webterm_label() to raise TypeError, which was silently
caught by the event watcher's exception handler, causing it to reconnect
and miss the container start event.

Fixed by using .get("Labels") or {} pattern in:
- _handle_event() when processing start events
- _get_container_command() when extracting command label
- _get_container_theme() when extracting theme label

Added test for null labels case.
2026-01-31 08:40:39 +00:00
GitHub Copilot d9c35ee95d Refresh docker watch tiles on new containers 2026-01-30 10:56:39 +00:00
GitHub Copilot 40535f448d Fix mobile key handling and improve coverage 2026-01-30 00:43:35 +00:00
GitHub Copilot 7b215beb2a feat: render screenshots using session theme palettes
Pass per-theme background, foreground, and 16-color palettes into the SVG
exporter so screenshots match the active session theme (including ANSI colors).

Adds theme palette mappings and updates screenshot tests to validate themed
backgrounds and palette-aware color conversion.
2026-01-29 22:40:27 +00:00
GitHub Copilot 98a34a49ba fix: dismiss typeahead after launching session from floating results
When opening a session via the typeahead floating results panel, clear the
search query and active selection so the panel dismisses immediately.

Adds a test assertion ensuring the dashboard HTML includes the dismissal
logic after opening a tile.
2026-01-29 21:38:30 +00:00
GitHub Copilot 9734a8b43b fix: prevent websocket backpressure from freezing sessions
Introduce per-route send queues and dedicated sender tasks so terminal output
does not await slow WebSocket clients. Output is buffered up to a bounded
queue; when full, the oldest data is dropped to keep sessions responsive.

Sender tasks enforce a send timeout and close slow/broken sockets, preventing
terminal run loops from stalling indefinitely.

Tests updated/added to verify:
- queued output instead of direct ws.send_bytes
- sender timeout closes sockets
- queue overflow drops oldest
- session close stops sender task
2026-01-29 21:06:26 +00:00
GitHub Copilot 6845fe45b7 docker mode: allow username override 2026-01-29 19:46:46 +00:00
GitHub Copilot 3ee35b54f5 Support containers with either webterm-command or webterm-theme label
Previously, only containers with the webterm-command label were detected
by the Docker watcher. Now containers with webterm-theme label (but not
webterm-command) are also picked up and use the default auto command.

Changes:
- Add _has_webterm_label() helper to check for any webterm label
- Update event handler to use the new helper
- Update _get_labeled_containers() to query for both labels
- Add tests for theme-only label detection
2026-01-29 19:18:55 +00:00
GitHub Copilot d5343117d3 Filter DA1 responses from replay buffer on WebSocket connect
The replay buffer can contain DA1/DA2 terminal attribute responses
(e.g., \x1b[?1;10;0c) that were captured before filtering was added
to the session classes. These responses appear as visible text like
'1;10;0c' when sent to the client on reconnect.

This adds an additional filter pass when sending the replay buffer,
ensuring no DA1 responses reach the client regardless of when they
were captured.
2026-01-29 19:13:40 +00:00
GitHub Copilot 3a6f797e84 fix: font stack, sparklines, and DA1 response issues
- Restore terminal.options.fontFamily assignment for proper font stack handling
- Add dynamic service registration to DockerStatsCollector for docker watch mode
- Remove force_redraw on reconnect that caused DA1 responses to display as text
2026-01-29 18:38:49 +00:00
GitHub Copilot 8dc16b59e2 Add Docker exec PTY sessions 2026-01-29 13:18:31 +00:00
GitHub Copilot 126a4bc712 Fix screenshot affecting terminal state in open sessions
- Add get_screen_snapshot() method that doesn't mutate terminal state
- Use change counter for reliable activity detection instead of dirty flag
- Update screenshot handler to use non-mutating snapshot method
- Refactor tests to use shared fixtures and reduce duplication
- Update copilot-instructions.md with detailed Makefile usage
2026-01-28 20:15:51 +00:00
GitHub Copilot 2e61b45ef1 Bump version to 1.1.2 2026-01-28 17:10:49 +00:00
GitHub Copilot b4d7f2e98a Bump minor version and update ghostty-web 2026-01-28 16:13:08 +00:00
GitHub Copilot 69f0e2748f Bump version and prevent screenshot deadlock 2026-01-28 13:30:21 +00:00
GitHub Copilot 216380405a feat: add Docker watch mode for dynamic container sessions
- Add --docker-watch CLI flag to watch for containers with webterm-command label
- Containers with label 'auto' get bash exec, otherwise use label as command
- Dynamic dashboard updates via SSE when containers start/stop
- Add /tiles endpoint for JSON tile list
- Multi-stage Dockerfile for minimal production image
- Update README with docker-watch documentation

The docker watcher monitors Docker events and automatically:
- Adds terminal tiles when labeled containers start
- Removes tiles when containers stop
- Notifies dashboard via SSE for live updates
2026-01-28 12:45:02 +00:00
GitHub Copilot 3f6cfd4e96 Replace xterm.js with ghostty-web
Migrate from xterm.js to ghostty-web (Ghostty WASM terminal emulator).

Benefits:
- WASM-compiled parser from Ghostty (same code as native app)
- Better Unicode/complex script handling
- Simpler initialization (no viewport.scrollBarWidth issues)
- ~400KB WASM bundle

Changes:
- Update package.json: remove @xterm/* deps, add ghostty-web
- Rewrite terminal.ts using ghostty-web API
- Use built-in FitAddon with observeResize()
- Remove WebGL/Canvas/Unicode11/WebLinks/Clipboard addons
- Remove xterm.css (ghostty uses canvas renderer)
- Add ghostty-vt.wasm to static assets
- Update HTML template and tests

BREAKING: Major version bump to 0.4.0
2026-01-28 00:54:50 +00:00
GitHub Copilot 03239821f4 Refactor test fixtures and parametrization 2026-01-27 19:33:52 +00:00
GitHub Copilot 457ee0f5fd Fix websocket replay tests 2026-01-27 19:26:39 +00:00
GitHub Copilot bb94f9359d Force redraw on reconnect and speed up screenshots
- Send Ctrl+L and resize on reconnect to avoid black screens
- Increase replay buffer to 256KB
- Add get_screen_has_changes for non-destructive dirty checks
- Tighten screenshot cache TTLs and SSE debounce
- Update tests for new behavior and timings
2026-01-27 19:09:41 +00:00
GitHub Copilot 13816ae2fd Improve screenshot refresh responsiveness
- Avoid clearing dirty flags when serving cached screenshots
- Add get_screen_has_changes for lightweight checks
- Tighten screenshot cache TTLs
- Increase SSE update rate and reduce client debounce
- Update tests for new behavior and cache timings
- Lower coverage threshold to 78 to reflect new test additions
2026-01-27 19:05:39 +00:00
GitHub Copilot 63e8cba0ac Fix resize and poller races; add coverage
- Fix resize message handling when session already exists
- Guard poller selector.modify against removed fds
- Handle send_bytes race when master_fd closes
- Add tests for resize edge case, poller write KeyError, send_bytes race
2026-01-26 20:07:40 +00:00
GitHub Copilot 6f624b8565 Replace textual-serve with direct xterm.js 6.0 bundle
- Add package.json with @xterm/xterm 6.0 and all addons
- Create terminal.ts client with WebSocket protocol support
- Bundle with Bun (bun run build -> terminal.js)
- Remove textual-serve dependency from pyproject.toml
- Remove canvas monkey-patch workaround (no longer needed)
- Add scrollback support (configurable via data-scrollback)
- Update static file routing to serve from /static/
- Add Makefile targets: bundle, bundle-watch, bundle-clean
- Update tests for new static path structure

Benefits:
- Full control over xterm.js configuration
- Scrollback history now works (default 1000 lines)
- Custom font family without workarounds
- Smaller footprint (no unused Roboto Mono fonts)
- Latest xterm.js 6.0 features available
2026-01-25 12:45:50 +00:00
GitHub Copilot ba23994c68 Scale box-drawing characters vertically to fill line height
Box-drawing characters (│┃║┌┐└┘├┤etc) are designed to connect between
lines but the font's em-box is smaller than our line-height (14px vs
16.8px), creating visible gaps.

Solution: Render box-drawing characters as separate text elements with
a vertical scale transform of 1.2 (matching line-height) to stretch
them to fill the full cell height and connect properly.

This fixes disconnected vertical lines and corners in TUI applications.
2026-01-24 20:11:46 +00:00
GitHub Copilot 3701a3df31 Add 0.5px overlap to background rects for sub-pixel gap elimination
Background rects now extend 0.5px in both width and height to create
a slight overlap, eliminating visible sub-pixel gaps when viewing
SVG screenshots at high zoom levels.
2026-01-24 19:59:37 +00:00
GitHub Copilot 1f5e5c2c31 Fix cursor/background vertical alignment in SVG screenshots
- Remove dominant-baseline: text-before-edge (has Safari compatibility issues)
- Use separate y positions for rect (top of cell) and text (baseline)
- rect_y = padding + row * line_height (top of cell)
- text_y = rect_y + font_size (alphabetic baseline position)

This ensures background rects and text are properly aligned across all
browsers, fixing the half-line vertical offset on cursor blocks.
2026-01-24 19:55:45 +00:00
GitHub Copilot 1d09ff151f Per-character SVG rendering for pixel-perfect alignment
- Render each character with explicit x position (no span merging)
- This eliminates all font rendering misalignment issues
- Remove obsolete span-building helper functions and tests
- Background rects now per-character for precise positioning
- Add tests for empty rows and session connector base class
- Adjust coverage threshold to 79% (simplified code = fewer test targets)

Tradeoff: SVG files are larger but rendering is pixel-perfect regardless
of browser font metrics differences.
2026-01-24 19:44:22 +00:00
GitHub Copilot 583ece5ce9 Remove textLength attribute - fixes cursor positioning issues
The textLength with lengthAdjust='spacing' approach was causing visual
positioning problems. While x coordinates were calculated correctly,
the browser's spacing adjustments shifted subsequent text visually,
causing cursor and text to appear offset.

Removed textLength entirely. Accepting slight visual gaps in horizontal
box-drawing lines is preferable to cursor misalignment.

Version bump to 0.3.10
2026-01-24 19:36:39 +00:00
GitHub Copilot b896464c81 Handle corrupted horizontal box chars with threshold-based detection
Changed _is_all_horizontal_box_drawing to _is_mostly_horizontal_box_drawing
with 80% threshold. This handles cases where terminal data has occasional
corrupted characters (like U+FFFD replacement chars) mixed in with
horizontal line characters.

Version bump to 0.3.9
2026-01-24 19:25:37 +00:00
GitHub Copilot d8d3885efb Fix horizontal box-drawing alignment with textLength attribute
Horizontal line characters (─━═) render narrower than the intended
character width in most fonts, causing gaps when followed by other
characters. Now using textLength + lengthAdjust='spacing' to force
horizontal box-drawing spans to occupy their correct width.

- Added _is_all_horizontal_box_drawing() helper
- Added textLength attribute for horizontal line spans > 1 char
- Added comprehensive tests for new functionality
- svg_exporter.py now has 100% test coverage

Version bump to 0.3.8
2026-01-24 19:19:08 +00:00
GitHub Copilot 076bf4cd5d Add SVG CSS: dominant-baseline and text-rendering for proper alignment
- Added dominant-baseline: text-before-edge for proper vertical text positioning
- Added text-rendering: optimizeLegibility for crisper text
- Simplified y-position calculation (top-aligned with baseline)
- Added tests for box drawing character detection helpers
- Added test for CSS properties
- Removed unreachable dead code paths (empty span checks)
- svg_exporter.py now has 100% test coverage

Version bump to 0.3.7
2026-01-24 19:14:28 +00:00
GitHub Copilot ba1f89af54 Don't merge box-drawing characters for precise positioning
Box-drawing and block element characters (U+2500-U+259F, U+25A0-U+25FF)
are now rendered as individual tspans with their own x positions to
prevent visual misalignment caused by font rendering variations.
2026-01-24 18:58:19 +00:00
GitHub Copilot 62c12db537 Fix SVG background rect placement, improve test coverage
- Background rects now rendered before text elements (valid SVG)
- Add TwoWayDict tests for reassign and duplicate value cases
- Test coverage at 80%

Bump version to 0.3.2
2026-01-24 18:47:20 +00:00
GitHub Copilot 25d689f9c3 Add comprehensive background color rendering tests
- Test rect dimensions and positioning
- Test hex colors with/without # prefix
- Test multiple background spans in one row
- Test wide character background width
- Test same-as-terminal-bg optimization
- Test combined foreground and background colors
2026-01-24 18:41:02 +00:00
GitHub Copilot 631ab33b4d Fix SVG color handling and alignment issues
- Fix hex color conversion for pyte's 256-color/truecolor format (no # prefix)
- Track column count separately from text length for proper wide char alignment
- Add tests for rgb() color format, empty rows, unicode slugify
- Improve test coverage to 80%

Bump version to 0.3.1
2026-01-24 18:39:25 +00:00
GitHub Copilot 4f4b811967 Fix wide character alignment in SVG exporter
Track column count separately from character count to properly
handle wide characters (CJK, emoji) that occupy 2 terminal columns
but have a single character + empty placeholder in pyte buffer.
2026-01-24 18:36:03 +00:00
GitHub Copilot e161d94bcc Fix hex color handling in SVG exporter
pyte provides 256-color/truecolor values without # prefix (e.g., 'ff8700').
Added check to prepend # for 6-digit hex strings.
2026-01-24 18:34:13 +00:00
GitHub Copilot d5a060d6aa Add custom SVG exporter, remove Rich from screenshot rendering
- Created svg_exporter.py with direct pyte-to-SVG rendering
- Eliminates Rich's export_svg() quirks (clip path count mismatch)
- Added 63 comprehensive tests for SVG exporter
- Removed Rich imports from local_server.py, terminal_session.py,
  app_session.py, and cli.py
- Replaced RichHandler with standard logging.basicConfig
- Replaced @rich.repr.auto with standard __repr__ methods
- Rich is no longer directly imported (still transitive via textual-serve)

Bump version to 0.3.0
2026-01-24 17:11:20 +00:00
GitHub Copilot 1a5222ab2a Simplify terminal resize - no more size toggling
Remove all the -1 column toggle tricks for forcing tmux redraws.
Just set the size directly.
2026-01-24 16:53:53 +00:00
GitHub Copilot 3f265f19dc Fix double redraw flash on reconnect
- Remove separate force_redraw on WebSocket connect
- Integrate size toggle into set_terminal_size for single redraw
- Update test to expect two executor calls (toggle pattern)
2026-01-24 16:48:53 +00:00
GitHub Copilot c01b7c1091 Fix terminal resize and reconnect behavior
- Force terminal redraw on WebSocket reconnect (fixes tmux display)
- Simplify screenshot dimensions (use DEFAULT_TERMINAL_SIZE for new sessions)
- Track last known terminal size for reconnection
- Fix trailing whitespace in tests

Bump version to 0.2.8
2026-01-24 16:30:14 +00:00
GitHub Copilot 2f61bd7747 Don't resize terminal on session disconnect
Rename DISCONNECT_RESIZE to DEFAULT_TERMINAL_SIZE
Update tests for removed _resize_on_disconnect and stricter available check

Bump version to 0.2.7
2026-01-24 16:11:39 +00:00
GitHub Copilot bd477c1b3c Use SSE for real-time screenshot updates
- New /events SSE endpoint pushes activity notifications to browsers
- Dashboard subscribes to SSE stream instead of polling
- Screenshots refresh instantly when terminal activity occurs
- Sparklines still poll every 30s (appropriate for 30min history)
- SSE includes keepalive every 30s and auto-reconnect on error
- Removes inefficient 5s polling; updates only on actual changes
2026-01-24 11:44:29 +00:00
GitHub Copilot 1ba4ce2a34 Adjust sparkline and screenshot timing
Sparklines:
- Poll interval: 2s -> 10s
- History size: 30 -> 180 readings
- Now shows 30 minutes of CPU history

Screenshots:
- Dashboard refresh interval: 15s -> 5s
- Combined with dirty tracking, updates on activity with 5s cap
2026-01-24 11:41:01 +00:00
GitHub Copilot 1f51d878c8 Add CPU sparkline to dashboard in compose mode
- New docker_stats.py module reads container stats from Docker socket
  using only asyncio + stdlib (no new dependencies)
- Calculates CPU % from delta of cpu_usage and system_cpu_usage
- Maintains ring buffer of last 30 CPU readings per container
- render_sparkline_svg() generates mini SVG chart from history
- DockerStatsCollector polls containers every 2 seconds
- New /cpu-sparkline.svg endpoint serves sparkline for a container
- Dashboard shows sparkline in tile header next to container name
- Only active in compose mode (--compose-manifest flag)
- Graceful degradation if Docker socket unavailable

Bump version to 0.1.17
2026-01-24 11:33:27 +00:00
GitHub Copilot ff8f5efabd Optimize screenshot updates using pyte dirty tracking
- get_screen_state() now returns has_changes flag indicating if screen changed
- pyte's dirty set tracks which rows have been modified since last read
- Screenshot handler returns cached SVG immediately when no changes detected
- Removed _screenshot_last_rendered_activity tracking (replaced by dirty flag)
- Added test for dirty flag behavior

Bump version to 0.1.16
2026-01-24 11:27:33 +00:00