m.replace events no longer appear as their own "* text" line — the
original row gets m.new_content and an (edited) marker. Media rows carry
a [Image]/[Video]/[Audio]/[File] prefix and the JSON export includes
mediaUrl; the UI notes attachments aren't included. Unit-tested.
Fixes#50Fixes#81
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Exporting a date range from an ENCRYPTED room over-paginated and mislabeled
"truncated": oldestRawTs (the how-far-back-have-we-paged boundary) was updated
only after the RoomMessage + decryption-failure filters, so undecryptable or
non-message old events never advanced it, the fromTs break never fired, and the
loop ran to MAX_EXPORT_PAGES. getTs() is unencrypted envelope metadata, so the
boundary update now runs for every event, above the filters.
Guarded with `ts > 0` so a bogus 0/negative origin_server_ts can't collapse the
boundary and cause the opposite failure — a silent early break / under-paginated
export (per review, silent omission in an export is worse than the loud
over-pagination this fixes). oldestTs (oldest collected in-range message) is
unchanged.
Two review agents (both confirmed getTs is decryption-independent, no
intra-page collection regression, oldestRawTs feeds only the fromTs break, no
plaintext regression); the second surfaced the 0-ts under-pagination edge, hence
the guard. Not unit-testable (embedded component + needs an E2EE room with
undecryptable history). Gate-green (tsc, eslint, prettier, 925 tests, build).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Web fixes from the Wave-2 bug-hunt (findings in LOTUS_TODO):
- F1 (security): wipe the decrypted-plaintext search index on SERVER-FORCED
logout too (token expiry / remote sign-out) — only manual logout did before.
F4: the delete no longer reports success while onblocked (waits, 3s cap).
- M1/M2 (data-loss): useBookmarks + useUserNotes account-data writes are now
serialized at MODULE scope (single queue + latestRef per client, echo-driven),
fixing the cross-instance lost-update clobber (useBookmarks mounts per message
row, so a per-instance queue was insufficient — caught in review).
- M6: room-history export gets a 200-page cap + Cancel + unmount-abort +
correct date-range early-break (raw paginated ts). M4: image compression
skips PNG (was flattening transparency to black), bakes EXIF orientation via
createImageBitmap, .jpg-renames, and falls back to the original on decode
failure instead of dropping the file. M5: MediaGallery lightbox opens the
right item (shared thumb guard). M8: audio speed survives async decrypt.
- Desktop web wiring: D2 badge sums leaf rooms only (space double-count, like
the favicon fix); D3 useTauriDnd re-hydrates from get_tray_dnd on mount; D5
updater has a terminal state.
Reviewed; M7 reverted (past-time clamp is an intentional, tested contract).
tsc/eslint/prettier clean, build OK, 678 tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ExportRoomHistory: make addEvents() async, call decryptEventIfNeeded()
before inspecting type/content so E2EE rooms export decrypted text
- UrlPreviewCard: remove Google S2 favicon (privacy leak); show
generic Icons.Link instead — no third-party external calls
- Profile: add statusDirtyRef so server presence sync cannot clobber
in-flight emoji insertions or keystrokes; cleared on save/clear
- useLocalMessageSearch: include m.sticker, m.poll.start, and
org.matrix.msc3381.poll.start in encrypted room search; index poll
question and answer bodies
- SeasonalEffect: z-index 9997 → 9999 so overlays render above
animated chat backgrounds
- LOTUS_BUGS.md: mark all resolved, document remaining blocked items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Export: timeline.getEvents() returns the entire growing window on every
pagination step, causing the same events to be added multiple times.
Fixed by tracking seen eventIds in a Set and skipping duplicates.
PiP mute: replace silence-inference with real remote participant mute
state. EC renders a [data-muted] attribute per participant tile with
aria-label=userId. Watch attribute changes via MutationObserver,
filter out local user, show overlay when any remote is muted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>