This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Sharecode is a real-time collaborative code editor. Users create rooms, share the URL, and edit code together with live cursor/selection sync. Rooms auto-delete 5 minutes after the last participant disconnects.
Tech stack: Go backend (stdlib net/http, gorilla/websocket) + React/TypeScript frontend (CodeMirror 6, Yjs CRDT, Vite, Tailwind).
go build ./cmd/server # compile
go run ./cmd/server # run prod (minimal logs)
DEBUG=1 go run ./cmd/server # run debug (all logs)
go test ./... # run all tests
go test ./internal/ws/... # run tests in a specific packagecd frontend
npm install # install deps
npm run dev # dev server on :5173 (proxies /api and /ws to :8080), VITE_DEBUG=true
npm run build # prod build → frontend/dist (no debug logs)
npm run build:debug # debug build → frontend/dist (console logs enabled)Start the Go server first (go run ./cmd/server), then npm run dev in frontend/. Vite proxies API and WebSocket requests to :8080.
make build # prod frontend build + go binary
make build-debug # debug frontend build + go binary
make run # prod build + run
make run-debug # debug build + run with DEBUG=1
make docker-build # prod Docker image → sharecode:prod
make docker-build-debug # debug Docker image → sharecode:debugdocker compose up --build # builds debug image (BUILD_MODE=debug, DEBUG=1) and starts on :8080| Package | Responsibility |
|---|---|
cmd/server |
Entry point: wires Store, Registry, Handler, and the SPA fallback file server |
internal/room |
Room (content + close-timer) and Store (thread-safe in-memory map, rate-limits 10 rooms per IP) |
internal/ws |
Registry (roomID → Hub map), Hub (goroutine per room: fan-out, Yjs sync), Client (read/write pumps) |
internal/api |
HTTP handler: REST endpoints + WebSocket upgrade |
internal/logger |
Debug-level logger; enabled via DEBUG=1 env var; no-op in prod |
Room lifecycle: POST /api/rooms creates a Room in the Store and spawns a Hub goroutine registered in Registry. DELETE /api/rooms/{id} calls hub.Shutdown() which closes all client connections. When the last client disconnects, the Hub starts a 5-minute time.AfterFunc that deletes the room from Store and Registry and stops the Hub.
The backend speaks a subset of the y-websocket protocol:
- messageSync (0) / syncStep1 (0): client sends on connect; server replies with an empty syncStep2 (all updates were pushed individually at register time).
- messageSync (0) / syncStep2 (update) (2): a Yjs document update; server stores it in
Room.Updatesand fans out to all other clients. - messageAwareness (1): cursor/selection awareness; server forwards to all other clients without storing.
New clients receive all accumulated Room.Updates replayed in order, then an empty syncStep2 ([0,1,2,0,0]) to signal sync completion.
RoomPage
└── useYjs(roomId, nickname) → Y.Doc + WebsocketProvider + yText + yMeta
└── useRoom(provider, yMeta) → participants (from awareness), language (from yMeta)
└── Editor → CodeMirror 6 view bound to yText via yCollab
└── Toolbar → language selector (writes to yMeta), font/theme controls
└── ParticipantList → displays awareness participants with their colors
yText (Y.Text named "content") is the shared document. yMeta (Y.Map named "meta") stores the shared language selection. Awareness (ephemeral, not persisted) carries { name, color } per participant.
useYjs initializes the Y.Doc and WebsocketProvider once via a ref to avoid re-creation on re-renders. The provider connects to /ws/<roomId> using the same host as the page (ws/wss is inferred from http/https).
/—LandingPage: create a room viaPOST /api/rooms, redirect to/room/:id/room/:id—RoomPage: checks room exists viaGET /api/rooms/:id, prompts for nickname, then loads the editor*— redirects to/
The Go server serves frontend/dist as a SPA (unknown paths fall back to index.html).