Feat/sheet live presence#311
Conversation
Spec for Google-Docs-style collaboration on the spreadsheet: - active-cell cursor presence with author color + name label - live broadcast of in-progress cell input + live recompute of dependent cells before commit ponytail-trimmed: one new SHEET_PRESENCE frame each direction; cleanup reuses existing USER_LEAVE and NEW_SHEET_OP.author; no server-side presence cache / snapshot / heartbeat in v1 (upgrade paths documented). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 TDD tasks: server relay (Go) + presence reducer/overlay (vitest) + grid decorations + editor wiring + two-session Playwright E2E. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
PR Summary by QodoAdd sheet live presence and live calculation overlays Description
Diagram
High-Level Assessment
Files changed (10)
|
Code Review by Qodo
1. Presence rate-limit drops
|
| } else if strings.Contains(decodedMessage, "SHEET_PRESENCE") { | ||
| var presence ws.SheetPresenceIncoming | ||
| if err := json.Unmarshal(message, &presence); err != nil { | ||
| logger.Error("Error unmarshalling SHEET_PRESENCE: ", err) | ||
| continue | ||
| } | ||
| c.Handler.HandlePresence(c, presence) |
There was a problem hiding this comment.
1. Presence rate-limit drops 🐞 Bug ☼ Reliability
SHEET_PRESENCE messages are sent on a ~60ms cadence but the server applies the global CommitRateLimiting check to every inbound websocket message, so presence frames will be dropped under default limits and can starve other message types from the same IP.
Agent Prompt
### Issue description
The websocket read loop applies `CheckRateLimit()` before routing any inbound message. With live-presence emitting ~16 msgs/sec (60ms throttle), the default commit rate limit (10 events/sec) will drop presence frames and can also prevent other message processing.
### Issue Context
Presence is ephemeral and high-frequency, unlike commits; it should not share the same tight limiter used to protect write/commit paths.
### Fix Focus Areas
- lib/ws/client.go[132-191]
- lib/settings/configRegistry.go[241-250]
- ui/src/js/sheet/sheetEditor.ts[42-73]
### Suggested fix
Route messages first (or minimally parse `event` + `data.data.type`) and apply rate limiting only to commit-like operations (e.g., `USER_CHANGES`, `SHEET_OP`, etc.). Alternatively, introduce a separate, higher-threshold limiter specifically for `SHEET_PRESENCE` (and possibly cursor-only frames).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| out := ws.SheetPresence{Type: "COLLABROOM"} | ||
| out.Data = ws.SheetPresenceData{ | ||
| Type: "SHEET_PRESENCE", | ||
| UserId: session.Author, | ||
| Name: name, | ||
| Color: color, | ||
| Sheet: msg.Data.Data.Sheet, | ||
| Row: msg.Data.Data.Row, | ||
| Col: msg.Data.Data.Col, | ||
| Editing: editing, | ||
| Raw: raw, | ||
| } |
There was a problem hiding this comment.
2. Live-edit coordinates unbounded 🐞 Bug ⛨ Security
Relayed SHEET_PRESENCE row/col values are trusted and fed into FormulaEngine.setGrid(), which allocates and indexes a grid by row/col; negative indices can throw at grid[c.row][c.col] and huge indices can trigger massive allocations, crashing viewers.
Agent Prompt
### Issue description
The server relays `row`/`col` from the client without validation, and the frontend stores them as live edits and recomputes via `engine.setGrid(...)`. `FormulaEngine.setGrid()` builds a rectangular `grid` up to `maxRow/maxCol` and then writes `grid[c.row][c.col] = ...`; this will throw for negative indices and can OOM for very large indices.
### Issue Context
SHEET_PRESENCE is an unauthenticated (within the pad) ephemeral channel; a malicious collaborator can send a single crafted frame to DoS other clients.
### Fix Focus Areas
- lib/ws/SheetHandler.go[153-199]
- ui/src/js/sheet/sheetPresence.ts[39-50]
- ui/src/js/sheet/sheetEditor.ts[96-99]
- ui/src/js/sheet/formulaEngine.ts[50-64]
### Suggested fix
Add strict bounds checks for `row`/`col`:
- Server-side: drop (or clamp) presence frames with `row < 0`, `col < 0`, or above a reasonable maximum (ideally tied to supported sheet dimensions for v1).
- Client-side defense-in-depth: ignore presence frames with invalid coordinates before inserting into `liveEdits`, and/or cap what is passed into `effectiveCells`.
- Harden `FormulaEngine.setGrid()` to skip any cell with out-of-range coordinates instead of indexing directly.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
* fix(sheet): follow-up fixes for live presence (#311) Delivers the fixes that landed after #311 was auto-merged: - vite: skip @rollup/plugin-commonjs for the sheet entry — it deadlocks the rolldown (vite 8) transform while bundling HyperFormula, so the sheet bundle never builds locally. - view: render the presence name tag via a CSS ::after pseudo-element (data-label) instead of a text node, so a peer's name no longer leaks into a cell's textContent (was e.g. "=A1*3anon"). - read-only: honor data.readonly on the client (non-editable cells), so no live-edit/commit frames originate from a viewer. - tests: two-session Playwright E2E for live presence/calculation, and a direct unit test for effectiveCells overlay-wins-on-collision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(build): build the sheet entry in build.js so CI/prod serve it ui/build.js (the esbuild build used by CI's Playwright job and release builds) bundled pad/welcome/timeslider but never the sheet entry, so assets/js/sheet/ was empty and /s/:pad served no sheet.js — the spreadsheet grid never rendered outside a local vite build. Add the sheet esbuild entry (object form so the output is sheet.js, matching the path served by sheetFrontend.go). esbuild handles HyperFormula's CommonJS natively (no rolldown deadlock). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No description provided.