Files
scrawl/go.mod
Fredrik Johansson 2b8c3ae6ee Initial build: scrawl, a shared ASCII canvas over SSH
ssh in and draw on a live, shared doodle wall -- everyone connected
sees everyone else's edits in real time. Same wish+bubbletea security
model as delve-term (no shell, no exec, structurally can't reach a
real shell on the host), extended with the one thing delve-term didn't
need: multiple sessions sharing live state. wish's bm.Middleware helper
hides the *tea.Program it creates, so this builds the program directly
instead, keeping a registry (game.Canvas) that broadcasts a redraw
signal to every other connected session the instant one of them paints.

Banner generated via `figlet -f slant SCRAWL` rather than hand-drawn --
slant felt right for a doodle/scribble tool.

Two real bugs caught by actually running this with real SSH sessions,
not just unit-testing the game logic in isolation:

1. Program.Send() blocks until that program's Run() event loop is
   actively reading from it. Broadcasting synchronously from inside
   Join()/Paint() (including a session broadcasting to its own,
   not-yet-running program on join) deadlocked every session before it
   ever reached Run() -- the very first connection just hung with
   nothing rendered. Fixed by sending asynchronously (go p.Send(...))
   everywhere the canvas notifies sessions of a change.

2. Subtler: lipgloss's default package-level styles detect color
   support from the *server process's* os.Stdout, not any given
   session's actual terminal -- and a server's stdout is typically
   redirected (a log file, systemd journal), so every connected session
   silently lost all color/background styling at once, server-wide.
   Manifested as painting a cell your own cursor already sat on being
   invisible (the cursor glyph before/after looked identical, so
   bubbletea's diffing renderer correctly sent zero bytes for a change
   that produced no visual diff) -- confirmed via server-side debug
   logging that painting itself worked correctly every time, isolating
   the bug to rendering, then confirmed via a Go test that forcing a
   real color profile was the difference between 0 and 1327 runes of
   diff between two frames that should look different. Fixed with a
   lipgloss.Renderer created per-session, bound to that session's
   actual output, forced to TrueColor; cursors now highlight whatever's
   already painted at that cell (background tint) rather than
   replacing the character, so a session's own paint is never masked
   by its own cursor marker sitting on top of it.

Verified end-to-end with two real, simultaneous SSH sessions (scripted
via pexpect): peer count syncs correctly, and one session's paint
genuinely arrives at the other via the live broadcast -- not just
unit-tested in isolation. Also verified the actual Docker image builds
and serves correctly over real SSH.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 19:28:17 +02:00

41 lines
1.7 KiB
Modula-2

module scrawl
go 1.24.0
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894
github.com/charmbracelet/wish v1.4.7
)
require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/keygen v0.5.3 // indirect
github.com/charmbracelet/log v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/conpty v0.1.0 // indirect
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/charmbracelet/x/termios v0.1.0 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.23.0 // indirect
)