rowGap 36→52 (40px visual gap between image rows), columnGap separate at 28.
The 52×52 buttons have 8px image overflow on each side so row gap needed to
account for 8+visual+8 = actual gap. Previous 36→20px visual was still tight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- chatBackground.ts: remove animRainGlowKeyframe and animGridBrightnessKeyframe
from LIGHT anim-rain and anim-pulse definitions (these were removed from the
import and from DARK variants in the previous session but the LIGHT variants
were missed, leaving stale references that would cause a build error)
- ProfileDecoration.tsx: increase decoration grid gap 20→36 (visual gap was
only 4px due to 8px image overflow beyond each 52×52 button), fix paddingBottom
4→8 and add paddingRight:8 to prevent edge clipping
- LOTUS_BUGS.md: correct bug #8 root cause (CSP, not lazy-loading), add
bugs #9 (grid spacing) and #10 (Windows taskbar badge)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 <select> for Seasonal Theme, UI Font, AFK Timeout, and
Join & Leave Sounds with SettingsSelect
Animated chat backgrounds bleeding onto content (Bug #6 / #7):
- Remove filter:brightness() and opacity animations from chatBackground.ts
(animRainGlowKeyframe, animGridBrightnessKeyframe, animFirefliesGlowKeyframe,
animFirefliesBlinkKeyframe). These were applied to the Page element which
caused ALL descendants (messages, composer) to flash in sync.
Also created a CSS stacking context on Page that pushed SeasonalEffect
(position:fixed; z-index:9997) behind the animated background layer.
- Only backgroundPosition / backgroundSize animations remain — safe, do not
affect descendants, and do not create stacking contexts.
- Remove now-unused animation keyframe imports from chatBackground.ts.
Voice ringing in persistent rooms (Bug #5):
- Narrow the ringing condition from (Invite|Knock|Restricted) to only Invite,
matching exactly the rooms where the call button is visible.
- Add room.isCallRoom() early-exit so m.join_rule:call rooms never ring.
Avatar decoration images not loading (Bug #8):
- Change loading="lazy" → loading="eager" in DecorationPreviewCell.
Lazy loading does not reliably trigger for images inside nested overflow
scroll containers (the settings panel scroll area), so images never loaded.
Docs: LOTUS_BUGS.md updated with root cause and resolution for all 5 new bugs.
Docs: LOTUS_TODO.md adds P5-35/P5-36 (deferred desktop notification/jump list).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Point DECORATION_CDN at Lotus Nextcloud WebDAV share instead of external
avatardecoration.com; all 99 APNG files are self-hosted and served via
direct DAV URL (no CORS issue for <img> elements)
- Add onError handler to AvatarDecoration.tsx to silently hide the overlay
if a file is missing or the CDN is unreachable
- Rewrite scripts/syncDecorations.mjs: now sends HTTP HEAD requests to the
live Nextcloud CDN (batches of 16 in parallel) and removes catalog entries
for files that return non-2xx; empty categories are pruned automatically.
Workflow: delete files from Nextcloud → run `npm run sync:decorations` →
commit the updated avatarDecorations.ts. No local files needed.
- Add public/decorations/ to .gitignore; delete the 85 MB local APNG cache
that was downloaded during development (files live on Nextcloud now)
- Add sync:decorations script to package.json
- Update LOTUS_FEATURES.md, LOTUS_TODO.md (P5-13 + P5-14 ✓), README.md
with avatar decoration documentation and catalog sync workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
256×256 APNG overlays from avatardecoration.com (open CORS CDN).
Stored in the user's Matrix profile as io.lotus.avatar_decoration via
MSC4133 so all Lotus Chat users see each other's decorations.
- avatarDecorations.ts: curated catalog of 110 original-IP decorations
across 9 categories (Gaming, Cyber, Space, Fantasy, Elements,
Japanese, Nature, Spooky, Cozy)
- useAvatarDecoration: per-user profile fetch with module-level cache
and in-flight deduplication so concurrent renders for the same userId
share one HTTP request
- AvatarDecoration: position:relative wrapper that overlays the APNG
8px beyond the avatar on all sides; renders nothing when no decoration
is set (zero cost for undecorated users)
- ProfileDecoration: scrollable grid picker in Settings → Profile,
grouped by category with live preview; Save button appears only when
the selection differs from what's saved
- Applied at all five avatar display sites: message timeline, members
drawer, knock list, @mention autocomplete, notifications inbox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- usePresenceUpdater: replace stale closure with readStatus() called at
invocation time so changing custom status in Profile Settings is never
silently overwritten by subsequent activity events
- CallEmbedProvider: fix m.space.parent state-key lookup by switching
getStateEvent → getStateEvents (plural); space channel voice rooms no
longer trigger the incoming-call ring/animation
- Add useUserNotes hook (io.lotus.user_notes account data, reactive via
useAccountDataCallback, 500-char limit, cross-device sync)
- UserRoomProfile: add UserPrivateNotes textarea with 800ms debounced
auto-save, saving indicator, char counter when <100 chars remain;
shown only when viewing another user's profile
- LOTUS_FEATURES.md: add Private Notes section, Status Revert fix note,
animation improvements subsection, Seasonal Themes section
- LOTUS_BUGS.md: mark presence revert + voice ringing bugs as resolved
- README.md + landing/index.html: document all new June 2026 features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 11 CSS-only seasonal overlays (Halloween, Christmas, New Year, Autumn,
April Fool's, Lunar New Year, Valentine's Day, St. Patrick's Day, Earth Day,
Deep Space, Retro Arcade) with date-based auto-detection and a manual override
dropdown in Settings → Appearance → Seasonal Theme. All themes respect
prefers-reduced-motion. SeasonalEffect mounts at z-index 9997 in App.tsx.
Also rewrites all 5 animated chat background keyframes for smoother, more
organic motion: Digital Rain gains a phosphor glow flicker; Star Drift now
loops each layer by exactly its own tile size (no more seam); Grid Pulse adds
an independent brightness oscillation at a prime period; Aurora Flow drives
all four gradient layers through distinct paths; Fireflies adds glow-pulse and
opacity-blink animations at prime periods for unsynchronised bioluminescence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends useTauriNotificationBadge to also invoke set_tray_unread (mirror the
unread-mention state onto the tray icon, visible when minimized) and flash_window
(flash the taskbar button when new mentions arrive while the window is unfocused).
No-op outside Tauri.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds useDeepLinkNavigate (mounted in ClientNonUIFeatures): listens for the
'lotus-deeplink' DOM CustomEvent that the Tauri shell dispatches when a
matrix:/matrix.to link opens the app, converts matrix: URIs to matrix.to, and
navigates via the same path as useMentionClickHandler (reusing matrix-to.ts
parsers + useRoomNavigate). No-op outside Tauri.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The committed file had the transparency 'checkerboard' baked in as opaque
gray/white pixels (it was fully opaque), so it would have produced icons with a
checkerboard background. Flood-filled the dove to protect it, removed the
checkerboard, and restored the dove — the lotus line-art + white dove now sit on
a real alpha-transparent background. Used as the source for the desktop icons.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Room Settings description still said 'Enforced locally by Lotus Chat
clients' from before the voice-limit-guard was deployed. The cap is now
enforced server-side (via the lk-jwt-service guard) for all Matrix clients.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vite-plugin-static-copy v4 preserves the full source path under dest, so the
old { src: '.../dist', dest: 'public', rename: 'element-call' } target placed
Element Call at dist/public/node_modules/@element-hq/element-call-embedded/dist/
instead of dist/public/element-call/. The call widget URL
(/public/element-call/index.html) therefore 404'd.
This broke voice/video calls on cinny-desktop (served via tauri-plugin-localhost
from a fresh build). The web client only kept working because its deployed
/public/element-call/ was a stale artifact from before the v4 bump — the next
web rebuild would have broken calls there too.
Fix: copy the dist directory to public/element-call with rename:{stripBase:4}
to strip the source path segments, mirroring the android/locales targets.
Verified: a fresh build now produces dist/public/element-call/index.html +
assets/ with intact relative asset refs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sounds (P5-16): browsers block the Web Audio context until a user gesture
starts it, so join/leave sounds — which fire later with no gesture — were
silent. unlockCallSounds() now primes/resumes the shared AudioContext inside
the Join click (centralized in useCallStart so every join path is covered),
making the per-client sounds reliably audible to everyone in the call.
Voice limit (P5-10): the limit is now a hard, cross-client server-side cap
enforced by the voice-limit-guard sidecar (matrix repo) that fronts
lk-jwt-service and refuses LiveKit tokens when a room is full. Updated
LOTUS_FEATURES.md / README.md / LOTUS_TODO.md to reflect that the client
'Channel Full' check is UX only and the server is authoritative.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P5-10 Voice Channel User Limit:
- New StateEvent.LotusVoiceLimit (io.lotus.voice_limit) with { max_users }
- RoomVoiceLimit admin control in Room Settings > General > Voice
(power-level gated via permissions.stateEvent)
- CallPrescreen reads the limit reactively and disables Join with a
'Channel Full (N/N)' message at capacity; existing members can rejoin
P5-16 Custom Join/Leave Sound Effects:
- useCallJoinLeaveSounds hook wired into CallUtils; detects participant
join/leave via MatrixRTCSession membership changes (sender|deviceId),
filters out self, only fires while joined
- Sounds synthesized in-browser with Web Audio (callSounds.ts) - no
assets bundled; styles Off/Chime/Soft/Retro
- 'Join & Leave Sounds' selector in Settings > Calls (previews on change)
Docs: LOTUS_FEATURES.md, README.md, LOTUS_TODO.md (P5-10/P5-16 marked done)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- LOTUS_FEATURES.md: added sections for Knock-to-Join Notifications for
Admins and AFK Auto-Mute in Voice under their parent headings
- LOTUS_TODO.md: marked P4-3 and P5-11 as [x] completed
- README.md: updated Calls & Voice bullet list with AFK auto-mute entry;
expanded knock admin badge entry with badge-count detail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
composedPath() returns an empty array once the native event is no longer
dispatching. In React 19, events from a portal-within-portal (EmojiBoard
inside #portalContainer, which already hosts the Settings Overlay) can reach
the synthetic event handler after the native dispatch window closes.
The fallback walks up from evt.target so handleGroupItemClick still finds
the emoji button and calls onEmojiSelect — fixing emoji selection in the
status-message editor in Settings > Account > Profile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P4-3 — Knock-to-join Notifications for Admins:
- usePendingKnocks() hook reactively tracks knocking members via
RoomMemberEvent.Membership; returns empty array if user lacks invite power
- Members icon in RoomViewHeader shows a Warning badge with the knock count
when there are pending requests; badge updates in real time without
needing to open the drawer; aria-label updated to describe pending count
P5-11 — AFK Auto-Mute in Voice:
- useAfkAutoMute() hook opens a monitoring-only getUserMedia stream,
connects it to an AnalyserNode, and polls RMS every 500ms
- If mic is on and RMS stays below threshold for the configured timeout,
calls callEmbed.control.setMicrophone(false) and shows an in-app toast
- Hook is called inside CallControls so monitoring is only active during calls
- Settings: afkAutoMute toggle + afkTimeoutMinutes select (1/5/10/20/30 min,
default 10) added to Settings → Calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pushing to cinny-desktop main already fires release.yml via on:push.
The explicit API dispatch call was redundant, caused double-job runs,
and failed with 401 when the temporary admin token expired. Removed.
DISPATCH_TOKEN secret is no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EditHistoryModal: decrypt fetched edit events in E2EE rooms via
mx.decryptEventIfNeeded() before rendering; previously events not
found in the room cache showed ciphertext or "(no text)"
- CallEmbedProvider: add touch support for PiP resize corners;
extracted shared applyResize() helper; onTouchStart wired to all
four corners alongside existing onMouseDown
- RoomView: skip chatBgStyle when glassmorphism is active; document.body
already carries the background for the blur effect, rendering it twice
doubled CSS animation work unnecessarily
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ACTIONS_TOKEN's dispatch attempts were failing silently. DISPATCH_TOKEN is
a new cinny repo secret with confirmed actions:write scope. Also fix the
HTTP check to use -ge/-lt arithmetic instead of -lt/-gt.
NOTE: DISPATCH_TOKEN should be replaced with a permanent Gitea API token
that has actions:write scope (create in Gitea user settings → Applications).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RELEASE_TOKEN may lack Actions write scope. ACTIONS_TOKEN already exists
as a repo secret and is the correct token for dispatching workflows.
Also capture and print the HTTP response so failures are visible in logs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status message: Synapse clears status_msg when a user goes offline/reconnects.
Fix by caching to localStorage and re-sending on setOnline(). The sync
effect no longer overwrites the local value with an empty server event.
Timezone: PUT /profile/{userId}/m.tz is MSC1769 (unstable) and not
supported by standard Synapse — save/load silently fails. Fix by using
Matrix account data (im.lotus.timezone) instead, which is fully
supported. Own profile falls back to account data; other users still
try the m.tz profile endpoint (for federated servers that support it).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Omitting sendNotificationType for call rooms caused Element Call to
default to ring behavior. Now all starting-call events explicitly set
notification (or ring for DMs). Voice channels always get notification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Presence: subscribe on MatrixClient (mx) instead of individual User objects.
User EventEmitters have a 10-listener default; the same user appears in many
mounted components simultaneously (member list, avatars, presence rings) and
was accumulating 11+ listeners per event, causing MaxListenersExceededWarning
on User.presence, User.currentlyActive, and User.lastPresenceTs.
Fonts: download JetBrains Mono and Fira Code latin subsets to public/fonts/
and replace Google Fonts CDN links (two external origins) with a local CSS
file. Windows WebView2 tracking prevention was blocking googleapis.com and
gstatic.com, logging 18+ "blocked" warnings per session. VT323 remains on
Google Fonts as it's decorative and not a default option.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
trigger-desktop.yml was pushing a submodule bump commit (which fires
release.yml via the push event) AND then explicitly dispatching
release.yml via the API, causing every cinny push to produce two
back-to-back desktop builds. Drop the dispatch step; the push alone
is sufficient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Tauri command (set_badge_count) that draws a red circle badge
with a highlight count onto the Windows taskbar button overlay icon,
matching Discord's behavior. Badge shows @mention/highlight count only
(not total messages), clears to zero when all highlights are read.
Frontend: useTauriNotificationBadge hook reads roomToUnreadAtom and
calls set_badge_count via window.__TAURI_INTERNALS__.invoke whenever
the unread map changes. No-ops silently in the browser (non-Tauri).
Mounted as TauriEffects inside JotaiProvider in App.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8 individual SettingTile rows collapsed into a single wrapped chip grid.
Active chips show as Primary+outlined; inactive as Secondary. Clicking
any chip toggles it. Drops from ~83 lines to ~25 and reads at a glance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces all PWA, Android, Apple touch, and favicon assets with the
custom Lotus Chat logo (lotus_chat.png, 1254x1254 source).
Adds attribution section to README per CC BY 4.0 requirements:
the logo is a derivative of the original Cinny logo by Ajay Bura,
used under CC BY 4.0, and the modified version is likewise CC BY 4.0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- P5-21: Custom @mention highlight color picker in Settings → Appearance.
CSS vars with luminance-computed text color; resets cleanly to theme default.
- P5-22: Font selector (System, Inter, JetBrains Mono, Fira Code) in
Settings → Appearance. Fira Code added to Google Fonts preload.
- P5-27: Gaming/Work/Sleep preset buttons at top of Settings → Notifications.
Each atomically applies a group of notification settings.
- AppearanceEffects component in App.tsx applies CSS vars on settings change.
- LOTUS_BUGS.md: mark presence + manifest icon bugs as resolved.
- LOTUS_TODO.md: mark P3-6, P5-21, P5-22, P5-27 as [x].
- LOTUS_FEATURES.md: document all four completed features.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each button (Format, Emoji, Sticker, GIF, Location, Poll, Voice,
Schedule) can be individually hidden in Settings → Editor.
All default to on, stored in composerToolbarButtons object.
getSettings deep-merges the nested object so new buttons default
to true for existing users with saved settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove status_msg from setOnline/setUnavailable so server preserves
any custom status message the user has set
- Fix manifest icons array paths (./public/android/ -> ./res/android/)
Junior had it backwards: shortcut icon was correct, main icons were wrong
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GifPicker: replace #FF6B00 and #060c14 with var(--lt-accent-orange) and
var(--lt-bg-secondary); rgba opacity values use color-mix()
- VoiceMessageRecorder: replace #FF6B00 and #00FF88 waveform/dot colors
with var(--lt-accent-orange) / var(--lt-accent-green)
- Presence: replace aria-labelledby referencing a lazy tooltip ID with a
direct aria-label so screen readers always have the text in the DOM
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useFileDrop: reset drag overlay when mouse leaves browser window
(relatedTarget === null signals viewport exit, counter was getting stuck)
- useDeviceVerificationStatus: add member count to useMemo deps so new
room members' devices get checked, not just initial joined members
- index.css: define --bg-surface-variant used by VoiceMessageRecorder,
MessageSearch, SearchFilters, UserRoomProfile (was falling back to transparent)
- syntaxHighlight: fix Python inline comments — # after space/tab was
treated as plain text; only start-of-line was recognised
- usePresenceUpdater: replace internal baseUrl cast with mx.getHomeserverUrl()
- useLocalMessageSearch: scan all linked timelines via getUnfilteredTimelineSet()
not just the live window, so scrolled-back history is included in E2EE search
- RoomViewHeader: show search button in encrypted rooms — local search is
implemented and handles them; the guard was a holdover from before it existed
- recent-emoji: return emojis in recency order (array is already unshifted on
use) instead of sorting by total usage count
Skipped: media gallery memory leak (needs virtualization refactor),
bookmark race condition (needs queue/lock), Night Light portal coverage
(position:fixed already covers full viewport — not a real bug).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the contents-API workaround with a direct workflow_dispatch call
using ACTIONS_TOKEN (which has Actions:write scope). Cascade prevention
in Gitea blocked all previous push-based approaches.
The submodule bump is kept for correct SHA tracking in git history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gitea suppresses workflow events from Actions runner pushes to prevent
infinite loops, so the submodule bump commit was never firing release.yml.
Add a second step that writes .cinny-version via the REST contents API —
that creates a user-attributed commit Gitea does not suppress.
The submodule bump is kept for correct SHA tracking; the API commit is
the actual trigger.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
workflow_dispatch API requires Actions:write token scope which
RELEASE_TOKEN doesn't have. Worse, even a successful dispatch would
check out the old pinned submodule SHA, not the new cinny commit.
New approach: clone cinny-desktop, point the cinny submodule at the
current commit SHA, commit, and push. The push to cinny-desktop/main
fires release.yml naturally — no special permissions needed beyond
repo write (which RELEASE_TOKEN already has for release uploads).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LOTUS_FEATURES.md: full reference of every custom Lotus feature with
implementation details, file paths, and API notes
- LOTUS_BUGS.md: audit of confirmed bugs with root causes and fixes
- LOTUS_TODO_REFERENCE.md: technical implementation notes for backlog items
- LOTUS_TODO.md: trim completed audit results section, link to FEATURES doc
- README.md: rewrite to marketing-friendly feature list format; fix
incorrect claim that screenshare auto-reverts view to grid (removed)
Fix: auto-revert spotlight on screenshare was removed (600ms grid-click
caused fullscreen to show avatars); corrected in LOTUS_FEATURES.md and
removed from README.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Giphy API key removed from config.json (was tracked in git — now purged
from all history via git-filter-repo). Key lives in /etc/lotus-deploy.env
on LXC 106; lotus_deploy.sh injects it into config.json after each rsync.
config.json now has:
- homeserverList: lotusguild.org + matrix.org + mozilla.org
- allowCustomHomeservers: true
- gifApiKey: "" (placeholder — injected at deploy time)
.gitignore: removed the incorrect config.json entry (tracked files are
unaffected by .gitignore; the entry was misleading).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config.json is managed directly on each deploy host (contains the GIF
API key and homeserver list) and is intentionally not committed. It was
already untracked so git reset --hard left it alone, but git clean would
have deleted it. Adding it to .gitignore makes the intent explicit and
protects it from accidental git clean runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs fixed:
1. Invisible toggle: index.css applies appearance:none to ALL
input[type=checkbox], making the native checkbox invisible. Replaced
the hidden <input type="checkbox"> with the folds Switch component
which is properly styled and visible in both TDS and non-TDS themes.
2. Compressed size never appeared (stale closure bug): handleChange was
async. After the first setMetadata() call the fileItem in selectedFiles
was replaced with a new object. The .then() callback still held the
OLD fileItem reference, so its setMetadata() call silently failed to
find the item (REPLACE action couldn't match the stale ref). Fix:
compression now runs in a useEffect triggered by the checked/compressible
state. fileItemRef and metadataRef are updated each render so the
async callback always writes to the current item. A stable fileKey
string (name + size) is used as the dep instead of the File object
reference so the effect doesn't re-run on every metadata update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Space voice channels send room-level RTC notifications (m.mentions.room)
whenever any member joins. Previously this triggered the incoming call
ring modal for every member of the space, as if they were personally
being called.
Fix: before setting callInfo, check whether the room is a DM (in
mDirectAtom) or a private non-space group (invite/knock/restricted join
rule AND no m.space.parent state event). Space channels and public rooms
are excluded — joining them should never ring other members.
Added JoinRule to the matrix-js-sdk import. Added directs to the
useCallback dependency array so the closure sees the current DM set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The checkbox was only shown for image/jpeg and image/png. Users
uploading WebP, GIF, AVIF, BMP, TIFF, HEIC (iPhone photos) or any
other raster format never saw the checkbox at all.
Fix: isCompressible now checks file.type.startsWith('image/') and
excludes only image/svg+xml (vector — would rasterise) and empty type
strings. compressImage signature widened to File | Blob so it matches
the TUploadContent type without unsafe casts.
The send-path guard in handleSendUpload was also widened from the
hardcoded jpeg/png check to use isCompressible(), keeping the two gates
in sync. The Blob-safe id attribute uses the .name fallback so it
doesn't break when originalFile is a Blob without a name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
matrix.lotusguild.org remains the default (index 0). matrix.org and
mozilla.org added as dropdown options. allowCustomHomeservers: true
lets users type any homeserver address for friends on other servers.
Also applied directly to /var/www/html/config.json — live immediately,
no deploy needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>