Lotus Bot ac5cd1195d chore: upgrade matrix-js-sdk and react-google-recaptcha
- matrix-js-sdk 41.5.0 → 41.6.0-rc.0
- react-google-recaptcha 2.1.0 → 3.1.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:41:14 -04:00
2022-12-20 20:47:51 +05:30
2022-01-30 20:58:38 +05:30
2022-12-20 20:47:51 +05:30
2024-05-22 21:56:44 +10:00
2024-09-07 19:15:55 +05:30
2023-02-24 17:28:04 +05:30

Lotus Chat

A Matrix client for Lotus Guild — forked from Cinny v4.12.1.

Deployed at chat.lotusguild.org.


Changes from upstream Cinny

Branding & Identity

  • Package renamed to lotus-chat, description updated to "Lotus Chat — Matrix client for Lotus Guild"
  • App title changed from "Cinny" to "Lotus Chat" throughout
  • Favicon, PWA icons, and all icon sizes (57×57 → 180×180 Apple touch icons) replaced with Lotus.png variants
  • Logo in About dialog and Auth page replaced with official Lotus.png
  • Auth footer rewritten: shows dynamic version from package.json, links to lotusguild.org, chat.lotusguild.org, and matrix.lotusguild.org
  • Welcome page tagline changed from "Yet another matrix client" to "A Matrix client for Lotus Guild"
  • Encryption key export filename changed from cinny-keys.txt to lotus-keys.txt
  • manifest.json updated with Lotus name, description, and branding colors

LotusGuild Terminal Design System (TDS) v1.2

A full custom theme engine layered on top of Cinny's vanilla-extract theming:

Dark mode (LotusTerminalTheme):

  • CRT terminal aesthetic: scanline overlay, vignette, phosphor glow
  • Palette: bg #030508, orange #FF6B00, cyan #00D4FF, green #00FF88, text #c4d9ee
  • Monospace font stack, terminal-style scrollbars
  • Custom hex-grid and circuit-board CSS background patterns
  • Matrix-style boot messages on the welcome page (press Escape to skip)
  • CSS variables: --lt-* family covering colors, glow effects, borders, animations

Light mode (LotusTerminalLightTheme):

  • Full light palette: bg #edf0f5, orange #c44e00, cyan #0062b8, green #006d35, text #111827
  • No CRT effects (scanlines, vignette disabled)
  • Light-mode scrollbars, adjusted code block colors, semantic color overrides
  • Scoped to html[data-theme="light"] body.lotusTerminalBodyClass
  • ThemeManager.tsx sets data-theme attribute based on active theme kind

Chat Backgrounds (20+ custom patterns, all TDS-aware):

  • Blueprint grid, carbon fiber, starfield, topographic contours, herringbone, crosshatch
  • Chevron, polka dots, triangles, plaid
  • All patterns use CSS custom properties — adapt to both TDS dark and light themes
  • Settings toggle for showing per-message sender profiles

Voice / Video Call Improvements

  • Element Call 0.19.3: Upgraded from 0.16.3. Dist copied to public/element-call/ by vite at build time.
  • Camera default OFF: Camera no longer persists across sessions via localStorage. Always starts disabled. Optional cameraOnJoin setting for explicit opt-in.
  • Deafen button: Tooltip corrected to "Deafen" / "Undeafen" (was "Turn Off Sound" / "Turn On Sound")
  • Screenshare confirmation: A confirm dialog appears before screenshare is broadcast to call participants
  • Auto-revert spotlight on screenshare: When someone starts screensharing, EC normally forces all participants into spotlight view. Patched in CallControl.ts onControlMutation() — detects the screenshare button going primary and clicks gridButton after 600ms to revert to grid layout. Participants choose to watch screenshare manually.
  • Push to Talk (PTT):
    • Configurable keybind (default: Space) via Settings > General > Calls
    • Mic activates on keydown, deactivates on keyup; mic muted on tab blur/focus to prevent stuck-on mic
    • Visual indicator: plain folds Chip by default; when LotusGuild TDS is active: orange PTT — Hold SPACE / green ● LIVE in JetBrains Mono
    • Listens on both main window and EC iframe contentWindow for reliable key capture
    • Implemented via CallControl.setMicrophone() public method on the widget bridge
    • Mic state preservation: when enabling PTT mode mid-call, the user's previous mic state is saved and restored when PTT is disabled — prevents unwanted unmute if the user had manually muted before switching to PTT.
  • Noise suppression toggle: Settings > General > Calls — passes noiseSuppression URL parameter to the embedded Element Call widget
  • Call button scoping: The upstream Cinny 4.12.1 call button (voice + video dropdown) is restricted to DMs and private group chats only. Specifically: direct messages, or invite-only rooms that have no m.space.parent state event (i.e. not a space/guild text channel). Public rooms and space channels are excluded to prevent accidental mass-notifications. Room.tsx switches to CallView layout when a call embed is active in the current room.
  • Poll display: m.poll.start events (both stable Matrix 1.7 m.poll content key and MSC3381 unstable org.matrix.msc3381.poll.start) render as read-only poll cards inside the standard message bubble — question and answer options shown. Registered as top-level event renderers AND inside the EncryptedContent callback so encrypted polls also display after decryption. "Open in Element to vote" note displayed. Implemented in PollContent.tsx.
  • Deleted message placeholder: Redacted m.room.message, m.room.encrypted, and m.sticker events no longer disappear from the timeline. Instead they reach the existing RedactedContent component (trash icon + italic "This message has been deleted" with reason if provided), matching Element, FluffyChat, Commet, and Nheko behaviour. One-line change in the eventRenderer filter in RoomTimeline.tsx.
  • Picture-in-picture (PiP): When navigating away from a call room while in an active call, the call embed shrinks to a 280x158px floating window in the bottom-right corner. The PiP window is draggable — drag it anywhere on screen to move it out of the way. Clicking (without dragging) navigates back to the call room. Drag vs click distinguished by a 5px movement threshold; touch drag supported. Imperative style overrides on callEmbedRef.current via useEffect — a wrapper div cannot be used because useCallEmbedPlacementSync writes top/left/width/height directly onto that element.

Messaging Enhancements

  • GIF picker: Giphy-powered GIF search and send. Button appears in the message composer only when gifApiKey is set in config.json. Sends GIF as m.image — fetches blob, uploads via mx.uploadContent, sends with mx.sendMessage. FocusTrap handles click-outside / Escape to close. When TDS is active: dark navy background (#060c14), orange dim border, // GIF_SEARCH header, CSS overrides for Giphy SDK search bar (dark bg, orange border/focus ring, JetBrains Mono), custom orange scrollbar. All TDS styles live in lotus-terminal.css.ts — no runtime <style> injection, eliminating flash of unstyled content.
  • Message forwarding: Forward any message to any room from the message context menu.
  • Image/video captions: Caption text field on image and video upload — sent as a single event with the media.
  • Location sharing: Map embed view for incoming location events + static share button. Renders m.location events inline with a map tile.
  • Deleted message placeholders: Redacted m.room.message, m.room.encrypted, and m.sticker events render as "This message has been deleted" with reason (if provided) rather than disappearing. One-line change in the eventRenderer filter in RoomTimeline.tsx.

Per-Message Read Receipts

Full per-message read receipt system — shows who has read each message directly in the timeline.

Architecture:

  • useRoomReadPositions(room) hook — computes a Map<eventId, userId[]> from all joined members' room.getEventReadUpTo() positions. Subscribes to RoomEvent.Receipt for live updates (debounced at 150ms to batch burst updates from mass-read events).
  • nearestRenderableId(liveEvents, evtId) — receipts can land on reaction/edit events that RoomTimeline skips (renders null). This walks backwards from the receipt event through the live timeline until it finds a non-reaction/non-edit event to attach to.
  • ReadPositionsContext — React context providing the positions map from RoomTimeline down to all Message instances without prop drilling.
  • ReadReceiptAvatars component — renders a pill-shaped row of overlapping StackedAvatar circles (24px, SurfaceVariant outline) below messages with readers. Pill uses color.SurfaceVariant.Container background for visibility on any wallpaper. Max 5 avatars shown + +N overflow count. Avatar fallback uses colorMXID(userId) for distinctive per-user color.
  • Clicking the pill opens the "Seen by" modal (EventReaders) listing all readers with their avatar, display name, and a formatted read timestamp ("Today at 3:42 PM", "Yesterday at 10:15 AM", "May 14 at 9:00 AM"). Timestamps use room.getReadReceiptForUserId(userId)?.data.ts and respect the user's 24-hour clock setting.
  • Authenticated media (mxcUrlToHttp utility) used for all avatar loads, matching the correct Lotus utility signature.

Delivery Status Indicators

Own messages display a small status marker below the message content (when no read receipts are visible yet):

  • — message is being sent / encrypting
  • — message confirmed sent (local echo)
  • — message failed to send (shown in red; orange glow in TDS mode)
  • Status hidden once the server confirms receipt (status === null) — read receipts take over at that point

URL Preview Cards (TDS)

URL preview cards (UrlPreviewCard) styled for terminal mode:

  • Dark transparent background with cyan border-left accent (Anduril Orange)
  • Link text in cyan, hover switches to orange with glow
  • Light TDS variant: off-white background with blue accent

Reaction Chips (TDS)

Emoji reaction buttons styled for terminal mode via button[data-reaction-key] selector:

  • Unselected: rgba(0,212,255,0.06) background, cyan border
  • Hover: brighter background + box-shadow glow
  • Own reaction (aria-pressed=true): orange tint rgba(255,107,0,0.12), orange border
  • Light TDS: equivalent blue/orange variants

DM Call Improvements

  • Incoming call ring: DM calls trigger a ring tone with Answer/Decline UI. 30-second auto-dismiss if unanswered. Implemented in Room.tsx and RoomViewHeader.tsx.

Infrastructure

  • Authenticated media: All avatar/media loads use mxcUrlToHttp(mx, mxcUrl, useAuthentication, w, h, 'crop') from ../../utils/matrix — the Lotus utility that handles MSC3916 authenticated media. (Upstream Cinny uses the SDK method with incorrect argument order for authenticated endpoints.)
  • Upstream tracking: git remote add upstream https://github.com/cinnyapp/cinny.git. Merge strategy: git fetch upstream && git merge upstream/main. Daily check via cinny-upstream-check.sh on LXC 106 — notifies Matrix on new upstream commits.

Build

npm ci
npm run build   # outputs to dist/

Vite's render-chunks phase requires ~6 GB Node heap. If OOM killed, set:

NODE_OPTIONS=--max_old_space_size=6144 npm run build

Deployment

Built files are served from /var/www/html/ on LXC 106 (nginx). Config lives at /opt/lotus-cinny/config.json (vite copies it to dist/):

{
  "defaultHomeserver": 0,
  "homeserverList": ["matrix.lotusguild.org"],
  "allowCustomHomeservers": false,
  "gifApiKey": "<giphy_key>"
}

Key Custom Files

File Purpose
src/lotus-terminal.css.ts All TDS CSS tokens, global styles, light/dark variants
src/lotus-boot.ts Boot sequence animation (runs once per session)
src/app/hooks/useRoomReadPositions.ts Per-message read receipt position map
src/app/features/room/ReadPositionsContext.ts React context for read positions
src/app/components/read-receipt-avatars/ Read receipt avatar pill component
src/app/components/event-readers/EventReaders.tsx "Seen by" modal with timestamps
src/app/components/GifPicker.tsx GIF search + send
src/app/features/call/CallControls.tsx PTT badge + keybind logic
src/app/plugins/call/CallControl.ts EC widget bridge (screenshare revert, PTT mic)
src/app/components/CallEmbedProvider.tsx PiP + draggable call embed
S
Description
Lotus Guild fork of Cinny — custom Matrix web client
Readme AGPL-3.0 90 MiB
Languages
TypeScript 98.2%
JavaScript 1.4%
CSS 0.3%
HTML 0.1%