Client audit 2026-10: tracking index #281

Closed
opened 2026-10-04 17:41:58 -04:00 by jared · 5 comments
Owner

October 2026 client audit: tracking issue

Scope: the Lotus-specific integrations on top of upstream: cinny lotus @ 30e2dbcd, cinny-desktop main @ 8f67ec7 (Linux nightly 4.12.377), element-call lotus @ 3b0050e8, and the production config/CSP in matrix.

How

  • Playwright against a local Synapse + LiveKit + voice-limit guard, with a seeded world: a space, a rich-content room (threads, polls, images, files, edits, reactions, redactions), a voice room, a DM and an invite.
  • Viewports: desktop 1440, laptop, phone 390 and 360 px, in dark and light themes.
  • Every screen: room settings, message menus, composer pickers, sign-in pages.
  • Per screen: console/page errors, overflow and clipping checks, and an axe-core pass.
  • Real two-person calls: join, PiP, soundboard, audio output, rejoin after reload, channel-full limit, phone layout.
  • Desktop: the Linux nightly driven through WebKitGTK WebDriver.
  • Production: live CSP and config checks.
  • Code reading: the call bridge (cinny ↔ fork widget actions), widgets, settings sync, the outbox, share target, scheduling, delete-my-messages, the topic editor and the Server ACL page.

Every finding was reproduced or verified before filing. Issues already open (e.g. #126, #166, #199, #205, #225) were not refiled.

Labels: one priority: *, one type (bug/ux/security/a11y), one area: *, as in #89. New labels created for this audit: cinny-desktop ux, security, area: config; matrix bug.

Totals: 25 issues (1 high, 7 medium, 17 low): cinny 22, cinny-desktop 2, matrix 1.

High

  • #259 Media Gallery / Activity Log make the room timeline drop loaded messages, and it continues after the panel is closed (regression of #163) bug area: messaging

Medium

  • #260 ML noise-suppression failures are never shown: io.lotus.denoise_state is unhandled, and the old lotus-denoise-status listener is dead and accepts any window bug area: calls
  • #261 Server ACL settings treat a missing allow_ip_literals as false, so the first save silently blocks IP-literal servers bug area: moderation
  • #262 Room topic editor mangles URLs and aliases that contain _ or * (broken links for every client) bug area: settings
  • #263 Room header: opening Threads or Gallery shifts the header icons, so a second click opens Widgets instead of closing the panel ux area: navigation
  • #264 Phone: composer emoji/sticker board opens 31 px off the left edge of the screen bug area: mobile
  • #265 Phone (portrait) call: call controls wrap to 4 rows (~40% of the screen) and your own tile isn't shown ux area: calls
  • cinny-desktop#33 Desktop CSP frame-src blocks room widgets (Jitsi, Etherpad, …); web allows them bug area: config

Low

  • matrix#18 Web CSP blocks the TikTok oEmbed lookup, so TikTok short links never play on chat.lotusguild.org bug infra
  • #266 Widget consents survive logout, and the protected-state list misses space and call state security area: auth-session
  • #267 Share target: any website can POST into /share-target, and stashed shared files aren't cleared on logout security area: messaging
  • #268 Offline outbox: two open tabs overwrite each other's saved queue bug area: messaging
  • #269 Read-receipt avatars float at the end of the message text instead of the message's right edge bug area: messaging
  • #270 Room Insights: the top 'Most Active Members' bar pushes its message count out of view bug area: settings
  • #271 Settings → Calls: the Noise Suppression description renders at body size, larger than every other description ux area: settings
  • #272 Avatar Decoration settings: the preview is an invisible circle and never shows your own avatar ux area: settings
  • #273 Settings layout: grouped settings run together, and the Sync tile is squeezed on phones ux area: settings
  • #274 'Connecting…' strip stays about 30 s after the app is already usable ux area: navigation
  • #275 Schedule Message lets you pick a time beyond 24 h; the server rejects it with a raw technical error bug area: messaging
  • #276 Desktop build number isn't shown anywhere, and the web version is a static string ux area: desktop
  • #277 Call bar always shows 'Mute Screenshare Audio' (a speaker icon), even when nobody is sharing ux area: calls
  • #278 Accessibility: axe-core findings across the main screens a11y area: settings
  • #279 Copy nits: room soundboard text talks about a 'space', Push to Deafen help, a pinned-messages typo ux area: settings
  • #280 Tracking stripper removes Reddit context=, which changes what a comment permalink shows bug area: messaging
  • cinny-desktop#34 Desktop config.json has drifted from web production: custom homeservers allowed, and upstream's sample featured communities shown bug needs-human-review area: config

Checked and found OK (not filed)

  • September criticals stay fixed:
    • scheduled messages are refused in encrypted rooms;
    • bookmarks, GIFs and compression no longer leak plaintext.
  • Settings sync: only starts after the initial sync, so no clobbering.
  • Offline outbox: resends with the same txn id, which Synapse de-duplicates for 24 h.
  • Call bridge, cinny ↔ fork:
    • only the parent window at the host origin is accepted;
    • widget-action input is validated;
    • inject_audio refuses while muted.
  • Room widgets: consent prompt, scoped to their own room, protected state types.
  • Delete-my-messages: state events skipped, 429 handled, resumable.
  • Voice channel limit: enforced on every join path.
  • Rejoin after reload: prompt shows and works when others are still in the call.
  • Calls end to end: PiP, soundboard and audio output all behave.
  • Desktop deep links: URLs are JSON-encoded before being injected.
  • Production config: isitup, Giphy and decoration CDN are all allowed by the web CSP.

Round 2 — feature test matrix (2026-10-04)

Every Lotus feature found in the fork histories was driven through Playwright: 1,036 cinny commits since the upstream merge base, 365 cinny-desktop commits and ~100 element-call fork commits. The runs used a local Synapse + LiveKit stack, with two or three real accounts and E2EE where relevant. ✅ = works as described; the ❌ items are filed above or in round 1.

Area ✅ verified working ❌ filed
Composer draft survives reload + nav draft marker; tracking params stripped on paste and send; paste-as-code offer → language-js block; markdown; KaTeX; up-arrow edit; length counter; @mention and :emoji autocomplete; poll creation; location share; voice message record/pause/send; schedule tray + Send now #284 vote on own new poll; #275 >24 h schedule
Message actions link-mismatch dialog; edit-history word diff (hover + modal); collapse long messages; code Wrap/Copy; Seen-by modal; bookmark + Saved Messages panel; Remind Me (account data); forward with comment + provenance line; Copy Lotus Link (uses webAppUrl on desktop); Copy Text; Report; reply media thumbnail; media grid; audio-file inline player #286 deceptive preview; #287 Translate no-op; #269 receipt pill
Navigation favourites, low priority, custom u.* sections, mark unread (both m. and famedly keys), rename-for-me, room filter, sort menu, DM previews, quick switcher, jump to time, back-to-where-you-were, invite QR, title unread count, knock → Request to join #285 matrix.to invite/QR; #289 empty filter; #263 header shift
Threads thread chip, panel header, reply + up-arrow edit, threads list filters —
Presence/profile custom status presets #282 Invisible/Idle/DND don't stick; #283 DND "dnd" status
Search from:, in: (+ quoted), after:/before: relative, recent searches, operator → chip #288 edit duplicates
Room settings retention, call policy (screenshare off hides the button in calls), Delete all my messages (5/5 redacted, others untouched), soundboard upload, export TXT/JSON/HTML #261 ACL default; #270 Insights
Calls two-person join, PiP, soundboard panel, audio output list, rejoin-after-reload toast, channel-full limit, deafen hotkey, push-to-talk hold, space-rail live label, call-ended summary toast, DM ring → "Incoming Voice Call", missed-call line, ML denoise (all four models on a production build) #260 denoise failure silent; #265 phone layout; #277 screenshare-audio button; #293 dev denoise
E2EE encrypted round trip between browsers; unverified-devices warning; undecryptable placeholder; recovery key leaves the clipboard after 60 s —
Appearance 11 seasonal overlays + Off; 9 theme presets; Lotus Terminal dark/light; animated backgrounds; night light; JetBrains font; monochrome; reduced motion stops all overlay animations #271, #272, #273
Sync/PWA settings sync between two devices (≈5 s); manifest maskable icons; dev SW active #267 forged share confirmed; #292 portrait lock
Desktop (Linux nightly 4.12.377) desktop settings render; keychain says "not available yet"; CSP blocks widgets (confirmed) cinny-desktop#33; #290 settings placement; cinny-desktop#35 README

Not reachable from here (left to the existing QA issues): Windows-only native features (#18, #20); OIDC (#183); real-phone checks (#198).

Measured but not filed. Seasonal overlays render at ~15 fps without a GPU (software raster) against 61 fps with them off. Main-thread cost stays at 1–6%, so this only matters on machines without GPU compositing.

Corrections to round 1

  • cinny-desktop#34 was rewritten. The desktop's featured communities are deliberate Lotus choices (b1ecb0c4/e132719); it's the web config (matrix repo) that never received them.
  • All 25 round-1 issues gained a More context section: introducing commit, affected users, manual repro, acceptance criteria.

Round 2 issues

High

  • #282 Invisible, "Hide Online Status", Idle and DND don't stick: the next /sync sets you back to online

Medium

  • #283 Do Not Disturb overwrites your status message with "dnd", and the "dnd" stays after you switch back
  • #284 You can't vote on a poll you just created (until a reload): the vote targets the poll's local-echo id and throws
  • #285 Room invite links and QR codes use matrix.to, which can't open Lotus Chat
  • cinny-desktop#35 README is still upstream Cinny's: download links and signing key point at cinnyapp, and no Lotus desktop features are documented

Low

  • #286 A link whose text names a different site still gets a genuine preview card for the site it pretends to be
  • #287 Translate silently does nothing when the language can't be detected, or the message is already in your language
  • #288 Message search lists an edited message once per version
  • #289 Sidebar room filter with no matches shows empty section headers and no "No rooms match" message
  • #290 Desktop-only settings are mixed into the Appearance section
  • #291 Invalid HTML nesting in Settings: blocks inside the tile description's

    (React warnings on every Settings page)

  • #292 Installed PWA is locked to portrait, and the "New Message" shortcut just opens the home page
  • #293 ML noise suppression can't be tested with npm start, and every call logs a lotusTransparent/lotusTheme warning
  • #294 LOTUS_FEATURES.md is missing ~55 shipped features
  • element-call#43 The Lotus fork has no documentation: the io.lotus.* widget protocol and URL flags are only described in code comments

Round 3 (2026-10-04)

Decisions recorded (Jared):

  • Real busy presence for DND: yes. The Synapse change is filed as matrix#19 and linked from #282 and #283; the client fix is still needed.
  • Featured communities on web: yes, matrix#20. Desktop keeps allowing custom homeservers (cinny-desktop#34).
  • Lotus links for invites/QR, matrix.to kept as secondary: yes (#285).
  • Scope: audit and report only; no fixes in this pass.

New issues

  • #295 Starting a call in a voice room @room-pings every member when a moderator or admin starts it bug area: calls (medium)
  • matrix#21 Web CSP blocks mozilla.org discovery, so its login option always says "Failed to connect" (low)
  • #291 gained the UploadCardRenderer → TauriDesktopFeatures setState-in-render warning (fires on every image upload).

Additionally verified working

  • Photo metadata stripping: GPS, Make/Model and timestamps stripped from JPEG, WebP and PNG messages and from avatars; JPEG orientation kept (dimensions swapped correctly).
  • Search, widgets and moderation:
    • encrypted-room search through the local cache;
    • adding a room widget, and the https-only URL check;
    • policy-list viewer (including legacy org.matrix.mjolnir.rule.*).
  • Notifications and embeds: keyword push rule (with highlight); YouTube facade (nothing loads until Play, then youtube-nocookie).
  • Calls:
    • screenshare end to end (confirm prompt → Bob receives 960×540);
    • room quality caps reach the fork (io.lotus.set_quality);
    • AFK auto-mute at the 1-minute timeout;
    • DM ringing.
  • Sign-in: matrix.org login on production (password + SSO).
  • Server info: MSC1929 support contact (/.well-known/matrix/support).

Inconclusive / not filed

  • Muted-talk nudge: didn't fire for Chrome's synthetic tone (it likely needs speech).
  • Focus camera: the menu entry wasn't reachable from my selectors.
  • In-app toasts: headless Chrome reports the window unfocused, so OS notifications are used instead.

Round 4 (2026-10-04)

New issues

  • #298 A tiny m.room.retention permanently deletes opted-in users' messages and hides the room for everyone. The 10-minute safety floor is never applied. Verified: max_lifetime: 1000 blanked the timeline, and 4/4 of my messages were redacted on the server in 40 s. bug area: messaging (high)
  • #296 The persistent encrypted-search opt-in survives logout, so the next account indexes plaintext without opting in. security area: auth-session (medium)
  • #297 When an admin turns off screen sharing, the sharer gets two contradictory toasts ("window was closed" + "turned off by a room admin"). ux area: calls (low)
  • #299 Notification inline reply doesn't re-check encryption at send time, and uses the newest session rather than the notification's account. security area: notifications (low)
  • #300 Status auto-clear only works on the device that set it, while it's open. bug area: settings (low)

Additionally verified working

  • Verification: two-session device verification. The verified session starts it; the new one gets the QR code with "Verify with emoji instead", the emoji match, and it ends "Your device is verified".
  • Calls:
    • incoming DM call while in a voice-room call → "Incoming voice call · Answer"; answering leaves the voice room cleanly (membership 1 → 0) and joins the DM call;
    • call-load watchdog → "The call failed to load…" (no retry button: see below);
    • SFU token refusal → "Can't join this call · This voice channel is full (4/4)";
    • LiveKit unreachable → "Couldn't connect to voice" with VPN/antivirus advice and Try again;
    • admin disables screen sharing mid-share: the share stops and the sharer is told (see #297 for the extra toast);
    • undeafen catch-up toast ("While you were deafened: … joined").
  • Messages:
    • caption edit after sending;
    • Media Gallery audio tab + jump to message;
    • undisclosed polls hide results until the end, and max selections is enforced;
    • scheduled message edit + cancel;
    • reminder fires as a toast within one 30 s tick and removes itself;
    • avatar decoration shows on your messages.
  • Moderation: knock → admin sees Approve/Deny → Approve invites.
  • Phone: long-press opens the action sheet and Reply works from it; swipe in the full-screen viewer (1/3 → 2/3).
  • Search: the persistent encrypted search index survives a reload.
  • Logout: wipes drafts, outbox, the search index and crypto/sync stores; no plaintext left in localStorage (apart from the opt-in flag, #296).
  • Look and accessibility: Compact / Bubble / Lotus Terminal layouts render cleanly with Lotus content; seasonal overlays and animated backgrounds never block clicks; skip link, keyboard-only quick switcher, focus return after Escape, and the ? shortcuts dialog traps focus.
  • Desktop and code-level checks:
    • "Delete all my messages" resume needs an explicit click;
    • Linux desktop native commands answer (check_for_update, update_install_kind, close behaviour; keychain and global hotkeys report unsupported on Linux as expected);
    • Sentry was removed entirely (24d6460e), so no third-party error reporting.

Small items — now filed: #301 (call-load screen has no Try again), #302 (rejoin wording/ended DM calls); pinned-popout focus added to #278; Autumn/Deep Space label added to #279.

Noted, not filed

  • Call-load error screen: "The call failed to load. Check your connection and try again." offers only Back, no Try again.
  • Rejoin prompt in DMs: it says "in the call in Bob Builder" for a DM (should be "with Bob Builder"), and was offered for a DM call whose other party had just left.
  • Pinned popout: opening it doesn't move focus into the popout.
  • Seasonal labels: Settings shows Autumn as "Sep 21 – Oct 14", but Deep Space takes Oct 4–10.
  • Service-worker precache: 110 files, about 11.8 MB uncompressed on first visit (5.6 MB of it the crypto wasm). It includes lazy chunks such as pdf.js, KaTeX and jsQR.

Round 5 (2026-10-04): cross-browser

  • #303 Firefox hangs on "Heating up" after login until the persistent-storage prompt is answered. initClient awaits navigator.storage.persist(), which stays pending in Firefox. (high)
  • #304 Escape doesn't close the emoji/sticker board in any browser (upstream behaviour, retitled after Chromium/Firefox checks). (low)
  • Verified in WebKit (Safari engine): login, send, image viewer, Settings, call join, and the in-frame "Share your screen?" prompt.
  • Verified in Firefox (with the persist prompt answered): login, send, image viewer, Settings.
  • Verified on iPhone 14 emulation: the emoji board fits the screen.
  • Firefox call join (persist prompt stubbed): joins the voice room, the Soundboard control appears, and Start Screenshare shows the in-frame "Share your screen?" prompt.
  • Playwright can't emulate mobile in Firefox (no isMobile/touch), so phone checks were run in WebKit (iPhone 14) and Chromium only.

Round 6 (2026-10-04): rendering cost and infrastructure

  • #305 Reactions row loops forever between "wrapped" and "one row + N" when the chips are within 4 px of fitting. The collapsed margin: -2 makes the measured width 4 px larger. Chromium loops at 1348–1351 px (33 layouts and 216 ms of script per 2 s); Firefox loops at the default 1440 px. The same style also wipes the timeline's 8 px marginTop (−2 px while collapsed, 0 px after expanding again). (medium)
  • matrix#22 Avatar decorations come from Nextcloud public DAV: no Cache-Control, and a new PHP session (four cookies) for every image. (low)
  • Fixed a duplicated audit footer on the issues filed in rounds 3–5 (body text only).

Round 7 (2026-10-05): outbox, status banner, security nudge, share target, call extras

  • #306 Delivery indicator gets stuck. With ≥100 ms send latency, your last message keeps "Sending..." until something else re-renders the room. A message auto-retried by the outbox keeps "Failed to send. Click to retry" after it was delivered. Same root cause as #284. (medium)
  • #310 + element-call#44 The call-bar audio output switch can't pick a specific device. Host and iframe deviceIds differ, the fork falls back to Default, and the host menu still shows ✓. (medium)
  • #307 A dismissed Kuma announcement keeps the security nudge hidden while it stays posted. (low)
  • #308 Status banner Details strips every #, _, *, > and backtick (#room → room, <b> → <b). (low)
  • #309 duplicate of #267 (share target): new evidence added there.
  • #311 Consent prompt: Retry also snoozes it for 10 s, so a retry that fails again shows nothing. (low)
  • Verified working:
    • Offline outbox:
      • "Queued. Will send when you're back online", plus an "Offline" label;
      • 3 offline messages are delivered in order;
      • a send survives closing the tab, and the outbox is emptied afterwards;
      • an encrypted room sends ciphertext only after reload;
      • a thread reply stays in its thread after reload;
      • entries older than 1 h are not auto-sent;
      • a lost response after the server accepted gives no duplicate (same txnId).
    • Kuma banner (desktop and Pixel 7):
      • maintenance "until …" with Details;
      • long announcements wrap;
      • dismiss persists and comes back when the incident is edited;
      • calls-down strip and the Join note;
      • "having problems" while sync is OK;
      • login-screen server-down and sign-in-down strips;
      • the live matrix status page has the expected groups, 60 s beats, CORS *, and prod CSP allows it.
    • Security nudge: hidden during the 24 h grace period; appears about 23 s after load once past it; "Set up" opens Settings → Devices.
    • Other features:
      • pasted image → caption focus, Enter sends, Esc returns to the composer;
      • the "who can see this" line matches room state;
      • share target end-to-end (SW 303 → /share → draft + upload card; prod 303 OK);
      • ringtones: classic plays call.ogg, chime uses oscillators, silent is silent;
      • incoming call has Answer/Reject; camera wording only with "camera on join";
      • consent prompt and its javascript: URI guard;
      • mic level meter animates.
  • Already filed / upstream:
    • the "Connecting..." strip lingering is #274;
    • React setState in render warning from UploadCardRenderer.startUpload() (the fork's taskbar-progress hook is the subscriber).
  • #312 After a reload or restart, a call that already ended (or that you rejected) rings again, and the DM shows "1 Live" plus the call screen for minutes. The RTC session stays stale after the catch-up sync; refreshRoom() fixes it. (high)
  • #313 Reject sends the MSC4310 decline referencing the call.member event instead of the rtc.notification. The caller keeps "Calling…" for 90 s, Element X declines don't stop Lotus ringing, and declined calls are logged as "Missed call". (medium)
  • Verified: both sessions ring; answering on one stops the other; Reject stops both of your sessions; the missed-call line appears live; leaving a voice room clears the membership in about 1 s; the hung X embed shows "taking too long" + Retry after 20 s, and a deleted X post shows "isn't available".
  • #314 Opening a room marks every thread you don't follow as read, so the Threads "Unread" filter never shows them. "Mark all read" works for followed threads. (low)
  • element-call#45 The "You're muted" nudge never fires. The shared mic sampler restarts every ~1 s (allConnections$ → switchMap), and each restart resets speakingWhileMuted before the host's 1.5 s hold. (medium)
  • Verified: the voice-limit/audio-only header chip ("Voice limit 10 (0 in call) · Audio-only room", 0/10, gone when cleared); the "N Live" badge aria-label names members ("1 in call: Bob Builder"); the paste-as-code offer; the composer toolbar hide setting; search operators (operator-only queries other than from: ask for a word, as designed).
  • #315 Pressing Enter in the Poll, Schedule or Report dialogs closes them and discards the input. The header ✕ is an untyped IconButton, so it's the form's default submit button. (medium)
  • Verified: Report User sends POST /users/{id}/report with [Category] reason (200); a private note saves to io.lotus.user_notes and survives reload; pronouns save to m.pronouns; Pause Notifications ("Paused until …", Resume); the thread notification menu (4 modes, saved to account data); Sort rooms (3 orders); the Advanced Push Rules editor.
  • matrix#23 The prod SPA shell (/, /home/…, /login/…) has no Cache-Control (the location / rewrite bypasses the no-cache block), and deploys rsync --delete the old hashed assets, so a heuristically cached shell can load a blank app after a deploy. Also: duplicate HSTS and Cache-Control headers. (medium)
  • Verified on prod: sw.js, config.json, manifest.json and the call page are no-cache; hashed assets are cached for 1 y, immutable; the call page has frame-ancestors https://chat.lotusguild.org and a scoped Permissions-Policy; the desktop WebView permission allow-list includes the call origin http://127.0.0.1:44548.
  • Not covered yet: desktop tray call state, start-minimized and first-close choice (need a real Windows/macOS desktop session); keychain mirror (unsupported on Linux); tap-to-preview stickers on touch; m.pronouns format vs MSC4247 (not confirmed offline).
  • Verified: pinch-zoom in the phone media viewer (scale 1 → 5, the viewer stays open).

Round 8 (2026-10-05): leaks, resilience, cross-browser

  • #316 Every call leaks about 11 MB. An 11th state_update subscriber trips MaxListenersExceededWarning, and the console keeps the warning, the CallControl and the whole Element Call document alive. Measured 52 → 97 MB over 5 calls; with the warning suppressed, 0 retained. (medium)
  • #317 A stalled homeserver goes unnoticed for up to 110 s: no strip, and Kuma isn't asked (SDK sync timeout 30 + 80 s). (medium)
  • element-call#46 A ~30 s homeserver stall ends everyone's call ("Connection lost — Reconnect"), even with prod's 60 s delayed-leave window. The membership manager's 10-retry budget runs out in about 20 s. Likely the "everyone dropped out" part of the 2026-09-29 incident. (high)
  • #318 The host's delayed-event restarts pile up during a stall (404/429 burst on recovery), and the host keeps showing "in call" after Element Call says Connection lost. (medium)
  • Comments: #275 (scheduling while offline shows a raw fetch failed), #306 (the stuck "Sending..." reproduced with a real stall).
  • Verified:
    • No leaks elsewhere: 100 room switches; 10× open/close of settings, threads, emoji, members, quick switcher and gallery.
    • Offline: edits made offline survive a reload; delete-offline shows a clear error.
    • Cross-browser: the outbox in Firefox and WebKit (queued, then delivered once); the Kuma strip in Firefox and WebKit (WebKit only with the SW blocked, a routing artifact).
    • Hotkeys: deafen and push-to-talk with focus inside the call frame.
    • Accessibility: no new axe violations in the Poll, Schedule and Report Room dialogs.
    • Desktop updates: package update commands point at existing release assets (.deb and .pkg.tar.zst are unsigned: cinny-desktop#36).
    • Deploy webhook: HMAC plus ref match on the public hook.
  • #319 Logging out (manually or by remote sign-out) leaves the open composer's draft in localStorage. The pagehide draft flush rewrites it after the wipe. (medium, security)
  • cinny-desktop#36 Linux package updates (.deb/.pkg.tar.zst) have no signature or checksum; the pacman command relies on that. (low, security)
  • cinny-desktop#37 Linux without a tray host (stock GNOME): the first-close dialog defaults to "Keep running in the tray", which hides the app with no icon. A relaunch restores it. Start-minimized has the same issue. (low)
  • Verified:
    • Remote sign-out: logout/all from another device wipes IndexedDB and the outbox, then shows the login screen (except the draft, #319).
    • Soundboard: a played clip reaches the other participant as a separate LiveKit track; when muted the host says "You are muted" and nothing is sent.
    • Linux desktop: the voice room shows "Calls aren't available in the desktop app on Linux yet…" with Open in browser (opens https://chat.lotusguild.org/home/<room>, which the web app redirects to the space path); Join is disabled; the first-close dialog appears and relaunching restores a hidden window.
  • #320 Inter hasn't been used since May. The CSS asks for 'InterVariable', but @fontsource-variable/inter 5 registers 'Inter Variable', so prod renders in the system fallback (Liberation Sans, Arial, DejaVu…). On the Linux desktop, emoji also mix monochrome DejaVu and colour Noto glyphs. (medium)
  • #321 The policy list viewer double-lists rules published under both the stable and legacy types (de-dup uses the raw recommendation). (low)
  • Verified:
    • Privacy settings on the wire: private read receipts → only m.read.private (room open, thread, sidebar "Mark as read"); hide activity → no typing and private receipts.
    • Room quality caps: reach the fork (audioMaxBitrate: 32000, screenshareMaxBitrate: 1500000, screenshareMaxFramerate: 15).
    • Call bar: "Focus camera" from the member glance sends io.lotus.focus_participant with the per-device id.
    • Translations and fonts: i18n strings render (no raw keys); the monospace font names (JetBrains Mono, Fira Code, VT323) match their @font-face.

Round 9 (2026-10-05): cross-device, call UI, notifications

  • #322 Scheduled messages are tracked only in one browser's localStorage. Other devices can't see or cancel them, and after logout they still send, uncancellable. Synapse's GET …/msc4140/delayed_events lists them. (medium)
  • #323 The call PiP opens on top of the composer (Send/Emoji; the whole message box on phones). It can be dragged or double-click-snapped over the call bar's mute/deafen/end controls, and there's no way to minimise it. (low)
  • #324 In-app message toasts only appear with the browser's notification permission granted, so people who declined get a sound and no toast. (low)
  • Verified:
    • Settings sync: per-device keys (hardware, bitrates, denoise, zoom, notifications permission) stay local.
    • Call resilience: a 25 s LiveKit stall recovers ("Reconnecting…", then normal within 5 s); the call-token guard is threaded with 5–15 s upstream timeouts.
    • Call UI: PiP drag, click-to-return and clamping on resize; light/dark theme carried into the call frame.
    • Mark as unread: writes m.marked_unread plus com.famedly.marked_unread and clears both when the room is opened.
  • #325 Quiet hours don't start (or end) while the app stays open. useNotificationsQuiet is only evaluated on re-render; measured toast + sound inside the window with the app left idle, silence only after a reload. The snooze expiry likely has the same problem. (medium)
  • #326 Any website can make its link render as a playable YouTube (or other provider) card: the embed is resolved from the page's own og:url with no domain check, and the card links to the attacker's site. (low, security)
  • Verified:
    • Notifications: OS notifications for encrypted rooms carry only the sender's name, with no inline Reply (N106, code-checked; Xvfb has no notification daemon). In-app toasts appear when the browser permission is granted (headed Chromium).
    • Rendering safety: KaTeX runs with trust: false and maxSize/maxExpand caps; the sanitizer tightens colours and pre classes compared with upstream.
    • PWA manifest: valid icons, including maskable ones.

Round 10 (2026-10-05): upgrades, trust boundaries

  • #327 Upgrading a room silently drops all Lotus room state (voice limit, audio-only and quality caps, soundboard) and its m.room.retention policy, so a 7-day-retention room keeps messages forever after an upgrade. (medium)
  • matrix#24 lk-jwt-service listens on 0.0.0.0:8071 (open from another LAN host), so LAN clients can mint tokens directly and skip the voice-limit/audio-only guard. Also other internals bound to 0.0.0.0. (low, security)
  • Verified secure:
    • Desktop deep links: matrix: URIs only navigate; unjoined rooms go to the preview/join flow; no auto-join or auto-DM.
    • Widget impersonation: a foreign iframe posting as widgetId: "call-embed" is rejected (restrictWidgetMessages checks source and origin for the call embed and room widgets).
    • Desktop IPC: the call page origin (127.0.0.1:44548) has no IPC object (capability remote.urls = localhost:44548 only).
    • HTML room export: room name, sender and body are escaped.
  • Noted, not filed: prod keeps matrix-widget-api's [PostmessageTransport] Sending object log; during calls the fork's io.lotus.mic_level (~10/s) prints two console lines per message.
  • #328 Calls in private group rooms are admin-only. call.member stays at state_default 50 (the PL-0 override exists only for voice rooms). Members get no ring, and see "1 Live · Join", then "You don't have permission to join!". (medium)
  • #329 "Private Note — Notes only visible to you" is stored in plaintext account data (io.lotus.user_notes) that server admins can read. (low, security)
  • #330 Forwarding out of an encrypted room gives no warning that the destination isn't encrypted (the attachment is re-uploaded as plaintext), and the original author's ID is attached without saying so. (low)
  • Verified:
    • Space soundboard: packs are inherited by child voice rooms.
    • Bookmarks: text is stripped for E2EE rooms before upload.
    • Room Insights: states its scope ("Based on N locally cached messages").
  • element-call#47 The soundboard silently fails in any room with a call policy (audio-only, camera-off or screenshare-off). Clips are published as an Unknown-source track that the guard's narrowed canPublishSources refuses ("insufficient permissions"), and the host shows nothing. (medium)
  • Verified:
    • Call policy enforcement: audio-only rooms really are enforced server-side; the LiveKit grant is canPublishSources: ["microphone"], and open rooms are unrestricted.
    • Avatar decorations: only catalog slugs are accepted (isValidDecorationSlug), including those sent to calls.
    • Logged-out room links: after login you return to the room, voice channel or DM, even through a server switch on the login page.

Round 11 (2026-10-05)

  • #331 "Compress image before uploading" turns transparent WebP/GIF backgrounds black (measured: transparent corner → rgba(0,0,0,255)) and animated GIF/WebP into a still JPEG; only PNG is skipped. (low)
  • Comment on #327: space upgrades also drop the space's io.lotus.soundboard, and children keep m.space.parent pointing at the old space.
  • Verified:
    • Calls: audio decodes in an encrypted voice room (per-participant E2EE; received peak RMS 0.052 vs 0.050 unencrypted); Lotus's call-bar menu (Spotlight View, Reactions, Settings) drives the frame.
    • Bulk delete: "Delete all my messages" honours 429 Retry-After.
    • Uploads: EXIF stripping is chunk-level (no re-encode), and the setting text states its scope.
  • Upstream, not filed: "Couldn't connect to voice" page errors on the second joiner come from a duplicate-connection race inside Element Call's ConnectionManager (the second connection succeeds).
  • #332 "Remove Photo Metadata" doesn't cover emoji/sticker pack images, pack avatars or power-level badges, all published to the whole room or space. (low, security)
  • Repo test suites at the audited code (all green):
    • cinny npm test: 1319 pass / 2 skipped.
    • cinny tsc --noEmit: clean.
    • cinny ESLint: 0 errors, 36 warnings (exactly at the --max-warnings 36 limit).
    • cinny Playwright e2e (chromium): 29 pass / 3 skipped.
    • element-call vitest src/lotus: 116 pass.
    • Most fork features audited here (calls, outbox, scheduling, share target, soundboard, PiP) have no e2e coverage; the issues above each suggest a test.
  • Verified: room and space avatars are metadata-stripped like the user avatar.
  • #333 "Mute for 1 hour" mutes the room on every device (server-side push rule), but only the device that set it ever unmutes it; the timer stays in that browser's localStorage, including after logout. Same class as #300. (low)
  • Verified: prod web rejects custom homeservers (/login/evil-homeserver.example/ → /login/matrix.lotusguild.org), so a link can't turn the real Lotus login page into a credential phish.

Round 12 (2026-10-05)

  • #334 The keyboard shortcuts dialog (?) lists 7 shortcuts. It omits Ctrl+K, ↑ to edit, all composer formatting keys, and the user's push-to-talk and deafen keys. (low, a11y)
  • #335 The link-mismatch warning is bypassed by putting any word around the trusted name ("Log in at chat.lotusguild.org"), or by shared hosting suffixes (x.github.io vs evil.github.io, pages.dev); there's no Public Suffix List. (low, security)
  • Comment on #280: Instagram's img_index (carousel position) is stripped as if it were tracking.
  • Verified: analyzeLink flags userinfo (good@evil), lookalike-suffix (good.org.evil.example) and punycode homograph links.

Round 13 (2026-10-05)

  • #336 "Rename for me" names are used only in the sidebar, header, intro and call bar. Ctrl+K can't find a room by its custom name ("Zebra Lounge" → "No match found"), and the forward/share pickers, search results, inbox and notifications show the server name. (low)
  • #337 Animated chat backgrounds animate background-position, so the whole chat area repaints every frame (45–70 % main-thread busy while idle in headless testing; pause / reduced motion are respected). (low, performance)
  • #338 "Remind me" fires as a 4-second, non-sticky in-app toast only (0 OS notifications even with permission granted) and then deletes the reminder, so a reminder due while you're away is lost. (medium)
  • #339 Closing or reloading any Lotus tab erases your custom status for everyone: the pagehide write {"presence":"offline"} omits status_msg, which Synapse treats as clearing it (verified directly; intermittent because of the 10 s presence limit). Linked from #300. (medium)
  • Comment on #297: the same double toast ("You stopped sharing your screen" plus "…the shared window was closed") when a share ends outside the app.
  • Verified:
    • Last room, left elsewhere: reopening the app after leaving that room on another device falls back to /home/.
    • Security nudge on phones: fits the screen (wraps, both buttons reachable, no sideways scroll).
    • Screenshare ended outside the app: the fork's notice reaches the host.
  • Verified (round 13, continued):
    • Cold-opening another device: a device opened with no pagehide keeps your custom status, both while you're online and after Synapse marks you unavailable (the heartbeat re-sends the server's status).
    • Desktop hidden to tray: reports document.hidden; requestAnimationFrame drops to 0 frames and timers are throttled (comment on #337).
    • Idle footprint: 5 min idle in a room shows a flat heap (38 → 37 MB) and flat DOM nodes and listeners.

Round 14 (2026-10-10)

  • #340 One click on the composer's globe icon posts your exact location: no preview or confirm, the fix's accuracy is ignored, shown to 5 decimals. (medium, security)
  • #341 Giphy/Tenor link previews are a fixed ~400×200 cropped box: square GIFs lose 50% of their height, portrait 75%, small ones are upscaled 4×. Reported by a user. Fix in PR #344. (medium)
  • #342 "Channel Full" counts devices, not people: one user on two devices shows 2/2 and disables Join for others, while the guard returns 200. (medium)
  • #343 Collapsed long messages: Tab into a hidden link scrolls the message inside its box (top cut off, stays that way); "Read more" has no aria-expanded; the fade leaves a band in bubble layout. (low, a11y)
  • LotusGuild/matrix#25 The voice limit is only checked at token issue: simultaneous joins both got into a 1-user channel (1 of 2 runs), and an early token ignores the limit. (low)
  • Verified:
    • Poll end: only accepted from the creator or someone with redact power, and votes after the end are ignored.
    • Delete my messages: redacted 17/17 own events (texts, reaction, edit, thread reply, poll vote, sticker) in 3 s, with a clear warning that uploads stay on the server.
    • GIF picker: Giphy's SDK defaults to rating pg-13; picker sends carry the real downsized w/h.
    • Offline outbox (#112): 3 offline messages survived closing the page and were delivered once, in order. With a lost reply (server accepted, response dropped) there was exactly one copy, in plain and E2EE rooms (same txnId reused).
    • Map embeds: location maps are click-to-load (#66 holds).
  • #345 CI is red for every PR: npm audit fails on the new high advisory in source-map-js 1.2.1 (sanitize-html → postcss); 1.2.2 fixes it. (high, build-ci)
  • Verified: call summary across a 45 s SFU freeze: LiveKit reconnected by itself and "Call ended · 2 min · 1 reconnect" showed once at hang-up. The room context menu is keyboard-reachable (options button on focus-within).
  • Merged and deployed (2026-10-11): #346 (deps: source-map-js 1.2.2, i18next-http-backend 4.0.2, katex 0.18.11; closes #345), then #344 (GIF previews keep their size and shape; closes #341). The lotus push CI at 022e5e69 is green incl. e2e, and the desktop build was triggered. chat.lotusguild.org serves the new bundle (KaTeX chunk 0.18.11, the new GIF card).
  • #347 Logout leaves lotus-per-user-volume (who you adjusted in calls, with their volumes) and settings-sync-meta:<userId> in localStorage. (low, privacy)
  • Verified: Element Call's volume memory is keyed by Matrix user ID, not device, so it survives rejoining from another session.
  • #348 Settings sync carries "ENTER for Newline" between devices: turning it on for the phone switches the desktop to Ctrl+Enter. (low)
  • Verified: push-to-talk/deafen ignore keys typed in inputs and the composer (also for desktop global hotkeys).
  • #349 "Translation isn't available… use the Lotus desktop app": the Linux/macOS apps (WebKit, no Translator API) can't translate either, and they show this advice inside the app. (low)
  • Verified: translation is on-device only (Chromium Translator API, no network engine); its cache is swept on logout.

Fix batch 1 (2026-10-12), merged and deployed

  • #350 closes #339: closing a tab no longer erases your custom status.
  • #351 closes #342: "Channel Full" counts people, not devices.
  • #352 closes #338: reminders stay until seen, with a system notification when away.
  • #353 closes #340: location sharing asks first, shows ±accuracy, offers an approximate share and sends ;u=.
  • lotus push CI at 1618d71e is green incl. e2e; chat.lotusguild.org redeployed 03:35 GMT.

Fix batch 2 (2026-10-12)

  • #354 closes #282: Invisible, Hide Online Status, Idle and DND stick (the SDK sync presence is set too). Verified 60 s per mode.
  • #355 partly fixes #283: leaving DND restores your status. The literal "dnd" shown to other clients needs matrix#19 (MSC3026).

Fix batch 3 (2026-10-12)

  • #356 closes #348: "ENTER for Newline" stays per device.
  • #357 closes #347: logout clears the call-volume list and the settings-sync stamp.
  • #358 closes #343: collapsed messages: focus expands, aria-expanded, mask fade, rem cap.
  • #359 refs #336: personal room names in Ctrl+K, the forward/share pickers and notifications (search results, inbox and threads list still open).

Fix batch 4 (2026-10-16/17), merged and deployed 01:09 GMT

  • #360 closes #335: the link warning catches a trusted name inside a sentence and shared-hosting look-alikes (github.io, pages.dev, …).
  • #361 closes #337: animated chat backgrounds use steps(), so idle CPU drops from 51–72% to 4–6%. Aurora/pulse motion is now even-paced.
  • #363: no "Missed call" in voice rooms or space channels (one shared ring rule, utils/callRing.ts). Reported by Jared.
  • #362: e2e boot test accepts the register probe's 403 now that sign-up is closed (was failing every PR).
  • Server: open registration disabled on matrix.lotusguild.org, the leftover registration token was deleted, and the README/landing page now describe SSO + LLDAP (matrix #27, #29).
  • Draupnir encryption: tried (#26) and reverted (#28). It needs a device-bound access token first.
# October 2026 client audit: tracking issue **Scope:** the Lotus-specific integrations on top of upstream: cinny `lotus` @ `30e2dbcd`, cinny-desktop `main` @ `8f67ec7` (Linux nightly 4.12.377), element-call `lotus` @ `3b0050e8`, and the production config/CSP in `matrix`. **How** - **Playwright** against a local Synapse + LiveKit + voice-limit guard, with a seeded world: a space, a rich-content room (threads, polls, images, files, edits, reactions, redactions), a voice room, a DM and an invite. - **Viewports:** desktop 1440, laptop, phone 390 and 360 px, in **dark and light** themes. - **Every screen:** room settings, message menus, composer pickers, sign-in pages. - **Per screen:** console/page errors, overflow and clipping checks, and an **axe-core** pass. - **Real two-person calls:** join, PiP, soundboard, audio output, rejoin after reload, channel-full limit, phone layout. - **Desktop:** the Linux nightly driven through WebKitGTK WebDriver. - **Production:** live CSP and config checks. - **Code reading:** the call bridge (cinny ↔ fork widget actions), widgets, settings sync, the outbox, share target, scheduling, delete-my-messages, the topic editor and the Server ACL page. Every finding was reproduced or verified before filing. Issues already open (e.g. #126, #166, #199, #205, #225) were not refiled. **Labels:** one `priority: *`, one type (`bug`/`ux`/`security`/`a11y`), one `area: *`, as in #89. New labels created for this audit: cinny-desktop `ux`, `security`, `area: config`; matrix `bug`. **Totals:** 25 issues (1 high, 7 medium, 17 low): cinny 22, cinny-desktop 2, matrix 1. ## High - [ ] #259 Media Gallery / Activity Log make the room timeline drop loaded messages, and it continues after the panel is closed (regression of #163) `bug` `area: messaging` ## Medium - [ ] #260 ML noise-suppression failures are never shown: `io.lotus.denoise_state` is unhandled, and the old `lotus-denoise-status` listener is dead and accepts any window `bug` `area: calls` - [ ] #261 Server ACL settings treat a missing `allow_ip_literals` as false, so the first save silently blocks IP-literal servers `bug` `area: moderation` - [ ] #262 Room topic editor mangles URLs and aliases that contain `_` or `*` (broken links for every client) `bug` `area: settings` - [ ] #263 Room header: opening Threads or Gallery shifts the header icons, so a second click opens Widgets instead of closing the panel `ux` `area: navigation` - [ ] #264 Phone: composer emoji/sticker board opens 31 px off the left edge of the screen `bug` `area: mobile` - [ ] #265 Phone (portrait) call: call controls wrap to 4 rows (~40% of the screen) and your own tile isn't shown `ux` `area: calls` - [ ] cinny-desktop#33 Desktop CSP `frame-src` blocks room widgets (Jitsi, Etherpad, …); web allows them `bug` `area: config` ## Low - [ ] matrix#18 Web CSP blocks the TikTok oEmbed lookup, so TikTok short links never play on chat.lotusguild.org `bug` `infra` - [ ] #266 Widget consents survive logout, and the protected-state list misses space and call state `security` `area: auth-session` - [ ] #267 Share target: any website can POST into `/share-target`, and stashed shared files aren't cleared on logout `security` `area: messaging` - [ ] #268 Offline outbox: two open tabs overwrite each other's saved queue `bug` `area: messaging` - [ ] #269 Read-receipt avatars float at the end of the message text instead of the message's right edge `bug` `area: messaging` - [ ] #270 Room Insights: the top 'Most Active Members' bar pushes its message count out of view `bug` `area: settings` - [ ] #271 Settings → Calls: the Noise Suppression description renders at body size, larger than every other description `ux` `area: settings` - [ ] #272 Avatar Decoration settings: the preview is an invisible circle and never shows your own avatar `ux` `area: settings` - [ ] #273 Settings layout: grouped settings run together, and the Sync tile is squeezed on phones `ux` `area: settings` - [ ] #274 'Connecting…' strip stays about 30 s after the app is already usable `ux` `area: navigation` - [ ] #275 Schedule Message lets you pick a time beyond 24 h; the server rejects it with a raw technical error `bug` `area: messaging` - [ ] #276 Desktop build number isn't shown anywhere, and the web version is a static string `ux` `area: desktop` - [ ] #277 Call bar always shows 'Mute Screenshare Audio' (a speaker icon), even when nobody is sharing `ux` `area: calls` - [ ] #278 Accessibility: axe-core findings across the main screens `a11y` `area: settings` - [ ] #279 Copy nits: room soundboard text talks about a 'space', Push to Deafen help, a pinned-messages typo `ux` `area: settings` - [ ] #280 Tracking stripper removes Reddit `context=`, which changes what a comment permalink shows `bug` `area: messaging` - [ ] cinny-desktop#34 Desktop config.json has drifted from web production: custom homeservers allowed, and upstream's sample featured communities shown `bug` `needs-human-review` `area: config` ## Checked and found OK (not filed) - **September criticals stay fixed:** - scheduled messages are refused in encrypted rooms; - bookmarks, GIFs and compression no longer leak plaintext. - **Settings sync:** only starts after the initial sync, so no clobbering. - **Offline outbox:** resends with the same txn id, which Synapse de-duplicates for 24 h. - **Call bridge, cinny ↔ fork:** - only the parent window at the host origin is accepted; - widget-action input is validated; - inject_audio refuses while muted. - **Room widgets:** consent prompt, scoped to their own room, protected state types. - **Delete-my-messages:** state events skipped, 429 handled, resumable. - **Voice channel limit:** enforced on every join path. - **Rejoin after reload:** prompt shows and works when others are still in the call. - **Calls end to end:** PiP, soundboard and audio output all behave. - **Desktop deep links:** URLs are JSON-encoded before being injected. - **Production config:** isitup, Giphy and decoration CDN are all allowed by the web CSP. ## Round 2 — feature test matrix (2026-10-04) Every Lotus feature found in the fork histories was driven through Playwright: 1,036 cinny commits since the upstream merge base, 365 cinny-desktop commits and ~100 element-call fork commits. The runs used a local Synapse + LiveKit stack, with two or three real accounts and E2EE where relevant. ✅ = works as described; the ❌ items are filed above or in round 1. | Area | ✅ verified working | ❌ filed | |---|---|---| | Composer | draft survives reload + nav draft marker; tracking params stripped on paste and send; paste-as-code offer → `language-js` block; markdown; KaTeX; up-arrow edit; length counter; @mention and :emoji autocomplete; poll creation; location share; voice message record/pause/send; schedule tray + Send now | #284 vote on own new poll; #275 >24 h schedule | | Message actions | link-mismatch dialog; edit-history word diff (hover + modal); collapse long messages; code Wrap/Copy; Seen-by modal; bookmark + Saved Messages panel; Remind Me (account data); forward with comment + provenance line; Copy Lotus Link (uses `webAppUrl` on desktop); Copy Text; Report; reply media thumbnail; media grid; audio-file inline player | #286 deceptive preview; #287 Translate no-op; #269 receipt pill | | Navigation | favourites, low priority, custom `u.*` sections, mark unread (both `m.` and famedly keys), rename-for-me, room filter, sort menu, DM previews, quick switcher, jump to time, back-to-where-you-were, invite QR, title unread count, knock → Request to join | #285 matrix.to invite/QR; #289 empty filter; #263 header shift | | Threads | thread chip, panel header, reply + up-arrow edit, threads list filters | — | | Presence/profile | custom status presets | **#282 Invisible/Idle/DND don't stick**; #283 DND "dnd" status | | Search | `from:`, `in:` (+ quoted), `after:`/`before:` relative, recent searches, operator → chip | #288 edit duplicates | | Room settings | retention, call policy (screenshare off hides the button in calls), Delete all my messages (5/5 redacted, others untouched), soundboard upload, export TXT/JSON/HTML | #261 ACL default; #270 Insights | | Calls | two-person join, PiP, soundboard panel, audio output list, rejoin-after-reload toast, channel-full limit, deafen hotkey, push-to-talk hold, space-rail live label, call-ended summary toast, DM ring → "Incoming Voice Call", missed-call line, ML denoise (all four models on a production build) | #260 denoise failure silent; #265 phone layout; #277 screenshare-audio button; #293 dev denoise | | E2EE | encrypted round trip between browsers; unverified-devices warning; undecryptable placeholder; recovery key leaves the clipboard after 60 s | — | | Appearance | 11 seasonal overlays + Off; 9 theme presets; Lotus Terminal dark/light; animated backgrounds; night light; JetBrains font; monochrome; reduced motion stops all overlay animations | #271, #272, #273 | | Sync/PWA | settings sync between two devices (≈5 s); manifest maskable icons; dev SW active | #267 forged share confirmed; #292 portrait lock | | Desktop (Linux nightly 4.12.377) | desktop settings render; keychain says "not available yet"; CSP blocks widgets (confirmed) | cinny-desktop#33; #290 settings placement; cinny-desktop#35 README | **Not reachable from here** (left to the existing QA issues): Windows-only native features (#18, #20); OIDC (#183); real-phone checks (#198). **Measured but not filed.** Seasonal overlays render at ~15 fps without a GPU (software raster) against 61 fps with them off. Main-thread cost stays at 1–6%, so this only matters on machines without GPU compositing. **Corrections to round 1** - cinny-desktop#34 was rewritten. The desktop's featured communities are deliberate Lotus choices (b1ecb0c4/e132719); it's the **web** config (matrix repo) that never received them. - All 25 round-1 issues gained a **More context** section: introducing commit, affected users, manual repro, acceptance criteria. ### Round 2 issues **High** - [ ] #282 Invisible, "Hide Online Status", Idle and DND don't stick: the next /sync sets you back to online **Medium** - [ ] #283 Do Not Disturb overwrites your status message with "dnd", and the "dnd" stays after you switch back - [ ] #284 You can't vote on a poll you just created (until a reload): the vote targets the poll's local-echo id and throws - [ ] #285 Room invite links and QR codes use matrix.to, which can't open Lotus Chat - [ ] cinny-desktop#35 README is still upstream Cinny's: download links and signing key point at cinnyapp, and no Lotus desktop features are documented **Low** - [ ] #286 A link whose text names a different site still gets a genuine preview card for the site it pretends to be - [ ] #287 Translate silently does nothing when the language can't be detected, or the message is already in your language - [ ] #288 Message search lists an edited message once per version - [ ] #289 Sidebar room filter with no matches shows empty section headers and no "No rooms match" message - [ ] #290 Desktop-only settings are mixed into the Appearance section - [ ] #291 Invalid HTML nesting in Settings: blocks inside the tile description's <p> (React warnings on every Settings page) - [ ] #292 Installed PWA is locked to portrait, and the "New Message" shortcut just opens the home page - [ ] #293 ML noise suppression can't be tested with `npm start`, and every call logs a lotusTransparent/lotusTheme warning - [ ] #294 LOTUS_FEATURES.md is missing ~55 shipped features - [ ] element-call#43 The Lotus fork has no documentation: the io.lotus.* widget protocol and URL flags are only described in code comments ## Round 3 (2026-10-04) **Decisions recorded (Jared):** - **Real busy presence for DND:** yes. The Synapse change is filed as matrix#19 and linked from #282 and #283; the client fix is still needed. - **Featured communities on web:** yes, matrix#20. Desktop keeps allowing custom homeservers (cinny-desktop#34). - **Lotus links for invites/QR, matrix.to kept as secondary:** yes (#285). - **Scope:** audit and report only; no fixes in this pass. **New issues** - [ ] #295 Starting a call in a voice room @room-pings every member when a moderator or admin starts it `bug` `area: calls` (medium) - [ ] matrix#21 Web CSP blocks mozilla.org discovery, so its login option always says "Failed to connect" (low) - #291 gained the `UploadCardRenderer` → `TauriDesktopFeatures` setState-in-render warning (fires on every image upload). **Additionally verified working** - **Photo metadata stripping:** GPS, Make/Model and timestamps stripped from JPEG, WebP and PNG messages and from avatars; JPEG orientation kept (dimensions swapped correctly). - **Search, widgets and moderation:** - encrypted-room search through the local cache; - adding a room widget, and the https-only URL check; - policy-list viewer (including legacy `org.matrix.mjolnir.rule.*`). - **Notifications and embeds:** keyword push rule (with highlight); YouTube facade (nothing loads until Play, then `youtube-nocookie`). - **Calls:** - screenshare end to end (confirm prompt → Bob receives 960×540); - room quality caps reach the fork (`io.lotus.set_quality`); - AFK auto-mute at the 1-minute timeout; - DM ringing. - **Sign-in:** matrix.org login on production (password + SSO). - **Server info:** MSC1929 support contact (`/.well-known/matrix/support`). **Inconclusive / not filed** - **Muted-talk nudge:** didn't fire for Chrome's synthetic tone (it likely needs speech). - **Focus camera:** the menu entry wasn't reachable from my selectors. - **In-app toasts:** headless Chrome reports the window unfocused, so OS notifications are used instead. ## Round 4 (2026-10-04) **New issues** - [ ] #298 **A tiny m.room.retention permanently deletes opted-in users' messages and hides the room for everyone.** The 10-minute safety floor is never applied. Verified: `max_lifetime: 1000` blanked the timeline, and 4/4 of my messages were redacted on the server in 40 s. `bug` `area: messaging` (**high**) - [ ] #296 The persistent encrypted-search opt-in survives logout, so the next account indexes plaintext without opting in. `security` `area: auth-session` (medium) - [ ] #297 When an admin turns off screen sharing, the sharer gets two contradictory toasts ("window was closed" + "turned off by a room admin"). `ux` `area: calls` (low) - [ ] #299 Notification inline reply doesn't re-check encryption at send time, and uses the newest session rather than the notification's account. `security` `area: notifications` (low) - [ ] #300 Status auto-clear only works on the device that set it, while it's open. `bug` `area: settings` (low) **Additionally verified working** - **Verification:** two-session device verification. The verified session starts it; the new one gets the QR code with "Verify with emoji instead", the emoji match, and it ends "Your device is verified". - **Calls:** - incoming DM call while in a voice-room call → "Incoming voice call · Answer"; answering leaves the voice room cleanly (membership 1 → 0) and joins the DM call; - call-load watchdog → "The call failed to load…" (no retry button: see below); - SFU token refusal → "Can't join this call · This voice channel is full (4/4)"; - LiveKit unreachable → "Couldn't connect to voice" with VPN/antivirus advice and Try again; - admin disables screen sharing mid-share: the share stops and the sharer is told (see #297 for the extra toast); - undeafen catch-up toast ("While you were deafened: … joined"). - **Messages:** - caption edit after sending; - Media Gallery audio tab + jump to message; - undisclosed polls hide results until the end, and max selections is enforced; - scheduled message edit + cancel; - reminder fires as a toast within one 30 s tick and removes itself; - avatar decoration shows on your messages. - **Moderation:** knock → admin sees Approve/Deny → Approve invites. - **Phone:** long-press opens the action sheet and Reply works from it; swipe in the full-screen viewer (1/3 → 2/3). - **Search:** the persistent encrypted search index survives a reload. - **Logout:** wipes drafts, outbox, the search index and crypto/sync stores; no plaintext left in localStorage (apart from the opt-in flag, #296). - **Look and accessibility:** Compact / Bubble / Lotus Terminal layouts render cleanly with Lotus content; seasonal overlays and animated backgrounds never block clicks; skip link, keyboard-only quick switcher, focus return after Escape, and the `?` shortcuts dialog traps focus. - **Desktop and code-level checks:** - "Delete all my messages" resume needs an explicit click; - Linux desktop native commands answer (`check_for_update`, `update_install_kind`, close behaviour; keychain and global hotkeys report unsupported on Linux as expected); - Sentry was removed entirely (24d6460e), so no third-party error reporting. **Small items — now filed:** #301 (call-load screen has no Try again), #302 (rejoin wording/ended DM calls); pinned-popout focus added to #278; Autumn/Deep Space label added to #279. **Noted, not filed** - **Call-load error screen:** "The call failed to load. Check your connection and try again." offers only **Back**, no Try again. - **Rejoin prompt in DMs:** it says "in the call in Bob Builder" for a DM (should be "with Bob Builder"), and was offered for a DM call whose other party had just left. - **Pinned popout:** opening it doesn't move focus into the popout. - **Seasonal labels:** Settings shows Autumn as "Sep 21 – Oct 14", but Deep Space takes Oct 4–10. - **Service-worker precache:** 110 files, about 11.8 MB uncompressed on first visit (5.6 MB of it the crypto wasm). It includes lazy chunks such as pdf.js, KaTeX and jsQR. ## Round 5 (2026-10-04): cross-browser - [ ] #303 **Firefox hangs on "Heating up" after login** until the persistent-storage prompt is answered. `initClient` awaits `navigator.storage.persist()`, which stays pending in Firefox. (**high**) - [ ] #304 Escape doesn't close the emoji/sticker board in any browser (upstream behaviour, retitled after Chromium/Firefox checks). (low) - **Verified in WebKit (Safari engine):** login, send, image viewer, Settings, call join, and the in-frame "Share your screen?" prompt. - **Verified in Firefox** (with the persist prompt answered): login, send, image viewer, Settings. - **Verified on iPhone 14 emulation:** the emoji board fits the screen. - **Firefox call join** (persist prompt stubbed): joins the voice room, the Soundboard control appears, and Start Screenshare shows the in-frame "Share your screen?" prompt. - Playwright can't emulate mobile in Firefox (no `isMobile`/touch), so phone checks were run in WebKit (iPhone 14) and Chromium only. ## Round 6 (2026-10-04): rendering cost and infrastructure - [ ] #305 **Reactions row loops forever** between "wrapped" and "one row + N" when the chips are within 4 px of fitting. The collapsed `margin: -2` makes the measured width 4 px larger. Chromium loops at 1348–1351 px (33 layouts and 216 ms of script per 2 s); Firefox loops at the default 1440 px. The same style also wipes the timeline's 8 px `marginTop` (−2 px while collapsed, 0 px after expanding again). (medium) - [ ] matrix#22 Avatar decorations come from Nextcloud public DAV: no `Cache-Control`, and a new PHP session (four cookies) for every image. (low) - Fixed a duplicated audit footer on the issues filed in rounds 3–5 (body text only). ## Round 7 (2026-10-05): outbox, status banner, security nudge, share target, call extras - [ ] #306 **Delivery indicator gets stuck.** With ≥100 ms send latency, your last message keeps "Sending..." until something else re-renders the room. A message auto-retried by the outbox keeps "Failed to send. Click to retry" after it was delivered. Same root cause as #284. (medium) - [ ] #310 + element-call#44 **The call-bar audio output switch can't pick a specific device.** Host and iframe `deviceId`s differ, the fork falls back to Default, and the host menu still shows ✓. (medium) - [ ] #307 A dismissed Kuma announcement keeps the security nudge hidden while it stays posted. (low) - [ ] #308 Status banner Details strips every `#`, `_`, `*`, `>` and backtick (`#room` → `room`, `<b>` → `<b`). (low) - ~~#309~~ duplicate of #267 (share target): new evidence added there. - [ ] #311 Consent prompt: Retry also snoozes it for 10 s, so a retry that fails again shows nothing. (low) - **Verified working:** - **Offline outbox:** - "Queued. Will send when you're back online", plus an "Offline" label; - 3 offline messages are delivered in order; - a send survives closing the tab, and the outbox is emptied afterwards; - an encrypted room sends ciphertext only after reload; - a thread reply stays in its thread after reload; - entries older than 1 h are not auto-sent; - a lost response after the server accepted gives no duplicate (same txnId). - **Kuma banner** (desktop and Pixel 7): - maintenance "until …" with Details; - long announcements wrap; - dismiss persists and comes back when the incident is edited; - calls-down strip and the Join note; - "having problems" while sync is OK; - login-screen server-down and sign-in-down strips; - the live `matrix` status page has the expected groups, 60 s beats, CORS `*`, and prod CSP allows it. - **Security nudge:** hidden during the 24 h grace period; appears about 23 s after load once past it; "Set up" opens Settings → Devices. - **Other features:** - pasted image → caption focus, Enter sends, Esc returns to the composer; - the "who can see this" line matches room state; - share target end-to-end (SW 303 → /share → draft + upload card; prod 303 OK); - ringtones: classic plays `call.ogg`, chime uses oscillators, silent is silent; - incoming call has Answer/Reject; camera wording only with "camera on join"; - consent prompt and its `javascript:` URI guard; - mic level meter animates. - **Already filed / upstream:** - the "Connecting..." strip lingering is #274; - React `setState in render` warning from `UploadCardRenderer.startUpload()` (the fork's taskbar-progress hook is the subscriber). - [ ] #312 **After a reload or restart, a call that already ended (or that you rejected) rings again**, and the DM shows "1 Live" plus the call screen for minutes. The RTC session stays stale after the catch-up sync; `refreshRoom()` fixes it. (**high**) - [ ] #313 Reject sends the MSC4310 decline referencing the `call.member` event instead of the `rtc.notification`. The caller keeps "Calling…" for 90 s, Element X declines don't stop Lotus ringing, and declined calls are logged as "Missed call". (medium) - **Verified:** both sessions ring; answering on one stops the other; Reject stops both of your sessions; the missed-call line appears live; leaving a voice room clears the membership in about 1 s; the hung X embed shows "taking too long" + Retry after 20 s, and a deleted X post shows "isn't available". - [ ] #314 Opening a room marks every thread you don't follow as read, so the Threads "Unread" filter never shows them. "Mark all read" works for followed threads. (low) - [ ] element-call#45 **The "You're muted" nudge never fires.** The shared mic sampler restarts every ~1 s (`allConnections$` → `switchMap`), and each restart resets `speakingWhileMuted` before the host's 1.5 s hold. (medium) - **Verified:** the voice-limit/audio-only header chip ("Voice limit 10 (0 in call) · Audio-only room", 0/10, gone when cleared); the "N Live" badge aria-label names members ("1 in call: Bob Builder"); the paste-as-code offer; the composer toolbar hide setting; search operators (operator-only queries other than `from:` ask for a word, as designed). - [ ] #315 **Pressing Enter in the Poll, Schedule or Report dialogs closes them and discards the input.** The header ✕ is an untyped `IconButton`, so it's the form's default submit button. (medium) - **Verified:** Report User sends `POST /users/{id}/report` with `[Category] reason` (200); a private note saves to `io.lotus.user_notes` and survives reload; pronouns save to `m.pronouns`; Pause Notifications ("Paused until …", Resume); the thread notification menu (4 modes, saved to account data); Sort rooms (3 orders); the Advanced Push Rules editor. - [ ] matrix#23 **The prod SPA shell (`/`, `/home/…`, `/login/…`) has no `Cache-Control`** (the `location /` rewrite bypasses the no-cache block), and deploys `rsync --delete` the old hashed assets, so a heuristically cached shell can load a blank app after a deploy. Also: duplicate HSTS and Cache-Control headers. (medium) - **Verified on prod:** `sw.js`, `config.json`, `manifest.json` and the call page are no-cache; hashed assets are cached for 1 y, immutable; the call page has `frame-ancestors https://chat.lotusguild.org` and a scoped Permissions-Policy; the desktop WebView permission allow-list includes the call origin `http://127.0.0.1:44548`. - **Not covered yet:** desktop tray call state, start-minimized and first-close choice (need a real Windows/macOS desktop session); keychain mirror (unsupported on Linux); tap-to-preview stickers on touch; `m.pronouns` format vs MSC4247 (not confirmed offline). - **Verified:** pinch-zoom in the phone media viewer (scale 1 → 5, the viewer stays open). ## Round 8 (2026-10-05): leaks, resilience, cross-browser - [ ] #316 **Every call leaks about 11 MB.** An 11th `state_update` subscriber trips `MaxListenersExceededWarning`, and the console keeps the warning, the `CallControl` and the whole Element Call document alive. Measured 52 → 97 MB over 5 calls; with the warning suppressed, 0 retained. (medium) - [ ] #317 **A stalled homeserver goes unnoticed for up to 110 s:** no strip, and Kuma isn't asked (SDK sync timeout 30 + 80 s). (medium) - [ ] element-call#46 **A ~30 s homeserver stall ends everyone's call ("Connection lost — Reconnect")**, even with prod's 60 s delayed-leave window. The membership manager's 10-retry budget runs out in about 20 s. Likely the "everyone dropped out" part of the 2026-09-29 incident. (**high**) - [ ] #318 The host's delayed-event restarts pile up during a stall (404/429 burst on recovery), and the host keeps showing "in call" after Element Call says Connection lost. (medium) - Comments: #275 (scheduling while offline shows a raw `fetch failed`), #306 (the stuck "Sending..." reproduced with a real stall). - **Verified:** - **No leaks elsewhere:** 100 room switches; 10× open/close of settings, threads, emoji, members, quick switcher and gallery. - **Offline:** edits made offline survive a reload; delete-offline shows a clear error. - **Cross-browser:** the outbox in Firefox and WebKit (queued, then delivered once); the Kuma strip in Firefox and WebKit (WebKit only with the SW blocked, a routing artifact). - **Hotkeys:** deafen and push-to-talk with focus inside the call frame. - **Accessibility:** no new axe violations in the Poll, Schedule and Report Room dialogs. - **Desktop updates:** package update commands point at existing release assets (`.deb` and `.pkg.tar.zst` are unsigned: cinny-desktop#36). - **Deploy webhook:** HMAC plus ref match on the public hook. - [ ] #319 **Logging out (manually or by remote sign-out) leaves the open composer's draft in localStorage.** The `pagehide` draft flush rewrites it after the wipe. (medium, security) - [ ] cinny-desktop#36 Linux package updates (`.deb`/`.pkg.tar.zst`) have no signature or checksum; the pacman command relies on that. (low, security) - [ ] cinny-desktop#37 Linux without a tray host (stock GNOME): the first-close dialog defaults to "Keep running in the tray", which hides the app with no icon. A relaunch restores it. Start-minimized has the same issue. (low) - **Verified:** - **Remote sign-out:** `logout/all` from another device wipes IndexedDB and the outbox, then shows the login screen (except the draft, #319). - **Soundboard:** a played clip reaches the other participant as a separate LiveKit track; when muted the host says "You are muted" and nothing is sent. - **Linux desktop:** the voice room shows "Calls aren't available in the desktop app on Linux yet…" with Open in browser (opens `https://chat.lotusguild.org/home/<room>`, which the web app redirects to the space path); Join is disabled; the first-close dialog appears and relaunching restores a hidden window. - [ ] #320 **Inter hasn't been used since May.** The CSS asks for `'InterVariable'`, but `@fontsource-variable/inter` 5 registers `'Inter Variable'`, so prod renders in the system fallback (Liberation Sans, Arial, DejaVu…). On the Linux desktop, emoji also mix monochrome DejaVu and colour Noto glyphs. (medium) - [ ] #321 The policy list viewer double-lists rules published under both the stable and legacy types (de-dup uses the raw recommendation). (low) - **Verified:** - **Privacy settings on the wire:** private read receipts → only `m.read.private` (room open, thread, sidebar "Mark as read"); hide activity → no typing and private receipts. - **Room quality caps:** reach the fork (`audioMaxBitrate: 32000`, `screenshareMaxBitrate: 1500000`, `screenshareMaxFramerate: 15`). - **Call bar:** "Focus camera" from the member glance sends `io.lotus.focus_participant` with the per-device id. - **Translations and fonts:** i18n strings render (no raw keys); the monospace font names (JetBrains Mono, Fira Code, VT323) match their `@font-face`. ## Round 9 (2026-10-05): cross-device, call UI, notifications - [ ] #322 **Scheduled messages are tracked only in one browser's localStorage.** Other devices can't see or cancel them, and after logout they still send, uncancellable. Synapse's `GET …/msc4140/delayed_events` lists them. (medium) - [ ] #323 The call PiP opens on top of the composer (Send/Emoji; the whole message box on phones). It can be dragged or double-click-snapped over the call bar's mute/deafen/end controls, and there's no way to minimise it. (low) - [ ] #324 In-app message toasts only appear with the browser's notification permission granted, so people who declined get a sound and no toast. (low) - **Verified:** - **Settings sync:** per-device keys (hardware, bitrates, denoise, zoom, notifications permission) stay local. - **Call resilience:** a 25 s LiveKit stall recovers ("Reconnecting…", then normal within 5 s); the call-token guard is threaded with 5–15 s upstream timeouts. - **Call UI:** PiP drag, click-to-return and clamping on resize; light/dark theme carried into the call frame. - **Mark as unread:** writes `m.marked_unread` plus `com.famedly.marked_unread` and clears both when the room is opened. - [ ] #325 **Quiet hours don't start (or end) while the app stays open.** `useNotificationsQuiet` is only evaluated on re-render; measured toast + sound inside the window with the app left idle, silence only after a reload. The snooze expiry likely has the same problem. (medium) - [ ] #326 Any website can make its link render as a playable YouTube (or other provider) card: the embed is resolved from the page's own `og:url` with no domain check, and the card links to the attacker's site. (low, security) - **Verified:** - **Notifications:** OS notifications for encrypted rooms carry only the sender's name, with no inline Reply (N106, code-checked; Xvfb has no notification daemon). In-app toasts appear when the browser permission is granted (headed Chromium). - **Rendering safety:** KaTeX runs with `trust: false` and `maxSize`/`maxExpand` caps; the sanitizer tightens colours and `pre` classes compared with upstream. - **PWA manifest:** valid icons, including maskable ones. ## Round 10 (2026-10-05): upgrades, trust boundaries - [ ] #327 **Upgrading a room silently drops all Lotus room state** (voice limit, audio-only and quality caps, soundboard) **and its `m.room.retention`** policy, so a 7-day-retention room keeps messages forever after an upgrade. (medium) - [ ] matrix#24 `lk-jwt-service` listens on `0.0.0.0:8071` (open from another LAN host), so LAN clients can mint tokens directly and skip the voice-limit/audio-only guard. Also other internals bound to `0.0.0.0`. (low, security) - **Verified secure:** - **Desktop deep links:** `matrix:` URIs only navigate; unjoined rooms go to the preview/join flow; no auto-join or auto-DM. - **Widget impersonation:** a foreign iframe posting as `widgetId: "call-embed"` is rejected (`restrictWidgetMessages` checks source and origin for the call embed and room widgets). - **Desktop IPC:** the call page origin (`127.0.0.1:44548`) has no IPC object (capability `remote.urls` = `localhost:44548` only). - **HTML room export:** room name, sender and body are escaped. - **Noted, not filed:** prod keeps matrix-widget-api's `[PostmessageTransport] Sending object` log; during calls the fork's `io.lotus.mic_level` (~10/s) prints two console lines per message. - [ ] #328 **Calls in private group rooms are admin-only.** `call.member` stays at `state_default` 50 (the PL-0 override exists only for voice rooms). Members get no ring, and see "1 Live · Join", then "You don't have permission to join!". (medium) - [ ] #329 "Private Note — Notes only visible to you" is stored in plaintext account data (`io.lotus.user_notes`) that server admins can read. (low, security) - [ ] #330 Forwarding out of an encrypted room gives no warning that the destination isn't encrypted (the attachment is re-uploaded as plaintext), and the original author's ID is attached without saying so. (low) - **Verified:** - **Space soundboard:** packs are inherited by child voice rooms. - **Bookmarks:** text is stripped for E2EE rooms before upload. - **Room Insights:** states its scope ("Based on N locally cached messages"). - [ ] element-call#47 **The soundboard silently fails in any room with a call policy** (audio-only, camera-off or screenshare-off). Clips are published as an Unknown-source track that the guard's narrowed `canPublishSources` refuses ("insufficient permissions"), and the host shows nothing. (medium) - **Verified:** - **Call policy enforcement:** audio-only rooms really are enforced server-side; the LiveKit grant is `canPublishSources: ["microphone"]`, and open rooms are unrestricted. - **Avatar decorations:** only catalog slugs are accepted (`isValidDecorationSlug`), including those sent to calls. - **Logged-out room links:** after login you return to the room, voice channel or DM, even through a server switch on the login page. ## Round 11 (2026-10-05) - [ ] #331 "Compress image before uploading" turns transparent WebP/GIF backgrounds black (measured: transparent corner → `rgba(0,0,0,255)`) and animated GIF/WebP into a still JPEG; only PNG is skipped. (low) - Comment on #327: space upgrades also drop the space's `io.lotus.soundboard`, and children keep `m.space.parent` pointing at the old space. - **Verified:** - **Calls:** audio decodes in an **encrypted** voice room (per-participant E2EE; received peak RMS 0.052 vs 0.050 unencrypted); Lotus's call-bar menu (Spotlight View, Reactions, Settings) drives the frame. - **Bulk delete:** "Delete all my messages" honours 429 `Retry-After`. - **Uploads:** EXIF stripping is chunk-level (no re-encode), and the setting text states its scope. - **Upstream, not filed:** "Couldn't connect to voice" page errors on the second joiner come from a duplicate-connection race inside Element Call's ConnectionManager (the second connection succeeds). - [ ] #332 "Remove Photo Metadata" doesn't cover emoji/sticker pack images, pack avatars or power-level badges, all published to the whole room or space. (low, security) - **Repo test suites at the audited code (all green):** - cinny `npm test`: 1319 pass / 2 skipped. - cinny `tsc --noEmit`: clean. - cinny ESLint: 0 errors, 36 warnings (exactly at the `--max-warnings 36` limit). - cinny Playwright e2e (chromium): 29 pass / 3 skipped. - element-call `vitest src/lotus`: 116 pass. - Most fork features audited here (calls, outbox, scheduling, share target, soundboard, PiP) have no e2e coverage; the issues above each suggest a test. - **Verified:** room and space avatars are metadata-stripped like the user avatar. - [ ] #333 "Mute for 1 hour" mutes the room on every device (server-side push rule), but only the device that set it ever unmutes it; the timer stays in that browser's localStorage, including after logout. Same class as #300. (low) - **Verified:** prod web rejects custom homeservers (`/login/evil-homeserver.example/` → `/login/matrix.lotusguild.org`), so a link can't turn the real Lotus login page into a credential phish. ## Round 12 (2026-10-05) - [ ] #334 The keyboard shortcuts dialog (`?`) lists 7 shortcuts. It omits Ctrl+K, ↑ to edit, all composer formatting keys, and the user's push-to-talk and deafen keys. (low, a11y) - [ ] #335 The link-mismatch warning is bypassed by putting any word around the trusted name ("Log in at chat.lotusguild.org"), or by shared hosting suffixes (`x.github.io` vs `evil.github.io`, `pages.dev`); there's no Public Suffix List. (low, security) - Comment on #280: Instagram's `img_index` (carousel position) is stripped as if it were tracking. - **Verified:** `analyzeLink` flags userinfo (`good@evil`), lookalike-suffix (`good.org.evil.example`) and punycode homograph links. ## Round 13 (2026-10-05) - [ ] #336 "Rename for me" names are used only in the sidebar, header, intro and call bar. Ctrl+K can't find a room by its custom name ("Zebra Lounge" → "No match found"), and the forward/share pickers, search results, inbox and notifications show the server name. (low) - [ ] #337 Animated chat backgrounds animate `background-position`, so the whole chat area repaints every frame (45–70 % main-thread busy while idle in headless testing; pause / reduced motion are respected). (low, performance) - [ ] #338 "Remind me" fires as a 4-second, non-sticky in-app toast only (0 OS notifications even with permission granted) and then deletes the reminder, so a reminder due while you're away is lost. (medium) - [ ] #339 Closing or reloading any Lotus tab erases your custom status for everyone: the `pagehide` write `{"presence":"offline"}` omits `status_msg`, which Synapse treats as clearing it (verified directly; intermittent because of the 10 s presence limit). Linked from #300. (medium) - Comment on #297: the same double toast ("You stopped sharing your screen" plus "…the shared window was closed") when a share ends outside the app. - **Verified:** - **Last room, left elsewhere:** reopening the app after leaving that room on another device falls back to `/home/`. - **Security nudge on phones:** fits the screen (wraps, both buttons reachable, no sideways scroll). - **Screenshare ended outside the app:** the fork's notice reaches the host. - **Verified (round 13, continued):** - **Cold-opening another device:** a device opened with no `pagehide` keeps your custom status, both while you're online and after Synapse marks you unavailable (the heartbeat re-sends the server's status). - **Desktop hidden to tray:** reports `document.hidden`; `requestAnimationFrame` drops to 0 frames and timers are throttled (comment on #337). - **Idle footprint:** 5 min idle in a room shows a flat heap (38 → 37 MB) and flat DOM nodes and listeners. ## Round 14 (2026-10-10) - [ ] #340 One click on the composer's globe icon posts your exact location: no preview or confirm, the fix's accuracy is ignored, shown to 5 decimals. (medium, security) - [ ] #341 Giphy/Tenor link previews are a fixed ~400×200 cropped box: square GIFs lose 50% of their height, portrait 75%, small ones are upscaled 4×. **Reported by a user. Fix in PR #344.** (medium) - [ ] #342 "Channel Full" counts devices, not people: one user on two devices shows 2/2 and disables Join for others, while the guard returns 200. (medium) - [ ] #343 Collapsed long messages: Tab into a hidden link scrolls the message inside its box (top cut off, stays that way); "Read more" has no `aria-expanded`; the fade leaves a band in bubble layout. (low, a11y) - [ ] LotusGuild/matrix#25 The voice limit is only checked at token issue: simultaneous joins both got into a 1-user channel (1 of 2 runs), and an early token ignores the limit. (low) - **Verified:** - **Poll end:** only accepted from the creator or someone with redact power, and votes after the end are ignored. - **Delete my messages:** redacted 17/17 own events (texts, reaction, edit, thread reply, poll vote, sticker) in 3 s, with a clear warning that uploads stay on the server. - **GIF picker:** Giphy's SDK defaults to rating `pg-13`; picker sends carry the real `downsized` w/h. - **Offline outbox (#112):** 3 offline messages survived closing the page and were delivered once, in order. With a lost reply (server accepted, response dropped) there was exactly one copy, in plain and E2EE rooms (same txnId reused). - **Map embeds:** location maps are click-to-load (#66 holds). - [ ] #345 CI is red for every PR: `npm audit` fails on the new high advisory in source-map-js 1.2.1 (sanitize-html → postcss); 1.2.2 fixes it. (high, build-ci) - **Verified:** call summary across a 45 s SFU freeze: LiveKit reconnected by itself and "Call ended · 2 min · 1 reconnect" showed once at hang-up. The room context menu is keyboard-reachable (options button on focus-within). - **Merged and deployed (2026-10-11):** #346 (deps: source-map-js 1.2.2, i18next-http-backend 4.0.2, katex 0.18.11; closes #345), then #344 (GIF previews keep their size and shape; closes #341). The `lotus` push CI at 022e5e69 is green incl. e2e, and the desktop build was triggered. chat.lotusguild.org serves the new bundle (KaTeX chunk 0.18.11, the new GIF card). - [ ] #347 Logout leaves `lotus-per-user-volume` (who you adjusted in calls, with their volumes) and `settings-sync-meta:<userId>` in localStorage. (low, privacy) - **Verified:** Element Call's volume memory is keyed by Matrix user ID, not device, so it survives rejoining from another session. - [ ] #348 Settings sync carries "ENTER for Newline" between devices: turning it on for the phone switches the desktop to Ctrl+Enter. (low) - **Verified:** push-to-talk/deafen ignore keys typed in inputs and the composer (also for desktop global hotkeys). - [ ] #349 "Translation isn't available… use the Lotus desktop app": the Linux/macOS apps (WebKit, no Translator API) can't translate either, and they show this advice inside the app. (low) - **Verified:** translation is on-device only (Chromium Translator API, no network engine); its cache is swept on logout. ## Fix batch 1 (2026-10-12), merged and deployed - #350 closes #339: closing a tab no longer erases your custom status. - #351 closes #342: "Channel Full" counts people, not devices. - #352 closes #338: reminders stay until seen, with a system notification when away. - #353 closes #340: location sharing asks first, shows ±accuracy, offers an approximate share and sends `;u=`. - `lotus` push CI at 1618d71e is green incl. e2e; chat.lotusguild.org redeployed 03:35 GMT. ## Fix batch 2 (2026-10-12) - #354 closes #282: Invisible, Hide Online Status, Idle and DND stick (the SDK sync presence is set too). Verified 60 s per mode. - #355 partly fixes #283: leaving DND restores your status. The literal "dnd" shown to other clients needs matrix#19 (MSC3026). ## Fix batch 3 (2026-10-12) - #356 closes #348: "ENTER for Newline" stays per device. - #357 closes #347: logout clears the call-volume list and the settings-sync stamp. - #358 closes #343: collapsed messages: focus expands, aria-expanded, mask fade, rem cap. - #359 refs #336: personal room names in Ctrl+K, the forward/share pickers and notifications (search results, inbox and threads list still open). ## Fix batch 4 (2026-10-16/17), merged and deployed 01:09 GMT - #360 closes #335: the link warning catches a trusted name inside a sentence and shared-hosting look-alikes (github.io, pages.dev, …). - #361 closes #337: animated chat backgrounds use steps(), so idle CPU drops from 51–72% to 4–6%. Aurora/pulse motion is now even-paced. - #363: no "Missed call" in voice rooms or space channels (one shared ring rule, `utils/callRing.ts`). Reported by Jared. - #362: e2e boot test accepts the register probe's 403 now that sign-up is closed (was failing every PR). - Server: open registration disabled on matrix.lotusguild.org, the leftover registration token was deleted, and the README/landing page now describe SSO + LLDAP (matrix #27, #29). - Draupnir encryption: tried (#26) and reverted (#28). It needs a device-bound access token first.
Author
Owner

Fix batch (October audit, continued)

PR Fixes State
#368 #209 filtered timeline double count queued
#375 #313 reject/decline points at the right event queued
#376 #328 members can start/join calls in new private rooms merged
#377 #305 reactions row flicker merged
#378 #295 voice rooms no longer @room-ping (EC#48, lotus.23) merged
#379 #316 CallControl listener limit (~11 MB/call) queued
#380 #317 stall watchdog strip queued
#381 #318 one delayed-event restart in flight queued
#382 #322 scheduled messages from the server + logout prompt queued
#383 #284 vote on a poll you just created (+e2e) queued
#384 #285 share/invite links open Lotus Chat queued
#385 #301 call-failed Try again queued
#386 #304 Escape closes emoji/sticker board queued
#387 #289 room filter: empty sections / no-match message queued
#388 #288 search folds edits queued
#389 #302 DM-aware rejoin, no rejoin into ended DM call queued
#390 #308 banner Details keeps #, _, >, * queued
#391 #307 dismissed announcement no longer hides the nudge (+e2e) queued
#392 #321 policy viewer de-dup queued
#393 #333 timed mutes in account data queued
#394 #300 status auto-clear in account data queued
#395 #324 in-app toasts without browser permission queued
#396 #323 PiP default position (partial) queued
#397 #299 SW reply re-checks encryption + account queued
#398 #297 no false "window was closed" toast queued
#399 #331 compression keeps transparency / animation queued

Element Call fork

  • EC#48: sendNotificationType=none, released as 0.25.0-lotus.23 and pinned in #378.
  • EC#49: the frame reports its audio outputs, needed for #310. Tagged 0.25.0-lotus.24; the cinny half of #310 is waiting for that package to publish.

Not fixed in a PR

  • #283 (DND): needs MSC3026 busy presence enabled on Synapse, so it's a production config decision. Findings are on the issue.
  • #323: the minimise / collapsed-pill parts are new UI and stay open.
  • #318: the "host still shows in call after Connection lost" half needs a fork change and stays open.

Every PR has before/after measurements against the local stack in its description. The merges go through one CI runner, about 20 min per PR.

🤖 Claude Code

### Fix batch (October audit, continued) | PR | Fixes | State | |---|---|---| | #368 | #209 filtered timeline double count | queued | | #375 | #313 reject/decline points at the right event | queued | | #376 | #328 members can start/join calls in new private rooms | merged | | #377 | #305 reactions row flicker | merged | | #378 | #295 voice rooms no longer @room-ping (EC#48, lotus.23) | merged | | #379 | #316 CallControl listener limit (~11 MB/call) | queued | | #380 | #317 stall watchdog strip | queued | | #381 | #318 one delayed-event restart in flight | queued | | #382 | #322 scheduled messages from the server + logout prompt | queued | | #383 | #284 vote on a poll you just created (+e2e) | queued | | #384 | #285 share/invite links open Lotus Chat | queued | | #385 | #301 call-failed Try again | queued | | #386 | #304 Escape closes emoji/sticker board | queued | | #387 | #289 room filter: empty sections / no-match message | queued | | #388 | #288 search folds edits | queued | | #389 | #302 DM-aware rejoin, no rejoin into ended DM call | queued | | #390 | #308 banner Details keeps #, _, >, * | queued | | #391 | #307 dismissed announcement no longer hides the nudge (+e2e) | queued | | #392 | #321 policy viewer de-dup | queued | | #393 | #333 timed mutes in account data | queued | | #394 | #300 status auto-clear in account data | queued | | #395 | #324 in-app toasts without browser permission | queued | | #396 | #323 PiP default position (partial) | queued | | #397 | #299 SW reply re-checks encryption + account | queued | | #398 | #297 no false "window was closed" toast | queued | | #399 | #331 compression keeps transparency / animation | queued | **Element Call fork** - **EC#48:** `sendNotificationType=none`, released as **0.25.0-lotus.23** and pinned in #378. - **EC#49:** the frame reports its audio outputs, needed for #310. Tagged **0.25.0-lotus.24**; the cinny half of #310 is waiting for that package to publish. **Not fixed in a PR** - **#283** (DND): needs MSC3026 busy presence enabled on Synapse, so it's a production config decision. Findings are on the issue. - **#323:** the minimise / collapsed-pill parts are new UI and stay open. - **#318:** the "host still shows in call after Connection lost" half needs a fork change and stays open. Every PR has before/after measurements against the local stack in its description. The merges go through one CI runner, about 20 min per PR. 🤖 Claude Code
Author
Owner

Follow-up batch

cinny

PR Fixes
#413 #260: toast when ML noise suppression fails or falls back; removes the spoofable postMessage listener
#414 #323: minimise the call PiP to a bar; phones start minimised
#415 element-call#47: soundboard says it isn't available in audio-only channels
#416 cinny-desktop#37: first-close dialog defaults to Quit when there's no tray
#417 #310: call-bar output menu uses the frame's devices (pins lotus.24)
branch fix/call-disconnected-bar #318: "Call disconnected" + Reconnect, auto-reconnect on sync. Waits for element-call#50 to be released

element-call: #50 (call error / reconnect), #51 (#45 mic-sampler churn), #52 (#47 not_permitted), #53 (#43 LOTUS.md protocol docs), #54 (#46: a stall shorter than the leave window no longer ends the call; with prod config a 40 s stall went from "Connection lost" to no drop). #44 closed (lotus.24).

cinny-desktop (open PRs, not merged):

  • #38: #33, frame-src https: so room widgets load.
  • #39: #35, README for Lotus Chat.
  • #40: #37, has_tray_host native command.

matrix (open PRs, production changes, waiting for the owner):

  • #30: #23, no-cache app shell, keep old assets.
  • #31: #24, lk-jwt loopback bind (docs plus a host command).
  • #32: #18, #20, #21, CSP and featured communities.
  • #33: #25, voice-limit join race (old guard [200, 200] → new [200, 403]).

🤖 Claude Code

### Follow-up batch **cinny** | PR | Fixes | |---|---| | #413 | #260: toast when ML noise suppression fails or falls back; removes the spoofable postMessage listener | | #414 | #323: minimise the call PiP to a bar; phones start minimised | | #415 | element-call#47: soundboard says it isn't available in audio-only channels | | #416 | cinny-desktop#37: first-close dialog defaults to Quit when there's no tray | | #417 | #310: call-bar output menu uses the frame's devices (pins lotus.24) | | branch `fix/call-disconnected-bar` | #318: "Call disconnected" + Reconnect, auto-reconnect on sync. Waits for element-call#50 to be released | **element-call:** #50 (call error / reconnect), #51 (#45 mic-sampler churn), #52 (#47 not_permitted), #53 (#43 `LOTUS.md` protocol docs), #54 (#46: a stall shorter than the leave window no longer ends the call; with prod config a 40 s stall went from "Connection lost" to no drop). **#44 closed** (lotus.24). **cinny-desktop** (open PRs, not merged): - #38: #33, `frame-src https:` so room widgets load. - #39: #35, README for Lotus Chat. - #40: #37, `has_tray_host` native command. **matrix** (open PRs, production changes, waiting for the owner): - #30: #23, no-cache app shell, keep old assets. - #31: #24, lk-jwt loopback bind (docs plus a host command). - #32: #18, #20, #21, CSP and featured communities. - #33: #25, voice-limit join race (old guard [200, 200] → new [200, 403]). 🤖 Claude Code
Author
Owner

First-round findings (#259–#280): fix batch

PR Fixes State
#426 #261 Server ACL: a missing allow_ip_literals means allowed merged
#427 #262 topic editor keeps URLs, #alias:server, @user:server merged
#428 #263 one width for every right-hand panel (icons no longer shift) merged
#429 #266 widget consents swept on logout, more protected state · #267 forged shares refused, shared files cleared on logout merged
#430 #268 two tabs no longer overwrite each other's outbox merged
#431 #269 receipt pill at the row edge · #270 insights counts · #271 · #272 decoration preview with your avatar · #273 dividers and phone Sync tile queued
#432 #274 "Connecting…" only while catching up (no 30 s strip after load) queued
#433 #275 schedule limited to 24 h with a clear message queued
#434 #276 About shows the desktop version and the web commit and build date queued
#435 #279 copy nits · #280 Reddit context= kept queued
#436 #278 axe: dialog names, aria-pressed, labels, avatar alt, placeholder contrast; the a11y spec covers more pages queued
(next) #265 one-row phone call bar · #277 screenshare-audio mute only when useful waits for element-call lotus.27 (#57, #58 merged)
  • Closed without code: #260 (fixed by #413) and #264 (re-measured: the emoji board fits at 320–414 px).
  • Notes:
    • #267: the suggested Sec-Fetch-Site check can't work, because fetch-metadata headers never reach a service worker (logged). The worker uses Origin instead, and /share now says where the content came from.
    • #278: the placeholder fix from #222 never applied, because Slate's inline opacity: 0.333 overrode it.

🤖 Claude Code

### First-round findings (#259–#280): fix batch | PR | Fixes | State | |---|---|---| | #426 | #261 Server ACL: a missing `allow_ip_literals` means allowed | merged | | #427 | #262 topic editor keeps URLs, `#alias:server`, `@user:server` | merged | | #428 | #263 one width for every right-hand panel (icons no longer shift) | merged | | #429 | #266 widget consents swept on logout, more protected state · #267 forged shares refused, shared files cleared on logout | merged | | #430 | #268 two tabs no longer overwrite each other's outbox | merged | | #431 | #269 receipt pill at the row edge · #270 insights counts · #271 · #272 decoration preview with your avatar · #273 dividers and phone Sync tile | queued | | #432 | #274 "Connecting…" only while catching up (no 30 s strip after load) | queued | | #433 | #275 schedule limited to 24 h with a clear message | queued | | #434 | #276 About shows the desktop version and the web commit and build date | queued | | #435 | #279 copy nits · #280 Reddit `context=` kept | queued | | #436 | #278 axe: dialog names, `aria-pressed`, labels, avatar alt, placeholder contrast; the a11y spec covers more pages | queued | | (next) | #265 one-row phone call bar · #277 screenshare-audio mute only when useful | waits for element-call lotus.27 (#57, #58 merged) | - **Closed without code:** #260 (fixed by #413) and #264 (re-measured: the emoji board fits at 320–414 px). - **Notes:** - #267: the suggested `Sec-Fetch-Site` check can't work, because fetch-metadata headers never reach a service worker (logged). The worker uses `Origin` instead, and /share now says where the content came from. - #278: the placeholder fix from #222 never applied, because Slate's inline `opacity: 0.333` overrode it. 🤖 Claude Code
Author
Owner

Complete. All 25 audit issues are closed: cinny #259–#280, cinny-desktop #33 and #34, and matrix #18. The checkboxes above were never ticked as they closed. Closing the index.

🤖 Generated with Claude Code

**Complete.** All 25 audit issues are closed: cinny #259–#280, cinny-desktop #33 and #34, and matrix #18. The checkboxes above were never ticked as they closed. Closing the index. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jared closed this issue 2026-10-25 19:25:11 -04:00
Author
Owner

Correction: the check covered every round, not just round 1. Every issue filed from this audit is closed:

  • cinny: #259–#349;
  • cinny-desktop: #33–#37;
  • matrix: #18–#25;
  • element-call: #43–#47.

🤖 Generated with Claude Code

**Correction:** the check covered every round, not just round 1. Every issue filed from this audit is closed: - cinny: #259–#349; - cinny-desktop: #33–#37; - matrix: #18–#25; - element-call: #43–#47. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#281