Commit Graph
100 Commits
Author SHA1 Message Date
jaredandClaude Sonnet 4.6 8c711f5f4a feat(mobile): Saved Messages accessible from room header More Options menu
CI / Build & Quality Checks (push) Successful in 10m29s
CI / Trigger Desktop Build (push) Successful in 15s
On mobile, SidebarNav (which contains BookmarksTab) is hidden while
inside a room. Added a Saved Messages toggle to the RoomMenu (···More
Options) so users can open/close the bookmarks panel without leaving
the room. Works on all screen sizes; UNTESTED on device.

Also marks Remind Me Later as done in LOTUS_TODO — it was already
fully wired (RemindMeDialog + ReminderMonitor + Message.tsx trigger).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 15:18:38 -04:00
jaredandClaude Sonnet 4.6 c4f00ed483 fix(mobile): apply useModalStyle to 7 more modal dialogs
ScheduleMessageModal, PollCreator, JoinAddressPrompt, JumpToTime,
EditHistoryModal, ForwardMessageDialog, RemindMeDialog — all now
render fullscreen on mobile (≤750px) and as a capped-width box on
desktop, consistent with the existing useModalStyle pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 15:14:30 -04:00
jaredandClaude Sonnet 4.6 f5c301d5c6 fix(mobile): useModalStyle on 3 more dialogs, GPU hint animated bg, document untested fixes
- DeviceVerification, InviteUserPrompt, LogoutDialog: apply useModalStyle for
  fullscreen on mobile, capped box on desktop
- chatBackground: inject willChange + contain:paint on animated variants to
  promote compositor layer and prevent descendant repaint flickering (Bug #2)
- LOTUS_BUGS.md: mark #7-#10 FIXED/UNTESTED, #2 FIXED/UNTESTED, chatBackground
  backgroundColor values as PATTERN CONTENT EXCEPTION

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:51:28 -04:00
jaredandClaude Sonnet 4.6 c395f7d16e fix(mobile): touch targets, keyboard viewport, PageNav overflow, modal fullscreen
CI / Build & Quality Checks (push) Successful in 10m27s
CI / Trigger Desktop Build (push) Successful in 5s
- Bug #10: use `100dvh` on <html> so layout shrinks when mobile virtual keyboard
  appears (prevents composer from being pushed off-screen)
- Bug #7: add `minWidth/minHeight: 44px` to all 8 composer toolbar IconButtons on
  mobile via mobileOrTablet() check (WCAG 2.1 AA touch target requirement)
- Bug #8: add `@media (max-width: 750px) { width: 100% }` to PageNav recipe
  variants so the nav panel fills full width on mobile instead of overflowing
  with its fixed desktop width
- Bug #9: introduce `useModalStyle(maxWidth)` hook — returns fullscreen styles on
  mobile (no border-radius, no max-width cap, height 100%) and desktop box styles
  otherwise; applied to LeaveRoomPrompt, LeaveSpacePrompt, ReportRoomModal,
  ReportUserModal
- Bug #11: mark as FALSE POSITIVE in LOTUS_BUGS.md — `useState(() => atom(...))`
  is the correct Jotai pattern for stable local atom references
- Scheduled Messages persistence: mark as FIXED — already uses atomWithStorage +
  createJSONStorage with error-safe JSON parsing
- UrlPreviewCard TDS colors: mark as BRAND EXCEPTION — SVG logo fills and site
  badge backgrounds are official third-party brand colors; cannot convert without
  inventing new CSS variables (violates TDS rule 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:34:40 -04:00
jaredandClaude Sonnet 4.6 26f900870b feat: MSC4260 Report User, Bug #6 mutual exclusion, TDS toast compliance
CI / Build & Quality Checks (push) Successful in 10m28s
CI / Trigger Desktop Build (push) Successful in 6s
- Add ReportUserModal.tsx — category dropdown + reason input, calls
  POST /_matrix/client/v3/users/{userId}/report via mx.http.authedRequest,
  inline success/error feedback, auto-closes 1500ms after success
- Wire Report User button into UserRoomProfile.tsx between UserModeration
  and UserDeviceSessions (hidden for own profile)
- Bug #6: enforce mutual exclusion between chat backgrounds and seasonal
  themes — ChatBgGrid clears seasonal→'off' on non-'none' pick;
  SeasonalBgGrid clears chatBackground→'none' on real theme pick;
  SeasonalEffect guards against legacy persisted state at render time
- TDS: strip all hardcoded hex/rgba fallbacks from LotusToastContainer.tsx
  (var(--lt-bg-card), --lt-accent-orange, --lt-text-primary/secondary,
  --lt-accent-orange-dim/border, --lt-box-glow-orange)
- Mark Bug #6 FIXED, MSC4260 DONE, toast TDS FIXED in LOTUS_BUGS.md and
  LOTUS_TODO.md; note EventReaders + CallControls already compliant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:37:44 -04:00
jaredandClaude Sonnet 4.6 bb99ad5611 fix(remind-me): match modal aesthetic to existing dialogs
CI / Build & Quality Checks (push) Successful in 10m28s
CI / Trigger Desktop Build (push) Successful in 10s
Replace raw CSS variables and Button presets with folds color/config
tokens and MenuItem components, matching the ForwardMessageDialog and
ScheduleMessageModal patterns exactly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:49:24 -04:00
jaredandClaude Sonnet 4.6 6c58e25211 style: fix prettier formatting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:37:54 -04:00
jaredandClaude Sonnet 4.6 b24ab838f8 feat: Remind Me Later, mobile bookmarks, bug fixes, and doc cleanup
CI / Build & Quality Checks (push) Successful in 10m37s
CI / Trigger Desktop Build (push) Successful in 8s
Features:
- Remind Me Later: message context menu item opens a preset time picker
  (20 min / 1 hr / 3 hr / tomorrow 9am); reminders persist to Matrix
  account data (io.lotus.reminders); ReminderMonitor fires a Lotus Toast
  when due, checks every 30s and on tab focus
- Mobile Bookmarks: BookmarksPanel now renders on all screen sizes;
  passes isMobile prop for full-screen absolute overlay on mobile

Bug fixes:
- usePan.ts: memory leak from stale closure in document listener cleanup
- EventReaders.tsx: replace hardcoded hex colors with TDS CSS variables
- CallControls.tsx: replace hardcoded hex colors with TDS CSS variables
- CustomHtml.css.ts: replace hardcoded yellow/black highlight with theme tokens

Docs:
- LOTUS_TODO.md: restore deleted content (Confirmed facts table, Pending
  Audits, P5-30 completed status, full feature descriptions), keep new
  additions (P4-7/8/9, P5-41–57, Implementation Reference), eliminate
  duplicate sections
- LOTUS_BUGS.md: merge RESILIENCE_AUDIT.md findings into Architectural &
  Resilience Audit table; delete RESILIENCE_AUDIT.md
- Remove stale LOTUS_DENOISE_ENGINEERING_REVIEW.md and LOTUS_TODO_REFERENCE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:26:43 -04:00
jaredandClaude Opus 4.8 cf7c66b99a Reorganize ML noise suppression settings UI
Move the model comparison out of the always-visible Noise Suppression
description and into the ML-only sub-settings. Add a compact info card
for the selected model (CPU / voice quality / transients / download) plus
a collapsible 4-model comparison. Group ML sub-settings into Model,
Enhancements, and Test & calibrate sections with clear labels and
separators. Fix invented --lt-border-color token and hardcoded
rgba background to real TDS tokens. Build the model dropdown and
DenoiseTester labels/compare buttons from DENOISE_MODELS so
DeepFilterNet 3 is handled correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:02:16 -04:00
jaredandClaude Opus 4.8 04b56ffacd feat(denoise): add self-hosted DeepFilterNet 3 ML noise-suppression model
Integrate DeepFilterNet 3 (deepfilternet3-noise-filter@1.2.1) as a new
client-side denoise model id 'deepfilternet', mirroring the DTLN pattern.

The npm package ships only an ESM whose AudioWorklet processor + wasm-bindgen
glue are inlined as a string (loaded via a Blob URL — no CDN for the worklet).
Its only runtime fetches are a single-threaded df_bg.wasm and an ONNX model
tarball, which previously loaded from an external CDN. We now VENDOR both
(build/denoise-vendor/deepfilternet/v2/...) and self-host them under
denoise/deepfilternet/, overriding the package's cdnUrl so nothing hits the
upstream CDN — keeping it self-hosted / Tauri-CSP safe.

The wasm is single-threaded (no SharedArrayBuffer / atomics / imported shared
memory), so it needs no COOP/COEP cross-origin isolation and runs fine in EC's
non-isolated iframe. Runs at 48 kHz fullband. Any init/runtime failure falls
back to the raw mic, like the other models.

- vite.config.js: copy ESM + vendored wasm/model into the EC denoise dir with a
  required-asset guard that aborts the build if any entry is missing.
- build/lotus-denoise.js: 'deepfilternet' branch — dynamic-import the ESM, build
  a DeepFilterNet3Core pointed at the self-hosted base, await init, return the
  worklet node; 48 kHz; raw-mic fail-safe preserved.
- denoisePipeline.ts: 'deepfilternet' branch for the in-app tester + sampleRate.
- settings.ts: add 'deepfilternet' to DenoiseModelId + getSettings whitelist.
- lotusDenoiseUtils.ts: add the comparison-chart row.
- General.tsx: add the "DeepFilterNet 3 (beta)" dropdown option.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:57:08 -04:00
jaredandClaude Opus 4.8 abb7f743b8 fix(calls): DTLN at 16kHz + raw-capture A/B; explains weak/robotic results
CI / Build & Quality Checks (push) Successful in 10m23s
CI / Trigger Desktop Build (push) Successful in 5s
Two issues found from real testing of the in-app tester:

1. Raw ≈ RNNoise ≈ Speex sounded identical in Record & compare because the clip
   was captured with browser noise suppression ON (the user's native-NS
   setting), so "Raw" was already cleaned and the models had nothing left to
   remove. Record & compare now captures fully raw audio (noiseSuppression /
   AGC / echoCancellation off) so each model's effect on real noise is audible.
   (Friends still heard differences in calls — the models work; the test was
   feeding them pre-cleaned audio.)

2. DTLN was robotic/choppy/quiet because @workadventure/noise-suppression
   targets 16 kHz (AUDIO_CONFIG.sampleRate) and does NOT resample internally,
   while we ran it at 48 kHz. Run DTLN's whole graph in a 16 kHz context:
   - denoisePipeline: add sampleRateFor(model) (16k for dtln, 48k otherwise);
     tester live-monitor + playback contexts use it (bufferSource resamples the
     48k clip down for DTLN).
   - shim (build/lotus-denoise.js): SAMPLE_RATE is now model-aware, so DTLN is
     correct in real calls too (it was previously broken at 48 kHz). The 16 kHz
     processed track is still published to LiveKit (WebRTC/Opus resamples).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:27:15 -04:00
jaredandClaude Opus 4.8 14cfa021c5 feat(calls): in-app denoise tester to audition models + calibrate gate
CI / Build & Quality Checks (push) Successful in 10m24s
CI / Trigger Desktop Build (push) Successful in 17s
The previous "Test Microphone" meter only showed a raw 0-100% level bar — it
never ran the gate or any model, and its scale wasn't dBFS, so it couldn't tell
you which threshold to pick or let you hear the models solo. Replace it with a
real tester that reuses the shipped worklets (/public/element-call/denoise/) in
a main-app AudioContext, mirroring the call pipeline (source -> gate -> model).

- denoisePipeline.ts: shared loader for the RNNoise/Speex flat worklets and the
  DTLN @workadventure helper, the noise gate, and a dBFS RMS meter helper.
- DenoiseTester.tsx:
  - Live monitor: hear yourself through the selected model (+gate) in real time
    (headphones) with In/Out dBFS meters and a threshold marker on the In meter
    so the gate value is meaningful to calibrate.
  - Record & compare: capture a short clip, then A/B the same audio Raw vs
    RNNoise vs Speex vs DTLN.
- Wire it into the ML settings block; remove the old raw-only MicMeter. Use real
  TDS tokens (--accent-*, --border-color, --bg-card) instead of the invented
  --lt-* names + hardcoded hex the old meter used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:53:57 -04:00
jaredandClaude Opus 4.8 86272b6b08 fix(calls): wire DTLN ML denoise correctly via @workadventure JS API
CI / Build & Quality Checks (push) Successful in 10m25s
CI / Trigger Desktop Build (push) Successful in 6s
The prior DTLN attempt (89a2321d) broke the build (missing dep, wrong
`cinny/` asset paths) and typecheck (`'dtln'` not in DenoiseModelId), and was
wired against an API the package doesn't expose. @workadventure/noise-
suppression is not a flat AudioWorklet — it's a self-contained ES module whose
processor name is `workadventure-noise-suppression` and which resolves its own
LiteRT WASM + TFLite models via import.meta.url. Driving it by hand-rolled
addModule + processorOptions cannot work.

- Re-add @workadventure/noise-suppression@0.0.4 (package.json + lockfile).
- vite: copy the package's whole dist/ tree intact to
  denoise/workadventure/ (preserving assets/ + vendor/litert) so import.meta
  resolution works at runtime; fail the build if the entry module is missing.
- shim: for the DTLN model, dynamic-import denoise/workadventure/audio-worklet
  .js and use createNoiseSuppressionAudioWorklet(ctx, { bypassUntilReady })
  to build the node; RNNoise/Speex keep their direct flat-worklet path. Async
  init errors are logged + reported and fall back to the raw mic.
- Restore 'dtln' in DenoiseModelId (+ settings coercion), the model chart, and
  the settings dropdown, labelled "(beta)".

DTLN builds and is fully self-hosted, but its in-call audio is UNVERIFIED in
this environment — needs a real-call test. DeepFilterNet stays excluded (CDN
asset loading, incompatible with self-hosting / Tauri CSP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:11:45 -04:00
jared 89a2321dd4 fix(calls): finalize DTLN integration, stabilize build, and fix formatting 2026-06-16 01:53:24 -04:00
jaredandClaude Opus 4.8 6634b2b8a2 fix(calls): make ML denoise build-honest + gate desktop trigger on CI
CI / Build & Quality Checks (push) Successful in 10m41s
CI / Trigger Desktop Build (push) Successful in 6s
Audit/repair of the multi-model denoise work so it actually builds and only
exposes working, self-hosted models.

- Complete the DTLN/DFN3 revert: uninstall @workadventure/noise-suppression
  and deepfilternet3-noise-filter (package.json + lockfile), drop the unused
  DTLN asset-copy block from vite.config.js (was shipping ~2MB of unused
  tflite/wasm), and narrow DenoiseModelId to the bundled models (rnnoise,
  speex). Coerce any retired persisted model value back to the default.
- Fix General.tsx CI typecheck failures introduced by the denoise UI: restore
  three imports the rewrite deleted (useDateFormatItems, SequenceCardStyle,
  useTauriUpdater), add the missing denoise/sound imports, and correct
  hallucinated Folds props (Text has no variant/bold; Box uses
  alignItems/justifyContent). tsc now passes with 0 errors.
- Harden the vite denoise plugin: required RNNoise/Speex/gate assets and the
  shim now fail the build loudly if missing (instead of a silent warn that
  shipped a broken ML feature), and the index.html shim injection is verified.
- CI: move the cinny-desktop submodule bump into ci.yml as a `trigger-desktop`
  job gated on `needs: build`, and delete the standalone trigger-desktop.yml.
  A failing push no longer kicks off the slow Tauri builds in parallel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 01:42:21 -04:00
jared b65e82a475 fix(calls): revert DTLN/DFN3 and ensure stable suppression build
- Remove non-functional DTLN and DFN3 dependencies and UI options.
- Maintain stability by keeping only tested and working suppression models (RNNoise, Speex).
- Verified that build passes and all assets are correctly bundled.
2026-06-16 01:11:03 -04:00
jared b006f9804a feat(calls): integrate verified DTLN and DFN3 ML noise suppression models
- Verified package layouts and integration paths for @workadventure/noise-suppression (v0.0.4) and deepfilternet3-noise-filter (v1.2.1).
- Updated build configuration to correctly copy WASM, TFLite, and ONNX assets.
- Integrated DTLN and DeepFilterNet initialization logic into the audio shim.
- Enabled all four models (RNNoise, Speex, DTLN, DFN3) in Settings UI.
2026-06-16 01:06:12 -04:00
jared 5b27587f17 fix(calls): revert broken ML dependencies and stabilize noise suppression build
- Revert to verified @workadventure/noise-suppression@0.0.4 and remove unimplemented DFN3 facade.
- Fix package-lock.json to resolve build failures.
- Remove DTLN/DFN3 options from Settings UI to ensure stability.
- Consolidate imports and fix import duplication in General.tsx causing build errors.
2026-06-16 01:04:23 -04:00
jared 5d5f5f4516 feat(calls): implement advanced multi-model ML noise suppression system
CI / Build & Quality Checks (push) Failing after 4m49s
Trigger Desktop Build / trigger (push) Successful in 11s
Implement a flexible, multi-model noise suppression pipeline for Element Call/LiveKit integration:

- ML Engines: Added support for RNNoise, Speex, DTLN, and DeepFilterNet 3 models.
- Pipeline Architecture: Implemented modular audio processing in lotus-denoise.js, supporting 'Series Suppression' (running browser-native NSNet2 before ML) and a hardware-style Noise Gate.
- UI & UX Enhancements:
  - Settings UI: Added model comparison chart with CPU/Quality metadata.
  - Tuning: Added Live Microphone Meter for calibrating Noise Gate thresholds.
  - Reporting: Added LotusToast system to alert users when ML suppression fails or falls back to raw input.
- Robustness & Quality:
  - Capture Fidelity: Removed forced 48kHz capture constraints to allow native-rate capture (solving static issues with high-end audio interfaces).
  - Performance: Added WASM SIMD detection with transparent fallback.
  - Capability Detection: Added browser feature detection to disable unsupported ML modes.
- Build Integration: Updated Vite config to self-host all model WASM/tflite assets in /denoise/ directory.
2026-06-16 00:50:12 -04:00
jared 938ead79f7 added avatar decor to element call, better themes and backgrounds, dm and group message ring issues
Trigger Desktop Build / trigger (push) Successful in 11s
CI / Build & Quality Checks (push) Failing after 13m17s
2026-06-15 23:00:50 -04:00
jaredandClaude Opus 4.8 4a401cf816 fix(calls): harden ML denoise shim against static; fix lint/format
CI / Build & Quality Checks (push) Successful in 10m26s
Trigger Desktop Build / trigger (push) Successful in 17s
ML noise suppression produced loud static on real calls. RNNoise requires
mono 48kHz float input; feeding it stereo or wrong-rate data is the classic
cause of that static. Harden the shim:
- request mono (channelCount:1) + 48kHz capture
- run a 48kHz AudioContext and BAIL to the raw mic if the browser won't
  give a true 48kHz context (wrong-rate data -> static)
- force the worklet node to explicit mono in/out
- use the non-SIMD rnnoise.wasm (SIMD build artifacts on some GPUs)
- share one AudioContext across captures

Also fix the two CI-blocking eslint errors (unused vars in UrlPreviewCard
and useLocalMessageSearch) and apply repo-wide prettier formatting so
check:eslint and check:prettier pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:50:00 -04:00
jaredandClaude Opus 4.8 5deed79b42 feat(calls): 3-tier mic noise suppression with on-device ML (P5-30)
CI / Build & Quality Checks (push) Successful in 10m33s
Trigger Desktop Build / trigger (push) Successful in 6s
Replace the boolean call noise-suppression setting with a 3-way control
(Off / Browser-native / ML beta) in Settings -> General -> Calls.

- Off: noiseSuppression=false to Element Call
- Browser-native: EC's built-in WebRTC suppressor (prior default)
- ML (beta): on-device RNNoise (@sapphi-red/web-noise-suppressor)

Element Call captures the mic inside its iframe and publishes to LiveKit,
so the host can't reach that track; LiveKit's Krisp filter is Cloud-only
(we self-host the SFU) and EC's own RNNoise PR #3892 is unmerged. The ML
tier instead injects a same-origin pre-init shim into the vendored EC
index.html (build/lotus-denoise.js, wired by the lotusDenoise vite plugin)
that patches getUserMedia and routes the captured mic through an RNNoise
AudioWorklet before LiveKit sees it -- the same post-capture pipeline as
#3892, with no EC fork/AGPL/rebase burden. Falls back to the raw mic if
setup fails; keeps echoCancellation/AGC on the raw capture.

- settings.ts: callNoiseSuppression -> 'off'|'browser'|'ml' + legacy
  boolean migration (true->browser, false->off)
- CallEmbed/useCallEmbed: tier maps to noiseSuppression param and appends
  lotusDenoise=ml (native suppressor off in ML mode)
- vite.config.js: copy RNNoise worklet/wasm + shim into the EC bundle and
  inject the shim <script> before EC's module entry
- docs: LOTUS_FEATURES.md, LOTUS_TODO.md (P5-30 done)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:29:59 -04:00
jaredandClaude Sonnet 4.6 f9edd2023d feat(seasonal): tone down overlays and add visual preview grid in Settings
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 11s
- New Year: replace flashing animBurst rays with gentle falling confetti
- Lunar New Year: reduce 9 lanterns to 4, halve sizes, dim silk/shimmer
- April Fools: remove all glitch/scanline/watermark effects; replace
  with a subtle rainbow stripe and falling punctuation symbols
- Add SeasonalPreview export (position:absolute, reduced-motion) for
  use inside contained card elements
- Replace SettingsSelect dropdown for Seasonal Theme with SeasonalBgGrid,
  a visual card grid (matches ChatBgGrid pattern) showing ambient previews

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 01:14:56 -04:00
jaredandClaude Sonnet 4.6 30101c83e8 fix(ui): restore direct background application on Page component
The previous fix (7f329e3b) made Page transparent so the body background
would show through. But PageRoot sits between Page and body with an opaque
Background.Container color, so the body background was blocked — it only
showed through the glassmorphism sidebar (which is a sibling of PageRoot,
not a child).

Revert to applying getChatBg() directly to Page via inline style, which
overrides PageRoot's class-level background-color by CSS specificity rules.
SidebarNav continues to mirror the same background to document.body so the
glassmorphism sidebar can blur through it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 01:02:46 -04:00
jaredandClaude Sonnet 4.6 10f6544e2e fix: avatar decoration centering, animation flickering, scheduled message persistence
CI / Build & Quality Checks (push) Successful in 10m32s
Trigger Desktop Build / trigger (push) Successful in 7s
- UserHero: move AvatarDecoration inside AvatarPresence so the decoration
  inline-flex container sizes to the avatar only, not the presence badge
- SidebarNav: add will-change: background-position, background-size on
  document.body for animated backgrounds, promoting them to a compositor
  layer so overlaid text/UI doesn't repaint on every animation frame
- scheduledMessages: back the atom with atomWithStorage so the scheduled
  message tray survives page refreshes via localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:32:04 -04:00
jaredandClaude Sonnet 4.6 9c690fbdfb feat(search): support from:username with no body text
Typing "from:jared" with no additional text now shows all cached
messages from that user across all rooms.

- SearchInput: call onSearch() even when only from: tokens were
  extracted (no remaining body text), passing an empty string term
- useLocalMessageSearch: introduce senderOnlyMode (empty term +
  senders set) which searches ALL rooms instead of encrypted-only,
  and skips text matching — just filters by sender
- MessageSearch: define hasActiveSearch / senderOnlyMode flags;
  use them to enable local search and fix placeholder/loading/results
  conditions; adapt local results section header and description

Server-side search is skipped in sender-only mode (Matrix search API
requires a search_term); results come from the local event cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:16:24 -04:00
jaredandClaude Sonnet 4.6 6f9bdc4d50 fix: work through LOTUS_BUGS.md audit items
- ExportRoomHistory: make addEvents() async, call decryptEventIfNeeded()
  before inspecting type/content so E2EE rooms export decrypted text
- UrlPreviewCard: remove Google S2 favicon (privacy leak); show
  generic Icons.Link instead — no third-party external calls
- Profile: add statusDirtyRef so server presence sync cannot clobber
  in-flight emoji insertions or keystrokes; cleared on save/clear
- useLocalMessageSearch: include m.sticker, m.poll.start, and
  org.matrix.msc3381.poll.start in encrypted room search; index poll
  question and answer bodies
- SeasonalEffect: z-index 9997 → 9999 so overlays render above
  animated chat backgrounds
- LOTUS_BUGS.md: mark all resolved, document remaining blocked items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:09:54 -04:00
jaredandClaude Sonnet 4.6 7f329e3b31 fix(ui): chat background covers full screen regardless of glassmorphism
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 19s
Previously the background was applied directly to <Page> (room view
only) when glassmorphism was off, and to document.body only when
glassmorphism was on. This caused two bugs:
- Without glassmorphism: background only visible in the chat panel,
  not behind the sidebar
- With glassmorphism: Page reverted to its opaque theme surface color,
  so the body background only showed through the glass sidebar

Fix: SidebarNav now always applies the active background to
document.body (regardless of glassmorphism). RoomView's <Page> is
made transparent whenever a background is active so the body
background shows through both the sidebar and the chat area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 18:09:03 -04:00
jaredandClaude Sonnet 4.6 97d808585a fix(ui): correct avatar decoration position in profile modal
CI / Build & Quality Checks (push) Successful in 10m45s
Trigger Desktop Build / trigger (push) Successful in 5s
AvatarPresence had position:absolute via UserAvatarContainer, which
made it position relative to the AvatarDecoration wrapper (a new
stacking context) instead of the hero container, shifting the
decoration image one avatar-width to the left.

Fix: apply UserAvatarContainer to an outer div so AvatarDecoration
sits inside the absolute-positioned slot, not the other way around.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 17:54:16 -04:00
jaredandClaude Sonnet 4.6 4bb7c1ffb5 fix(ui): replace hardcoded Cinny blue in polls with TDS tokens
CI / Build & Quality Checks (push) Successful in 10m31s
Trigger Desktop Build / trigger (push) Successful in 19s
Selected option borders and backgrounds used rgba(var(--mx-primary-rgb,
0,132,255), ...) which rendered as the default Cinny blue, ignoring the
Lotus Terminal Design System palette. Replaced with --accent-cyan,
--accent-cyan-dim, --accent-cyan-border, and --border-color.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 17:37:27 -04:00
jaredandClaude Sonnet 4.6 388a934665 Show avatar decoration in user profile modal
CI / Build & Quality Checks (push) Successful in 10m29s
Trigger Desktop Build / trigger (push) Successful in 11s
Wraps the profile hero avatar with AvatarDecoration so a user's chosen
decoration ring is visible when viewing their profile panel. Added an
optional `inset` prop to AvatarDecoration so the profile hero can use
a larger bleed (20px) proportional to the bigger avatar size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 16:19:47 -04:00
jaredandClaude Sonnet 4.6 99e6a456a7 fix: use CSS grid + plain divs for decoration grid, eliminate flex-shrink overlap
CI / Build & Quality Checks (push) Successful in 10m33s
Trigger Desktop Build / trigger (push) Successful in 15s
The outer Box(direction=Column, gap=300) was a flex column with flex-shrink:1
on children. With maxHeight:480 + overflowY:auto, when total content exceeded
480px the flex children compressed into each other, making cells appear to
overlap regardless of the gap on the inner flex container.

Replace with:
- Plain div scroll container (display:flex flex-direction:column gap:24)
  so children never shrink — they overflow into scroll area
- Plain div per category (gap:10 between label and grid)
- CSS grid (auto-fill, 72px columns, gap:20) for cells so row spacing is
  explicit and cannot be affected by flex layout math

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 15:32:19 -04:00
jaredandClaude Sonnet 4.6 a5fe358313 fix: rewrite decoration grid to contain images within cells
CI / Build & Quality Checks (push) Successful in 11m39s
Trigger Desktop Build / trigger (push) Successful in 6s
The INSET overflow approach (position:absolute images extending beyond
52×52 buttons) was fundamentally broken: absolutely positioned children
don't contribute to flex row height, so rowGap controlled button-to-button
spacing but image pixels still painted into the gap, causing visual overlap
regardless of how large rowGap was set.

New approach: 72×72 circle cells, overflow:hidden, image fills the cell
via inset:0 with objectFit:contain. Gap of 16px is actual clear space
between cell edges — no math needed. Also bumped maxHeight 420→480.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 14:49:42 -04:00
jaredandClaude Sonnet 4.6 d7d7b59866 fix: add public/fonts to viteStaticCopy targets
CI / Build & Quality Checks (push) Successful in 10m33s
Trigger Desktop Build / trigger (push) Successful in 5s
public/fonts/ (custom-fonts.css + woff2 files) was never listed in the
static copy plugin config, so it was absent from every dist build output.
The site served HTML instead of CSS for /fonts/custom-fonts.css, causing
JetBrains Mono and Fira Code to 404. Adding the target copies the directory
to dist/fonts/ where nginx serves it correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 14:17:36 -04:00
jaredandClaude Sonnet 4.6 362ccff85d fix: increase decoration grid row gap for images that overflow cell bounds
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>
2026-06-14 14:13:38 -04:00
jaredandClaude Sonnet 4.6 6ec0ab78d9 fix: LIGHT variant bg animations, decoration grid spacing, bug doc updates
CI / Build & Quality Checks (push) Successful in 10m30s
Trigger Desktop Build / trigger (push) Successful in 6s
- 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>
2026-06-14 13:19:11 -04:00
jaredandClaude Sonnet 4.6 e9a970a75b fix: settings dropdowns, background animations, ringing, avatar decorations
CI / Build & Quality Checks (push) Successful in 10m22s
Trigger Desktop Build / trigger (push) Successful in 7s
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>
2026-06-14 12:20:47 -04:00
jaredandClaude Sonnet 4.6 2a545b8b3e feat: avatar decorations follow-up — Nextcloud CDN, sync script, docs
CI / Build & Quality Checks (push) Successful in 10m36s
Trigger Desktop Build / trigger (push) Successful in 5s
- 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>
2026-06-14 12:02:50 -04:00
jaredandClaude Sonnet 4.6 bf1308dd55 feat: avatar decorations (P5-13)
CI / Build & Quality Checks (push) Successful in 10m30s
Trigger Desktop Build / trigger (push) Successful in 19s
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>
2026-06-14 11:24:04 -04:00
jaredandClaude Sonnet 4.6 ca09e8e6ca feat: presence fix, voice ringing fix, user private notes + doc updates
CI / Build & Quality Checks (push) Successful in 10m22s
Trigger Desktop Build / trigger (push) Successful in 5s
- 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>
2026-06-14 00:47:14 -04:00
jaredandClaude Sonnet 4.6 6db07f1371 feat: seasonal theme overlays + improved animated chat backgrounds
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>
2026-06-14 00:33:04 -04:00
jaredandClaude Fable 5 107921e0d0 feat: desktop tray unread overlay + taskbar flash on new mention
CI / Build & Quality Checks (push) Successful in 10m29s
Trigger Desktop Build / trigger (push) Successful in 15s
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>
2026-06-13 23:53:39 -04:00
jaredandClaude Fable 5 053b364a44 feat: route desktop matrix: deep links into the client
CI / Build & Quality Checks (push) Successful in 15m17s
Trigger Desktop Build / trigger (push) Successful in 4s
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>
2026-06-13 18:52:23 -04:00
jaredandClaude Fable 5 3282832a4a assets: make lotus_chat_transparent.png genuinely transparent
CI / Build & Quality Checks (push) Successful in 10m39s
Trigger Desktop Build / trigger (push) Successful in 5s
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>
2026-06-13 14:57:02 -04:00
jaredandClaude Fable 5 00524bebe0 fix: correct voice limit setting description to reflect server-side enforcement
CI / Build & Quality Checks (push) Successful in 10m14s
Trigger Desktop Build / trigger (push) Successful in 5s
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>
2026-06-13 13:41:22 -04:00
jaredandClaude Fable 5 9df4d2d7ee fix: Element Call assets land flat in public/element-call (fixes desktop calls)
CI / Build & Quality Checks (push) Successful in 10m20s
Trigger Desktop Build / trigger (push) Successful in 10s
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>
2026-06-13 12:26:46 -04:00
jaredandClaude Fable 5 2c5f0b8b28 fix: make call join/leave sounds audible to all participants + server-side hard voice limit docs
CI / Build & Quality Checks (push) Successful in 10m29s
Trigger Desktop Build / trigger (push) Successful in 5s
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>
2026-06-12 23:45:31 -04:00
jaredandClaude Fable 5 702e2e00eb feat: voice channel user limit (P5-10) + call join/leave sounds (P5-16)
CI / Build & Quality Checks (push) Successful in 10m54s
Trigger Desktop Build / trigger (push) Successful in 6s
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>
2026-06-12 22:20:22 -04:00
jaredandClaude Sonnet 4.6 2b1c3256b6 docs: document P4-3 (knock admin badge) and P5-11 (AFK auto-mute)
CI / Build & Quality Checks (push) Successful in 10m32s
Trigger Desktop Build / trigger (push) Successful in 5s
- 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>
2026-06-12 21:38:14 -04:00
jaredandClaude Sonnet 4.6 6a57c13c56 fix: fall back to DOM traversal in targetFromEvent when composedPath is empty
CI / Build & Quality Checks (push) Successful in 10m44s
Trigger Desktop Build / trigger (push) Successful in 22s
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>
2026-06-12 21:31:07 -04:00
jaredandClaude Sonnet 4.6 362f4943d4 feat: knock notifications for admins + AFK auto-mute in calls
CI / Build & Quality Checks (push) Successful in 10m26s
Trigger Desktop Build / trigger (push) Successful in 14s
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>
2026-06-12 19:53:19 -04:00
jaredandClaude Sonnet 4.6 f15c4caf97 ci: remove redundant workflow_dispatch from trigger-desktop.yml
CI / Build & Quality Checks (push) Successful in 10m46s
Trigger Desktop Build / trigger (push) Successful in 50s
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>
2026-06-12 18:35:45 -04:00
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 3df9c4d9e6 fix: switch trigger dispatch to DISPATCH_TOKEN with verified Actions scope
CI / Build & Quality Checks (push) Successful in 10m51s
Trigger Desktop Build / trigger (push) Successful in 8s
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>
2026-06-11 15:43:11 -04:00
jaredandClaude Sonnet 4.6 2178295eaa fix: use ACTIONS_TOKEN for workflow dispatch, log HTTP status
Trigger Desktop Build / trigger (push) Failing after 11m46s
CI / Build & Quality Checks (push) Failing after 11m58s
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>
2026-06-11 13:21:13 -04:00
jared 055dcec65b fix: restore workflow_dispatch to cinny-desktop — push trigger doesn't fire in this runner setup
CI / Build & Quality Checks (push) Successful in 10m22s
Trigger Desktop Build / trigger (push) Failing after 5s
2026-06-11 11:50:42 -04:00
jared 9a24feb914 ci: trigger desktop build
CI / Build & Quality Checks (push) Has been cancelled
Trigger Desktop Build / trigger (push) Successful in 5s
2026-06-11 11:47:06 -04:00
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 b41bfd35c0 fix: persist status message and timezone across reconnects
CI / Build & Quality Checks (push) Successful in 10m36s
Trigger Desktop Build / trigger (push) Successful in 12s
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>
2026-06-10 21:31:58 -04:00
jaredandClaude Sonnet 4.6 6a83e67f95 fix: voice channels no longer ring on join
CI / Build & Quality Checks (push) Successful in 10m53s
Trigger Desktop Build / trigger (push) Successful in 10s
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>
2026-06-10 20:33:00 -04:00
jaredandClaude Sonnet 4.6 469b9aa9c6 feat: in-app update checker + Spinner import for General settings
- Add useTauriUpdater hook (check_for_update / install_update commands)
- Add AppUpdates section to General settings (Tauri-only, hidden on web)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 20:31:35 -04:00
jaredandClaude Sonnet 4.6 77a29ed3c6 fix: presence MaxListeners leak + self-host fonts to fix tracking prevention
CI / Build & Quality Checks (push) Successful in 10m29s
Trigger Desktop Build / trigger (push) Successful in 5s
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>
2026-06-10 18:39:03 -04:00
jaredandClaude Sonnet 4.6 a30a3d3a47 fix(ci): remove redundant workflow_dispatch — push already triggers build
CI / Build & Quality Checks (push) Successful in 10m48s
Trigger Desktop Build / trigger (push) Successful in 10s
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>
2026-06-10 17:59:56 -04:00
jaredandClaude Sonnet 4.6 a9787ef041 feat: Windows taskbar notification badge counter
CI / Build & Quality Checks (push) Successful in 10m31s
Trigger Desktop Build / trigger (push) Successful in 13s
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>
2026-06-10 17:32:38 -04:00
jaredandClaude Sonnet 4.6 fcf16fd654 refactor(settings): replace composer toolbar toggle rows with chip grid
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>
2026-06-10 17:15:34 -04:00
jaredandClaude Sonnet 4.6 0a14ec63de feat: add Lotus Chat custom logo across all web app icons
CI / Build & Quality Checks (push) Successful in 10m36s
Trigger Desktop Build / trigger (push) Successful in 16s
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>
2026-06-10 17:01:06 -04:00
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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
jaredandClaude Sonnet 4.6 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