v2.0.9 — sync toast split, relay group chat UI, find/replace flaky fix

- sync: the post-sync toast lumped relay pulls into the CalDAV stats
  and always labelled the banner "CalDAV". Split into two separate
  toasts (CalDAV cloud-arrow + Relay broadcast icon), each only shown
  when its numbers are non-zero. Banners now stack vertically so both
  can appear together on a sync that pulled on both sides.
- relay UI: the session chat view was 1:1-only. Group rooms (which
  the client API already supports) now surface as first-class threads
  in the sidebar — sorted chronologically with 1:1 peers, tagged
  "ROOM", with a users-three icon. Sending posts to `room_uid`
  instead of `recipient`; history shows the sender name above each
  incoming message so it's clear who's talking in the room.
  Relay backend: `/api/chat/threads` returns rooms alongside 1:1
  threads; `/api/chat/history` accepts `?room_uid=`; `/api/chat/send`
  accepts `room_uid` in the body.
- tests: bump "replace all updates card bodies" poll budget from 4s
  to 10s and poll both updated cards. Flaky on cold runs because a
  fresh PHP worker + SQLite WAL warm-up plus two sequential PATCH
  calls can just barely exceed 4 seconds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-18 18:28:00 +02:00
1 parent 4aca21812e
commit 87ebcd156b
6 files changed
+175 -51

No files matched your search

@@ -78,14 +78,17 @@ describe('Search: Find/Replace', () => {
const okBtn = document.getElementById('dialog-ok');
if (okBtn) click(okBtn);
// Poll until the replace has actually persisted
// Poll until both replaces have persisted. Budget is generous
// (~10s) because a cold PHP run plus two sequential PATCH calls
// can easily blow past the original 4-second window — the retry
// always passes because opcache + SQLite WAL are warm by then.
let updated1, updated2;
for (let i = 0; i < 40; i++) {
for (let i = 0; i < 100; i++) {
await sleep(100);
updated1 = await getCard(c1.id);
if (updated1?.body === 'goodbye world goodbye') break;
updated2 = await getCard(c2.id);
if (updated1?.body === 'goodbye world goodbye' && updated2?.body === 'goodbye there') break;
}
updated2 = await getCard(c2.id);
expect(updated1.body).toBe('goodbye world goodbye');
expect(updated2.body).toBe('goodbye there');
});