From e9a970a75b6212bd142a623ca87f465728bf5ffa Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 14 Jun 2026 12:20:47 -0400 Subject: [PATCH] fix: settings dropdowns, background animations, ringing, avatar decorations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings dropdowns (Bug #3): - Add reusable SettingsSelect component using Menu+PopOut+FocusTrap — exact same pattern as Message Layout, so all dropdowns look consistent - Replace raw ` elements, which look different from the custom-styled `Menu` used elsewhere. -* **Recommended Fix:** Replace raw selects with the `Menu` + `PopOut` pattern used in the "Message Layout" setting. +* **Issue:** The dropdowns for "Join & Leave Sounds", "UI Font", and "Seasonal Theme" used raw HTML `` elements replaced with a reusable `SettingsSelect` component using the Menu+PopOut+FocusTrap pattern consistent with the rest of the settings UI. ### 4. No Camera Focus During Screenshare **File:** `src/app/features/call/CallControls.tsx` -**Status:** UX Bug +**Status:** UX Bug — blocked by Element Call internals * **Issue:** When someone is screensharing and another participant turns on their camera, there is no way to switch the primary display to the camera or go fullscreen on it. -* **Recommended Fix:** Implement a "Pin/Focus" toggle on participant tiles that overrides the automatic screenshare spotlight. +* **Root Cause:** Element Call's spotlight/layout is controlled internally by EC. Lotus Chat injects the call iframe and cannot easily override EC's participant tile behavior without forking the EC widget. +* **Recommended Fix:** Implement a "Pin/Focus" toggle on participant tiles that overrides the automatic screenshare spotlight — requires EC upstream changes or a custom message bridge. + +### 5. Ringing Modal Fires in Persistent Voice Rooms +**File:** `src/app/components/CallEmbedProvider.tsx` (line 337–342) +**Status:** ✅ RESOLVED (June 2026) + +* **Issue:** Joining a persistent voice room (not a DM or transient group call) showed the incoming call ringing modal and animation. +* **Root Cause:** The `isPrivateGroup` condition included `JoinRule.Restricted` and `JoinRule.Knock` rooms. Lotus Guild voice rooms are `Restricted` join-rule rooms. Their `m.space.parent` state event was being checked but some rooms were set up with only the space-side `m.space.child` relationship, leaving no `m.space.parent` on the room itself — so they passed as `isPrivateGroup` and triggered ringing. +* **Fix Applied:** Narrowed `isPrivateGroup` to only `JoinRule.Invite` to match the exact set of rooms where the call button is shown. Also added `room.isCallRoom()` early-exit so rooms with `m.join_rule.call` type never ring. + +### 6. Animated Chat Backgrounds Affect Message Content +**File:** `src/app/features/lotus/chatBackground.ts` +**Status:** ✅ RESOLVED (June 2026) + +* **Issue:** Animated backgrounds that use `filter: brightness()` or `opacity` animations (Digital Rain glow, Grid Pulse brightness, Fireflies glow/blink) applied those effects to the entire `` element, causing all message content and the composer to flash/flicker in sync with the animation. +* **Root Cause:** `filter` and `opacity` CSS properties affect an element AND all its descendants. Applying these as part of the `animation` shorthand on the `Page` container made them "inherited" visually by everything inside the room view. +* **Side Effect:** `filter` animation also created a CSS stacking context on Page, which pushed Seasonal Theme overlays (position:fixed; z-index:9997) behind the Page compositor layer. +* **Fix Applied:** Removed `animRainGlowKeyframe`, `animGridBrightnessKeyframe`, `animFirefliesGlowKeyframe`, and `animFirefliesBlinkKeyframe` from `chatBackground.ts`. Only `backgroundPosition` / `backgroundSize` animations remain — these are safe and do not affect descendants or create stacking contexts. + +### 7. Seasonal Themes Display Behind Chat Background +**File:** `src/app/components/seasonal/SeasonalEffect.tsx` +**Status:** ✅ RESOLVED (June 2026) — root cause was Bug #6 + +* **Issue:** Seasonal theme overlays (position:fixed; z-index:9997) appeared behind animated chat backgrounds. +* **Root Cause:** The `filter` animation on `` created a CSS stacking context, causing Page's GPU compositing layer to render above the fixed-position seasonal overlay in some browsers. Removing the filter animations (Bug #6 fix) resolves the stacking context issue. +* **Fix Applied:** See Bug #6. No additional changes to SeasonalEffect required. + +### 8. Avatar Decoration Images Not Rendering in Settings +**File:** `src/app/features/settings/account/ProfileDecoration.tsx` +**Status:** ✅ RESOLVED (June 2026) + +* **Issue:** Under Settings → Account → Avatar Decoration, no decoration images were visible. +* **Root Cause:** The `DecorationPreviewCell` used `loading="lazy"` on decoration images. The browser's lazy-loading algorithm determines image visibility from the viewport, but the decoration grid is inside a nested `overflowY: auto` scroll container inside a settings panel — the browser did not correctly detect these images as near the viewport and never triggered them to load. +* **Fix Applied:** Changed `loading="lazy"` to `loading="eager"` in `DecorationPreviewCell`. The settings panel is user-initiated, so eager loading is appropriate. diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index f6d730b61..ddcb1ec4c 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -363,6 +363,24 @@ Themes: --- +### [ ] P5-35 · Desktop — Notification Click Opens Room (DEFERRED) + +**What:** Clicking a system tray notification navigates to the relevant room. Quick-reply from the notification toast would send the reply without opening the window. +**Status:** Deferred — `tauri-plugin-notification` has no Rust click/action callback API. Quick-reply would need a custom WinRT toast activator + COM registration, which can't be compile-tested without a Windows build environment. +**Note:** Tray icon and `matrix:` deep links already bring the window forward on most interactions. Revisit when tauri-plugin-notification gains click handler support upstream. +**Complexity:** High (platform-specific native code required). + +--- + +### [ ] P5-36 · Desktop — Windows Jump List (DEFERRED) + +**What:** Right-clicking the taskbar icon shows a jump list with recent/favorite rooms for quick navigation. +**Status:** Deferred — implementing the Windows COM jump list API in Tauri requires iterating on C++/COM code that can only be compile-checked on Windows, making blind CI iteration impractical. +**Action when unblocked:** Revisit when a Tauri plugin abstracts the Windows Shell `ICustomDestinationList` interface, or when a Windows build environment is available for local iteration. +**Complexity:** High (Windows-only native COM). + +--- + ## Blocked Features These features are confirmed desirable but cannot be built until the listed dependency is resolved. diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index ed3183e67..87f3e3369 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -325,21 +325,15 @@ function IncomingCallListener({ callEmbed, joined }: IncomingCallListenerProps) ); if (!hasCallPermission) return; - // Only ring for DMs or private non-space group chats. - // Space voice channels and public rooms fire room-level RTC notifications - // whenever anyone joins — ringing every member is incorrect behaviour. + // Only ring in rooms where the call button is visible: DMs or invite-only rooms + // with no space parent. Persistent voice rooms (call rooms), space channels, + // restricted rooms, and public rooms must never trigger ringing. + if (room.isCallRoom()) return; const isDirect = directs.has(room.roomId); - // m.space.parent uses the parent space ID as the state key, so getStateEvent - // (which defaults to stateKey='') always returns undefined. Use getStateEvents - // (no key filter) to detect any space parent relationship. const isSpaceChild = getStateEvents(room, StateEvent.SpaceParent).length > 0; const joinRule = room.getJoinRule(); - const isPrivateGroup = - !isSpaceChild && - (joinRule === JoinRule.Invite || - joinRule === JoinRule.Knock || - joinRule === JoinRule.Restricted); - if (!isDirect && !isPrivateGroup) return; + const isPrivateInviteGroup = !isSpaceChild && joinRule === JoinRule.Invite; + if (!isDirect && !isPrivateInviteGroup) return; const info: IncomingCallInfo = { room, diff --git a/src/app/features/lotus/chatBackground.ts b/src/app/features/lotus/chatBackground.ts index 4dafefcd6..ea36da248 100644 --- a/src/app/features/lotus/chatBackground.ts +++ b/src/app/features/lotus/chatBackground.ts @@ -2,14 +2,10 @@ import { CSSProperties } from 'react'; import { ChatBackground } from '../../state/settings'; import { animRainKeyframe, - animRainGlowKeyframe, animStarsDriftKeyframe, animGridPulseKeyframe, - animGridBrightnessKeyframe, animAuroraKeyframe, animFirefliesKeyframe, - animFirefliesGlowKeyframe, - animFirefliesBlinkKeyframe, } from '../../styles/Animations.css'; export const BG_OPTIONS: { value: ChatBackground; label: string }[] = [ @@ -210,7 +206,7 @@ const DARK: Record = { ].join(','), backgroundSize: '40px 200px, 12px 200px', backgroundPosition: '0 0, 0 0', - animation: `${animRainKeyframe} 8s linear infinite, ${animRainGlowKeyframe} 2.1s ease-in-out infinite`, + animation: `${animRainKeyframe} 8s linear infinite`, }, // Animated: drifting star field — three seamlessly-tiling layers at different speeds @@ -236,7 +232,7 @@ const DARK: Record = { 'linear-gradient(90deg, rgba(0,212,255,0.06) 1px, transparent 1px)', ].join(','), backgroundSize: '60px 60px, 60px 60px, 12px 12px, 12px 12px', - animation: `${animGridPulseKeyframe} 4s ease-in-out infinite, ${animGridBrightnessKeyframe} 3.3s ease-in-out infinite`, + animation: `${animGridPulseKeyframe} 4s ease-in-out infinite`, }, // Animated: aurora borealis — four bands each travel an independent path @@ -263,7 +259,7 @@ const DARK: Record = { ].join(','), backgroundSize: '200px 200px, 280px 280px, 160px 160px', backgroundPosition: '0 0, 120px 80px, 60px 140px', - animation: `${animFirefliesKeyframe} 30s linear infinite, ${animFirefliesGlowKeyframe} 2.3s ease-in-out infinite, ${animFirefliesBlinkKeyframe} 1.7s ease-in-out infinite`, + animation: `${animFirefliesKeyframe} 30s linear infinite`, }, }; @@ -481,7 +477,7 @@ const LIGHT: Record = { ].join(','), backgroundSize: '200px 200px, 280px 280px, 160px 160px', backgroundPosition: '0 0, 120px 80px, 60px 140px', - animation: `${animFirefliesKeyframe} 30s linear infinite, ${animFirefliesGlowKeyframe} 2.3s ease-in-out infinite, ${animFirefliesBlinkKeyframe} 1.7s ease-in-out infinite`, + animation: `${animFirefliesKeyframe} 30s linear infinite`, }, }; diff --git a/src/app/features/settings/account/ProfileDecoration.tsx b/src/app/features/settings/account/ProfileDecoration.tsx index 943ba1d19..d2c6da7cf 100644 --- a/src/app/features/settings/account/ProfileDecoration.tsx +++ b/src/app/features/settings/account/ProfileDecoration.tsx @@ -60,7 +60,7 @@ function DecorationPreviewCell({ {name} = { value: T; label: string }; + +function SettingsSelect({ + value, + options, + onChange, +}: { + value: T; + options: SettingsSelectOption[]; + onChange: (v: T) => void; +}) { + const [menuCords, setMenuCords] = useState(); + const selectedLabel = options.find((o) => o.value === value)?.label ?? value; + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (v: T) => { + onChange(v); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {options.map((opt) => ( + handleSelect(opt.value)} + > + {opt.label} + + ))} + + + + } + /> + + ); +} + function SystemThemePreferences() { const themeKind = useSystemThemeKind(); const themeNames = useThemeNames(); @@ -417,42 +493,25 @@ function Appearance() { title="Seasonal Theme" description="Decorative overlays that activate automatically on holidays and events, or choose one manually." after={ - + onChange={(v) => setSeasonalThemeOverride(v as typeof seasonalThemeOverride)} + options={[ + { value: 'auto', label: '🗓 Auto (date-based)' }, + { value: 'off', label: 'Off' }, + { value: 'newyear', label: '🎆 New Year' }, + { value: 'lunar', label: '🏮 Lunar New Year' }, + { value: 'valentines', label: '💖 Valentine\'s Day' }, + { value: 'stpatricks', label: '🍀 St. Patrick\'s Day' }, + { value: 'aprilfools', label: '🃏 April Fool\'s Day' }, + { value: 'earthday', label: '🌱 Earth Day' }, + { value: 'autumn', label: '🍂 Autumn' }, + { value: 'halloween', label: '🎃 Halloween' }, + { value: 'christmas', label: '❄️ Christmas' }, + { value: 'arcade', label: '👾 Retro Arcade Day' }, + { value: 'deepspace', label: '🚀 Deep Space Week' }, + ]} + /> } /> @@ -556,26 +615,18 @@ function Appearance() { title="UI Font" description="Font used throughout the interface." after={ - + options={[ + { value: 'system', label: 'System Default' }, + { value: 'inter', label: 'Inter (default)' }, + { value: 'jetbrains-mono', label: 'JetBrains Mono' }, + { value: 'fira-code', label: 'Fira Code' }, + ]} + /> } /> @@ -1263,25 +1314,17 @@ function Calls() { title="Idle Timeout" description="How long to wait before auto-muting." after={ - + setAfkTimeoutMinutes(Number(v))} + options={[ + { value: '1', label: '1 minute' }, + { value: '5', label: '5 minutes' }, + { value: '10', label: '10 minutes' }, + { value: '20', label: '20 minutes' }, + { value: '30', label: '30 minutes' }, + ]} + /> } /> )} @@ -1291,26 +1334,16 @@ function Calls() { title="Join & Leave Sounds" description="Play a sound when someone joins or leaves a call you are in." after={ - + onChange={(v) => handleJoinLeaveSoundChange(v as 'off' | 'chime' | 'soft' | 'retro')} + options={[ + { value: 'off', label: 'Off' }, + { value: 'chime', label: 'Chime' }, + { value: 'soft', label: 'Soft' }, + { value: 'retro', label: 'Retro' }, + ]} + /> } />