PollContent read mEvent.getId() once at render. For your own new poll
that is the local echo id (~!room:txn), and nothing re-rendered it when
the SDK swapped in the real $id, so the vote related to the local id
and the SDK threw synchronously (getPendingEvents under chronological
ordering): no vote was ever sent. Track the id via LocalEventIdReplaced
and Status, block voting while it is still local, and catch synchronous
throws. Adds the Playwright regression the issue asks for.
Fixes#284
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
"Ahead" is now reported only once it has held for a minute of fresh
samples (a stalled server delivers late and reads as ahead). The test sends
its ticks, checks nothing is shown yet, fast-forwards the page clock past a
minute and sends two more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
markAsRead ran on every room visit (timeline at the bottom and focused) and
sent a threaded receipt for every unread thread, so a reply in a thread you
started or replied in lost its unread badge the moment you glanced at the
room, without opening the thread.
Reads from just viewing the timeline are now "passive":
- threads you follow (started, replied in, or were mentioned in) stay unread
until their panel is opened; other threads are still cleared so they don't
keep the room dot lit forever;
- while a followed thread has an unread reply, the main receipt is scoped to
the main timeline instead of unthreaded, because an unthreaded receipt
also reads every older thread reply (the next main message would clear the
thread anyway). The check also asks whether the latest reply is read, since
the thread's count lags when the reply and a main message share a sync;
- the thread open in the panel is skipped, as the panel sends its own
receipt (was two identical receipts per reply).
Explicit "mark as read" (room menu, Escape, bulk actions) still clears
everything. Unit tests for each rule plus a local-homeserver e2e that checks
the server's per-thread count survives a reply + newer main message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Two new Playwright projects — 'webkit' (Desktop Safari) for tests tagged
@webkit and 'iphone' (iPhone 14 descriptor) for @ios — covering boot,
login + send/receive, the thread panel and the gallery lightbox. The CI
e2e job installs webkit next to chromium.
WebKit reports handled fetch failures (well-known probes, a wasm fetch cut
short by our own navigation) as page errors with its own wording, so the
benign allowlist now applies to page errors as well.
Locally: 8/8 green twice in a row against the local Synapse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The e2e job now runs scripts/dev-homeserver.sh start + dev-seed.py before
Playwright (installing python3-venv if the runner lacks it) and stops it
afterwards. e2e/local-homeserver.spec.ts registers its own users and rooms
over the CS API and drives the built client — no prod secrets — covering the
fixes that were reproduced with scratch scripts this week: login/send/receive,
own-message scroll (#212), /kick toast (#216), upload 413 wording (#213),
forward provenance, thread panel at 1400px (#218), timeline lightbox (#219),
clock-skew banner via page.clock (#158), status save under the presence limit
(#226), long-press action sheet on a Pixel 7 emulation (#166). Skips itself
when no homeserver answers, so
> lotus-chat@4.12.7-lotus test:e2e
> playwright test
Running 16 tests using 1 worker
✓ 1 [chromium] › e2e/boot.spec.ts:8:3 › boot › client boots to the login screen without errors (1.5s)
✓ 2 [chromium] › e2e/boot.spec.ts:27:3 › boot › service worker script is served and registers (1.4s)
✓ 3 [chromium] › e2e/boot.spec.ts:54:3 › boot › bundled Element Call loads in a frame (3.8s)
- 4 [chromium] › e2e/e2ee-composer.spec.ts:67:3 › E2EE composer › logs in with a password and reaches the client
- 5 [chromium] › e2e/e2ee-composer.spec.ts:84:3 › E2EE composer › creates a private encrypted room and sends a text message
- 6 [chromium] › e2e/e2ee-composer.spec.ts:146:3 › E2EE composer › attaches a compressed image and it is sent encrypted
✓ 7 [chromium] › e2e/local-homeserver.spec.ts:31:3 › local homeserver regression › logs in, opens a room, sends and receives (4.5s)
✓ 8 [chromium] › e2e/local-homeserver.spec.ts:58:3 › local homeserver regression › your own message scrolls into view even after scrolling up (#212) (11.4s)
✓ 9 [chromium] › e2e/local-homeserver.spec.ts:76:3 › local homeserver regression › /kick failure is reported, not swallowed (#216) (5.4s)
✓ 10 [chromium] › e2e/local-homeserver.spec.ts:90:3 › local homeserver regression › upload failure shows a plain sentence, never the raw MatrixError (#213) (3.7s)
✓ 11 [chromium] › e2e/local-homeserver.spec.ts:122:3 › local homeserver regression › forwarded message carries its provenance header (6.7s)
✓ 12 [chromium] › e2e/local-homeserver.spec.ts:143:3 › local homeserver regression › thread panel: opens from the chip and yields the member drawer at 1400px (#218) (4.7s)
✓ 13 [chromium] › e2e/local-homeserver.spec.ts:172:3 › local homeserver regression › timeline image opens the gallery lightbox (#219) (3.9s)
✓ 14 [chromium] › e2e/local-homeserver.spec.ts:204:3 › local homeserver regression › warns when the local clock is far off the server (#158) (6.5s)
✓ 15 [chromium] › e2e/local-homeserver.spec.ts:221:3 › local homeserver regression › status save survives the presence rate limit (#226) (12.6s)
✓ 16 [chromium] › e2e/local-homeserver.spec.ts:265:3 › local homeserver regression › touch: long-press opens the message action sheet (#166) (5.0s)
3 skipped
13 passed (1.2m) still works cold. 13 pass
locally against dist + the dev homeserver in 1.8 min.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA