Documentation
¶
Overview ¶
Package web bridges a bubbletea Program to an xterm.js terminal in a page.
The design rests on one fact: xterm.js already speaks the terminal protocol. It turns DOM key events into escape sequences and, once bubbletea asks for mouse tracking, emits SGR mouse sequences — and bubbletea already parses both. So this is a byte pipe, not a translation layer, and key and mouse parity with the terminal build comes for free rather than being maintained.
The rule that will break everything if ignored ¶
A js.Func callback must never block. Go on WebAssembly runs on the single JavaScript thread: while a Go callback is running, no other JS event can fire. A callback that blocks waiting for something another event would deliver deadlocks the whole runtime, and the symptom is a page that draws one frame and then freezes with nothing in the console.
So every callback here does the same three things and returns: take the lock, append, signal without blocking. Nothing calls Program.Send from inside a callback either — Send blocks until the event loop reads it.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func LocalStorage ¶
LocalStorage is a store.KV over the browser's localStorage.
Every call is wrapped, because localStorage throws rather than returning an error and an unrecovered panic in Go on WebAssembly kills the program. Two throws are ordinary rather than exceptional: Safari refuses access at all in private mode, and any browser raises QuotaExceededError once the origin's allowance is full. Neither should cost the player their game, so the first is answered by falling back to memory (see Storage) and the second by returning an error that reaches the UI's error line.
Types ¶
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is the xterm.js instance the page created for us.
func Open ¶
Open finds the terminal the page set up. boot.js publishes it as globalThis.surmise = { term, … } before starting the Go program, so the contract between the two halves is that one object.
func (*Terminal) Attach ¶
Attach wires the resize event to the running Program.
send is Program.Send, which blocks until the event loop reads the message — so it is called from a goroutine, never from the callback. The channel holds one pending size and drops the rest: a drag across the screen produces hundreds of these, and only the last one is true.
func (*Terminal) Done ¶
func (t *Terminal) Done()
Done reveals the page's "that's all" overlay. A returned main tears the Go instance down, so without this the player is left looking at a dead terminal.
type Transfer ¶ added in v0.5.0
type Transfer struct {
// contains filtered or unexported fields
}
Transfer is the browser's implementation of the UI's backup file transfer.
func NewTransfer ¶ added in v0.5.0
NewTransfer wires up to the page. It returns an error when the page half is not there, which is what the Node smoke test sees: the game then runs with no backup row rather than with one that cannot work.
func (*Transfer) Load ¶ added in v0.5.0
Load opens the page's file picker and waits for the answer.
It blocks, which is safe here and nowhere else in this package: the UI calls it from a bubbletea command, which runs on its own goroutine while the JS event loop keeps turning — so the callback that will wake it can still fire.
A player who closes the picker without choosing gets no bytes and no error. Choosing nothing is not a failure, and reporting it as one would put an error on screen for pressing escape.