Audit of every rendered time found five families of ad-hoc formatting:
the shared Time component + copies of its today/yesterday branch
(forwarded header, thread summary, read receipts, device tile, moderation
alerts, edit history), locale-default toLocale*String calls that ignored
the user's 12/24 h and date-format settings (scheduled tray, reminders,
schedule preview, notification snooze, bookmarks, threads list, search
cache line, room insights, media gallery), a hard-coded en-US date in the
activity log, and three relative-age variants.
utils/formatTimestamp.ts now holds the rules — today → time; yesterday /
tomorrow → day word + time; last 6 days → weekday + time; older → date +
time in dateFormatString — plus autoDate / time / date / dateTime styles,
formatDayDivider (full weekday), formatShortAge (room list) and
formatRelativeAge (list rows). useTimestampFormatter binds them to the
settings. 11 unit tests with an injected 'now'.
Visible changes are limited to consistency: 12 h times keep the existing
zero-padded hh:mm A; the a11y label and Created-by line use the user's
date format instead of a fixed long month.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
The schedule modal took only a plain body and rebuilt {body, msgtype},
dropping formatted_body, m.mentions and m.relates_to. It now receives
the full IContent; an unedited body is sent verbatim, an edited body
drops the now-stale formatted_body but keeps mentions and the reply/
thread relation. Reschedule from the tray preserves them too.
Unit-tested (mergeScheduledBody).
Fixes#36
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
ScheduleMessageModal had a local formatSendAt(Date) byte-equivalent to the
tested formatFriendlyDateTime (utils/datetimeInput). Reuse the shared, unit-
tested helper instead of a second copy — identical output. (Also prettier-clean.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Remind Me dialog only offered four fixed presets, so you couldn't set a
reminder for an arbitrary time. Add a "Custom time…" option that reveals date +
time pickers (validated >= 1 minute in the future) and sets the reminder at that
absolute timestamp.
Also extract the local date/time <input> helpers (toLocalDate, toLocalTime,
parseLocalDateTime, pickerInputStyle) into a shared, unit-tested
utils/datetimeInput.ts and reuse them in ScheduleMessageModal (deduped from an
inline copy) — identical output, now covered by tests.
Documents the previously-undocumented Message Reminders feature in
LOTUS_FEATURES.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address findings from 3 review agents on the edit/reschedule feature:
- Race (correctness): handleEdit now marks the old message as cancelling
while its cancel is in flight, so its Edit/Cancel buttons are disabled.
Previously the old row stayed live during the fire-and-forget cancel, so
a fast second edit could orphan a still-scheduled event and send twice.
- Durability (correctness): on a failed cancel-old, re-insert the old
message if auto-prune removed its row while the modal was open, so the
still-live delayed event stays visible and retriable instead of failing
silently. Also clear any stale cancel error when starting an edit.
- a11y: per-row Edit/Cancel buttons now carry distinct aria-labels that
include the message preview and send-time, so screen-reader users can
tell which of several scheduled messages each button targets.
- UX: modal gains a submitLabel prop; the edit flow shows "Reschedule"
instead of "Schedule". Modal now focuses the message body on open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The scheduled-messages tray was cancel-only. Add an inline edit button
that re-opens ScheduleMessageModal seeded with the existing body and
send-time, letting the user change the text and/or when it sends.
MSC4140 has no in-place edit, so an edit is schedule-new + cancel-old.
Order matters: the modal schedules the new delayed event first, then we
cancel the old one and only prune it from local state once the server
confirms. A failed cancel therefore leaves a visible, retriable copy in
the tray instead of silently letting the stale message fire or losing
the edit. Edits go through the plain-text composer, so rich content
collapses to m.text (acceptable for v1).
ScheduleMessageModal gains optional initialSendAt (seed the pickers) and
title props so it is reusable for both scheduling and editing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaced raw style={{ opacity: N }} de-emphasis on folds <Text> with the
`priority` prop across search, schedule, profile, and tray UI. Left the cases
that aren't Text-priority candidates (an Icon opacity, a Box-row opacity, and a
Text with an explicit color token).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both rendered as <Box as="form" role="dialog"> with manually assembled
background/borderRadius(R400)/boxShadow. Switch to <Dialog as="form"
variant="Surface"> so the surface comes from the design system (R300 radius),
matching the other message-action dialogs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ScheduleMessageModal, PollCreator, JoinAddressPrompt, JumpToTime,
EditHistoryModal, ForwardMessageDialog, RemindMeDialog — all now
render fullscreen on mobile (≤750px) and as a capped-width box on
desktop, consistent with the existing useModalStyle pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
API fix: delay was embedded in the path string causing 404 — moved to
proper query param object; prefix changed from '' to the full MSC4140
unstable prefix so authedRequest builds the correct URL. Cancel and
restart endpoints fixed the same way.
Date/time picker: replaced single datetime-local (hard to use time
portion) with separate date + time inputs side by side; colorScheme:'dark'
hints the browser to render calendar/clock popups in dark mode to match
the app. Preview row now shows as a styled Primary.Container chip with
clock icon. Relative time ("in 2h 15m") shown below the label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schedule message: modal now always opens (even with empty composer);
includes its own message textarea pre-filled from editor content;
removed null-content early-return guard from handleScheduleClick;
fixed error text to use color.Critical.Main not CSS var
Image compression: removed 200KB size threshold — checkbox now shows
for all JPEG/PNG uploads (not just large ones); 'no significant saving'
message handles already-small files gracefully
Activity log: auto-paginate on mount — state events are absent from
initial sync window, so the log was always empty until Load More was
clicked manually
Insights summary: Text size H4→H5 with toLocaleString() formatting and
overflow:ellipsis so large numbers don't push tiles off screen
Bookmarks panel: replaced var(--bg-*) CSS vars (undefined in folds
themes) with color.Surface/SurfaceVariant/Primary folds tokens; added
left accent border on message preview block, count badge, clear button
in search, improved empty state, cleaner button hierarchy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P3-1: Message Bookmarks — right-click any message to bookmark; saved to
io.lotus.bookmarks account data (max 500, syncs across devices); star
icon in sidebar opens BookmarksPanel with filter, Jump-to-message, and
remove buttons; reactive to AccountData events
P3-2: Message Scheduling (MSC4140) — clock button next to send opens
ScheduleMessageModal with datetime-local picker; validates ≥1 min future;
calls PUT org.matrix.msc4140 delayed event API; collapsible
ScheduledMessagesTray above composer lists pending messages with cancel;
local Jotai atom tracks scheduled messages per room
P3-3: File Upload Compression — opt-in checkbox per JPEG/PNG file ≥200KB
in upload preview; canvas API compresses at 0.82 quality; shows before/
after size estimate; compressed blob used in upload when checked
P3-7: Room Insights — new Insights tab in room settings; top 5 active
members (bar chart), top 5 reactions (chips), media breakdown (4 tiles),
24-hour activity heatmap (CSS bar chart); all from local cache only with
disclaimer banner; never the first tab shown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>