1949 Commits

Author SHA1 Message Date
jared 5469740f4c feat: P5-21 mention color, P5-22 font selector, P5-27 notification presets; update docs
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 7s
- 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>
2026-06-10 15:39:35 -04:00
jared 891f2daf99 feat(P3-6): configurable composer toolbar buttons
CI / Build & Quality Checks (push) Successful in 10m24s
Trigger Desktop Build / trigger (push) Successful in 7s
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>
2026-06-10 13:20:29 -04:00
jared b7daabe2e0 fix: presence no longer wipes custom status, correct manifest icon paths
CI / Build & Quality Checks (push) Successful in 10m36s
Trigger Desktop Build / trigger (push) Failing after 7s
- 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>
2026-06-10 11:36:55 -04:00
jared d78f81c3a7 fix(ci): show HTTP response body when dispatch fails for debugging
CI / Build & Quality Checks (push) Successful in 10m43s
Trigger Desktop Build / trigger (push) Failing after 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 10:05:50 -04:00
jared 170d22eebb docs: update LOTUS_BUGS.md to reflect all recently fixed items
CI / Build & Quality Checks (push) Successful in 10m39s
Trigger Desktop Build / trigger (push) Failing after 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:05:25 -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 bafd9cbe75 fix: address confirmed bugs from LOTUS_BUGS.md audit
CI / Build & Quality Checks (push) Successful in 10m54s
Trigger Desktop Build / trigger (push) Failing after 8s
- 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>
2026-06-09 22:56:06 -04:00
jared f11b308f91 fix(ci): use workflow_dispatch to trigger cinny-desktop build
CI / Build & Quality Checks (push) Successful in 11m31s
Trigger Desktop Build / trigger (push) Failing after 6s
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>
2026-06-09 20:43:25 -04:00
jared 81e1a25de6 fix(ci): trigger cinny-desktop build via Gitea contents API
CI / Build & Quality Checks (push) Successful in 11m46s
Trigger Desktop Build / trigger (push) Successful in 29s
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>
2026-06-09 19:31:42 -04:00
jared 8ff2f33d3a fix(ci): replace workflow dispatch with submodule bump push
CI / Build & Quality Checks (push) Successful in 12m21s
Trigger Desktop Build / trigger (push) Successful in 8s
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>
2026-06-09 19:02:58 -04:00
jared ea15db430c docs: add feature reference, bug tracker, and implementation notes
CI / Build & Quality Checks (push) Successful in 11m51s
Trigger Desktop Build / trigger (push) Failing after 4s
- 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>
2026-06-09 18:41:24 -04:00
jared 7e4178f7e2 Trigger cinny-desktop build on lotus branch push
CI / Build & Quality Checks (push) Successful in 10m34s
Trigger Desktop Build / trigger (push) Successful in 5s
2026-06-07 12:32:47 -04:00
jared 71386f4ef2 config: 3 homeservers, gifApiKey moved to server secrets
CI / Build & Quality Checks (push) Successful in 10m38s
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>
2026-06-05 21:58:40 -04:00
jared ec391100bd chore: add config.json to .gitignore
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>
2026-06-05 21:33:53 -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 a78cb46bfe config: add matrix.org + mozilla.org homeservers, allow custom
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>
2026-06-05 14:49:22 -04:00
jared 78090d1c2d fix: glassmorphism actually visible + ESLint import/order
Glassmorphism root cause (from audit): the body-background useEffect
had `lotusTerminal && chatBackground === 'none' ? 'tactical'` as the
fallback guard — meaning the tactical grid only appeared when Lotus
Terminal mode was active. With default settings (TDS off, no chat
background chosen), the body got no background at all, so
backdrop-filter had a flat solid colour to blur — identical to unblurred.
Fix: drop the `lotusTerminal &&` guard so the tactical dot-grid is
always the fallback when chatBackground is 'none', regardless of theme.
Glassmorphism is now visible in all themes without any additional setup.

ESLint: RoomProfile.tsx had `../../../components/emoji-board` imported
before `matrix-js-sdk` violating import/order. Moved it after.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 14:47:18 -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 dc01480175 docs: update LOTUS_TODO + README for P5-4 and glassmorphism fix
Mark P5-4 (animated chat backgrounds) complete with implementation
notes. Update README Settings section with animated backgrounds entry
and correct glassmorphism description (now explains the body-background
fix that makes backdrop-filter actually visible).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:49:30 -04:00
jared 9447e64e5e feat(P5-4): animated chat backgrounds + pause toggle
5 new CSS-only animated backgrounds in the chat background picker:
- Digital Rain: two-layer vertical stripe scroll with parallax (wide
  stripes at 8s, narrow at 4s via single keyframe with split positions)
- Star Drift: three-layer radial-gradient star field drifting diagonally
- Grid Pulse: neon grid lines that expand/contract (backgroundSize keyframe)
- Aurora Flow: large radial gradient bands sweeping across 200% canvas
- Fireflies: three layers of glowing dots drifting across the viewport

All use vanilla-extract keyframes (GPU-composited transforms/positions,
no canvas, no JS timers). prefers-reduced-motion is respected in
getChatBg() by stripping the animation property at call time. A "Pause
Background Animations" toggle in Settings → Appearance provides an
in-app override for the same purpose.

BG labels de-duplicated ("Digital Rain", "Star Drift", "Aurora Flow")
to avoid the duplicate "Stars" and "Aurora" entries that had appeared.
LIGHT anim-fireflies background corrected from near-black #0a0a10 to
warm white #fffdf0. Four unused keyframe exports removed from
Animations.css.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:46:16 -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 5d525d4246 docs: update TODO and README for ring fix, E2EE edit history fix, reaction count
- P5-17: 5 → 3 emoji count
- P5-18: box-shadow/borderRadius → outline/cloneElement implementation note
- P0-6 edit history: document getClearContent() E2EE fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:33:51 -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 8866da0a82 fix: reduce quick reaction emoji count from 5 to 3 (toolbar too wide)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:22:09 -04:00
jared d1de438f67 ci: make Prettier check continue-on-error like TypeScript and ESLint
Prettier formatting issues blocked two deploys today. Build is the only
hard CI gate that should block deployment — style checks are informational.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:16:56 -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 517e992dec fix: prettier formatting in toast.ts (trailing commas)
Fixes CI Prettier check failure on commit 4876c2e4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:01:54 -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 4876c2e4ca feat: quick emoji reactions on hover, in-app notification toasts, mention pulse audit
P5-17: MessageQuickReactions moved from 3-dots menu to hover toolbar;
shows 5 recent emoji directly on hover. Clicking a quick-reaction also
closes any open emoji picker (setEmojiBoardAnchor). Line separator
removed from component.

P5-7: LotusToastContainer slides in from bottom-right when window is
focused — replaces OS notification for in-focus events. Correct room
path (DM vs home) derived from mDirectAtom. Invite toast routes to
inbox. 4s auto-dismiss. Full TDS styling via CSS custom properties.

P5-8: Confirmed already implemented upstream (MentionHighlightPulse,
0.6s scale+glow, one-shot, prefers-reduced-motion). Marked complete.

Code-review fixes: toast navigation used nonexistent /room/ route;
emoji picker stayed open after toolbar quick-reaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:32:37 -04:00
jared 24e6882e72 docs: mark P3-5, P3-9, P5-19, P5-23, P5-26 complete in TODO and README
Inline GIF preview, policy list viewer, collapsible long messages,
message send animation, and right-click room context menu improvements
documented and checked off.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:56:55 -04:00
jared 6a91904469 fix: MSC4140 correct endpoint — standard v3 send + org.matrix.msc4140.delay param
Synapse 1.153 MSC4140 implementation does NOT use an unstable-prefix rooms
send path. The correct API is:
  PUT /_matrix/client/v3/rooms/{id}/send/{type}/{txnId}
      ?org.matrix.msc4140.delay={ms}
which returns { delay_id } instead of { event_id }.

Cancel/restart remain at:
  POST /_matrix/client/unstable/org.matrix.msc4140/delayed_events/{id}
  body: { action: 'cancel' | 'restart' }

Also: context menu — copy link removed, mute durations converted to
PopOut submenu using RectCords pattern (e.currentTarget.getBoundingClientRect)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 17:59:39 -04:00
jared 08e7f33cba fix: remove copy link, convert mute to PopOut submenu
Copy Link removed — invite link is already in the invite modal.

Flat mute duration items replaced with a single "Mute →" MenuItem
that opens a PopOut submenu (Right/Start) with the 5 durations:
15 minutes / 1 hour / 8 hours / 24 hours / Indefinitely.
Anchor uses RectCords pattern (e.currentTarget.getBoundingClientRect)
matching the existing menu pattern in this file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 17:51:43 -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 fbdd0e7083 fix: message scheduling 404 and date/time picker UX
API fix: delay was embedded in the path string causing 404 — moved to
proper query param object; prefix changed from '' to the full MSC4140
unstable prefix so authedRequest builds the correct URL. Cancel and
restart endpoints fixed the same way.

Date/time picker: replaced single datetime-local (hard to use time
portion) with separate date + time inputs side by side; colorScheme:'dark'
hints the browser to render calendar/clock popups in dark mode to match
the app. Preview row now shows as a styled Primary.Container chip with
clock icon. Relative time ("in 2h 15m") shown below the label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 15:05:09 -04:00
jared c80f8c6427 fix: unverified device warning — rounded corners, left accent border
Replaced full-width sharp-cornered flat block with a compact rounded
notice: R300 border-radius, 3px amber left border accent, slight side
margins (S300) and bottom gap (S100) so it sits naturally above the
composer without looking jarring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:48:19 -04:00
jared 696e958a00 docs: mark P2-4 through P3-7 complete in LOTUS_TODO and README
LOTUS_TODO.md: 12 features changed [  ] → [x] with COMPLETED June 2026
summaries: P2-4 export history, P2-6 activity log, P2-7 link previews
(13 domains), P2-8 extended profile fields, P2-9 local time display,
P2-10 unverified device warning, P2-11 push rule editor, P2-12 server
ACL editor, P3-1 bookmarks, P3-2 message scheduling, P3-3 compression,
P3-7 room insights

README.md: added entries for all 12 new features in their respective
sections; added 11 new rows to Key Custom Files table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:40: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 c6760b0ba4 fix: schedule button, compression visibility, activity log, insights overflow, bookmarks UI
Schedule message: modal now always opens (even with empty composer);
includes its own message textarea pre-filled from editor content;
removed null-content early-return guard from handleScheduleClick;
fixed error text to use color.Critical.Main not CSS var

Image compression: removed 200KB size threshold — checkbox now shows
for all JPEG/PNG uploads (not just large ones); 'no significant saving'
message handles already-small files gracefully

Activity log: auto-paginate on mount — state events are absent from
initial sync window, so the log was always empty until Load More was
clicked manually

Insights summary: Text size H4→H5 with toLocaleString() formatting and
overflow:ellipsis so large numbers don't push tiles off screen

Bookmarks panel: replaced var(--bg-*) CSS vars (undefined in folds
themes) with color.Surface/SurfaceVariant/Primary folds tokens; added
left accent border on message preview block, count badge, clear button
in search, improved empty state, cleaner button hierarchy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:07:12 -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 160db1eaef fix: export deduplication and PiP remote mute detection
Export: timeline.getEvents() returns the entire growing window on every
pagination step, causing the same events to be added multiple times.
Fixed by tracking seen eventIds in a Set and skipping duplicates.

PiP mute: replace silence-inference with real remote participant mute
state. EC renders a [data-muted] attribute per participant tile with
aria-label=userId. Watch attribute changes via MutationObserver,
filter out local user, show overlay when any remote is muted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 22:44:11 -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 6d0b778755 docs: mark P2-1/2/3/5 complete; update README and landing page
- LOTUS_TODO.md: [x] P2-1 (upstream JumpToTime), P2-2 (custom sounds),
  P2-3 (sort rooms), P2-5 (quiet hours)
- README.md: new Notification Enhancements section (custom sounds, quiet
  hours); Room sort order added to UX & Composer section
- landing/index.html: three new comparison rows — custom notification
  sounds, quiet hours, room sort order

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 20:32:22 -04:00
jared 5ae84cbeaf feat: P2-3 sort rooms, P2-5 quiet hours, P2-2 custom notification sounds
P2-3 — Sort Non-Space Rooms:
- homeRoomSort: 'recent' | 'alpha' | 'unread' setting (default 'recent')
- factoryRoomIdByUnread comparator: unread rooms first, tie-break by count
- Sort icon button in Rooms NavCategoryHeader opens PopOut menu with
  three options (Recent Activity / A→Z / Unread First), checkmark on active
- Collapsed state still filters to unread-only regardless of sort choice

P2-5 — Notification Quiet Hours:
- quietHoursEnabled / quietHoursStart / quietHoursEnd added to settings
  (defaults: false, '23:00', '08:00')
- isInQuietHours() helper handles both normal and overnight spans;
  start===end treated as zero-length window (disabled) to avoid silent no-op
- Both InviteNotifications and MessageNotifications gate notify() and
  playSound() behind the quiet-hours check
- Settings → Notifications: new Quiet Hours card with Switch + two
  <input type="time"> fields (only shown when enabled)

P2-2 — Custom Notification Sounds:
- messageSoundId / inviteSoundId settings: 'notification'|'invite'|'call'|'none'
- notificationSounds.ts: shared NOTIFICATION_SOUND_MAP (removes duplication
  between ClientNonUIFeatures and SystemNotification — code review fix)
- Audio source updated reactively via useEffect when sound ID changes
- Settings → Notifications: Message Sound + Invite Sound selects expand
  when the master sound toggle is on; each has a ▶ preview button
- playPreview() catches audio.play() rejections (code review fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 19:41:02 -04:00