Files
xterm.js/AGENTS.md
T
Cursor AgentandDaniel Imms c569e5ab19 Full common split: composite tsconfigs and target-* modules
- Add composite TypeScript projects for common/primitives and common/runtime
- Decouple primitives from runtime (buffer service interfaces, UnicodeProperties, CellTypes, etc.)
- Move Types.ts to runtime; re-export primitive types from runtime/Types
- Rename src/browser -> src/target-browser, src/headless -> src/target-headless
- Update import paths (target-browser/*, target-headless/*) across repo
- Fix esbuild outbase so common/* and target-* resolve under out-esbuild/
- Point unicode addons at primitives for UnicodeTypes/UnicodeProperties
- Update ARCHITECTURE.md and AGENTS.md

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-31 00:19:43 +00:00

5.2 KiB

xterm.js Copilot Instructions

Architecture Overview

Core Structure: xterm.js is a multi-target terminal emulator with these main source areas:

  • src/common/primitives/: Building blocks (buffer, parser, input, data, utilities); TypeScript project src/common/primitives/tsconfig.json
  • src/common/runtime/: Services, CoreTerminal, InputHandler, public API adapters; TypeScript project src/common/runtime/tsconfig.json
  • src/target-browser/: Browser terminal with DOM rendering
  • src/target-headless/: Node terminal without DOM

See src/ARCHITECTURE.md for the dependency graph and import-path conventions (common/*, target-browser/*, target-headless/*).

Key Classes:

  • Terminal (browser/headless): Public API wrapper
  • CoreTerminal (common): Core terminal logic and state
  • CoreBrowserTerminal (browser): Browser-specific terminal implementation

Development Workflow

Build System:

npm run build && npm run esbuild # Build all TypeScript and bundle

Testing:

  • Unit tests: npm run test-unit (Mocha)
  • Unit tests filtering to file: `npm run test-unit -- **/fileName.ts
  • Per-addon unit tests: npm run test-unit -- addons/addon-image/out-esbuild/*.test.js
  • Integration tests: npm run test-integration (Playwright across Chrome/Firefox/WebKit)
  • Integration tests by file: npm run test-integration -- test/playwright/InputHandler.test.ts. Never use grep to filter tests, it doesn't work
  • Integration tests by addon: npm run test-integration -- --suite=addon-search. Suites always follow the format addon-<something>
  • Lint changes: npm run lint-changes to lint only changed files, npm run lint-changes-fix to fix them

Addon Development Pattern

All addons follow this structure:

export class MyAddon implements ITerminalAddon {
  activate(terminal: Terminal): void {
    // Called when loaded via terminal.loadAddon()
    // Register handlers, access terminal APIs
  }
  dispose(): void {
    // Cleanup when addon is disposed
  }
}

Key Examples:

  • addons/addon-fit/: Terminal sizing
  • addons/addon-webgl/: GPU-accelerated rendering
  • addons/addon-search/: Text search functionality

Project-Specific Conventions

TypeScript Project Structure: Uses TypeScript project references (tsconfig.all.json) for incremental builds across browser/headless/addons.

API Design:

  • Browser and headless terminals share the same public API
  • Proposed APIs require allowProposedApi: true option
  • Constructor-only options (cols, rows) cannot be changed after instantiation

Disposable Management:

  • When a disposable object can be replaced over time, prefer a registered MutableDisposable over manual dispose/reassign logic.
  • Register it on the owning class (for example, this._register(new MutableDisposable())) and assign through .value; this automatically disposes the previous value and avoids accidentally leaking resources.

TypeScript Constants:

  • Prefer const enum over top-level const declarations for primitive constants when appropriate, since values are inlined and avoid runtime property lookups.

Testing Utilities: Use TestUtils.ts helpers:

  • openTerminal(ctx, options) for setup
  • pollFor(page, fn, expectedValue) for async assertions
  • writeSync(page, data) for terminal input

Common Patterns

Parser Integration: Register custom escape sequence handlers:

terminal.parser.registerCsiHandler('m', params => {
  // Handle SGR sequences
  return true; // Handled
});

Buffer Access: Read terminal content via buffer API:

const line = terminal.buffer.active.getLine(0);
const cell = line?.getCell(0);

Events: All terminals emit standard events (onData, onResize, onRender) plus platform-specific ones.

Critical Implementation Details

  • Terminal rendering uses either DOM or WebGL renderers
  • Buffer lines are immutable; create new instances for modifications
  • Character width handling supports Unicode 11+ and grapheme clustering
  • Mouse events translate web events to terminal protocols (X10, VT200, etc.)
  • Color theming supports both palette and true color modes

Writing unit tests

  • Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.

Cursor Cloud specific instructions

Demo server: Start with npm start (port 3000). The demo server uses node-pty to spawn real shell sessions over WebSocket. Integration tests auto-start it via Playwright's webServer config, so you don't need to start it manually for npm run test-integration.

Build before testing: Always run npm run build && npm run esbuild before npm run test-unit. Integration tests also need npm run esbuild-demo-client and npm run esbuild-demo-server. The update script handles this automatically on session start.

No external services: This project has zero external dependencies (no databases, Docker, or APIs). Everything runs locally with Node.js.

TypeScript compiler: The project uses tsgo (native TypeScript compiler preview) rather than standard tsc. It's installed via the @typescript/native-preview package.

Lint only changed files: Prefer npm run lint-changes over npm run lint when iterating on code changes — it's significantly faster.