Finalize Go-only migration, runtime hardening, and CI/container optimization
This commit consolidates the full repository transition to a Go-first codebase and captures the follow-up performance/reliability work completed in the same stream. Highlights: - Remove Python implementation and test suites (, , , ) and retire Python-specific docs/instructions. - Move and standardize static web assets under , updating Bun/TypeScript build paths and server static resolution logic. - Rewrite developer workflow to Makefile-first Go targets (vet/test/race/coverage/fuzz/build) and align repository guidance/docs accordingly. - Update Docker and CI/CD for leaner artifacts: - switch to Alpine-based multi-stage build with stripped Go binary - install only minimal runtime deps (, ) - tighten Docker build context via - ensure workflows build/publish the target. - Improve runtime correctness/latency and reduce duplication: - explicit WebSocket outbound frame typing (text vs binary) instead of payload-byte heuristics - SSE activity fan-out outside global lock and safer subscriber lifecycle - shared session output/snapshot helpers to reduce duplicated logic - restart-safe channel lifecycle for Docker watcher/stats start-stop-start flows - faster screenshot cold-start path (poll-until-ready within timeout vs fixed sleep). - Add/expand regression coverage for the above lifecycle and helper paths. Validation run: - bun run build [32mBundled 3 modules in 10ms[0m [34mterminal.js[33m 0.68 MB [2m(entry point)[0m (Bun typecheck + bundle) - cd go && go vet ./... cd go && go test ./... ok github.com/rcarmo/webterm-go-port/cmd/webterm (cached) ok github.com/rcarmo/webterm-go-port/internal/terminalstate (cached) ok github.com/rcarmo/webterm-go-port/webterm (cached) cd go && go test ./webterm -coverprofile=coverage.out && go tool cover -func=coverage.out ok github.com/rcarmo/webterm-go-port/webterm (cached) coverage: 81.0% of statements github.com/rcarmo/webterm-go-port/webterm/cli.go:14: RunCLI 51.6% github.com/rcarmo/webterm-go-port/webterm/config.go:25: DefaultConfig 100.0% github.com/rcarmo/webterm-go-port/webterm/config.go:29: LoadLandingYAML 82.6% github.com/rcarmo/webterm-go-port/webterm/config.go:70: LoadComposeManifest 76.9% github.com/rcarmo/webterm-go-port/webterm/config.go:114: extractLabel 92.3% github.com/rcarmo/webterm-go-port/webterm/config.go:138: asString 80.0% github.com/rcarmo/webterm-go-port/webterm/constants.go:27: EnvBool 50.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:30: Read 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:56: NewDockerExecSession 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:72: Open 90.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:99: Start 85.7% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:119: readLoop 83.3% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:143: handleOutput 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:153: createExec 75.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:183: startExecSocket 60.7% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:219: resizeExec 83.3% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:237: Close 90.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:250: Wait 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:257: SetTerminalSize 81.8% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:274: ForceRedraw 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:281: SendBytes 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:294: SendMeta 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:298: IsRunning 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:304: GetReplayBuffer 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:308: GetScreenSnapshot 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_exec_session.go:316: UpdateConnector 80.0% github.com/rcarmo/webterm-go-port/webterm/docker_http.go:23: DockerSocketPath 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_http.go:37: newUnixHTTPClient 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_http.go:47: sharedUnixClient 91.7% github.com/rcarmo/webterm-go-port/webterm/docker_http.go:64: unixJSONRequest 84.2% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:33: NewDockerStatsCollector 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:47: Available 72.7% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:64: Start 80.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:78: Stop 75.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:90: AddService 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:101: RemoveService 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:115: GetCPUHistory 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:124: pollLoop 95.2% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:160: discoverContainers 65.4% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:198: pollContainer 77.8% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:223: calculateCPUPercent 69.2% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:260: RenderSparklineSVG 89.3% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:299: max 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:306: toAnyMap 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:323: toStringMap 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:331: toAnySlice 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:340: toStringSlice 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:351: toUint 91.7% github.com/rcarmo/webterm-go-port/webterm/docker_stats.go:376: toInt 85.7% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:34: NewDockerWatcher 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:54: hasWebtermLabel 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:60: isAutoLabel 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:67: getContainerCommand 80.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:76: getContainerTheme 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:81: getContainerName 42.9% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:93: containerToSlug 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:98: addContainer 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:119: removeContainer 100.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:143: listLabeledContainers 94.1% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:168: handleEvent 80.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:202: watchEvents 85.7% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:239: ScanExisting 60.0% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:249: Start 83.3% github.com/rcarmo/webterm-go-port/webterm/docker_watcher.go:265: Stop 81.8% github.com/rcarmo/webterm-go-port/webterm/identity.go:12: GenerateID 94.1% github.com/rcarmo/webterm-go-port/webterm/normalize.go:13: FilterDASequences 83.3% github.com/rcarmo/webterm-go-port/webterm/replay.go:14: NewReplayBuffer 66.7% github.com/rcarmo/webterm-go-port/webterm/replay.go:21: Add 100.0% github.com/rcarmo/webterm-go-port/webterm/replay.go:43: Bytes 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:95: OnData 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go💯 OnBinary 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:105: OnMeta 0.0% github.com/rcarmo/webterm-go-port/webterm/server.go:107: OnClose 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:112: NewLocalServer 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:163: findStaticPath 62.5% github.com/rcarmo/webterm-go-port/webterm/server.go:184: markRouteActivity 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:207: enqueueWSFrame 77.8% github.com/rcarmo/webterm-go-port/webterm/server.go:233: stopWSClient 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:246: wsSender 80.0% github.com/rcarmo/webterm-go-port/webterm/server.go:256: createTerminalSession 66.7% github.com/rcarmo/webterm-go-port/webterm/server.go:278: clampInt 60.0% github.com/rcarmo/webterm-go-port/webterm/server.go:288: parseResizePayload 88.9% github.com/rcarmo/webterm-go-port/webterm/server.go:303: handleWebSocket 81.1% github.com/rcarmo/webterm-go-port/webterm/server.go:425: chooseRouteForScreenshot 50.0% github.com/rcarmo/webterm-go-port/webterm/server.go:440: screenshotTTL 66.7% github.com/rcarmo/webterm-go-port/webterm/server.go:457: handleScreenshot 55.7% github.com/rcarmo/webterm-go-port/webterm/server.go:541: handleCPUSparkline 94.4% github.com/rcarmo/webterm-go-port/webterm/server.go:566: handleEvents 76.0% github.com/rcarmo/webterm-go-port/webterm/server.go:602: toIntFromQuery 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:609: dashboardTiles 81.8% github.com/rcarmo/webterm-go-port/webterm/server.go:631: handleTiles 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:636: getWSURL 65.2% github.com/rcarmo/webterm-go-port/webterm/server.go:671: handleRoot 56.8% github.com/rcarmo/webterm-go-port/webterm/server.go:732: htmlEscape 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:736: htmlAttrEscape 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:740: handleHealth 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:744: setupDockerFeatures 40.0% github.com/rcarmo/webterm-go-port/webterm/server.go:791: shutdown 62.5% github.com/rcarmo/webterm-go-port/webterm/server.go:814: Handler 100.0% github.com/rcarmo/webterm-go-port/webterm/server.go:829: Run 77.8% github.com/rcarmo/webterm-go-port/webterm/session.go:31: OnData 0.0% github.com/rcarmo/webterm-go-port/webterm/session.go:32: OnBinary 0.0% github.com/rcarmo/webterm-go-port/webterm/session.go:33: OnMeta 0.0% github.com/rcarmo/webterm-go-port/webterm/session.go:34: OnClose 0.0% github.com/rcarmo/webterm-go-port/webterm/session.go:36: dispatchSessionOutput 100.0% github.com/rcarmo/webterm-go-port/webterm/session.go:47: snapshotFromTracker 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:20: NewSessionManager 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:34: SetSessionFactory 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:40: defaultSessionFactory 87.5% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:57: splitCommand 75.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:65: shlexSplit 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:69: AddApp 87.5% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:88: RemoveApp 87.5% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:101: Apps 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:107: AppBySlug 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:114: GetDefaultApp 80.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:123: NewSession 50.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:167: OnSessionEnd 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:176: CloseAll 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:189: CloseSession 87.5% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:201: GetSession 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:207: GetSessionByRouteKey 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:217: GetSessionIDByRouteKey 100.0% github.com/rcarmo/webterm-go-port/webterm/session_manager.go:223: GetFirstRunningSession 85.7% github.com/rcarmo/webterm-go-port/webterm/shellsplit.go:5: shlexSplitImpl 100.0% github.com/rcarmo/webterm-go-port/webterm/slugify.go:13: Slugify 100.0% github.com/rcarmo/webterm-go-port/webterm/svg_exporter.go:35: RenderTerminalSVG 92.6% github.com/rcarmo/webterm-go-port/webterm/svg_exporter.go:113: colorToHex 87.5% github.com/rcarmo/webterm-go-port/webterm/svg_exporter.go:139: isHex 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:37: NewTerminalSession 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:49: Open 86.7% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:90: Start 85.7% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:110: readLoop 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:132: handleOutput 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:142: Close 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:160: Wait 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:167: SetTerminalSize 80.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:190: ForceRedraw 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:198: SendBytes 88.9% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:211: SendMeta 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:215: IsRunning 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:221: GetReplayBuffer 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:225: GetScreenSnapshot 100.0% github.com/rcarmo/webterm-go-port/webterm/terminal_session.go:233: UpdateConnector 80.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:14: NewTwoWayMap 100.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:21: Set 88.9% github.com/rcarmo/webterm-go-port/webterm/twoway.go:36: DeleteKey 100.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:45: Get 100.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:52: GetKey 100.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:59: Keys 100.0% github.com/rcarmo/webterm-go-port/webterm/twoway.go:70: UnsafeForward 100.0% total: (statements) 81.0% - cd go && go test -race ./... ok github.com/rcarmo/webterm-go-port/cmd/webterm (cached) ok github.com/rcarmo/webterm-go-port/internal/terminalstate (cached) ok github.com/rcarmo/webterm-go-port/webterm (cached) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,65 +1,28 @@
|
||||
# Screenshot Debugging Skill
|
||||
|
||||
## Purpose
|
||||
Diagnose terminal screenshot corruption caused by incomplete escape-sequence handling.
|
||||
Diagnose terminal screenshot corruption caused by incomplete terminal-state updates.
|
||||
|
||||
## When to Use
|
||||
- SVG screenshot shows stale or overlaid content after clear/redraw.
|
||||
- Behavior differs between live terminal output and screenshot snapshots.
|
||||
- Issues appear inside tmux/vim/less or other full-screen TUIs.
|
||||
- SVG screenshots show stale or overlaid text.
|
||||
- Live WebSocket output looks correct but `/screenshot.svg` does not.
|
||||
- Issues appear with full-screen TUIs (tmux/vim/less).
|
||||
|
||||
## Procedure
|
||||
1. **Reproduce and capture raw output**
|
||||
- Capture PTY output around the failing action (e.g., `clear` inside tmux).
|
||||
- Ensure capture includes the full sequence before and after the command.
|
||||
|
||||
2. **Replay into the emulator**
|
||||
- Feed captured bytes into the same emulator used for screenshots (pyte + AltScreen).
|
||||
- Inspect the rendered buffer for stale cells or overlay.
|
||||
|
||||
3. **Scan for unhandled escape modes**
|
||||
- Look for private modes: `?47`, `?1047`, `?1048`, `?1049`.
|
||||
- Check erase semantics: `ED` (`J`), `EL` (`K`), `ECH` (`X`).
|
||||
- Verify C1 controls are normalized to 7-bit ESC equivalents.
|
||||
|
||||
4. **Fix emulator handling**
|
||||
- Update AltScreen to recognize any missing alternate buffer modes (e.g., `?47`).
|
||||
- Ensure mode toggles save/restore the main buffer and mark dirty lines.
|
||||
|
||||
1. **Capture terminal bytes**
|
||||
- Capture PTY output around the failing action.
|
||||
2. **Replay into the Go tracker**
|
||||
- Feed bytes through `internal/terminalstate` (go-te based).
|
||||
- Confirm whether buffer state diverges from expected terminal behavior.
|
||||
3. **Check preprocessing/filtering**
|
||||
- Validate DA filtering and any partial-sequence buffering logic.
|
||||
4. **Check dirty/refresh logic**
|
||||
- Verify tracker updates happen before screenshot cache decisions.
|
||||
5. **Add regression coverage**
|
||||
- Add a focused test that replays the sequence and asserts the buffer is cleared.
|
||||
- Include any new mode variants in existing parameterized tests.
|
||||
|
||||
- Add a focused Go test and (when useful) fuzz corpus seed.
|
||||
6. **Verify**
|
||||
- Run `make check`.
|
||||
- Re-test the real scenario and confirm screenshots match the live terminal.
|
||||
|
||||
## Minimal Capture Snippet (PTY -> pyte)
|
||||
```python
|
||||
import os, pty, select, time, pyte
|
||||
from webterm.alt_screen import AltScreen
|
||||
|
||||
def read_all(fd, timeout=0.5):
|
||||
out = b""
|
||||
end = time.time() + timeout
|
||||
while time.time() < end:
|
||||
r, _, _ = select.select([fd], [], [], 0.05)
|
||||
if not r:
|
||||
continue
|
||||
try:
|
||||
data = os.read(fd, 4096)
|
||||
except OSError:
|
||||
break
|
||||
if not data:
|
||||
break
|
||||
out += data
|
||||
return out
|
||||
|
||||
screen = AltScreen(80, 24)
|
||||
stream = pyte.ByteStream(screen)
|
||||
stream.feed(raw_bytes)
|
||||
```
|
||||
- Run `make check` and re-test the real scenario.
|
||||
|
||||
## Notes
|
||||
- tmux often uses `DECSET ?47` (legacy alt buffer) instead of `?1049`.
|
||||
- Always validate with real output captures, not just synthetic sequences.
|
||||
- Prefer reproductions from real PTY captures over synthetic minimal sequences.
|
||||
- If rendering differs only in dashboard thumbnails, inspect SSE activity gating and screenshot cache TTL/invalidations.
|
||||
|
||||
Reference in New Issue
Block a user