This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Install dependencies and prepare for development
make dev
# Run all tests
make test
# Run pre-commit hooks
pre-commit run --all-files
# Clean all build artifacts
make cleanThe hardware metaphor is load-bearing: the board is the machine, a terminal is the chrome a human looks at, and two full-duplex ports connect the board to its outside world.
Board (src/bittty/devices/board.py)
- The whole emulator: hosts the devices and registers (focus, window state, console
registers), owns the child process and its PTY, and routes parser operations to device
handlers through a flat
registrydict - No UI dependencies; runs headless. The public emulator API (
input_*,resize,capture_pane,start_process) lives here
Devices (src/bittty/devices/)
- Single-responsibility cards plugged into the board: charset, control, cursor, keyboard,
modes, mouse, palette, printer, query, style, title — and the Blitter
(
devices/blitter.py), the device that writes video memory
Video (src/bittty/video.py)
- Video memory: a 2D cell grid, each cell a (Style, char) pair. The board writes it through
the blitter; terminals read it (pull) via
capture_pane()/get_line(). Pages: page memory for the primary screen (several on the VT510), and the alternate screen
Parser (src/bittty/parser/core.py)
- State machine for processing ANSI escape sequences (C0, CSI, OSC, DCS, DEC private modes)
- One-pass ground scanner with bound fast paths:
print_textfor printable runs and memoized registry-direct CSI dispatch — keep these hot paths intact
Terminal (src/bittty/terminals/base.py)
- The chrome ABC. Composes a Board (never subclasses it), plugs into its display port,
receives present events through typed
on_*hooks, and pushes physical facts up (caps, focus, resize, input) - StdioTerminal (
terminals/stdio.py): the reference terminal, whose venue is this process's stdio/tty
Ports (src/bittty/connections.py)
- Full-duplex jacks on the board. HostPort carries bytes both ways to the child: a
Connection(PTY, pipe, socket) plugs in and the port pumps its receive side into the parser. DisplayPort carries typed events both ways to the chrome: present events down, input/focus/caps up. Its name is the video-connector pun, kept on purpose
Peripherals (src/bittty/peripherals/)
- Simulations of hardware on the far end of a cable:
peripherals/printeris a virtual printer (DEC PPL / IBM PPDS, page store). A device is part of the terminal; a peripheral is what you plug into it. Core imports nothing from here —tests/unit/test_peripheral_boundary.pyenforces it. Seedocs/peripherals.mdfor the option/configuration/connection tiers
Options (src/bittty/options.py)
- Hardware fitted at power-on: what the terminal is. An
Optioncontributes mode capabilities (and, for a port, its protocol repertoire), soModel.capabilitiesis the model's own set unioned with its options. Fitting a port enables modes; plugging something into it only changes status reports. Seedocs/peripherals.md
Model (src/bittty/model.py)
- The model number: the emulation profile as data (XTERM, VT220, LINUX, ...) — DA responses, keymaps, mode repertoire, charsets
Style (src/bittty/style.py)
- Packed-int text styling (colors, bold, italic, underline, etc.)
- Parses SGR (Select Graphic Rendition) sequences; 16-color, 256-color, and RGB
- Provides style diffing for efficient rendering
- UnixPTY: Uses os.openpty() for Unix-like systems
- WindowsPTY: Uses Windows ConPTY API
- StdioPTY: For testing with stdin/stdout streams
- All implement the
Connectioninterface for process spawning and I/O
| Term | Means | Never means |
|---|---|---|
| board | the emulator machine | the chrome |
| device | a card in the terminal | something you plug in |
| peripheral | a simulation of what's on the far end of a cable | a device |
| terminal | the chrome a human looks at | the emulator core |
| video | the cell-grid memory (pages) | — |
| blitter | the device that writes video | a renderer |
| model | the model number (XTERM, VT220) | MVC-model |
| renderer | chrome-side output production | anything board-side |
| connection | a cable implementation (PTY, pipe, socket) | — |
| port | a full-duplex jack on the board | — |
- "display" survives only in
DisplayPort, deliberately. bittty.Terminalis deliberately not exported at top level; import chrome classes frombittty.terminals.- There are no compat aliases: the pre-0.1.0 names (
TerminalBoard,Buffer,Personality,WritableTransport,DisplayCaps, the old backendTerminal) are gone.
- Streaming parser: Preserves parser state across input chunks and dispatches completed operations.
- Platform Abstraction: PTY implementations hide platform differences behind common interface
- Separation of Concerns: Board logic separate from UI, making it framework-agnostic
- Style Objects: Immutable style representation allows efficient diffing and caching
- Observe the Zen of Python.
- When there's a bug, write a failing test case for it.
- Failing tests are good tests - they have earned their keep.
- The only required functionality is functionality covered by tests. The only exception to this is when it has a comment explaining what it supposed to do, and why it is important enough to exist yet simultaneously not be important enough to be covered by a test. Such excuses should be rare.
- Do not invent mocks for tests. They affirm false assumptions and make a mockery of the test suite.
- The project will degrade into verbose, brittle spaghetti if left unchecked. This applies at the architectural level too. Periodically propose simplifications and feature freezes for refactoring.
- Branches are a source of shame and disgust, and so should be used sparingly. Branches at boundaries are often unavoidable pities towards an interface, they are not our shame.
- Defensive programming is for the weak; guards belong at trust boundaries, not defending against our own code. Make our code trustworthy by defending our borders.
- Do not guess, read the docs and code. All the files are in source control or
in the
.venvdir at the project root.
docs/DEC_private.mdis the capability inventory. A mode is supported only when it has observable behaviour, not merely a parser entry or stored flag.- Character sets, scroll regions, origin mode, alternate buffers, tab stops, margins, focus reporting, bracketed paste, and basic/button/any SGR mouse reporting are implemented.
Tests use pytest with functional style (no unittest classes). Key test categories:
- Parser tests: Verify escape sequence parsing and state transitions
- Terminal tests: Test terminal operations (cursor, scrolling, clearing, etc.)
- Integration tests: End-to-end parsing with real terminal instances
- Performance tests: Benchmarking parser performance.
- Line length: 120 characters (configured in pyproject.toml)
- Python 3.10+ required. So type hints rarely need
typingmodule. - Uses ruff for linting and formatting
- Pre-commit hooks configured for code quality
- All imports should be at module level (not in functions)
- Use pytest functional style for tests