From 3045a9f014de4245987e19738315aeb18fa370d8 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 8 Jul 2026 00:05:05 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20add=20DP1-DP18=20test=20items=20(LOTUS?= =?UTF-8?q?=5FTESTING=20=C2=A7R),=20retire=20DP=20backlog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The discovery-pass items DP1-DP18 are implemented + TPVR-reviewed, so move their manual QA steps into LOTUS_TESTING.md (new section R) and replace the DP backlog block in LOTUS_TODO.md with a done-breadcrumb pointing at §R + the commits. Co-Authored-By: Claude Opus 4.8 --- LOTUS_TESTING.md | 34 +++++++++++++ LOTUS_TODO.md | 123 +---------------------------------------------- 2 files changed, 36 insertions(+), 121 deletions(-) diff --git a/LOTUS_TESTING.md b/LOTUS_TESTING.md index ca086573f..b7448b615 100644 --- a/LOTUS_TESTING.md +++ b/LOTUS_TESTING.md @@ -699,6 +699,40 @@ Paste each of these into a room and confirm a media tile (not a plain link) with --- +## R. Discovery-pass fixes (DP1–DP18, 2026-07) + +Agent-surveyed + TPVR-verified low/med issues, now fixed (commits `8eb961b6` `db864326` `6cf18c3b` `165714e1` `8c0e2b42` `e545706c` `b1ee3ada` `4fc3f7a3` `101e4116` `4fa4327a` `c2598d21`). Behavioral items have concrete checks; the refactors just need a "still works" regression pass. + +### Correctness + +- [ ] **DP1 — slash-command errors are visible.** Run a slash command that must fail — e.g. `/kick @nobody:server` in a room where you lack permission, or `/join` a bad alias. **Expected:** an error toast appears (not a silent no-op); a successful command still clears the composer normally. +- [ ] **DP2 — no invite re-notify on reload.** With ≥1 pending invite, hard-reload (Ctrl+F5). **Expected:** NO "you have N new invitation" toast/sound on load. Then have someone invite you while the app is open → you DO get one notification for the new invite. (👥 2 accounts) +- [ ] **DP3 — status clear syncs across devices.** Set a status message on device A, confirm it shows on B; clear it on A. **Expected:** B clears too and does NOT re-publish the old status on its next presence heartbeat. Toggling Invisible must not wipe a real saved status. (👥 2 sessions) +- [ ] **DP4 — tag-toggle failure surfaced once.** With the network offline, toggle a room's Favourite/Low-priority. **Expected:** a single error toast (not two) on failure; on success the tag updates as before. +- [ ] **DP5 — soundboard packs update on room switch.** Open a soundboard in room A, then switch to room B (different pack) in the same mounted view. **Expected:** B's packs show without needing an unrelated event. +- [ ] **DP6 — declining a call still dismisses.** Decline an incoming call. **Expected:** the ringing UI dismisses even if the decline send fails (best-effort). (👥 2 accounts) + +### a11y / UX (needs a screen reader + a narrow viewport) + +- [ ] **DP7 / DP8 — call-control buttons announce correctly.** In a call with a screen reader: the deafen button announces "Deafen" when sound is on (not "Undeafen"); Sound / Video / Screenshare announce a consistent pressed/unpressed state like Mic. +- [ ] **DP9 — GIF picker focus + width.** Open the GIF picker, close it (Esc / click-out) → focus returns to the GIF button. On a ~320px viewport the picker doesn't overflow the page. +- [ ] **DP10 — search-filter clears by keyboard.** In message search, toggle a filter chip (Has link / msg-type / pinned) off with Enter; the date-range clears via its menu's Clear. No mouse-only clear needed. +- [ ] **DP11 — voice recorder fits + announces.** On a ~360px viewport, start a voice message → the recorder row doesn't overflow the composer; a screen reader can query the duration (role="timer") without being spammed. +- [ ] **DP12 — live-call count announced.** With a screen reader, when someone joins/leaves an active call, the "{n} Live" change is announced (polite status region). + +### TDS colors (Lotus Terminal theme) + +- [ ] **DP14 — send-status + receipt colors follow the theme.** In **TDS light** mode: the message send-status "failed" icon and the read-receipt pill use the theme's darker red/blue (from `--lt-*` tokens), NOT bright dark-mode cyan/red. TDS dark still looks right; non-TDS themes unchanged. + +### Refactor regression pass (no behavior change intended) + +- [ ] **DP13 — bookmarks / reminders / notes still work** (they now share one store engine). Add/remove a bookmark, set/clear a reminder, write/clear a user note; each persists across reload; rapid consecutive writes don't clobber each other. +- [ ] **DP15 / DP16 — state-event + account-data writes still work.** Edit room name/topic/avatar, join-rules, power levels, an emoji/soundboard pack (state events); toggle a setting stored in account data. All save + reflect correctly. +- [ ] **DP17 — link previews render.** Open a TikTok / Spotify / Steam / Reddit link preview; provider icons render (folds icons, not raw glyphs) and brand colors look right. +- [ ] **DP18 — member avatars/names live-update.** Read receipts + the "seen by" reader list show correct avatars/names and update live when a member changes their avatar or display name (no reload). + +--- + ## Priority if you're short on time 1. **O1 + O2** (threads + per-thread notifications) — the largest new surface; the main-timeline change is user-visible. diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index eb50cb435..118d3750b 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -63,128 +63,9 @@ Built and gate-green; verify per [LOTUS_TESTING.md](./LOTUS_TESTING.md), then gr ## 🔴 Open — Actionable -### 🔍 Discovery pass (2026-07) — agent-surveyed, TPVR-verified +### ✅ Discovery pass (2026-07) — DP1–DP18 DONE -Three discovery agents swept the fork's own code (`src/`) for correctness / a11y / -tech-debt items beyond the tracked backlog; every item was then independently -re-verified (TPVR, 2026-07) against the code + installed SDK. **All 18 are real** — -`✅` = confirmed as claimed, `◐` = real but the claim was imprecise (correction inline). -`Sev` = user/maintenance impact, `Eff` = S/M/L. IDs `DP1–DP18`. - -**Correctness (user-facing):** - -- [ ] **DP1 — Silent slash-command failures (Sev High · Eff S–M) · ✅.** `RoomInput.tsx:542` - calls `commandContent.exe(plainText)` (all `exe` are async, several reject) with no - `await`/`.catch`, then unconditionally resets the editor as if it succeeded; no global - `unhandledrejection` handler exists. `/kick` `/ban` `/invite` `/join` `/leave` `/ignore` - `/myroomnick` `/acl` `/delete` etc. fail with **zero** feedback. **TPVR context:** `/kick` - & `/ban` also route through `rateLimitedActions` (`utils/matrix.ts:452`) whose `to()` - swallows all non-429 errors — so a real fix must surface errors **per-command**, not just - await line 542. -- [ ] **DP2 — Pending invites re-notify on every reload (Sev Med · Eff S) · ✅.** - `ClientNonUIFeatures.tsx:202-217` + `usePreviousValue(invites.length, 0)` (ref inits `0`). - **TPVR context:** on warm reload the invite atom is populated synchronously from cached - `mx.getRooms()` while the client is already `SYNCING`, so the sync-state gate (`:203`) - doesn't protect → `notify(N)` + sound fire for pre-existing invites. Fix: seed previous to - `invites.length` and/or a first-render ref. -- [ ] **DP3 — Status cleared on another device is resurrected (Sev Med · Eff S) · ✅.** - `Profile.tsx:370` gates on `presence?.status` truthy, so a remote **clear** (falsy) skips - both the input reset and the `localStorage` write; `usePresenceUpdater.readStatus()` - re-sends the stale `status_msg` on the next heartbeat. Fix: treat `status !== undefined` - as an explicit clear + `localStorage.removeItem(STATUS_MSG_KEY)`. -- [ ] **DP4 — Favourite / Low-priority toggles fail silently (Sev Low–Med · Eff S) · ✅.** - `RoomNavItem.tsx:337-351` — `setRoomTag`/`deleteRoomTag` fire-and-forget with no optimistic - UI (state derives from the account-data echo), so a rejected write leaves the menu closed, - no change, no toast. UX-only, bites on failure. -- [ ] **DP5 — Soundboard pack hooks go stale on room/space change (Sev Low · Eff S) · ✅.** - `useSoundboardPacks.ts:80,103,125` — `useState(() => …)` inits once. **TPVR context:** both - consumers usually remount when the room changes so the stale window is narrow, but it's a - real footgun. Fix: derive via `useMemo`/resync effect keyed on the room list. -- [ ] **DP6 — Call-decline event fire-and-forget (Sev Low · Eff S) · ✅.** - `CallEmbedProvider.tsx:547` — uncaught `mx.sendEvent(RTCDecline)`; UI dismisses - unconditionally (`:553`), so a failed decline looks handled locally while the caller keeps - ringing. Content shape verified spec-correct. Fix: best-effort `.catch` + still dismiss. - -**a11y / UX / mobile:** - -- [ ] **DP7 — Deafen button announces the OPPOSITE action to screen readers (Sev High · Eff S) · ✅.** - `call-status/CallControl.tsx:70` — `aria-label={enabled ? 'Undeafen' : 'Deafen'}` is - inverted; the tooltip (`:58`) is the **correct** one. When sound is ON, pressing deafens → - label should say "Deafen". aria-only fix, no visual risk. -- [ ] **DP8 — Sound/Video/ScreenShare toggles missing `aria-pressed` (Sev Med · Eff S) · ✅.** - Only `MicrophoneButton` (`CallControl.tsx:38`) exposes it; `:52,90,131` omit. **TPVR note:** - also pick a consistent pressed-semantics across all four (mic's `aria-pressed={!enabled}` - = "pressed means muted" is debatable). -- [ ] **DP9 — GifPicker drops focus + fixed width overflow (Sev Med · Eff S/M) · ✅ focus / ◐ width.** - `GifPicker.tsx:106` `returnFocusOnDeactivate:false` (+ `initialFocus:false`) drops focus to - `` on dismiss — confirmed. **Correction:** `PICKER_WIDTH=312` is ~314px total and only - overflows *below* ~314 CSS-px or when PopOut positions near a screen edge (not a flat - "≤320px"). Fix: return focus + `min(312px, calc(100vw - 16px))`. -- [ ] **DP10 — Search-filter "clear" icons: redundant mouse-only affordance (Sev Low–Med · Eff S) · ◐.** - `message-search/SearchFilters.tsx:800,826,850` (+ `DateRangeButton:648`) — `` - inside a `Chip`(`