Commit Graph

237 Commits

Author SHA1 Message Date
jared aa48c9ef8a Fix three open bugs from LOTUS_BUGS.md
CI / Build & Quality Checks (push) Successful in 10m38s
Trigger Desktop Build / trigger (push) Failing after 5s
- 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>
2026-06-12 18:17:35 -04:00
jared 46567555e1 fix: ESLint errors and Prettier formatting
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 5s
ESLint errors:
- usePresenceUpdater: remove redundant `const userId` inside handlePageHide
  that shadowed the outer declaration (no-shadow)
- RoomViewHeader: prefix unused encryptedRoom with _ (no-unused-vars)

Prettier: reformat 14 files to match project style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 22:55:32 -04:00
jared c8ff7b0718 fix: remaining LOTUS_BUGS.md items — hardcoded hex and a11y
- 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>
2026-06-10 00:01:12 -04:00
jared 67fb0a5120 fix: image compression toggle visible + compressed size actually shows
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>
2026-06-05 21:31:05 -04:00
jared 6ec908407b fix: call ring modal only fires for DMs and private group chats
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>
2026-06-05 20:25:50 -04:00
jared 58b19995b8 fix: image compression checkbox now shows for all raster image types
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>
2026-06-05 20:22:38 -04:00
jared 08937c6278 fix: ESLint duplicate import + glassmorphism child opacity
ESLint: UploadCardRenderer.tsx had two separate imports from
../../utils/matrix — merged tryDeleteMxcContent into the existing
import statement. Removed now-unnecessary eslint-disable directive from
chatBackground.ts (the _anim prefix already suppresses the rule).

Glassmorphism: the Scroll inside SidebarNav had variant="Background"
which set a solid backgroundColor on the entire scrollable area,
completely hiding the sidebar's semi-transparent glass + backdrop-filter.
Fix: pass variant={undefined} when glassmorphismSidebar is on so the
inner scroll area is transparent and the blur effect is visible through
it. The document.body background (set by the previous useEffect fix)
now shows through the frosted glass as intended.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 13:41:29 -04:00
jared 8ac63e3771 fix: glassmorphism sidebar background visibility + image upload cleanup
Glassmorphism: the sidebar is a flex sibling of the room view, so
backdrop-filter had nothing behind it to blur. Fix: apply the active
chat background to document.body when glassmorphismSidebar is on
(cleaned up when it's turned off or the component unmounts). Now the
sidebar blurs through the same background pattern as the room view,
making the frosted-glass effect obvious.

Image upload cleanup: delete the pre-uploaded original MXC from the
homeserver after the compressed version is successfully uploaded
(Synapse 1.97+ DELETE /_matrix/client/v1/media/{server}/{mediaId}).
Also delete on cancel when a successful upload is removed by the user.
Both are best-effort — failures are swallowed so UX is unaffected.
Added tryDeleteMxcContent() utility to src/app/utils/matrix.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:25:19 -04:00
jared b5d831cc12 fix: presence ring shape mismatch, edit history (no text) on E2EE, reaction count
- PresenceRingAvatar: replace circular wrapper div (borderRadius 50%) with
  React.cloneElement injecting outline+outlineOffset directly onto the child
  Avatar element — outline follows the child's actual border-radius so the
  ring matches the avatar shape in every context

- EditHistoryModal: use getClearContent() for the Original entry instead of
  evt.event.content, which is still the ciphertext for E2EE messages and has
  no body field. getClearContent() returns decrypted content bypassing the
  _replacingEvent chain, fixing the "(no text)" shown for encrypted originals

- MessageQuickReactions: 5 → 3 emoji (toolbar too wide with 5)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:30:42 -04:00
jared c6932b45fb feat: glassmorphism sidebar toggle (P5-3)
Settings → Appearance: "Glassmorphism Sidebar" toggle (off by default).
When enabled, applies backdrop-filter: blur(12px) and a semi-transparent
background to the left sidebar so chat background patterns show through.
SidebarGlass vanilla-extract class in Sidebar.css.ts; wired in
SidebarNav.tsx via classNames conditional.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:02:18 -04:00
jared c5fbc20394 feat: presence avatar border rings (P5-18) + room emoji prefix support (P5-6)
P5-18: PresenceRingAvatar wrapper component applies a 2px box-shadow
ring to user avatars — green (online), yellow (idle/unavailable), red
(DND via status_msg='dnd'), no ring (offline). Applied to: message
timeline sender avatars, members drawer (members + knock requests),
@mention autocomplete, and inbox notification senders.

P5-6: Leading emoji in room names renders at 1.15× in the sidebar via
Unicode emoji regex detection in RoomNavItem. Emoji picker (EmojiBoard
in PopOut) added to all three room-name inputs: Create Room dialog
(converted to controlled input), Room Settings name field (shown only
when canEditName), and the "Rename for me" local rename dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:56:19 -04:00
jared 657ca3a5ca feat: GIF previews, room context menu, policy lists, mention pulse, collapsible messages, send animation, D&D fix
P3-5: Giphy/Tenor URL preview cards — full-width thumbnail from og:image
mxc URL, GIF badge overlay, site badge + title footer; GifCard shared by
both; BadgeGiphy (teal) and BadgeTenor (blue) CSS classes

P3-9: Policy list viewer — read-only panel in Room Settings + Space
Settings (admin/50+ PL only); enter room ID or alias; tabs for Users /
Rooms / Servers; glob pattern warning color; Ban badge; entity + reason

P5-8: Mention highlight pulse — 0.6s scale+glow keyframe on incoming
@mention messages; prefers-reduced-motion aware; only fires on new
incoming messages (isNewRef), not on history load; onAnimationEnd cleanup

P5-19: Collapsible long messages — ResizeObserver clamps text bodies
>320px with gradient fade + "Read more ↓" / "Show less ↑" button; resets
on eventId change; skips images/video/audio/file; smooth CSS transition

P5-23: Message send animation — own messages fade+scale in (0.97→1,
0.4→1 opacity, 150ms ease-out); prefers-reduced-motion aware; one-shot
via isNewRef + onAnimationEnd clear

P5-26: Room context menu — Copy Link (matrix.to URL, 1.5s Copied!
feedback), Mute with duration (15m/1h/8h/24h/indefinite, localStorage
timer key io.lotus.mute_timers), Mark as read; Icons.Link + Icons.BellMute

BUG D&D: dragCounter ref replaces fragile dragState machine — enter
increments, leave decrements (hides at 0), drop resets to 0; fixes
spurious dragleave from child element boundary crossings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 15:51:18 -04:00
jared d3dcf93f1a fix: compression UI — proper before/after display with folds tokens
Always shows Original size pill even before checkbox is checked.
After checking: shows 'compressing...' then reveals Compressed pill
with exact size and percentage saved. Green tint on compressed pill
when >5% saving; neutral when minimal gain.

Replaced all var(--bg-*) / var(--tc-*) CSS vars with folds
color.Surface.* and color.SurfaceVariant.* tokens so the UI renders
correctly in all themes. Blob cleanup is automatic — the compressed
Blob is referenced only during upload and GC'd with the upload atom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:37:11 -04:00
jared 8f5afcda08 fix: prettier formatting on UploadCardRenderer.tsx
CI caught missing prettier pass on the file modified by P3-3
image compression feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:38:34 -04:00
jared 9273eb5f2e feat: bookmarks, message scheduling, image compression, room insights
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>
2026-06-04 10:26:08 -04:00
jared ad508ac61e feat(P2-7): deep link preview cards for TikTok, X, Twitch, Reddit, YT Shorts
YouTube Shorts: portrait 9:16 thumbnail, red Shorts badge, channel parse
TikTok: portrait thumbnail, @user extract, caption parse (3 OG formats),
  hashtag chips, dark ♫ placeholder fallback
Twitter/X: tweet text parse from all og:title formats, media image when
  og:image:width>=300, profile vs tweet URL distinction, 𝕏 SVG badge
Twitch: live/clip/VOD detection, pulsing LIVE badge with CSS keyframes,
  game extraction from og:description, channel from URL
Reddit: r/subreddit badge, u/author + upvote + comment count parsed from
  og:description, post thumbnail 80x60, redd.it short URL support

Shared PortraitThumbnail (80x142) reused by TikTok + Shorts.
All brand hex colors in CSS file only, never in TSX.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 09:42:26 -04:00
jared 73921cb2a1 feat(P2-7): expand link previews to 13 domain-specific card types
YouTube, Vimeo (shared VideoCard with thumbnail + play overlay),
GitHub (repo name/description parse), Twitter/X (tweet text parse),
Reddit (subreddit + post title), Spotify (artwork + track/artist),
Twitch (thumbnail + LIVE badge), Steam (game header image),
Wikipedia (clean text card), Discord (server icon + member count),
npm (package name/description), Stack Overflow (question excerpt),
IMDb (portrait poster + title/rating)

Generic fallback gains favicon from google/s2/favicons; empty cards
(no title or description) are suppressed. Shared SiteBadge component
with brand-colour CSS classes per domain in UrlPreview.css.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:34:23 -04:00
jared 01ba24df12 feat: richer link preview cards and user local time display
P2-7: Domain-specific URL preview cards — YouTube shows mqdefault.jpg
thumbnail with ▶ play overlay + title; GitHub shows inline SVG icon +
owner/repo parsed from og:title + star/language info from og:description;
generic cards get a Google favicon when og:image is absent; empty cards
(no title or description) are suppressed entirely

P2-9: Live local time in user profiles — useLocalTime(timezone, hour12)
uses Intl.DateTimeFormat with the user's m.tz IANA zone; updates every
60s; shows clock icon + formatted time + timezone abbreviation (EST/JST
etc.) in dim text; respects existing hour24Clock setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:15:29 -04:00
jared 51a355fe77 feat: extended profile fields, push rule editor, server ACL editor
P2-8: Pronouns (m.pronouns) and Timezone (m.tz) fields in Settings →
Account → Profile; saved via MSC4133 PUT /profile/{userId}/{field};
useExtendedProfile hook fetches both in parallel; UserHero displays
pronouns below display name and timezone string below username

P2-11: Full push rule editor in Settings → Notifications below keyword
rules; covers override/room/sender/underride rule kinds; enable/disable
toggle per rule, human-readable labels for built-in rules, delete button
for custom rules, add-rule form for room and sender rules

P2-12: Server ACL viewer/editor in room settings (Server ACL tab);
reads m.room.server_acl state event; allow/deny server lists with
wildcard validation; allow IP literals toggle; power-level gated
(edit requires sufficient PL, otherwise read-only view)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:13:33 -04:00
jared ee717e8361 feat: PiP mute indicator, export history, activity log, unverified device warning
- PiP call window: mute overlay using MutationObserver on EC iframe's
  [data-testid="incall_mute"] button (data-kind="primary" = muted),
  same pattern as screenshare detection in CallControl.ts

- P2-4 Export Room History: new tab in room settings — Plain Text / JSON /
  HTML formats, optional date range, progress counter, paginated via
  paginateEventTimeline, blob download; E2EE-aware (skips failed decryptions)

- P2-6 Room Activity Log: new tab in room settings — filterable log of
  m.room.member, m.room.power_levels, m.room.name/topic/avatar/server_acl
  events with human-readable descriptions, relative timestamps, Load More
  pagination

- P2-10 Unverified Device Warning: warnOnUnverifiedDevices setting (default
  off); Warning.Container banner above composer in encrypted rooms with
  unverified devices; toggle in Settings → General → Privacy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 22:13:22 -04:00
jared 6d2bf9a582 fix: invite modal QR toggle — replace {} icon with "QR Code" button label
Icons.BlockCode is the {} curly-braces icon, which is unintuitive for a
QR code toggle. No QR-specific icon exists in folds, so replace the
IconButton with a labeled Button ("QR Code") that clearly communicates
its purpose. Also fix var(--bg-surface-border) → color.Surface.ContainerLine
in the QR panel border (the CSS variable doesn't exist in Cinny's theme).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:59:32 -04:00
jared 10cda6b632 fix: prettier formatting in PollContent and MediaGallery
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:40:28 -04:00
jared a3f776134f fix: ctrl+p print dialog, gallery 400 error, poll multi-choice UX
- Suppress Ctrl+P browser print dialog via SuppressPrintShortcut in
  ClientNonUIFeatures (no UI opened, just preventDefault)
- mxcUrlToHttp: build URL manually instead of delegating to SDK.
  The SDK forces allow_redirect=true when useAuthentication=true;
  Synapse's /_matrix/client/v1/media/thumbnail rejects that with 400.
  Manual construction omits allow_redirect entirely.
- Gallery: redesign using folds color tokens (color.Surface.*) instead
  of non-existent CSS custom properties; add ThumbState so broken
  images show an icon placeholder; use useAuthentication for thumbnails
  now that the URL builder is fixed; "Load More" always visible.
- PollCreator: replace raw <button> with folds Button components so the
  Single/Multiple choice toggle renders with actual visual difference.
- PollContent: support multiple-choice polls end-to-end —
  myVote:string → myVotes:Set<string>; computeVotes collects all
  m.selections (not just [0]); toggle-select for multi, radio for
  single; checkbox/radio indicator icons next to each option;
  "◉ Poll · Multiple choice" / "Single choice" label in header;
  sends full selections array on every vote event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:23:44 -04:00
jared 906884e434 feat: QR code in invite modal; fix CSP to allow api.qrserver.com
InviteUserPrompt: add QR code toggle button (Icons.BlockCode) in header.
When toggled, shows a 180x180 QR code image (api.qrserver.com) and the
raw invite URL below it, between the header and the search form.
inviteUrl computed once and shared between Copy Link and QR display.

Server: added https://api.qrserver.com to img-src in CSP header on
LXC 106 (/etc/nginx/sites-available/cinny) — nginx reloaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 10:10:25 -04:00
jared e18e089043 revert: remove redundant QuickSwitcher (Ctrl+K already does this better)
The existing SearchModalRenderer (Ctrl+K) is already a polished room/DM
switcher with avatars, unread badges, fuzzy search, and keyboard nav.
Our QuickSwitcher was an inferior duplicate. Removing it entirely:
- Delete QuickSwitcher.tsx
- Remove QuickSwitcherFeature from ClientNonUIFeatures
- Remove quickSwitcherKey from settingsAtom
- Remove Keyboard Shortcuts section from General settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:03:50 -04:00
jared 986e4bb93a fix: poll multiple-choice toggle + Sentry JAVASCRIPT-REACT-N
PollCreator: replace maxSelections/options.length stale-closure pattern
with isMultiple: boolean state. max_selections computed from filledOptions
at submit time. Radio inputs replaced with styled toggle buttons that
visually highlight the active selection.

PollContent: catch getPendingEvents error (Sentry JAVASCRIPT-REACT-N).
SDK throws Cannot call getPendingEvents with pendingEventOrdering ==
chronological when sending poll vote events with m.reference relation.
Silently catch so optimistic UI update stands — vote will retry on next
sync if needed.

Fixes JAVASCRIPT-REACT-N

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:55:50 -04:00
jared 7cf751a3a5 fix: CI Prettier, P1-6 poll button, P1-11 stale knock state
- LOTUS_TODO.md: Prettier formatting (CI gate fix)
- P1-6: Wire PollCreator into RoomInput — poll button (Icons.OrderList)
  opens modal, renders PollCreator when pollOpen is true
- P1-11: Reset knocked + knockError on room.roomId change via useEffect;
  add missing useEffect import to RoomIntro.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:14:55 -04:00
jared d43044ccbf feat: P1 features — quick switcher, media gallery, DM previews, knock-to-join, syntax highlighting
P1-1: Quick room switcher (Ctrl+K/Cmd+K) — QuickSwitcher.tsx + ClientNonUIFeatures hotkey
P1-2: Media gallery drawer (images/videos/files) — MediaGallery.tsx + RoomViewHeader toggle
P1-4: DM last message preview + relative timestamp in RoomNavItem when direct=true
P1-7: Code syntax highlighting — TDS tokenizer (syntaxHighlight.ts), custom CSS theme
       (.prism-tds-dark/.prism-tds-light), applied in react-custom-html-parser.tsx
P1-11: Knock-to-join — "Request to Join" in RoomIntro + Pending Requests in MembersDrawer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:45:57 -04:00
jared afe957015b feat: P1 features — voice speed, private receipts, room filter, favorites, invite link, poll creation
P1-5: Voice message playback speed toggle (0.75×/1×/1.5×/2×) in AudioContent.tsx
P1-10: Private read receipts toggle in Privacy settings; wired to notifications.ts
P1-3: Room filter input on Home tab and DMs tab (client-side, clears on tab switch)
P1-8: Favorite rooms via m.favourite tag — Favorites section in Home sidebar, star/unstar in right-click menu
P1-9: Room invite link + QR code in room settings (Share Room tile, api.qrserver.com QR)
P1-6: Poll creation modal in composer (PollCreator.tsx, sends m.poll.start)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:31:30 -04:00
jared a552e49ece fix: prefer-arrow-callback ESLint rule and Prettier formatting
Convert TypingIndicator named function expression to arrow function to
satisfy prefer-arrow-callback lint rule. Auto-format RoomInput.tsx to
resolve Prettier check failures in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 18:43:02 -04:00
jared dedbd54199 feat: typing indicator orange dots, push-to-deafen hotkey, night light filter, message length counter
- #108: TypingIndicator reads lotusTerminal setting; applies var(--lt-accent-orange)
  to container so dots inherit via backgroundColor:currentColor
- #100: CallControls registers KeyM as push-to-deafen (e.code, e.repeat guard,
  ownerDocument.body iframe-safe editable check, [callEmbed] dep array)
- P5-5: nightLightEnabled/nightLightOpacity settings; position:fixed rgba(255,140,0)
  overlay inside JotaiProvider; Night Light tile + intensity slider (5–80%) in
  Settings → Appearance
- #101: RoomInput charCount state via Slate onChange + toPlainText; resets on
  room switch; displayed before send button when count > 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 15:36:45 -04:00
jared 7b01cfebf5 fix: GIF CSP + edit history HTML rendering + unhandled rejection cleanup
- nginx (LXC 106, live): added https://*.giphy.com to connect-src CSP —
  browser was blocking fetch() to media2.giphy.com CDN with CSP violation
- EditHistoryModal: render formatted_body as sanitized HTML (via
  html-react-parser + sanitizeCustomHtml) with linkification for plain
  text, matching how messages render in the timeline
- useAsyncCallback + ThumbnailContent + ImageContent + VideoContent +
  ClientConfigLoader: use .catch(() => undefined) instead of void to
  silence unhandled promise rejections from fire-and-forget useEffect
  calls — errors already captured in AsyncState.Error for UI display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:34:46 -04:00
jared 7bafefa5e7 fix: suppress unhandled promise rejections from fire-and-forget useEffect loads
useAsync re-throws errors after storing them in state — correct for awaited
callers but causes unhandled rejections when load() is called without .catch()
in useEffects. The error is already captured in AsyncState.Error so the
re-throw provides no additional value in these fire-and-forget patterns.

Fixes JAVASCRIPT-REACT-M (Sentry: Media download failed: 401 Unauthorized)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:01:04 -04:00
jared 0bbfe17559 fix: GIFs, topic display, formatting toolbar, Copy Link in Invite, support URL
- RoomInput: GIF domain check uses endsWith('.giphy.com') — handles all
  Giphy CDN shards; all silent failure paths now show user-facing error
- RoomProfile: topic plain-text field is now HTML-stripped (no markdown
  syntax visible in header); formatting toolbar (B/I/S/code) above the
  textarea wraps selected text with correct markdown syntax
- InviteUserPrompt: Copy Link button added to dialog header with
  'Copied!' confirmation; removed Copy Link from both three-dot menus
- RoomViewHeader/RoomNavItem: unused copy-link imports removed
- nginx (live): support_page URL updated from lotusguild.org →
  matrix.lotusguild.org

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 09:55:04 -04:00
jared fc9ba03943 fix: remove unused useRoomName import, run prettier on all changed files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:38:35 -04:00
jared 16dddcb9f0 fix: comprehensive P0 quality pass — audit findings resolved
- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
  (--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
  accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
  cleanup, runtime JSON type guard, loading state, role display for all role values,
  remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
  hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
  FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
  getVersionBody handles formatted_body (strips HTML for text display),
  role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
  use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
  maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
  RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
  pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
  capitalization, improved encrypted preview warning text + Warning chip, add
  description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:30:27 -04:00
jared 3db87db03f chore: prettier formatting for P0 feature files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:55:06 -04:00
jared 3a72b7c1c5 feat: personal room name overrides (MSC4431-style)
Users can right-click any room and 'Rename for me...' to set a local
display name visible only to them. Stored in account data under
io.lotus.room_names. Shows a pencil indicator on renamed rooms.
useLocalRoomName() hook overrides useRoomName() when a local name exists.

Also includes:
- Rich room topic rendering via RoomTopicContent object (formatted_body
  support in RoomTopicViewer with HTML sanitization via sanitizeCustomHtml)
- Edit history viewer: clicking '(edited)' on a message opens a modal
  showing all prior versions with timestamps (EditHistoryModal.tsx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 17:21:11 -04:00
jared b243a18e01 fix(eslint): add missing useCallback deps, remove stale disable directives
- useMessageSearch: add fromTs/toTs to useCallback dep array (exhaustive-deps error)
- useMessageSearch: restore eslint-disable on the correct line for the `as any` cast
- VoiceMessageRecorder: remove two eslint-disable directives for rules that are
  globally off (jsx-a11y/media-has-caption) or not enabled (react/no-array-index-key)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:40:08 -04:00
jared 10f0093f8e feat(D3): apply terminal theme to voice message recorder
When lotusTerminal is enabled, the recording dot turns orange (#FF6B00),
the duration timer uses JetBrains Mono in green (#00FF88), and the
waveform bars match green — consistent with the PTT badge and GIF
picker terminal aesthetics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:48:49 -04:00
jared 48f9221f1c fix: resolve ESLint no-shadow errors in CallEmbedProvider
The rect variable in the onUp and onTouchEnd closures was shadowing the
outer rect declaration in handlePipMouseDown and handlePipTouchStart.
Renamed inner declarations to savedRect. Also renamed rect → elRect in
handlePipDoubleClick for the same reason.

Removed unused eslint-disable-next-line comment in MessageSearch.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:31:38 -04:00
jared bc63714a07 fix/polish: wave 1+2 improvements across six features
B1 - GIF upload progress: spinner on GIF button + disabled state while
     fetch+upload is in flight; clears on success or error
B2 - PiP position persistence: drag end saves left/top to localStorage;
     entering PiP restores saved position (clamped to current viewport)
B3 - PiP snap-to-corner: double-click the PiP overlay snaps to nearest
     corner with a 180ms CSS transition; saves new position
B4 - Device sessions loading state: useOtherUserDevices now returns
     {status:'loading'|'error'|'success', devices} instead of bare
     array; UserDeviceSessions shows spinner while loading
B5 - Device sessions error state: catch in hook sets status:'error';
     panel shows warning icon + 'Could not load sessions' message
B6 - Screenshare fullscreen Safari guard: hide button when
     document.fullscreenEnabled is false (iOS Safari, some mobile)
B7 - Status save error: show critical-coloured error text below Save
     button when saveState.status === AsyncStatus.Error
B9 - Encrypted search coverage counter: 'X / Y cached' badge next to
     'Encrypted Rooms' heading using existing localResult fields
D2 - PiP screenshare spotlight: track auto-spotlight in a ref; release
     spotlight when screenshare ends while in PiP mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:13:54 -04:00
jared f8ab136492 fix: cap status message length and constrain display overflow
Add maxLength=128 to the status input to prevent absurdly long statuses.
In UserHeroName (profile panel), tighten the status display to LineClamp2,
add overflow:hidden on the container, and use overflowWrap:anywhere so
unbroken strings (emoji chains, URLs) wrap instead of overflowing.
Member list already truncates via the truncate prop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:29:11 -04:00
jared 46da4458ff feat: custom status message — display + editor with emoji picker
- MembersDrawer: show presence.status as small muted text below
  username in every member row (live via useUserPresence)
- UserHero/UserHeroName: accept optional status prop; render below
  the @username handle in user profile popouts
- UserRoomProfile: pass presence?.status down to UserHeroName
- Profile settings: new ProfileStatus tile below Display Name
  * Input with inline emoji picker (lazy-loaded EmojiBoard)
  * Cursor-aware emoji insertion (preserves caret position)
  * Save via mx.setPresence({ status_msg }) / Clear button
  * Pre-fills from current presence; syncs on remote update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:39:51 -04:00
jared 0ebe24be20 feat: screenshare fullscreen button + pip spotlight, fix screenshare view
- Remove revert-to-grid logic that was overriding EC's natural screenshare
  spotlight, causing fullscreen to show user avatars instead of the screen
- Add fullscreen button to call controls (visible when screensharing) that
  requests fullscreen on the call embed container
- Add FullscreenButton component with enter/exit SVG icons to Controls.tsx
- PIP mode: sync setPipMode to CallControl; auto-enable spotlight when
  screenshare is active in pip so the screenshare fills the window
- Make useCallControlState accept undefined control for safe use in
  CallEmbedProvider
- Add package-lock.json to .gitignore (generated by local npm install)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 23:16:43 -04:00
jared 0d303169f2 fix: prevent editor blur on any click inside autocomplete menu
FocusTrap monitors focusin events and can redirect focus into the menu
container (blurring the editor) before individual MenuItem onMouseDown
handlers fire. Adding preventDefault at the container level ensures no
click anywhere inside the menu can steal focus from the editor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 21:35:38 -04:00
jared 54a912be1c fix: prevent editor blur on mouse click in autocomplete menus
Add onMouseDown preventDefault to all autocomplete suggestion MenuItems
so clicking a suggestion keeps the editor focused. Without this, the
mousedown event blurs the editor before onClick fires, causing Slate's
ReactEditor.toDOMNode to fail with "Cannot resolve a DOM node from Slate
node: {"text":""}" when Transforms.collapse tries to sync the selection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 21:10:01 -04:00
jared f3022f8be9 fix: defer moveCursor to next tick to prevent Slate DOM resolution crash
When selecting an autocomplete suggestion (user/room mention, emoticon,
command), Slate's replaceWithElement inserts a void inline node into the
model but React hasn't flushed the DOM update yet. Calling
Transforms.move + insertText immediately after causes ReactEditor.toDOMNode
to fail with "Cannot resolve a DOM node from slate node: {\"text\":\" \"}".

Fix: wrap moveCursor body in setTimeout(fn, 0) so React can flush the void
element's DOM node before Slate attempts to resolve the cursor position.
Also call ReactEditor.focus to restore editor focus after the autocomplete
menu item click blurs the editor.

Fixes all callers: UserMentionAutocomplete, RoomMentionAutocomplete,
EmoticonAutocomplete, CommandAutocomplete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 17:16:37 -04:00
jared 58cd88d815 ui: incoming call button hierarchy + EventReaders timestamp glow
- IncomingCall: Reject uses variant=Critical (red), Ignore uses variant=Secondary
  — previously both were variant=Success (green), making them visually identical
  to the Answer button
- EventReaders: TDS timestamp glow reduced from double-layer to single-layer
  (was 0 0 6px + 0 0 14px, now just 0 0 5px at 0.45 opacity)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:34:55 -04:00
jared 5d10afb7a6 ui: voice recorder pulse + TDS styling, wire mic denied error to input bar
- VoiceMessageRecorder recording dot now pulses (reuses pttLivePulse keyframe)
- Added data-voice-recorder / data-voice-rec-dot / data-voice-waveform attributes
  for TDS targeting: green pulsing dot, cyan waveform bars, subtle border in TDS dark
- Wire VoiceMessageRecorder onError to the same input-bar error display used by
  location errors (mic denied, media error surfaces to user instead of silent fail)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:30:12 -04:00