Skip to content

fix(sheet): follow-up fixes for live presence (#311)#313

Merged
SamTV12345 merged 2 commits into
mainfrom
fix/sheet-presence-followup
Jun 25, 2026
Merged

fix(sheet): follow-up fixes for live presence (#311)#313
SamTV12345 merged 2 commits into
mainfrom
fix/sheet-presence-followup

Conversation

@SamTV12345

Copy link
Copy Markdown
Member

Follow-up to #311 (live presence & live calculation), which auto-merged at an earlier state. This PR delivers the fixes that landed afterward.

Fixes

  • Build (ui/vite.config.ts) — skip @rollup/plugin-commonjs for the sheet entry. vite 8 is rolldown-based and handles CommonJS natively; the legacy plugin deadlocks the rolldown transform while bundling HyperFormula, so vite build --mode sheet hangs and the sheet bundle never builds locally. Other entries are unchanged.
  • Tag-pollution bug (sheetView.ts) — the presence name tag was a child text node of the cell, so a peer's name leaked into the cell's textContent (e.g. =A1*3anon). Now rendered via a CSS ::after/data-label pseudo-element, leaving the cell content clean.
  • Client-side read-only (sheetView.ts / sheetEditor.ts) — honor data.readonly: read-only viewers get non-editable cells, so no live-edit/commit frames originate from a viewer (the server already strips/rejects them). Closes the design's "enforced on both client and server" gap and the type-then-revert UX.

Tests

  • Playwright E2E (playwright/specs/sheet_presence.spec.ts) — two sessions: remote cursor tag, live formula text, dependent C2 = 31 before Enter, B2 = 30 after commit, cursor cleanup on disconnect. ✅
  • vitest — direct unit test for the load-bearing effectiveCells property (a remote live raw replaces a committed cell at the same coordinate). Suite 30/30.

Verified locally: sheet tsc clean, vitest 30/30, E2E passing on top of current main.

🤖 Generated with Claude Code

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>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix sheet presence follow-ups: sheet build hang, tag pollution, readonly UI
🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

Description

• Unblock vite build --mode sheet by skipping CommonJS plugin for the sheet entry.
• Prevent remote presence labels from polluting cell textContent via CSS pseudo-element tags.
• Enforce client-side read-only mode and add E2E/unit coverage for live presence overlays.
Diagram

graph TD
  A["Sheet route / UI"] --> B["sheetEditor.ts"] --> C["sheetView.ts"] --> D["Remote tag via data-label"]
  B --> E["readOnly flag"]
  F["vite.config.ts"] --> G["Sheet build (mode=sheet)"]
  H("Playwright E2E") --> A
  I("Vitest unit") --> C
Loading
High-Level Assessment

The PR’s approach is the most direct fix set: (1) conditionally skipping @rollup/plugin-commonjs only for mode === &#x27;sheet&#x27; avoids the rolldown deadlock without altering other entry builds; (2) moving presence labels to a CSS pseudo-element prevents cell textContent contamination while keeping DOM structure minimal; and (3) enforcing read-only at the DOM contentEditable level closes the client-side UX hole while remaining consistent with server-side enforcement. Alternatives like rendering tags outside the cell container or reworking the build into separate Vite configs add complexity with limited benefit.

Files changed (5) +81 / -5

Bug fix (2) +9 / -2
sheetEditor.tsPropagate readonly state into the sheet view +1/-0

Propagate readonly state into the sheet view

• Passes 'data.readonly' into the sheet view options as 'readOnly'. This enables the view layer to disable editing for read-only viewers and prevents client-originated live-edit/commit events via typing.

ui/src/js/sheet/sheetEditor.ts

sheetView.tsFix presence label pollution and enforce readonly contentEditable +8/-2

Fix presence label pollution and enforce readonly contentEditable

• Adds a 'readOnly' option and sets 'td.contentEditable' accordingly to prevent typing in read-only sessions. Changes remote presence label rendering from a text node to a 'data-label' attribute consumed by a CSS '::after' pseudo-element so cell 'textContent' remains clean.

ui/src/js/sheet/sheetView.ts

Tests (2) +67 / -0
sheet_presence.spec.tsAdd two-session E2E test for presence and live recalculation +57/-0

Add two-session E2E test for presence and live recalculation

• Introduces a Playwright spec that drives two browser contexts through a shared sheet. Verifies remote cursor tag visibility, live formula text propagation, dependent-cell recomputation before commit, final computed value after Enter, and cursor cleanup on disconnect.

playwright/specs/sheet_presence.spec.ts

sheetPresence.test.tsAdd unit test for effectiveCells overlay collision behavior +10/-0

Add unit test for effectiveCells overlay collision behavior

• Adds a test asserting that an in-progress remote edit replaces a committed cell at the same (row, col) in 'effectiveCells'. Ensures the overlay list does not contain duplicate coordinates and that the live raw wins.

ui/src/js/sheet/sheetPresence.test.ts

Other (1) +5 / -3
vite.config.tsSkip rollup commonjs plugin for sheet mode to avoid rolldown deadlock +5/-3

Skip rollup commonjs plugin for sheet mode to avoid rolldown deadlock

• Conditionally disables '@rollup/plugin-commonjs' when 'mode === 'sheet'' because Vite 8/rolldown handles CommonJS natively and the legacy plugin can deadlock when bundling HyperFormula. Leaves other modes/entries unchanged by retaining the plugin elsewhere.

ui/vite.config.ts

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Readonly blocks cursor presence 🐞 Bug ≡ Correctness
Description
DomSheetView sets td.contentEditable='false' for read-only, but selection/presence is triggered from
the cell's focus handler; non-focusable cells prevent onSelect from firing so read-only viewers
can't move their cursor presence. This contradicts the backend behavior that explicitly allows
read-only sessions to move a cursor (it only forces editing=false and clears raw).
Code

ui/src/js/sheet/sheetView.ts[R88-90]

+        // Read-only viewers get non-editable cells: with no typing, no live-edit
+        // or commit frames originate from the client (the server strips them too).
+        td.contentEditable = opts.readOnly ? 'false' : 'true';
Evidence
The PR change makes cells non-editable via contentEditable=false, but selection/presence emission
is wired to the focus event; without focus, onSelect is never called so no cursor frames are
sent. Server-side code explicitly supports read-only cursor presence by stripping only live edits
(editing=false, raw="") and a unit test asserts a read-only cursor frame is relayed.

ui/src/js/sheet/sheetView.ts[85-147]
lib/ws/SheetHandler.go[148-164]
lib/ws/sheet_handler_test.go[243-275]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Read-only mode currently sets `td.contentEditable = 'false'`, which makes the `<td>` effectively non-focusable via click. Because `DomSheetView.attach()` emits selection/presence from the `focus` event (`onSelect?.(r,c)`), read-only users cannot move their cursor/presence at all.

### Issue Context
The backend intentionally allows read-only sessions to send cursor moves while stripping live-edit payloads (`editing` forced `false`, `raw` cleared). The frontend should therefore keep read-only cells focusable/click-selectable while still preventing edits.

### Fix Focus Areas
- ui/src/js/sheet/sheetView.ts[85-147]
- lib/ws/SheetHandler.go[148-164]

### Suggested approach
- Keep cells focusable in read-only mode (e.g., set `td.tabIndex = 0` when `opts.readOnly`, and leave `contentEditable` false), and ensure a click/focus still calls `onSelect`.
- Guard edit-specific handlers when `opts.readOnly` is true (ignore `input`, prevent commit on `blur`, and avoid swapping to `rawValue` on focus if desired).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: playwright (macos-latest, amd64)

Failed stage: Run Playwright tests [❌]

Failed test name: [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions

Failure summary:

The action failed because a Playwright end-to-end test timed out and never observed the sheet UI
becoming ready:
- Failing test: [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence
& live calculation › cursor presence and live calculation across two sessions.
- Error:
TimeoutError: locator.waitFor: Timeout 20000ms exceeded while waiting for locator('.sheet-grid') to
be visible.
- The timeout occurs in openSheet() at playwright/specs/sheet_presence.spec.ts:10:37
after navigating to /s/${padId} (await page.goto(...)), and it failed consistently across Retry #1
and Retry #2.
- Because this test failed, the test command exited non-zero ([ELIFECYCLE] Test
failed), causing the job to end with Process completed with exit code 1.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

106:  Removing HTTP extra header
107:  [command]/opt/homebrew/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
108:  [command]/opt/homebrew/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
109:  Removing includeIf entries pointing to credentials config files
110:  [command]/opt/homebrew/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
111:  [command]/opt/homebrew/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
112:  [command]/opt/homebrew/bin/git config --file /Users/runner/work/_temp/git-credentials-c67c16dc-e247-4d7f-b5a4-afe8d13d5d55.config http.https://github.com/.extraheader AUTHORIZATION: basic ***
113:  [command]/opt/homebrew/bin/git config --local includeIf.gitdir:/Users/runner/work/etherpad-go/etherpad-go/.git.path /Users/runner/work/_temp/git-credentials-c67c16dc-e247-4d7f-b5a4-afe8d13d5d55.config
114:  [command]/opt/homebrew/bin/git config --local includeIf.gitdir:/Users/runner/work/etherpad-go/etherpad-go/.git/worktrees/*.path /Users/runner/work/_temp/git-credentials-c67c16dc-e247-4d7f-b5a4-afe8d13d5d55.config
115:  [command]/opt/homebrew/bin/git config --local includeIf.gitdir:/github/workspace/.git.path /github/runner_temp/git-credentials-c67c16dc-e247-4d7f-b5a4-afe8d13d5d55.config
116:  [command]/opt/homebrew/bin/git config --local includeIf.gitdir:/github/workspace/.git/worktrees/*.path /github/runner_temp/git-credentials-c67c16dc-e247-4d7f-b5a4-afe8d13d5d55.config
117:  ##[endgroup]
118:  ##[group]Fetching the repository
119:  [command]/opt/homebrew/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/* +6eaf2d36f355da8bdb5e937d2bffcba91a6df674:refs/remotes/pull/313/merge
120:  From https://github.com/ether/etherpad-go
121:  * [new branch]      125-error-reading-plugin-file            -> origin/125-error-reading-plugin-file
122:  * [new branch]      chore/dependabot-cooldown                -> origin/chore/dependabot-cooldown
...

661:  [WebServer] WARN	ws/PadMessageHandler.go:1658	Sending NEW_CHANGES to client for padFRONTEND_TESTS9de41743-eb33-4692-abc5-86cc21d026defrom rev6to7
662:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).UpdatePadClients
663:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:1658
664:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).handleUserChanges
665:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:385
666:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*ChannelOperator).AddToQueue.func1
667:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:81
668:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS34c67657-d83b-4711-aa8d-e9f2158dc3d0 socket:RLt6T9A5pKxIwZWcGnVvwyFX9hqJ3Qm6M13ZJhFsQQM IP:127.0.0.1 
669:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS34c67657-d83b-4711-aa8d-e9f2158dc3d0 socket:RLt6T9A5pKxIwZWcGnVvwyFX9hqJ3Qm6M13ZJhFsQQM IP:127.0.0.1 
670:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSfa9b3864-1f9f-43b0-b501-7ac31267cfe4 socket:oE01-tRok96Aw0sk-QRaTjOn7NXpxA2-VS29GEF6PIc IP:127.0.0.1 
671:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS9de41743-eb33-4692-abc5-86cc21d026de socket:r0vKu6wB365L7xjxl3UJzVxXtdo3mocJY-38A99Xiig IP:127.0.0.1 
672:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS9de41743-eb33-4692-abc5-86cc21d026de socket:9NWl2CFRq_yAmY_9fQPQBLzqeMOaOiGHtVSx5peps90 IP:127.0.0.1 
673:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSc975ccc5-9d46-4872-b3fe-f92aa683b740 socket:unzBGU8OytPrSS9bzM5eBnvcn40UFjpi_8efIGR5q9Y IP:127.0.0.1 
674:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTSc975ccc5-9d46-4872-b3fe-f92aa683b740 socket:unzBGU8OytPrSS9bzM5eBnvcn40UFjpi_8efIGR5q9Y IP:127.0.0.1 
675:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSfa9b3864-1f9f-43b0-b501-7ac31267cfe4 socket:PYVNJ5L2MMtaA6mQ7yyZdCxWTQj6KY_PzT_1O5_WGUU IP:127.0.0.1 
676:  [WebServer] WARN	ws/PadMessageHandler.go:1595	Error retrieving author for USER_NEWINFO send to new client
677:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).HandleClientReadyMessage
678:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:1595
679:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).HandleMessage
680:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:559
681:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*Client).readPump
682:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/client.go:147
683:  [WebServer] github.com/ether/etherpad-go/lib/ws.ServeWs
684:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/client.go:281
685:  [WebServer] github.com/ether/etherpad-go/lib/server.InitServer.func4
686:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/server/server.go:187
687:  [WebServer] github.com/gofiber/contrib/v3/websocket.New.func2.2
688:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/gofiber/contrib/v3/websocket@v1.2.1/websocket.go:198
689:  [WebServer] github.com/fasthttp/websocket.(*FastHTTPUpgrader).Upgrade.func1
690:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/fasthttp/websocket@v1.5.12/server_fasthttp.go:200
691:  [WebServer] github.com/valyala/fasthttp.hijackConnHandler
692:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/valyala/fasthttp@v1.71.0/server.go:2703
693:  [WebServer] 2026/06/25 20:10:31 error: websocket: close 1005 (no status)
694:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS7ef9b2f2-d020-4b9d-9552-5cc32917bba8 socket:2-IzC7p2ZcW1HNj9FHgR02kC7k4Amw5vzM26NeifwSk IP:127.0.0.1 
695:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTSfa9b3864-1f9f-43b0-b501-7ac31267cfe4 socket:PYVNJ5L2MMtaA6mQ7yyZdCxWTQj6KY_PzT_1O5_WGUU IP:127.0.0.1 
696:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS70e11a16-5581-48f9-afa9-b38854ade525 socket:h2qtQUyuu8Q0uAD1PBWtir5r3py4NxXLsFSYncfcHzs IP:127.0.0.1 
697:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS70e11a16-5581-48f9-afa9-b38854ade525 socket:h2qtQUyuu8Q0uAD1PBWtir5r3py4NxXLsFSYncfcHzs IP:127.0.0.1 
698:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS264c689a-cd42-422b-80e6-dd50d49dbc6c socket:ieGfJTtfFrqbjOcdapbP2vTGiailImoE60ttttYSoZ0 IP:127.0.0.1 
699:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS7ef9b2f2-d020-4b9d-9552-5cc32917bba8 socket:2-IzC7p2ZcW1HNj9FHgR02kC7k4Amw5vzM26NeifwSk IP:127.0.0.1 
700:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSf9c113bb-4823-4747-8cc9-412c02d3a2cf socket:_TqwQ0B9u6fnp6rh5JZpTR246dnkvdqg4Ygy0ajl0zs IP:127.0.0.1 
701:  [WebServer] WARN	ws/PadMessageHandler.go:466	Error retrieving read-only pad IDs: pad not found for readonly ID: r.jSGPmJPtErujeZms
702:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).HandleMessage
703:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/PadMessageHandler.go:466
704:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*Client).readPump
705:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/client.go:147
706:  [WebServer] github.com/ether/etherpad-go/lib/ws.ServeWs
707:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/ws/client.go:281
708:  [WebServer] github.com/ether/etherpad-go/lib/server.InitServer.func4
709:  [WebServer] 	/Users/runner/work/etherpad-go/etherpad-go/lib/server/server.go:187
710:  [WebServer] github.com/gofiber/contrib/v3/websocket.New.func2.2
711:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/gofiber/contrib/v3/websocket@v1.2.1/websocket.go:198
712:  [WebServer] github.com/fasthttp/websocket.(*FastHTTPUpgrader).Upgrade.func1
713:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/fasthttp/websocket@v1.5.12/server_fasthttp.go:200
714:  [WebServer] github.com/valyala/fasthttp.hijackConnHandler
715:  [WebServer] 	/Users/runner/go/pkg/mod/github.com/valyala/fasthttp@v1.71.0/server.go:2703
716:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:r.jSGPmJPtErujeZms socket:vD2-YBc-jlDks5PNpyCLQs1iZme4ONirecrAZ95mdRA IP:127.0.0.1 
717:  [WebServer] WARN	ws/PadMessageHandler.go:1212	Error retrieving author for disconnect
718:  [WebServer] github.com/ether/etherpad-go/lib/ws.(*PadMessageHandler).HandleDisconnectOfPadClient
...

948:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTSbb947fe6-627b-417a-9d48-2cdda104ae27 socket:31ReoUXRq_B_8IsW7_nbehgjH8cxuYGTsk-oNYr85VA IP:127.0.0.1 
949:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSdbddb607-1f90-4bfb-b4ad-bac8779c8b21 socket:l2yTdJyRpSdmBFGVvCWlipsRft0l8AbXTzkmPhyVr-E IP:127.0.0.1 
950:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTSdbddb607-1f90-4bfb-b4ad-bac8779c8b21 socket:l2yTdJyRpSdmBFGVvCWlipsRft0l8AbXTzkmPhyVr-E IP:127.0.0.1 
951:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS1b4e9a60-6588-4b87-8632-a779e6a2e451 socket:C7ygqriyujN_QOFor4z97wvd3BrxWFH3db8TamrKnvY IP:127.0.0.1 
952:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS1b4e9a60-6588-4b87-8632-a779e6a2e451 socket:C7ygqriyujN_QOFor4z97wvd3BrxWFH3db8TamrKnvY IP:127.0.0.1 
953:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS703b8d11-9b9c-4128-9748-3530c03044b9 socket:QyyJq9VhlJUF4nIYgS8Qpe6x7sP2v4Mv5THx-bC3Uzk IP:127.0.0.1 
954:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS703b8d11-9b9c-4128-9748-3530c03044b9 socket:QyyJq9VhlJUF4nIYgS8Qpe6x7sP2v4Mv5THx-bC3Uzk IP:127.0.0.1 
955:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTSb76d45f6-231e-403a-9370-001a208e723a socket:GwQnCZPnRYhwC7eR3LIYvW_njfDKpWpFxADZ31an7-4 IP:127.0.0.1 
956:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTSb76d45f6-231e-403a-9370-001a208e723a socket:GwQnCZPnRYhwC7eR3LIYvW_njfDKpWpFxADZ31an7-4 IP:127.0.0.1 
957:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS2f3ac7e2-4807-44a5-a333-adcb3cd1a85e socket:MpE6W6CH8uS_6OODZ9gCvmyciIWDg7HfVspfUgQC8ks IP:127.0.0.1 
958:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS2f3ac7e2-4807-44a5-a333-adcb3cd1a85e socket:MpE6W6CH8uS_6OODZ9gCvmyciIWDg7HfVspfUgQC8ks IP:127.0.0.1 
959:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS7ccee175-9baa-4b17-83f9-892477ea08d0 socket:3QuTXm23vR735shf-WnWS3zYVR1fXQeHWUX9sBNg1bg IP:127.0.0.1 
960:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS7ccee175-9baa-4b17-83f9-892477ea08d0 socket:3QuTXm23vR735shf-WnWS3zYVR1fXQeHWUX9sBNg1bg IP:127.0.0.1 
961:  [WebServer] INFO	ws/PadMessageHandler.go:1301	[CREATE] pad:FRONTEND_TESTS5aca1f85-ffac-4a20-8154-ac4581469f8a socket:5KANPdHjAy9OUuM507Tm_Jj3FYHMKfa-b4SKZGtQ5bM IP:127.0.0.1 
962:  [WebServer] INFO	ws/PadMessageHandler.go:1206	[LEAVE] pad:FRONTEND_TESTS5aca1f85-ffac-4a20-8154-ac4581469f8a socket:5KANPdHjAy9OUuM507Tm_Jj3FYHMKfa-b4SKZGtQ5bM IP:127.0.0.1 
963:  ···········×·······························×···F::error file=playwright/specs/sheet_presence.spec.ts,title=[chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions,line=10,col=37::  1) [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions %0A    TimeoutError: locator.waitFor: Timeout 20000ms exceeded.%0A    Call log:%0A      - waiting for locator('.sheet-grid') to be visible%0A%0A%0A       8 | async function openSheet(page: Page, padId: string): Promise<void> {%0A       9 |   await page.goto(`/s/${padId}`);%0A    > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });%0A         |                                     ^%0A      11 | }%0A      12 |%0A      13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {%0A        at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)%0A        at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
964:  ##[error]  1) [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions 
965:  
966:      Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
967:      TimeoutError: locator.waitFor: Timeout 20000ms exceeded.
968:      Call log:
969:        - waiting for locator('.sheet-grid') to be visible
970:  
971:  
972:         8 | async function openSheet(page: Page, padId: string): Promise<void> {
973:         9 |   await page.goto(`/s/${padId}`);
974:      > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });
975:           |                                     ^
976:        11 | }
977:        12 |
978:        13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {
979:          at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)
980:          at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
981:  ##[error]  1) [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions 
982:  
983:      Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
984:      TimeoutError: locator.waitFor: Timeout 20000ms exceeded.
985:      Call log:
986:        - waiting for locator('.sheet-grid') to be visible
987:  
988:  
989:         8 | async function openSheet(page: Page, padId: string): Promise<void> {
990:         9 |   await page.goto(`/s/${padId}`);
991:      > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });
992:           |                                     ^
993:        11 | }
994:        12 |
995:        13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {
996:          at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)
997:          at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
998:  1) [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions 
999:  TimeoutError: locator.waitFor: Timeout 20000ms exceeded.
1000:  Call log:
1001:  - waiting for locator('.sheet-grid') to be visible
1002:  8 | async function openSheet(page: Page, padId: string): Promise<void> {
1003:  9 |   await page.goto(`/s/${padId}`);
1004:  > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });
1005:  |                                     ^
1006:  11 | }
1007:  12 |
1008:  13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {
1009:  at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)
1010:  at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
1011:  attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
1012:  test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium/test-failed-1.png
1013:  ────────────────────────────────────────────────────────────────────────────────────────────────
1014:  Error Context: test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium/error-context.md
1015:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1016:  TimeoutError: locator.waitFor: Timeout 20000ms exceeded.
1017:  Call log:
1018:  - waiting for locator('.sheet-grid') to be visible
1019:  8 | async function openSheet(page: Page, padId: string): Promise<void> {
1020:  9 |   await page.goto(`/s/${padId}`);
1021:  > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });
1022:  |                                     ^
1023:  11 | }
1024:  12 |
1025:  13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {
1026:  at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)
1027:  at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
1028:  attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
1029:  test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry1/test-failed-1.png
1030:  ────────────────────────────────────────────────────────────────────────────────────────────────
1031:  Error Context: test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry1/error-context.md
1032:  attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
1033:  test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry1/trace.zip
1034:  Usage:
1035:  pnpm exec playwright show-trace test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry1/trace.zip
1036:  ────────────────────────────────────────────────────────────────────────────────────────────────
1037:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1038:  TimeoutError: locator.waitFor: Timeout 20000ms exceeded.
1039:  Call log:
1040:  - waiting for locator('.sheet-grid') to be visible
1041:  8 | async function openSheet(page: Page, padId: string): Promise<void> {
1042:  9 |   await page.goto(`/s/${padId}`);
1043:  > 10 |   await page.locator('.sheet-grid').waitFor({ state: 'visible', timeout: 20000 });
1044:  |                                     ^
1045:  11 | }
1046:  12 |
1047:  13 | async function commitCell(page: Page, r: number, c: number, text: string): Promise<void> {
1048:  at openSheet (/Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:10:37)
1049:  at /Users/runner/work/etherpad-go/etherpad-go/playwright/specs/sheet_presence.spec.ts:27:5
1050:  attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
1051:  test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry2/test-failed-1.png
1052:  ────────────────────────────────────────────────────────────────────────────────────────────────
1053:  Error Context: test-results/specs-sheet_presence-Sheet-59dea-ulation-across-two-sessions-chromium-retry2/error-context.md
1054:  1 failed
1055:  [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions 
1056:  4 skipped
1057:  121 passed (2.0m)
1058:  ##[notice]  1 failed
1059:      [chromium] › specs/sheet_presence.spec.ts:20:7 › Sheet live presence & live calculation › cursor presence and live calculation across two sessions 
1060:    4 skipped
1061:    121 passed (2.0m)
1062:  [ELIFECYCLE] Test failed. See above for more details.
1063:  ##[error]Process completed with exit code 1.
1064:  ##[group]Run actions/upload-artifact@v7

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>
@SamTV12345 SamTV12345 enabled auto-merge (squash) June 25, 2026 20:25
@SamTV12345 SamTV12345 merged commit 7f36311 into main Jun 25, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant