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
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
- 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
Horizontal lines (─━═) can merge since they form continuous lines.
Vertical lines (│┃║) and corners/junctions need separate x positioning
to align properly with adjacent characters.
- Same box-drawing chars merge (───), different ones stay separate (│╯)
- Use 8px char width instead of 8.4px for crisper rendering
Bump version to 0.3.6
Same box-drawing characters (like ───) can now merge into a single
tspan, but different box-drawing characters (like │╯) are kept
separate for precise positioning. This reduces SVG size while
maintaining alignment at character transitions.
Box-drawing and block element characters are now rendered as
separate tspans with precise x positions to prevent font-related
visual misalignment.
Bump version to 0.3.5
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.
textLength with lengthAdjust distorts glyphs badly. Use standard
x positioning instead - slight gaps with box-drawing chars are
preferable to distorted text.
Test coverage at 80%.
Bump version to 0.3.4
Use SVG textLength attribute with lengthAdjust='spacingAndGlyphs'
to enforce exact monospace character widths, fixing alignment of
box-drawing characters and other symbols.
Test coverage at 80%.
Bump version to 0.3.3
Use SVG textLength and lengthAdjust='spacingAndGlyphs' to enforce
exact character spacing, preventing gaps between box-drawing and
other characters that may render at slightly different widths.
Also include whitespace spans in output for proper alignment.
- 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
Background rect elements were being inserted inside text elements,
causing invalid SVG structure. Now collect all background rects first,
then render them before the text element for each row.
- 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
- 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
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.
- 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
- Skip empty placeholder cells for wide characters in SVG
- Single redraw on reconnect (integrated into set_terminal_size)
- Sync pyte to PTY size with redraw trigger for screenshots
- Fix extra line in SVG by not adding newline after last row
Bump version to 0.2.10
- 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)
- Sync pyte screen to actual PTY size before screenshots
- Toggle terminal size to force full tmux redraw on reconnect
- Query PTY size with TIOCGWINSZ to detect external resizes
Bump version to 0.2.9
- 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
Remove width/height query params from screenshot endpoint.
New sessions created by screenshot use DEFAULT_TERMINAL_SIZE.
Existing sessions keep their current size.
- Track last known terminal size in TerminalSession
- Add force_redraw() method that re-sends SIGWINCH to trigger redraw
- Call force_redraw() when WebSocket reconnects to existing session
- Helps tmux and similar apps restore proper display after disconnect
- Fix Ctrl-C to exit immediately by setting exit_event before cleanup
- Filter Docker containers by compose project name to match correct stack
- Derive compose project from manifest directory (matches docker-compose default)
- Improve Docker socket availability check to test actual connectivity
- Add DOCKER_HOST env var support for alternate socket paths
- Better error logging for socket permission issues
Bump version to 0.2.5
- Remove excessive debug logging
- Add single warning when no containers found (with hint about socket mount)
- Use HTTP/1.0 to avoid chunked encoding complexity
- Simplify response parsing
- Refactor into separate methods for cleaner code
- Better handling of chunked transfer encoding
- Add more debug logging to diagnose parsing failures
- Log body preview when JSON not found
Helps diagnose empty sparklines by logging:
- Container discovery results and matches
- Stats request failures
- CPU calculation results
- What containers/services were found vs expected
Server-side:
- Limit SSE notifications to max once per second per route
- Track last notification time per route
Client-side:
- Debounce screenshot refreshes with 2s minimum interval per tile
- Pending refreshes are scheduled if events arrive during debounce window
When terminal resizes, old screenshot content is stale until
the app (tmux etc) re-renders at new dimensions. Clear cache
to force re-capture after resize.
- Pass service names (not slugs) to DockerStatsCollector
- Create slug->name mapping for sparkline lookups
- Stats are stored by service name, looked up by slug
- Add debug logging when no containers found
New features:
- CPU sparklines on dashboard showing 30-minute container history
- Real-time screenshot updates via Server-Sent Events
- Auto-focus terminals on page load
- Tab reuse when clicking dashboard tiles
- pyte-based screenshot rendering with proper ANSI interpretation
- Dirty tracking for efficient screenshot caching
Documentation:
- Updated README with new features and API endpoints
- Added Dashboard Features section
- Documented compose mode CPU sparklines
- Added API endpoints table
Technical improvements:
- Docker stats collection via Unix socket (no new deps)
- SSE endpoint for activity notifications
- Proper pyte-to-Rich color mapping
- Terminal lifecycle race condition fixes