Skip to content

Add optional terminalBackend hook for alternate terminal engines - #339

Draft
maxandersen wants to merge 2 commits into
asciinema:developfrom
maxandersen:optional-terminal-backend
Draft

maxandersen wants to merge 2 commits into
asciinema:developfrom
maxandersen:optional-terminal-backend

Conversation

@maxandersen

@maxandersen maxandersen commented Oct 13, 2026 •

Copy link
Copy Markdown

I use ascinema player heavily on tamboui.dev and wish to use it more on jbang.dev but I keep hitting issues with emoji, grahemes and image protocols missing.

I've seen the various other attempts of replacing the core engine - this PR takes a different approach
and keeps vt core engine in place but allow for 100% optional External Terminal option.

Makes for a simpler PR and a contract that can be done without dragging any external deps to ascinema player.

Bascially I let an embedder supply a factory that owns VT state and rendering while the
player keeps the recording parser, playback clock, controls and seeking. The
default build imports and depends on nothing new; the hook is inert unless a
backend is provided.

This enables users to use external terminal implementation to get support for things like:

  • graphmemes
  • non-western languages
  • extended emojiis
  • image protocols (kitty, sixel, iterm2)

Here is quick recording showing the examples:

2026-10-13_13-04-55.mp4

As proof-of-concept I made (using AI) backend integration of wterm, xterm.js, ghostty-web and riojs (last one has some layout issues but it does work)

I've split the PR in two commit - one for changes to core; other for the examples so its clear to see the impact.

Prior discussion

https://github.com/orgs/asciinema/discussions/280
#308
https://github.com/orgs/asciinema/discussions/313

How it was tested

Just tested/tried on my local browsers.

AI assistance

pi.dev with Opus 4.8

Checklist

  • I fully understand the changes I'm proposing and can answer questions about them
  • I wrote the description above in my own words
  • The test suite passes locally (it didn't pass before I made changes either - but can look into that if this approach is interesting)

Let an embedder supply a factory that owns VT state and rendering while the
player keeps the recording parser, playback clock, controls and seeking. The
default build imports and depends on nothing new; the hook is inert unless a
backend is provided.

- opts/view: validate (must be a function) and thread the terminalBackend option
- Player: mount ExternalTerminal instead of the built-in Terminal when set
- ExternalTerminal: rAF-coalesced present(), stream-ordered write/resize/reset,
  async factory support, dispose-after-late-init, failure -> core.terminalFailed
- core/worker/ui: terminalFailed path (rejects startup, emits error) and
  removeEventListener
- Terminal: export getCssTheme so a backend can resolve the embedded theme
- index.d.ts: TerminalBackend/Factory/appearance/reset/size/theme types
- tests: lifecycle spec (replay order, late-init dispose, init-failure error)
@davsclaus

Copy link
Copy Markdown

Whoa this would be awesome, would love to have this to demonstrate more terminal videos for how to use Apache Camel with CLI and also how coding agents use the CLI and all can be captured and shown via this.

@maxandersen
maxandersen force-pushed the optional-terminal-backend branch from 1d01315 to cc0dc1c Compare October 13, 2026 12:56
Opt-in proof-of-concept adapters that drive alternate terminal engines through
the terminalBackend hook, plus one comparison launcher. None of this is part of
the player bundle; each backend is one small adapter (<name>/<name>-backend.js)
using the player core unchanged from ../dist.

Backends:
- ghostty-web: libghostty VT + Canvas
- wterm: wterm's built-in Zig core and @wterm/ghostty's libghostty core, both DOM;
  the libghostty core renders Kitty-protocol images
- xterm.js: DOM, with @xterm/addon-image for Sixel + iTerm2 inline images
- rioterm: librio (Rio) DOM; a canvas renderer is included but its viewport fit
  is unresolved, so it is not shown by default

Launcher (examples/index.html):
- pick any mix of backends via checkboxes or ?backends=default,xterm,...
- adaptable grid; each player scales to its cell (fit: width)
- playback synced across all players (any play/scrub drives the rest; pause
  stays put)

Build is one step for all backends: cd examples && npm ci && npm run build
(build.mjs bundles every adapter; gen-cast.mjs makes the played recording, which
exercises Kitty, Sixel and iTerm2 images). Serve the repo root and open
/examples/. Generated dist/, node_modules/ and unicode.cast are gitignored.
@maxandersen
maxandersen force-pushed the optional-terminal-backend branch from cc0dc1c to a1cdf46 Compare October 14, 2026 08:53
@maxandersen

Copy link
Copy Markdown
Author

pushed a few commits as found concurrency bug in the wterm adapter.

@maxandersen

Copy link
Copy Markdown
Author

Any feedback on this if this is an acceptable approach to enable customization of the embedded terminal?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants