BoardLink is an accessibility-first classroom screen sharing app designed for students with low vision. It lets a teacher share their screen in real time while each student gets visual assist features such as bold-ink filtering, color palettes, cursor glow tracking, focus zoom, and freeze-frame capture.
This README is intentionally end-to-end and realistic. It documents not just the happy path, but also the problems, conflicts, failed attempts, and production hardening decisions that happened while building the app.
In many classrooms, students with low vision still depend on physical seating position and board visibility. BoardLink aims to reduce that barrier with:
- Fast room-based join flow (4-character code)
- Real-time teacher screen stream to students
- Student-side accessibility controls without needing teacher intervention
- Resilient reconnect behavior when signaling drops temporarily
- Start a classroom session and generate a room code
- Share display using browser screen capture
- Broadcast stream to multiple students (1:N peer mesh)
- Send normalized cursor coordinates over data channels
- Join by room code
- Receive teacher stream in real time
- Use Bold-Ink filters:
none,light,medium,heavy - Switch color palettes: default, high-contrast dark/warm, cataracts, tritanopia, invert
- Freeze frames into draggable PiP cards (up to 3)
- Toggle Focus Pane with automatic focus tracking and manual override
- Keyboard shortcuts:
Space: capture freeze frameShift+F: cycle filter levels
- Protocol version guard (
SYS_OBSOLETE_CLIENT) - 4KB WebSocket payload cap
- Zod validation for incoming signaling messages
- Room-code format enforcement (
^[2-9A-Z]{4}$) - IP-based failed join rate limiting (5 fails / 15 min)
- 30-second student grace session for reconnect
- Room and cache garbage collection sweep every 60 seconds
- Teacher opens app and creates a room.
- Server returns a 4-character room code.
- Student joins with that code through signaling server.
- Teacher creates a dedicated
RTCPeerConnectionfor that student. - WebRTC media flows peer-to-peer.
- Student browser applies optional processing in workers.
- Signaling server handles orchestration, validation, and session lifecycle.
- Browsers handle media transport directly for lower server load.
- Workers keep heavy image operations off the main UI thread.
boardlink/
client/ # Vite browser app
src/main.js # app entry and UI wiring
src/signaling.js # signaling client with reconnect queue
src/webrtc.js # teacher/student WebRTC logic
src/ui/* # CursorGlow, FocusPane, PipHold
src/workers/* # processing and focus workers
__tests__/focus-worker.test.js
server/
index.js # ws signaling server + room lifecycle
__tests__/rooms.test.js
__tests__/validation.test.js
- Node.js 18+
- npm 9+
- Modern Chromium-based browser (WebRTC + OffscreenCanvas + ImageBitmap support)
From repo root:
npm install
cd server && npm install
cd ../client && npm install
cd ..npm run devThis starts:
- WebSocket signaling server at
ws://localhost:8082 - Vite client dev server (default Vite port)
- Open one browser tab as Teacher.
- Start share and get room code.
- Open one or more student tabs.
- Join with room code.
- Validate stream, cursor glow, filters, palette switch, and freeze frame.
- Close a student tab and reopen quickly to confirm grace-session rejoin behavior.
Executed locally on April 6, 2026.
cd server
npm testResult:
- 2 test suites passed
- 21 tests passed
cd client
npm testResult:
- 1 test suite passed
- 6 tests passed
This section captures the implementation journey in phases, including friction points.
Successes:
- Basic teacher-to-student screen stream worked.
- Room creation and join path was functional.
Problems:
- Signaling trusted payloads too much.
- One malformed message could cause undefined behavior.
- No robust session lifecycle handling for disconnects.
Outcome:
- Moved from permissive signaling to schema-validated signaling.
Successes:
- Added Zod wrapper validation for all incoming message envelopes.
- Enforced room code format and payload limits.
- Added protocol version check to reject stale clients safely.
Problems:
- Early dev builds produced confusing mismatches between client and server payload shape during iterative changes.
- Invalid or old clients failed silently before explicit
SYS_OBSOLETE_CLIENThandling.
Fixes:
- Standardized message wrapper (
v,type,roomCode,payload). - Added explicit obsolete client event handling in client signaling layer.
Successes:
- Teacher now creates one
RTCPeerConnectionper student. - Added targeted offer/ICE routing by peer id.
Problems:
- During migration, signaling and WebRTC modules disagreed on target identifier naming.
- Risk of broadcasting signaling to wrong recipients while refactoring.
Fixes:
- Unified around
peerIdandtargetPeerIdfor teacher-targeted signaling. - Added per-peer cleanup path when students leave.
Successes:
- Main thread no longer does all frame manipulation.
- Worker path supports
PROCESS_FRAME_BITMAPwithImageBitmap. OffscreenCanvasusage reduced UI stutter during filter operations.
Problems:
- Initial synchronous frame processing caused visible lag.
- Earlier experimental flow had race behavior where processed frames could arrive after render moved on.
Fixes:
- Added
processingInFlightguard to prevent queue flooding. - Used bitmap transfer and explicit
bitmap.close()cleanup in worker.
Successes:
- Cursor heatmap + temporal diff fusion drives focus target.
- Focus pane animates with spring smoothing instead of jumping.
- Keyboard support and ARIA labels improved usability.
Problems:
- Focus behavior initially felt noisy under rapid pointer movement.
- Manual focus and auto focus needed clear control handoff.
Fixes:
- Added heatmap decay and confidence visualization.
- Added manual override and keyboard toggle behavior.
Successes:
- Student sessions survive temporary disconnects (30s grace window).
- Stale rooms and expired rate-limit entries are swept.
Problems:
- Without null checks, room cleanup logic could crash when teacher references disappeared.
Fixes:
- Hardened abandoned room check for null teacher cases.
| Symptom | Likely Cause | Recovery |
|---|---|---|
Port 8082 is already in use. |
Another server process still running | Kill old process or change server port |
Cannot reach signaling server. |
Server not running or wrong URL | Start server and verify client points to ws://localhost:8082 |
Protocol version mismatch |
Client bundle older than server protocol | Refresh/rebuild client and restart dev session |
This code is invalid or has expired. |
Wrong room code or room garbage-collected | Ask teacher for current code and rejoin |
| Student stream not appearing | Offer/answer/ICE ordering issue or join race | Reconnect student, verify peer-specific signaling messages |
| Filter feels delayed under load | Worker already processing prior frame | Reduce load, keep processingInFlight guard, lower frame rate if needed |
-
Message contract drift:
- Conflict: signaling payload shape changed during hardening while client handlers still expected older fields.
- Resolution: introduced stable envelope and explicit helper methods in signaling client.
-
Module/test environment split:
- Conflict: server uses ESM while Jest defaults required extra VM module support.
- Resolution: server test script runs with
--experimental-vm-modules.
-
Multi-peer migration:
- Conflict: previous single
pcmodel on teacher side did not map to multiple students. - Resolution: replaced with
Map<peerId, { pc, dataChannel }>lifecycle model.
- Conflict: previous single
-
Worker throughput:
- Conflict: unbounded frame enqueue created processing lag.
- Resolution: single in-flight processing guard and transferable bitmap path.
- Architecture is currently 1:N mesh from teacher browser; large classrooms can stress teacher CPU/network.
- Signaling endpoint is hardcoded to
ws://localhost:8082in client code. - Automated tests are mostly unit-level; there is no full browser integration suite yet.
- Focus fusion defines a density weight in config, but current fusion path primarily uses cursor and temporal signals.
- Palette option is passed through processing pipeline but current worker path does not apply per-palette transformations in worker.
- Reliable test baseline exists for server validation and focus worker logic.
- Security posture improved versus initial prototype.
- Rejoin behavior significantly reduced classroom disruption after transient disconnects.
- Accessibility controls are integrated into runtime UI rather than hidden in configuration.
- Add browser-level integration tests for signaling + WebRTC join flows.
- Externalize signaling URL to environment config.
- Add optional TURN relay support for restrictive networks.
- Expand worker-side processing to include palette-aware transforms.
- Add classroom telemetry for FPS and reconnect metrics.
From repo root:
# start server + client
npm run dev
# run server tests
cd server && npm test
# run client tests
cd client && npm testIf you are evaluating this project for competition/demo use, review Sections 7 through 10 first. They capture the engineering tradeoffs, failure modes, and recovery logic that matter most in a live environment.