Resolve the eslint/prettier failures from the previous commit (non-blocking
in CI, but real): drop the banned `void` operator on fire-and-forget
transport.send().catch() calls, prefix the now-unused _denoiseNativeNS
param, and run prettier on the touched files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch to @lotusguild/element-call-embedded@0.20.1-lotus.1 (our self-built
fork) and turn on the source-level features it adds:
- #1 denoise CUTOVER: in-source ML denoise (lotusDenoiseSource=1) replaces
the build-time getUserMedia shim — removed the shim injection from
vite.config.js (denoise/ assets still shipped; the processor loads them).
Survives reconnects (fixes A7).
- #2 call-state: CallEmbed consumes io.lotus.call_state; useCallSpeakers /
useRemoteAllMuted prefer it over scraping EC's DOM (DOM fallback kept;
empty payloads ignored).
- #4 focus: CallControl.focusCameraParticipant sends io.lotus.focus_participant
(works during screenshare), replacing the DOM tile-click hack.
- #5 theming: lotusTransparent=1 (native transparent background).
- #6 decorations: LotusDecorationPusher sends each member's decoration URL
via io.lotus.decorations -> rendered on in-call tiles.
#3 soundboard / #7 quality ship dormant (EC-ready; no host UI sends them yet).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralized the global floating-UI stacking values into styles/zIndex.ts
(inCallBanner 9990 < seasonalEffect 9997 < nightLight 9998 < toast 10001;
folds modals sit at 9999 between). Same values, no behavior change — just
removes the magic numbers and documents the layering so future overlays don't
collide. Component-internal small z-index stays local.
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>
The full-screen media viewer was a raw <div role="dialog"> rendered in place
with manual focus. Wrapped it in folds Overlay (portal + backdrop, proper
stacking) and FocusTrap (focus management), keeping its own arrow/Escape key
handling. The light-on-dark chrome (#fff over the forced-black media stage) is
kept — it's a justified, always-dark media-viewer scrim, not theme chrome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaced the last raw native <select> (Profile timezone, colorScheme:'dark')
with SettingsSelect. Added an optional `disabled` prop to SettingsSelect for
the saving state. handleSubmit reads the `timezone` state (not the native form
field) so submission is unaffected; the now-unused handleSelectChange was
removed. No raw <select> elements remain in the settings UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extracted the folds-native dropdown (Button+PopOut+Menu) from General.tsx into a
shared components/settings-select/SettingsSelect.tsx, and used it to replace raw
native <select> elements (which render OS-styled and broke under non-default
themes via colorScheme:'dark'):
- Profile "auto-clear after" select
- PushRuleEditor add-rule mode select (dropped the now-unused handleModeChange)
The form-tied timezone <select> in Profile is left for a follow-up (it's wired
to native form submission + a disabled state and needs more care).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RouteError: raw <div>/<h2>/<p>/<button> (sans-serif, raw px) -> folds
Box/Text/Button with config tokens.
- CallEmbedProvider PiP fullscreen control: raw <button> with ⊡/⛶ glyphs ->
folds IconButton reusing the exported FullscreenIcon/ExitFullscreenIcon SVGs
from Controls (consistent with the main fullscreen button). The intentional
dark over-video scrim is kept.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LotusToastContainer was styled entirely with --lt-* CSS vars but rendered
unconditionally (not gated on lotusTerminal). Those vars only exist inside the
Lotus Terminal theme's scoped block with no global fallback, so in-app toast
notifications rendered with undefined background/border/colors on every stock
Cinny theme. Now the card uses folds tokens (color.Surface.*/Primary.*,
config.radii/space/borderWidth, color.Other.Shadow) by default, keeping the TDS
--lt-* glow/accents only when lotusTerminal is active. The raw <button> dismiss
control is now a folds IconButton.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "Pinned" toggle chip that narrows results to messages currently in
their room's m.room.pinned_events. Client-side post-filter mirroring the
has:image/file/video pattern: a pure filterGroupsByPinned(groups, enabled,
isPinned) helper consumes a predicate; MessageSearch builds a per-room
Map<roomId, Set<eventId>> from StateEvent.RoomPinnedEvents.
Review fix: the msgtype + pinned filters are now applied to BOTH the server
results AND the encrypted/local-cache results (via a shared applyResultFilters
useCallback), so the chips narrow the whole UI consistently — previously the
local/E2EE section bypassed them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a customAccentColor setting + a HexColorPickerPopOut in Settings →
Appearance. When set (and Lotus Terminal/TDS is OFF), it derives a full folds
Primary palette (Main/hover/active/line, contrasting OnMain, alpha-tiered
Container set, OnContainer) from the chosen color and overrides the folds
Primary CSS variables on document.body — resolving each var name from the
imported folds color.Primary.* token strings (e.g. "var(--oq6d07f)"), the
same body-level injection pattern used for mentionHighlightColor. The theme
class is on document.body, so an inline override on body wins over it.
Reverts to theme defaults when unset or when Lotus Terminal is enabled (TDS
keeps its fixed palette); the picker is disabled with a note in TDS mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add three msgtype toggle chips (Images/Files/Video) to the search filter
bar, mirroring the existing "Has link" chip. The Matrix search API can't
filter by msgtype server-side, so results are post-filtered client-side
(union match on event.content.msgtype, dropping now-empty groups); the
server request is unchanged. Visible count may be lower than the server
total — inherent to client-side filtering.
- Recent searches: last 10 distinct terms persisted via a new
state/recentSearches.ts (atomWithStorage, error-safe, mirrors
scheduledMessages). Shown as clickable chips when the search input is
focused + empty, with a Clear affordance; clicking re-runs the search.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- N124: denoise shim cleanup() now disconnects the noise gate AudioWorkletNode
(var-scoped, guarded), releasing the gate processor thread instead of leaking
it on every getUserMedia within a session.
- N125: denoise-status postMessage now targets the parent origin (derived from
the parentUrl widget param via new URL(...).origin, falling back to this
frame's origin) instead of broadcasting with '*'.
- N128: patch-folds.mjs fails hard (process.exit(1)) when the patch target is
missing, so an unpatched folds can't silently ship. The idempotent
"already applied" path still exits 0 (verified by re-run).
- N120: the avatar-decoration CDN URL is now single-sourced in
avatarDecorations.ts (DECORATION_CDN); syncDecorations.mjs extracts it by
regex (can't import across the build/app boundary) and fails hard if renamed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add grow="Yes" to ChatBgGrid and SeasonalBgGrid containers so they
expand to fill their flex parent — without it the Box shrank to one
column (~76px wide) because folds Box defaults to display:flex and
the wrapper is a flex-row with no explicit width.
- Mark N4 (PollContent) FIXED ✅ VERIFIED in LOTUS_BUGS.md after
confirmed pass on default Cinny themes and Lotus TDS.
- Mark B1 and B4 PASS in LOTUS_TESTING.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add sticky?: boolean to ToastNotif — sticky toasts skip the 4s auto-dismiss
timer entirely, staying until the user clicks or manually dismisses. Sticky
toasts also use cyan accent/glow (vs orange for messages) and allow the body
to wrap rather than truncate, so longer action-oriented copy is fully readable.
Update the Tauri update toast to: sticky: true, ⬆ prefix on the title,
"Click to install and restart" as explicit call to action.
Fixes: auto-dismiss before user noticed it, no visual distinction from
a regular message notification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both Retry and Leave called the same dismiss function; Retry implied a
reconnect attempt that never happened. Collapsed to a single Back button
that honestly describes returning to the prescreen.
docs: correct Gemini audit entries — sanitize-html not DOMPurify (Claim A),
retract inaccurate LiveKit replaceTrack soundboard approach (Claim B,
contradicts confirmed cross-origin iframe constraint), expand N95 fix note
to clarify track-stop vs AudioContext-suspend distinction.
docs(testing): add L1 N95 reproduction guide; update A7 to reflect single Back button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
N53: removed the duplicate lotusTerminal PTT-badge branch (raw <Box> with
--lt-* vars + bespoke rem/animation styling). The standard folds <Chip>
path now renders in all modes; TDS theming still flows through the CSS var
layer. Dropped the now-unused lotusTerminal read.
N81: ChatBgGrid / SeasonalBgGrid containers switched from flex-wrap with
fixed-width cells to a responsive CSS grid (repeat(auto-fill, minmax(76px,
1fr))), so swatches fill the row evenly instead of orphaning a lopsided
last row at arbitrary widths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a ringtoneId setting (classic | chime | soft | retro | none) so the
incoming-call ring is no longer hardcoded to call.ogg. The three synth
styles are generated in-browser via a new utils/ringtones.ts module
(mirroring the existing callSounds.ts WebAudio pattern), so no new binary
assets are bundled; 'classic' keeps the existing call.ogg clip and 'none'
is a silent, visual-only incoming-call UI.
- ringtones.ts: startRingtone() loops until stopped; previewRingtone()
plays a single non-looping preview and auto-cancels the prior preview.
- IncomingCall: ring driven by the setting; <audio> element removed.
- Settings > Calls: Ringtone selector with on-select preview, beside the
existing Ringtone Volume slider.
- settings.ts: persisted value whitelisted back to a known id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
msgContent media load/thumbnail failures now log only the error name+message,
not the full error/event object that may carry content data.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CallEmbed: 25s load watchdog that fails fast on iframe error / preparing-error /
timeout instead of hanging on a permanent spinner; additive onLoadError API,
cleared on ready/capabilities/joined.
- CallView: user-visible "call failed to load" overlay with Retry/Leave (folds +
tokens) via a new useCallLoadError hook.
- CallMemberCard: wrap the participant avatar in AvatarDecoration so decorations
render in the call roster (the tile rendered UserAvatar bare while member lists
already wrapped it).
Addresses LOTUS_BUGS item 3 (avatar decorations in calls) and EC iframe failure monitoring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RoomTimeline: wrap jump-to-latest/unread + mark-as-read handlers in useCallback
(the handlers passed to memoized message children were already memoized).
- RoomInput: wrap file/upload/emoji/sticker/location callbacks in useCallback so
the editor and toolbar don't re-render needlessly.
- EmojiBoard: hoist repeated mx.getRoom() pack-label lookups into a useMemo'd map
in the emoji and sticker sidebars (previously called per-render in map loops).
Behavior unchanged. (RoomTimeline/RoomInput already have ErrorBoundary wrappers
in RoomView, so no boundary added.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- N69: @mention highlight color now uses HexColorPickerPopOut + react-colorful
HexColorPicker behind a folds Button (color swatch); built-in onRemove
replaces the separate Reset, dropping the OS-native <input type="color">
- N10: mentionPulseKeyframes animates only box-shadow (dropped the imperceptible
scale(1.003)) so it no longer fights MsgAppearClass over `transform` on
self-sent @mention messages
- N22: Direct.tsx virtualizer estimateSize 38 -> 52 (two-line DM row height) to
avoid the initial-render jump before measureElement corrects each row
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract a shared ReportCategorySelect: folds Button trigger + PopOut +
FocusTrap + Menu + MenuItem (escape + arrow-key nav, like OrderButton),
replacing the OS-styled native <select> in both ReportRoomModal and
ReportUserModal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selecting a join/leave sound auto-plays a preview, but nothing communicated
that. Add it to the SettingTile description.
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>
ReportRoomModal/ReportUserModal rendered as <Box as="form" role="dialog">
with inline background/borderRadius(R400)/boxShadow. Switch both to
<Dialog as="form" variant="Surface"> so the surface (background, R300 radius,
shadow) comes from the design system, matching MessageReportItem and every
other message-action modal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- N14 ForwardMessageDialog: add folds <Header> with title + close IconButton
(was closeable only by clicking outside)
- N20 Notification presets: bare <button> with undefined --border-interactive-
normal / --bg-surface-low vars -> folds <Button variant="Secondary" fill="Soft">
- N68 syntaxHighlight tokenStyle: use the theme-aware --prism-* variable family
(keyword/selector/boolean/atrule/comment) instead of TDS-only --lt-accent-*
vars with dark-only Monokai fallbacks; comment uses --prism-comment not opacity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure formatting reflows (multi-line wrapping of long lines/imports/tables);
no behavior change. Clears the working tree of pending prettier diffs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The real reason the gallery didn't look or function like the Members drawer
or Saved Messages: it was a position:fixed overlay floating over the timeline,
mounted from RoomViewHeader. Now it docks into the room layout row exactly like
MembersDrawer.
- new mediaGalleryAtom (mirrors bookmarksPanelAtom) holds the open state
- RoomViewHeader toggles the atom instead of local useState and no longer
renders the panel
- Room.tsx renders <MediaGallery> as a flex sibling of the timeline with a
vertical Line separator on desktop and key={room.roomId} to reset per room
- MediaGallery.css: static width on desktop, position:fixed inset:0 full-screen
only on mobile (identical strategy to MembersDrawer.css); root Box shrink="No"
The panel now shares the row with the timeline instead of overlapping it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Media Gallery panel worked but didn't look like a first-party Cinny
drawer. Redesign the chrome to match MembersDrawer / Saved Messages and the
PolicyListViewer tab precedent:
- panel + header: Surface -> Background variant; header uses Text size="H4"
and a plain close IconButton (dropped the bespoke tooltip-wrapped button)
- tabs: moved into a bordered toolbar strip; adopt the repo's
variant={active?'Primary':'Secondary'} fill={active?'Solid':'Soft'} pattern
and show per-tab counts (Images (N) / Videos (N) / Files (N))
- month grouping: replaced the centered "lines + label" divider with a
left-aligned group label (the Cinny group-label pattern)
- thumbnail tiles: hover/focus border + caption overlay are now CSS-driven
(:hover / :focus-visible) instead of React state, and live in
MediaGallery.css.ts; grid + file rows tokenized
- caption overlay also reveals on keyboard focus (a11y)
All styling consolidated into MediaGallery.css.ts; no inline grid/tile styles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Avatar decorations: useAvatarDecoration cached ALL profile-field fetch
failures as "no decoration" permanently for the session. The member list
and timeline mount many avatars at once, so one rate-limited (429) burst
would wipe everyone's decoration until a full reload. Now only a genuine
404 (field unset) is cached; transient errors retry on the next mount.
Saved Messages panel — full redesign to match the canonical MembersDrawer:
- co-located BookmarksPanel.css.ts: toRem(266) + max-width:750px full-screen
media query, replacing the old position:absolute/zIndex:100 mobile "modal"
that had no backdrop or escape
- variant="Background" header; room avatars on each item (was a generic hash)
- priority tokens replace all raw opacity hacks; 3px borderLeft accent removed
- Escape-to-close; multi-line preview is now a proper folds Button (N38)
Media Gallery (N12): moved fixed positioning + width into MediaGallery.css.ts
using toRem(320) + a full-screen media query; border/header use config tokens;
added Escape-to-close on the panel (previously only the lightbox handled it).
Presence (SettingsTab / useUserPresence):
- N16: wrap presence-dot trigger in TooltipProvider; replace undefined
--bg-surface with color.Background.Container
- N17: add escapeDeactivates + isKeyForward/isKeyBackward to the FocusTrap
- N19: align reader labels (usePresenceLabel) to the setter vocabulary
(Online/Idle/Offline) so a chosen status matches the tooltip others see
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four changes to match screenshare full-screen UX for camera feeds:
1. Fullscreen button always visible
CallControls.tsx: remove `screenshare &&` gate — the ⛶ fullscreen
button now appears in camera-only calls, not just during screenshare.
2. Per-participant camera focus (CallControl.focusCameraParticipant)
Finds the target's video tile in the EC iframe DOM via:
[data-testid="videoTile"] / [data-video-fit]
closest ancestor of [aria-label="${userId}"]
Enables spotlight mode if not already active, then clicks the tile
so EC's internal focus handler runs. Falls back gracefully if the
tile is not in the DOM (camera off).
3. MemberGlance participant popup
Clicking a participant avatar in the call status bar now shows a
small menu: "Focus camera" (calls focusCameraParticipant) and
"View profile" (existing behaviour). Previously it opened the
profile immediately with no way to focus the camera.
4. PiP fullscreen button
A ⛶/⊡ icon button appears in the PiP overlay top-right area,
letting users go fullscreen directly from PiP mode without
navigating back to the call room first.
UNTESTED — requires a real multi-participant call to verify tile
clicking behaviour and fullscreen transitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace raw error object logging (which may contain Matrix event
payloads, user IDs, or message bodies) with e.message-only strings
in three files:
- CallEmbed.ts: state update and event widget feed errors
- msgContent.ts: image/video element load failures and thumb errors
- RoomInput.tsx: GIF send failure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap RoomTimeline in ErrorBoundary — a single bad event no longer
crashes the entire timeline; shows a graceful "Timeline unavailable"
message instead
- Wrap RoomInput in ErrorBoundary — composer crashes show a fallback
placeholder rather than a blank white section
- Animate SpeakerAvatarOutline with a 1.2s pulse keyframe so it's
visually distinct from a static ring; respects prefers-reduced-motion
- Fix var(--border-surface-variant) undefined variable in UserRoomProfile
device session rows; replaced with color.SurfaceVariant.ContainerLine
UNTESTED — verify at chat.lotusguild.org post-deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add SelectSenderButton: clickable people picker for the From filter
replacing the text-only from:@user syntax
- Add date preset shortcuts in DateRangeButton (Today, Last week,
Last month, Last year) for one-click range selection
- Add Has link chip backed by Matrix contains_url API filter; toggle
removes cleanly with X badge
- Wire containsUrl through URL params, useMessageSearch hook, and
SearchFilters props
UNTESTED — verify at chat.lotusguild.org post-deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces undefined --border-surface-variant CSS variable with
color.SurfaceVariant.ContainerLine from folds, and --bg-surface-variant
with color.SurfaceVariant.Container. Both are valid theme tokens
that adapt to light/dark mode correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>