Repository navigation
feat(playground): manual undo/redo for the editor - #20
Conversation
normalize() rebuilds the DOM after every input event, so the browser's
native undo history never survives; ctrl+z did nothing. Keep our own
stack instead of pulling in an editor component:
- snapshot commits land 400ms after the last mutation and store the
previous {source, caretOffset}, so one step rewinds a typed burst
- ctrl/cmd+z undoes, ctrl/cmd+shift+z and ctrl+y redo; the combos are
always swallowed since native undo would corrupt the rebuilt DOM
- programmatic replaces (autocomplete accept, example load) reset the
stack, as sketched in #18
- depth capped at 200 snapshots
Closes #18
Self-review found committed is only initialized because loadExample runs synchronously at startup; make commitUndoPoint adopt the current state instead of throwing if that init ever moves.
|
Self-reviewed the diff before merging. I walked the undo/redo transitions by hand: undo twice, redo twice, typing after an undo to confirm the redo side drops correctly, and ctrl+z landing while the 400ms commit timer is still pending (the flush path). The committed-state scheme stays consistent through all of those; I found no way to corrupt the stacks or lose the caret. Two things worth recording:
Also noted: snapshots store carets, not selections, so undoing an edit that replaced a multi-character selection leaves the caret at the boundary rather than re-selecting the range. Cosmetic for a playground this size. There is no JS test infrastructure in this repo, so verification was hand-tracing plus |
* feat(playground): vendor Hack as the editor font The CSS already asked for JetBrains Mono and Fira Code but loaded neither, so every visitor saw a fallback. Ship Hack woff2 (regular + bold, MIT license included) with @font-face and put it first in the mono stacks for editor, output, and autocomplete. * feat(playground): highlight string literals in the editor (#21) buildEditorFragment now splits quoted spans before emote substitution and wraps them in a .str span. The quote pairing mirrors the lexer's readString: no escapes, newlines allowed, unterminated runs to EOF. Emote names still render as images inside strings, as before. editorSource/forEachPiece/setCaretOffset already recurse into element nodes, so the span wrapper needs no caret-math changes. * feat(playground): line-number gutter with click-to-line The editor gets a real text-editor gutter: numbers derived from the source (never the DOM), vertical scroll mirrored from the editor, horizontal scrolls left alone, and clicking a number places the caret at that line's first character. applySnapshot updates the gutter too, so undo/redo keep the count honest. Two layout fixes make internal scrolling possible at all: main's grid row is capped with minmax(0,1fr) and the wide layout gives body a definite height, so long source scrolls inside the pane instead of stretching the page and dragging the gutter away. The editor switches from pre-wrap to pre for the same reason - a wrapped line would span several rows and desynchronize the numbers. * fix(playground): address self-review findings on the editor work - pair strings the way the lexer does: #-to-EOL comment regions are carved out first, so a quote in a comment can no longer open a phantom string across real code; comments render muted, emotes inside them still render as images - ignore gutter clicks on empty space below the last number instead of teleporting the caret to line 1 - resync gutter scroll after undo/redo restores, for engines that skip the scroll event when clamping scrollTop - drop hack-bold.woff2: no stylesheet requests bold monospace Found via the self-review posted on #23.
The playground normalizes its DOM after every input event, which discards the browser's native undo history, so ctrl+z did nothing in the editor. This adds a hand-rolled stack rather than pulling in an editor component; the only thing CodeMirror would buy here is highlighting, and the keywords are already emotes.
How it works:
normalize()schedules a commit 400ms after the last mutation. The commit stores the previous known{source, caretOffset}, so one undo step rewinds a whole typed burst instead of the final character.Closes #18
Verified with node --check web/main.js, go test ./..., go vet ./..., gofmt -l .