Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
297 commits
Select commit Hold shift + click to select a range
01eda13
docs(webtty): record ADR 048's measurements
Oct 14, 2026
c3bb34e
fix(server): pace the scrollback replay in chunk-sized pieces
Oct 14, 2026
26f3c0b
fix(server): send queued output before closing on shell exit
Oct 14, 2026
e7c85e4
test(server): accept the tty's doubled CR in the flow test
Oct 14, 2026
0b5a81c
docs(webtty): a slow legacy client sees 1006 under Bun, not 4003
Oct 14, 2026
4049911
docs(webtty): note that input shaped like an ack is consumed
Oct 14, 2026
87342c4
fix(client): keep the kitty overlay at 0x0 while it has no image
Oct 14, 2026
4f4c5b5
fix(client): repaint long runs in chunks on a small scratch canvas
Oct 14, 2026
b3d781b
fix(client): underline only the hovered OSC 8 link
Oct 14, 2026
7261ca2
fix(client): copy a selection once, only with copyOnSelect
Oct 14, 2026
36c5a0b
fix(client): send a wheel tick over the padding once
Oct 14, 2026
3c51bc5
fix(client): encode navigation and function keys with the key encoder
Oct 14, 2026
d2fa4e8
refactor(client): remove the input handler's paste path
Oct 14, 2026
9c7ffc3
refactor(client): remove the terminal API webtty does not use
Oct 14, 2026
5ec10af
refactor(client): trim the buffer API to what the client reads
Oct 14, 2026
7d4c7c9
refactor(client): remove unused engine types and binding methods
Oct 14, 2026
8de5aba
refactor(client): remove unused fit, link and renderer code
Oct 14, 2026
c53cec2
docs(client): ADR 049, mouse, cursor style and paste through libghost…
Oct 14, 2026
95b6741
feat(client): encode mouse reports with Ghostty's mouse encoder
Oct 14, 2026
47a8db5
feat(client): take the cursor shape and blink from Ghostty's state
Oct 14, 2026
277e901
feat(client): send pastes through ghostty_terminal_paste
Oct 14, 2026
f88a45a
fix(server): end a replay's extra queue allowance once it drains
Oct 14, 2026
5f51756
refactor(client): drop onSelectionChange and fix comments left by the…
Oct 14, 2026
4f95617
fix(client): ignore DEC mode 12 for the cursor blink, as Ghostty does
Oct 14, 2026
a1671fa
fix(client): copy wide characters without a space after each one
Oct 14, 2026
3795937
fix(client): move a trackpad gesture's first deltas at once
Oct 14, 2026
568f948
perf(client): copy the edge strips only when the canvas changed
Oct 14, 2026
13276b3
perf(client): cancel only zooming wheel events
Oct 14, 2026
79861b0
docs(client): record the manual check of uncancelled wheel events
Oct 14, 2026
6ef2c4e
docs: describe the terminal as libghostty-vt with a renderer from gho…
Oct 14, 2026
1524b08
fix(config): add STIX Two Text last in the default font stack
Oct 14, 2026
888e0ab
test(client): pin selection gestures before moving them to libghostty-vt
Oct 14, 2026
f883106
docs(client): ADR 050, selection words, lines and text from libghostt…
Oct 14, 2026
24cb2d9
feat(client): bind Ghostty's word, line and selection text calls
Oct 14, 2026
b524b69
feat(client): select words and lines and copy text through libghostty-vt
Oct 14, 2026
8f77612
docs(client): record the selection move to libghostty-vt in the spec …
Oct 14, 2026
43e6170
docs(config): list the characters STIX Two Text now draws accurately
Oct 14, 2026
b30a193
docs(client): ADR 051, drag, double click and right-click follow app …
Oct 14, 2026
8592ea0
feat(config): add contextMenu, paste and none right-click behaviours
Oct 14, 2026
c386d7c
feat(client): decide whether a press or a right-click is the app's in…
Oct 14, 2026
fddb7f9
feat(client): leave drags and double clicks to an app that tracks the…
Oct 14, 2026
f21ac24
feat(client): handle every right-click in one place, following app mo…
Oct 14, 2026
2f93283
docs(client): record the app-first mouse and right-click behaviours i…
Oct 14, 2026
49b2805
docs(client): ADR 052, programs write the clipboard with OSC 52
Oct 14, 2026
7e80023
feat(config): add clipboardWrite, served by /api/config
Oct 14, 2026
30ae05e
feat(client): take OSC 52 clipboard writes from libghostty-vt's CLIPB…
Oct 14, 2026
a09785b
feat(client): write the browser clipboard for OSC 52, held back after…
Oct 14, 2026
912f19b
docs(client): record OSC 52 clipboard writes and clipboardWrite in th…
Oct 14, 2026
512fbd4
fix(client): keep the selection on mouse reports, so a Shift selectio…
Oct 14, 2026
8fa6718
docs(client): ADR 051 and client spec on mouse reports keeping the se…
Oct 14, 2026
747de10
docs(client): ADR 053, focus reports, the app's title and the bell
Oct 14, 2026
c939594
feat(client): send mode 1004 focus reports, encoded by libghostty-vt
Oct 14, 2026
58d61f4
feat(config): add bellTitle, served by /api/config
Oct 14, 2026
8878892
feat(client): show the app's title in the tab, with a bell mark Ghost…
Oct 14, 2026
8ee21fd
docs(client): record focus reports, the tab title, the bell and bellT…
Oct 14, 2026
4c3c037
fix(client): resend the focus state when a socket opens with mode 100…
Oct 14, 2026
928bf29
fix(client): end the replay at its last piece, not at the first 1 s p…
Oct 14, 2026
f410e3e
docs(client): ADR 054, copy, paste and clear-screen keys as Ghostty b…
Oct 14, 2026
629ad23
feat(client): clear the screen and scrollback as Ghostty's clear_scre…
Oct 14, 2026
810bbb2
feat(client): Ctrl+Shift+C, Ctrl+Insert and Shift+Insert outside macO…
Oct 14, 2026
4384abf
docs(client): record the copy, paste and clear-screen keys in the specs
Oct 14, 2026
4598629
fix(client): write Cmd+K's clear where the output reaches ground, not…
Oct 14, 2026
9ef809b
docs(client): ADR 055, find in the scrollback with libghostty-vt's se…
Oct 14, 2026
bae70c7
feat(client): search the scrollback with libghostty-vt's ghostty_sear…
Oct 14, 2026
97633ba
feat(client): highlight find matches in the renderer in Ghostty's sea…
Oct 14, 2026
2de968b
feat(client): find in the terminal, scrolling to the selected match a…
Oct 14, 2026
9150490
feat(config): add the searchBackground, searchForeground and searchSe…
Oct 14, 2026
edb28b3
feat(client): a find bar on Cmd+F / Ctrl+Shift+F, Enter and Cmd+G to …
Oct 14, 2026
6e7cf6b
docs(client): record find in the scrollback in the specs and provenance
Oct 14, 2026
d3fbd98
fix(client): scroll to the bottom on a key or a paste sent to the pro…
Oct 14, 2026
dbed08c
fix(client): output on the alternate screen scrolls to the bottom wit…
Oct 14, 2026
d4c9f79
docs(client): ADR 056, desktop notifications from OSC 9 and OSC 777
Oct 14, 2026
25c7f67
feat(config): add desktopNotifications, served by /api/config
Oct 14, 2026
d5e982c
feat(client): take OSC 9 and OSC 777 notifications from libghostty-vt…
Oct 14, 2026
ff736f7
refactor(client): move the replay's end out of TabTitle into ReplayWi…
Oct 14, 2026
20adeca
feat(client): show OSC 9 and OSC 777 as browser notifications while t…
Oct 14, 2026
5cfb23e
docs(client): record desktop notifications and desktopNotifications i…
Oct 14, 2026
220d09f
fix(client): ask for notification permission only on an input that ac…
Oct 14, 2026
e41a216
docs(client): ADR 057, jump to shell prompts and select command outpu…
Oct 14, 2026
e28c662
feat(client): move by OSC 133 prompts and select command output with …
Oct 14, 2026
2a55ad3
feat(client): jump the view to OSC 133 prompts, and select a command'…
Oct 14, 2026
2a0435b
feat(client): Cmd+Up/Down (macOS) and Ctrl+Shift+Up/Down jump to the …
Oct 14, 2026
69c2177
docs(client): record prompt jumps, output selection and getting OSC 1…
Oct 14, 2026
9bc0ff6
fix(client): take a window blur and a tab switch as lost focus, as Ch…
Oct 14, 2026
947b6e4
feat(client): clear a selection once copyOnSelect copies it, as Claud…
Oct 14, 2026
c9b7090
docs(client): ADR 058, webtty's own OSC 133 prompt-mark snippets for …
Oct 14, 2026
fc6896f
docs(client): give the zsh and bash prompt-mark snippets in the clien…
Oct 14, 2026
d333069
docs(arch): architecture SPEC and ADRs 059-065 for a modular, testabl…
Oct 14, 2026
45737bc
docs(arch): revise the architecture SPEC and ADRs 060-062, 064, 065 a…
Oct 14, 2026
672a4b1
test(server): move the shared test helpers out of a *.test.ts file
Oct 14, 2026
ce460da
refactor(server): one runtime-detection module
Oct 14, 2026
dabc17d
refactor(config): move src/config.ts to src/config/index.ts
Oct 14, 2026
c4fae00
test(config): import the moved config tests from the folder index
Oct 14, 2026
6bd0b99
test(arch): architecture checker with a ratchet allowlist
Oct 14, 2026
66fb27b
docs(arch): the architecture checker's parts in the SPEC tree
Oct 14, 2026
91213c0
refactor(config): split the config module into schema, paths, default…
Oct 14, 2026
1917a40
test(arch): import scanner reads commented heads and stops at the nex…
Oct 14, 2026
3042df3
refactor(server): build /api/config from the one client key list
Oct 14, 2026
9408880
test(server): sort the routes test imports
Oct 14, 2026
f2c4939
chore(deps): happy-dom 20.14.5 for DOM tests (ADR 064)
Oct 14, 2026
faacc29
refactor(config): read the default shell through defaultShell(platfor…
Oct 14, 2026
2ca2bf2
test(config): split the config tests by module
Oct 14, 2026
6689ece
refactor(server): rename flow.ts to output-pacer.ts
Oct 14, 2026
7c94295
test(client): test support fakes, an injected happy-dom window and Di…
Oct 14, 2026
d63684c
refactor(server): findGhosttyWasm takes the project root from the entry
Oct 14, 2026
9882e64
refactor(client): app code imports vendor code through the barrel only
Oct 14, 2026
b57a68e
refactor(server): move static.ts to http/static-files.ts
Oct 14, 2026
571b287
refactor(server): move WS_CLOSE into ws/close-codes.ts
Oct 14, 2026
0e348d0
test(server): the server harness self-tests sit beside test-support/
Oct 14, 2026
11a8cf5
test(server): restore fetch even when the harness shut-down test fails
Oct 14, 2026
6f8d74e
Merge the C1 architecture checker into refactor/server-architecture
Oct 14, 2026
dec8482
refactor(client): move the existing app modules into their folders
Oct 14, 2026
fa49aa9
docs(arch): client and kitty specs name the moved module paths
Oct 14, 2026
8a24b28
refactor(server): session IDs in sessions/session-id.ts
Oct 14, 2026
c64390f
refactor(server): the session banner in sessions/session-banner.ts
Oct 14, 2026
5c1108e
refactor(client): the find bar takes the shortcut lookup from the root
Oct 14, 2026
490dc95
docs(arch): find keys take the shortcut lookup as an argument
Oct 14, 2026
4bd9af8
refactor(server): parse PTY client messages in ws/client-message.ts
Oct 14, 2026
22a875a
test(server): a session's output pacer pauses the PTY the session holds
Oct 14, 2026
57b2e4b
refactor(server): SessionStore replaces the module-global registry
Oct 14, 2026
27c1f0f
refactor(client): split the find bar into find-format, find-keys and …
Oct 14, 2026
0c503ee
refactor(client): wheel classifier and scroll arithmetic get modules …
Oct 14, 2026
d51917e
refactor(client): edge rects split from the edge extender view, which…
Oct 14, 2026
026807e
refactor(server): move session.ts into sessions/
Oct 14, 2026
a45bd26
refactor(client): composer geometry split from the composer view, whi…
Oct 14, 2026
af2623e
refactor(client): browser notifications adapter split from the deskto…
Oct 14, 2026
a1e6c23
test(server): a fake PTY and a fake socket in test-support/
Oct 14, 2026
1c67320
refactor(server): split websocket.ts into sessions/session.ts and ws/*
Oct 14, 2026
cebd9d7
docs(arch): record that a1e6c23 does not build and carries the websoc…
Oct 14, 2026
630d2b6
refactor(client): smooth scroll and wheel stream take their browser g…
Oct 14, 2026
5cb1659
refactor(client): split kitty.ts into features/kitty/protocol/
Oct 14, 2026
9da5359
test(server): golden tests for every route before moving routes
Oct 14, 2026
f4549ac
refactor(client): move graphics.ts to features/kitty/graphics/kitty-g…
Oct 14, 2026
d0df8c6
refactor(server): HTTP helpers and a shared NDJSON line splitter
Oct 14, 2026
517286f
refactor(client): split graphics.ts into features/kitty/graphics/
Oct 14, 2026
ed797b8
refactor(server): route table and per-resource route modules
Oct 14, 2026
2cf99c7
test(server): run-command with a fake spawn and the publish stream error
Oct 14, 2026
1704ec4
refactor(client): the terminal canvas selectors get one module, layou…
Oct 14, 2026
ea97a8c
refactor(client): config loader, terminal options and platform helper…
Oct 14, 2026
7b9300b
refactor(server): createServer factory, server paths and a thin entry
Oct 14, 2026
edf154b
refactor(client): ClientEnv adapter and ClientContext types in app/
Oct 14, 2026
b371c18
docs(arch): toTerminalOptions takes the engine, and ClientEnv's windo…
Oct 14, 2026
e434f3d
test(server): the missing-wasm startup report is testable and covered
Oct 14, 2026
86d12ee
refactor(client): isMacPlatform moves to app/platform, so app/ no lon…
Oct 14, 2026
359d80f
refactor(scripts): check-architecture fails app/, connection/ and lay…
Oct 14, 2026
1cc60d3
refactor(client): connection/ close codes, close policy, status line …
Oct 14, 2026
9a9fde3
refactor(client): PtyConnection owns the PTY socket, with one send gu…
Oct 14, 2026
acecb33
test(client): OutputAck on its default window timers
Oct 14, 2026
f5dd7f4
test(server): an in-process LiveServer built by createServer for the …
Oct 14, 2026
8ad9533
test(server): run route and websocket tests in-process
Oct 14, 2026
e4ec4bf
refactor(pty): name the PTY files after what they hold
Oct 14, 2026
61b9727
test(client): fakes for the window, ResizeObserver and a feature's Cl…
Oct 14, 2026
4f3fa60
refactor(client): layout/: Fitter, layout feature and repaint on visible
Oct 14, 2026
cc460f2
refactor(pty): spawnPty options object and testable backends
Oct 14, 2026
17c1751
test(client): recordListeners logs the listeners of a real DOM elemen…
Oct 14, 2026
47bb25c
refactor(client): tab title, notifications and clipboard feature inst…
Oct 14, 2026
34ae79f
refactor(cli): server process and browser opener out of http.ts
Oct 14, 2026
a3dea9a
refactor(cli): main, args, help and deps behind a thin entry
Oct 14, 2026
7a5a6f2
docs(arch): mark CliDeps commands table as interim; ADR 063 builds de…
Oct 14, 2026
95c4413
refactor(client): font zoom, paste and find feature installers, and t…
Oct 14, 2026
717b162
refactor(client): one key dispatcher for configured bindings and Ghos…
Oct 14, 2026
ec01a6b
refactor(cli): WebttyClient and one module per command
Oct 14, 2026
573194f
test(client): FakeTerminal records open, clearScreen, resyncFocus and…
Oct 14, 2026
b5fc01c
refactor(client): scroll and kitty feature installers, and SmoothScro…
Oct 14, 2026
552506e
docs(arch): ADR 063 and cli SPEC describe the CliDeps and WebttyClien…
Oct 14, 2026
a4674a0
test(client): fakeClientEnv runs the client root on fakes, with one l…
Oct 14, 2026
82371eb
refactor(client): app/start-client.ts is the composition root; index.…
Oct 14, 2026
daf8919
docs(arch): the client source layout by folder, connect-pty.ts and fa…
Oct 14, 2026
b046baa
test(client): the client root's kitty replies, find keys, replay-awar…
Oct 14, 2026
178b281
refactor(server): a testable package-version walk with a header
Oct 14, 2026
a1a2a2f
refactor(cli): createCliDeps hands startServer itself to server.start
Oct 14, 2026
4fd50fe
test(server): coverage pass and cleanup for the server track
Oct 14, 2026
c452240
refactor(client): startClient hands steps 9-13 to installInput, keepi…
Oct 14, 2026
1270852
refactor(scripts): split update-ghostty-vt into scripts/ghostty-vt/
Oct 14, 2026
e6ed5f6
test(scripts): the ghostty-vt pin library on fakes: minisign, inspect…
Oct 14, 2026
e91eddd
refactor(scripts): headers and tested pure parts for check-pkg-manage…
Oct 14, 2026
fbda7d3
docs(arch): scripts/ghostty-vt/ tests and test-support in the tree
Oct 14, 2026
f54f486
test(client): vendor tests and their fixtures move into vendor/tests/
Oct 14, 2026
4426cc4
test(client): one wasm path and one compile for the vendor tests, and…
Oct 14, 2026
8914575
test(client): the vendored Terminal's listener snapshot, on fake DOM …
Oct 14, 2026
c3ef133
refactor(client): the vendor types.ts split into domain-named type files
Oct 14, 2026
e44f9ff
refactor(client): event emitter docs, with its behaviour pinned by tests
Oct 14, 2026
b735dfa
refactor(client): engine split, part 1: pure engine modules out of gh…
Oct 14, 2026
80598e2
test(client): wasm-free unit tests for the pure engine modules
Oct 14, 2026
76f9187
refactor(client): engine/wasm-loader.ts, the wasm loading out of Ghos…
Oct 14, 2026
a6d7e71
refactor(client): engine/key-encoder-binding.ts and the EngineHost in…
Oct 14, 2026
9e87055
refactor(client): engine/official-terminal.ts, ghostty.ts becomes the…
Oct 14, 2026
2694d59
refactor(client): engine split, part 3: EngineContext, render state, …
Oct 14, 2026
8c844b0
test(client): split official-terminal.test.ts by engine area
Oct 14, 2026
e4bc6fa
refactor(client): engine split, part 4: per-feature bindings; Officia…
Oct 14, 2026
cf07f96
test(client): engine bindings tested directly beside the facade tests
Oct 14, 2026
217040e
docs(arch): engine split done; grid-refs and engine-config contents i…
Oct 14, 2026
deed66d
refactor(client): render split, part 1: contracts, theme, DPR, frame …
Oct 14, 2026
79f84ea
refactor(client): render split, part 1: one cell range for the render…
Oct 14, 2026
763aa37
refactor(client): render split, part 1: band planning and scrollbar g…
Oct 14, 2026
bd2ab21
refactor(client): render split, part 2: painters; CanvasRenderer a fa…
Oct 14, 2026
bda8663
docs(arch): render/ tree lists the scroll tracker, decoration tracker…
Oct 14, 2026
7ff16b4
refactor(client): input split: key table, key encoding, text input an…
Oct 14, 2026
e693e81
refactor(client): InputHandler takes one options object
Oct 14, 2026
b7b47f2
refactor(client): selection split: coords, gestures, auto-scroll, con…
Oct 14, 2026
db7fbd3
refactor(client): one link activation helper for both link providers
Oct 14, 2026
701a1da
docs(arch): the vendor tree lists providers/ and the selection test f…
Oct 14, 2026
51a51ae
refactor(client): terminal split, part 1: constants, geometry, option…
Oct 14, 2026
2702301
refactor(client): terminal split, part 2: focus, find, render loop, w…
Oct 14, 2026
06e8580
refactor(client): terminal split, part 3: mouse and wheel controllers
Oct 15, 2026
1c9110f
refactor(client): terminal split, part 4: scrollbar, DOM setup, Opene…
Oct 15, 2026
032accf
docs(arch): the core/ tree lists the terminal's controllers, parts an…
Oct 15, 2026
c452e52
refactor(client): opened parts typed by narrow part interfaces; split…
Oct 15, 2026
6576d01
fix(client): release every listener on dispose (ADR 065)
Oct 15, 2026
21575ce
docs(arch): listener teardown done; the snapshot and dispose test aft…
Oct 15, 2026
81756ed
test(client): the listener snapshot shows a leaking listener as '-> L…
Oct 15, 2026
937ce88
refactor(client): remove the vendor types.ts shim
Oct 15, 2026
95bd8cf
refactor(client): the vendor test mirror, the last flat tests moved
Oct 15, 2026
51ab194
refactor(client): every vendored file names its origin; PROVENANCE.md…
Oct 15, 2026
47bf50d
test(client): vendor coverage gaps: the controllers' hosts, Cmd+C and…
Oct 15, 2026
bf18e1c
docs(arch): the vendored split is done (ADR 061)
Oct 15, 2026
8895e22
docs(arch): PROVENANCE moves table names the real shim-removal commit
Oct 15, 2026
5905551
Merge branch 'refactor/server-architecture' into feat/client-libghost…
Oct 15, 2026
772fc70
refactor(client): ClientConfig is src/config's type; the close codes …
Oct 15, 2026
f4fb18f
test(arch): the ratchet allowlist is empty and strict
Oct 15, 2026
0818093
test(arch): per-layer coverage targets over bun's lcov report
Oct 15, 2026
54433b6
test(arch): ignore the coverage/ report folder test:coverage writes
Oct 15, 2026
b181966
refactor(client): the last modules without a header comment get one
Oct 15, 2026
85a3574
docs(arch): the refactor is done: ADR 062, 063 and 064 rows ✅, the tr…
Oct 15, 2026
aa9dca1
test(arch): drop the stale src/config.ts layer case now that config i…
Oct 15, 2026
f79a2b8
test(arch): the coverage check shares isTestCode and fails on source …
Oct 15, 2026
cfd34e0
refactor(client): the Cmd+K screen clear moves from the root to featu…
Oct 15, 2026
a738e9a
test(client): every feature installer is checked against FeatureInsta…
Oct 15, 2026
58d3851
test(client): one recordingContext in vendor/tests/support for the fo…
Oct 15, 2026
4f9f20d
refactor(client): OSC8LinkProvider.provideLinks scans, linkAt reads o…
Oct 15, 2026
ee7385e
docs(arch): headers name the second bell-clear path and non-text-past…
Oct 15, 2026
d1f07bb
docs(arch): client SPEC points paste requests and the replay feed at …
Oct 15, 2026
2c66663
docs(arch): ADRs 059-065 record what was built; the SPEC tree matches…
Oct 15, 2026
77d1aa0
test(client): the root's edge strips copy the renderer's visible area
Oct 15, 2026
dd5fd68
fix(scripts): inspect-wasm refuses a wasm that needs imports before i…
Oct 15, 2026
58ea652
docs(adr): 064 records Bun's uncovered class field initializers and t…
Oct 15, 2026
97ce48d
feat(scripts): scroll-bench measures blinks, paints and the scrolled …
Oct 15, 2026
3a99e28
fix(client): hold painting during an app's synchronized update, timed…
Oct 15, 2026
06c2f8a
fix(scripts): scroll-bench sizes its shift window per input, survives…
Oct 15, 2026
ae46c13
fix(client): output keeps a scrolled-back view on its text, through p…
Oct 15, 2026
ee3557a
docs(client): clearing the selection after copy-on-select follows her…
Oct 15, 2026
5a7893d
fix(client): a redraw in place repaints only what changed under a scr…
Oct 15, 2026
ede827a
fix(pty): deleting a session hangs up its shell, which an interactive…
Oct 15, 2026
28719f9
fix(scripts): scroll-bench records at the page's size, finds stream.s…
Oct 15, 2026
3346e21
fix(scripts): scroll-bench waits for the shell's prompt and reruns a …
jesse23 Oct 15, 2026
2886492
fix(client): after a held frame, a synchronized update is painted as …
jesse23 Oct 15, 2026
bde50f4
docs(scroll-benchmark): the scroll benchmark as a reference SPEC, and…
jesse23 Oct 15, 2026
0c54829
fix(client): an update's end is painted across a split sequence, the …
jesse23 Oct 15, 2026
3752b40
docs(scroll-benchmark): the SPEC and ADR 068 quote the reference run …
jesse23 Oct 15, 2026
afd2858
fix(client): a mouse wheel is told from a TrackPoint at any browser z…
jesse23 Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* text=auto eol=lf
*.wasm binary
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pnpm-lock.yaml

# Build outputs
dist/
coverage/

# npm publish artifacts
*.tgz
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ Follow [docs/development.md](docs/development.md) — Spec & ADR Driven Developm
```
webtty/
├── docs/
│ ├── specs/ # living architecture specs
│ ├── specs/ # living specs; specs/architecture.md maps the code
│ └── adrs/ # architecture decision records
├── src/ # cli/, server/, pty/, config/, client/ (tree in specs/architecture.md)
├── scripts/ # build, architecture and coverage checks, ghostty-vt updater
└── README.md
```

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![npm version](https://img.shields.io/npm/v/webtty)](https://www.npmjs.com/package/webtty)
[![CI](https://github.com/jesse23/webtty/actions/workflows/ci.yml/badge.svg)](https://github.com/jesse23/webtty/actions/workflows/ci.yml)

Terminal UI in the browser. Run CLI/TUI applications in a browser tab, across platforms. Powered by [ghostty-web](https://github.com/coder/ghostty-web).
Terminal UI in the browser. Run CLI/TUI applications in a browser tab, across platforms. Terminal emulation by [Ghostty](https://ghostty.org)'s official [libghostty-vt](https://github.com/ghostty-org/ghostty), with a renderer and input layer derived from [ghostty-web](https://github.com/coder/ghostty-web).

- [Why webtty?](docs/awesome-web.md#terminal)

Expand Down
8 changes: 7 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
},
"files": {
"ignoreUnknown": false,
"includes": ["**", "!**/node_modules", "!**/dist"]
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!src/client/vendor/ghostty-web",
"!src/client/vendor/ghostty-vt"
]
},
"formatter": {
"enabled": true,
Expand Down
14 changes: 11 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Bun's coverage threshold is one global setting, so this is the common floor of the
# per-layer targets; scripts/check-coverage.ts enforces each layer (architecture SPEC, ADR 064).
[test]
coverageThreshold = { line = 0.85, function = 0.80 }
2 changes: 2 additions & 0 deletions docs/adrs/011.cli.config-and-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ Several small UX issues surfaced during review:
- `copyOnSelect: boolean` (default `true`) — auto-copy to clipboard on mouseup, like kitty and Windows Terminal.
- `rightClickBehavior: "default" | "copyPaste"` (default `"default"`) — `"copyPaste"` copies + clears selection on right-click (Windows Terminal style); `"default"` leaves the native context menu untouched. Invalid values silently fall back to `"default"`.

**Update (ADR 051):** `rightClickBehavior` is `"contextMenu"` (default; `"default"` is still accepted), `"copyPaste"`, `"paste"` or `"none"`, and invalid values fall back to `"contextMenu"`. While the app tracks the mouse, a drag or right-click goes only to the app: webtty selects nothing, copies nothing and shows no menu, unless Shift is held. See [ADR 051](051.client.mouse-tracking-selection-and-right-click.md).

## Related Decisions

- [ADR 002 — CLI start/stop](002.cli.start-stop.md): established `startServer` and `isServerRunning` used by the no-arg path
Expand Down
4 changes: 4 additions & 0 deletions docs/adrs/013.client.cursor-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Intercept DECSCUSR sequences in `src/client/cursor.ts` before passing data to `t

The intercept lives in `src/client/cursor.ts`, isolated from the WebSocket and terminal wiring in `index.ts`. It is removed when ghostty-web implements DECSCUSR natively.

**Update (ADR 041):** DECSCUSR is now applied inside the vendored terminal (`src/client/vendor/ghostty-web/terminal.ts`): `write()` scans the output (`scanDecscusr`, which also carries a sequence split across writes) and keeps the app's shape apart from `options.cursorStyle` / `options.cursorBlink`, which stay the configured default. Ps 0 now clears the app's shape, so the configured cursor shows again (it was blinking block). `cursor.ts` and its call in `index.ts` are deleted. The Coder wasm does not export the cursor style, so the output is still scanned; reading it from the render state is ADR 041 step 5. ([ADR 047](047.client.official-engine-only.md) removed the Coder wasm but kept the scan: the official render state does expose the cursor style, and reading it from there is a follow-up with its own update to this ADR, since Ghostty's blink also follows mode 12.)

**Update (ADR 049):** the scan is gone. The configured style and blink are Ghostty's defaults (`DEFAULT_CURSOR_STYLE` / `DEFAULT_CURSOR_BLINK`), Ghostty applies DECSCUSR, and the renderer draws the shape and blink that the render state reports. Ps 0 and RIS still restore the configured default. Following Ghostty, the shape is kept per screen and DECSTR restores the default. Mode 12 is ignored, as in Ghostty with `cursor-style-blink` set, so only DECSCUSR changes the blink. See [ADR 049](049.client.official-mouse-cursor-paste.md).

## Considered Options

**Option A: Fix ghostty-web upstream (long-term)**
Expand Down
2 changes: 2 additions & 0 deletions docs/adrs/015.client.cursor-ghost-repaint.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ The force repaint is guarded by a `changed` flag and only fires when style or
blink actually differs from the current value — i.e., on vim mode switches,
which are infrequent. There is no per-message overhead.

**Update (ADR 041):** the repaint now lives in the vendored renderer (`src/client/vendor/ghostty-web/renderer.ts`): `setCursorStyle()` / `setCursorBlink()` flag a shape change, and the next frame redraws the cursor row as it does when the cursor moves, instead of a synchronous full repaint from `cursor.ts`. It also covers shape changes made through `term.options`.

---

## Considered Options
Expand Down
4 changes: 4 additions & 0 deletions docs/adrs/017.client.mouse-scroll.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ term.attachCustomWheelEventHandler((e: WheelEvent): boolean => {
});
```

**Update (ADR 041):** the intercept now lives in the vendored terminal (`src/client/vendor/ghostty-web/terminal.ts`): `Terminal.handleWheel` checks mouse tracking on either screen before its arrow-key and viewport paths and fires the SGR reports through `onData` (`wheelTicks`, `wheelSgr`); `mouseScrollSpeed` is a `Terminal` option. The custom wheel handler in `index.ts` now only does pixel-smooth scrollback (ADR 040) when the app does not track the mouse. The cell is measured from the canvas rather than the event target.

**Update (ADR 049):** wheel ticks are encoded by Ghostty's mouse encoder as presses of buttons 4/5 at the pointer's cell, in the format the app chose (SGR 64/65 under 1006, X10 bytes, UTF-8, urxvt or SGR-pixels), with Shift, Alt and Ctrl. `wheelSgr` is removed. See [ADR 049](049.client.official-mouse-cursor-paste.md).

When the custom handler returns `true`, `Terminal.handleWheel` returns early
and skips the arrow-key loop. When it returns `false` (mouse tracking off),
the default behaviour is preserved — arrow keys are still sent for apps that
Expand Down
5 changes: 4 additions & 1 deletion docs/adrs/032.client.kitty-graphics-overlay.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ Implement the protocol in the webtty client, in front of ghostty-web:
(`ESC _ G i=<id>;OK ESC \`) are sent back over the WebSocket as if typed.
Programs that size their output in pixels ask with `CSI 14 t` / `16 t` /
`18 t`; the client answers these itself from the renderer metrics, because
the WASM cannot know pixel size.
the WASM cannot know pixel size. (Changed by [ADR 042](042.client.official-vt-engine.md)
and [ADR 047](047.client.official-engine-only.md): the official wasm takes
the cell size through `OPT_SIZE` and answers them, and the client's replies
are deleted.)
7. **Pixel scale is `devicePixelRatio`.** Sizes reported to programs are
device pixels, as kitty and Ghostty report them, and images are drawn at
`size / scale` CSS pixels. Programs rely on this: terminal-browser divides
Expand Down
2 changes: 2 additions & 0 deletions docs/adrs/033.client.padding-fit.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Two changes, both aimed at the visible strip:
terminal colour, so the client removes it from the theme object before handing the
theme to ghostty-web.

**Update (ADR 041):** the reserve is now removed in the vendored `FitAddon` (`src/client/vendor/ghostty-web/addons/fit.ts`, `fitDimensions`), and `computeGrid` is gone. The client sets `config.padding` as the container's CSS padding and takes the grid from `FitAddon.proposeDimensions()`; the leftover is still placed by `computeInsets` in `src/client/fit.ts`.

## Considered Options

### Option A: Local fit without the reserve, plus `theme.padding` (chosen)
Expand Down
2 changes: 2 additions & 0 deletions docs/adrs/037.client.multiline-text-paste.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Send nonempty text to the PTY regardless of mode 2004: wrap multiline text in br
- A receiving app without bracketed paste may treat newlines as Enter, potentially submitting commands or accepting an fzf selection. There is no universal, application-independent way to ensure atomic multiline editing in such an app. Users should not paste untrusted multiline commands into an unbracketed shell or prompt. A nested app must enable/propagate mode 2004 for safe multiline editing.
- Image-only clipboard paste still forwards Ctrl+V, preserving the existing native clipboard path from ADR 014.

**Update (ADR 049):** pastes are encoded by `ghostty_terminal_paste`. With mode 2004 on, every paste is bracketed, single-line text too. Unsafe control bytes become spaces, and without 2004 newlines are sent as `\r`. Nonempty text is still always sent and never refused, and empty text still sends Ctrl+V. See [ADR 049](049.client.official-mouse-cursor-paste.md).

## Related Decisions

- [ADR 014 — Non-text paste](014.client.image-paste.md)
2 changes: 2 additions & 0 deletions docs/adrs/038.client.ime-composition-at-cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The client takes over how composition is presented. The data path that ghostty-w
2. **Anchor the textarea at the cursor.** On every animation frame, move the textarea to the pixel position of the terminal cursor cell, writing only the styles that changed. ghostty-web's `onRender` never fires and `onCursorMove` only reports row changes, so neither can drive this. The position is `term.buffer.active.cursorX` / `cursorY`, shifted down by `viewportY` and clamped to the grid, multiplied by the cell size from `renderer.getMetrics()`, and offset by the canvas position inside the container. The textarea stays anchored even before a composition starts, because macOS places the dictation microphone when dictation begins. The textarea gets the cell's line height. macOS then places the dictation microphone and the IME candidate window at the cursor.
3. **Show a themed preview at the cursor.** Add a small overlay element (`src/client/compose.ts`) with absolute position and `pointer-events: none`, placed over the cursor cell. It is updated from `compositionupdate` (`event.data`) and hidden on `compositionend` or `blur`. It uses the terminal's `fontFamily` and `fontSize`, `theme.foreground` on `theme.background`, and an underline, which is the usual style for uncommitted text. Long text is not wrapped and runs to the right edge. The textarea stays invisible, so the overlay is the only visible copy of the composition.

**Update (ADR 041):** steps 1 and 2 now live in the vendored terminal (`src/client/vendor/ghostty-web/terminal.ts`): `open()` adds the `focusin` and window `focus` redirects, and the render loop anchors the textarea at the cursor cell each frame (`cursorCellOffset`, unit-tested in `src/client/vendor/terminal.test.ts`). `compose.ts` keeps only the overlay, placed from `Terminal.getCursorCellOffset()` on each frame while composing.

## Considered Options

- **Remove `contenteditable` from the container.** This would stop the stray text node. However, ghostty-web adds the attribute deliberately and its `beforeinput` guard depends on it. Removing it would also leave a focused container that accepts no IME input at all. Sending focus to the textarea fixes the same problem without patching ghostty-web's assumptions.
Expand Down
4 changes: 3 additions & 1 deletion docs/adrs/039.client.device-pixel-ratio-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ Rounding up keeps `cols × cellWidth × dpr` an integer (cell sizes are whole CS
- **Patch ghostty-web to compare against `Math.round(...)`** — stops the per-frame resize at the exact ratio, but needs a dependency patch in the build and leaves the seams.
- **Round up the ratio (chosen)** — no patch, fixes both; costs a larger backing store at fractional ratios.

**Update (ADR 041):** the logic now lives in the vendored renderer (`src/client/vendor/ghostty-web/renderer.ts`): it rounds the ratio up (`wholeDevicePixelRatio`), rounds backing-store sizes the same way in `resize()` and `render()`, and follows changes itself (`watchDevicePixelRatio`); its next frame resizes and repaints the canvas. `index.ts` only schedules a fit on a change. The private-field cast is gone.

## Consequences

- Moving between displays of different scale keeps text crisp without a reload.
- At a fractional ratio the canvas no longer resizes every frame (measured at 1.36: 60/s on `main`, 0 with this change).
- At a fractional ratio the backing store is larger than strictly needed (e.g. 2× instead of 1.36×), and text is drawn at the higher ratio and scaled down by the browser rather than drawn at native resolution.
- Depends on a private field of ghostty-web's renderer; an upgrade that renames it would silently drop the fix (the cast compiles either way). Revisit once coder/ghostty-web#198 is fixed upstream.
- Depends on a private field of ghostty-web's renderer; an upgrade that renames it would silently drop the fix (the cast compiles either way). Revisit once coder/ghostty-web#198 is fixed upstream. (Resolved by ADR 041: the renderer owns the logic and the cast is gone.)

## Related Decisions

Expand Down
6 changes: 6 additions & 0 deletions docs/adrs/040.client.pixel-smooth-scroll.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Keep the scroll position as fractional lines in the client (`src/client/smooth-s

The alternate screen and mouse-tracking apps scroll their own view in whole lines, so no pixel offset is possible there. For mouse-tracking apps (herdr, Claude Code's fullscreen TUI, vim `mouse=a`), count pixel wheel deltas by distance instead: one SGR scroll tick per cell height scrolled, 1:1 with the OS like native macOS scrolling. `mouseScrollSpeed` (ADR 017) does not apply to pixel deltas, as Ghostty skips its scroll multiplier for precision scrolls; at 1.5 it made apps scroll visibly faster than native. Previously every wheel event was one tick, so a trackpad's many small events and momentum scrolled far ahead of the fingers. Line and page deltas still count one tick per event.

**Superseded in part by [ADR 043](043.client.scroll-copy-and-overscan.md):** the strip canvas and the `renderLine` swap are gone. The renderer draws rows above and below the viewport on a canvas of its own and shows the offset itself (`setScrollOffset`), moving that canvas by `translateY` and clipping it to the viewport. The bottom edge strip now extends the last row in view, and Kitty images move with the offset.

**Update (ADR 041):** the mouse-tracking tick counting now lives in the vendored terminal (`src/client/vendor/ghostty-web/terminal.ts`, `wheelTicks`), with ADR 017's SGR reports; `index.ts` keeps only the pixel-smooth scrollback handler.

**Update (ADR 045):** a mouse wheel notch in scrollback now glides to its target instead of jumping there. The tick counting for apps is in `src/client/vendor/ghostty-web/wheel.ts`.

## Consequences

- Trackpad scrolling through scrollback follows the fingers pixel by pixel, with macOS momentum, and no easing lag.
Expand Down
Loading
Loading