Commit Graph
7446 Commits
Author SHA1 Message Date
Lotus CIandClaude Opus 4.8 29592fbb18 lotus(#1): fix restart-silence (A7), 48kHz ctx; protocol + CI hardening
CI / Build embedded bundle (push) Successful in 1m20s
CI / Publish to Gitea npm registry (push) Has been skipped
Denoise deep-review (CRITICAL): restart() read opts.audioContext, which
LiveKit does NOT pass on restart — so reconnect (the A7 scenario) and mic
device-switch threw after stopping the old track, leaving the mic SILENT
(A7 reintroduced). Fix:
- Processor owns a dedicated 48kHz AudioContext (sapphi worklets require
  48kHz; H1), reused across restart, closed on destroy.
- restart() never throws and never leaves a stopped track on the sender:
  builds the new graph first, then disposes the old; on failure degrades
  to RAW mic audio rather than silence.
- Cache wasm per URL (no re-fetch each reconnect); gate threshold default
  -45 and accept an explicit 0 (M2); document the cross-repo asset contract.

Protocol audit:
- Non-silent warning when an unsupported denoise model (dtln/deepfilternet)
  is requested instead of silent rnnoise fallback (F3).
- Correct the call_state enum comment (immediate error-reply, not 10s) (F2).

Build/CI audit:
- Stamp VITE_APP_VERSION in CI; document the vX.Y.Z-lotus.N version scheme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:15:59 -04:00
Lotus CIandClaude Opus 4.8 b8543c3fe1 lotus(security): harden denoise base, audio-inject, decorations
CI / Build embedded bundle (push) Successful in 1m8s
CI / Publish to Gitea npm registry (push) Has been skipped
Holistic security audit findings:
- C1 (CRITICAL): force lotusDenoiseBase to same-origin before it reaches
  audioWorklet.addModule()/fetch — a crafted call-link param could
  otherwise load attacker JS/WASM as a worklet processing the live mic.
  Non-same-origin/malformed values fall back to bundled ./denoise/.
- H1 (HIGH): gate audio-inject behind explicit lotusAudioInject=1 (still
  acks the action so no transport hang) — it publishes under the local
  user's identity, so it must not be silently armed for every call.
- M1 (MED): cap the decoration roster at 512 entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:00:40 -04:00
Lotus CIandClaude Opus 4.8 39b57db3b2 lotus(#1): ML denoise as a first-class audio TrackProcessor (fixes A7)
CI / Build embedded bundle (push) Successful in 58s
CI / Publish to Gitea npm registry (push) Has been skipped
Implements RNNoise/Speex noise suppression as a LiveKit audio
TrackProcessor attached to the local mic track, replacing the host's
build-time getUserMedia monkeypatch. Because EC re-attaches the processor
on every (re)publish (LocalTrackPublished), denoise now survives EC's
mid-call reconnect — the root cause of A7 "mic dead after reconnect".
Reuses the worklet/wasm assets already shipped under ./denoise/ (no new EC
dependency); model/gate configured via lotusDenoise/lotusModel/lotusGate
URL params. Additive: no-op unless lotusDenoise=ml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:54:46 -04:00
Lotus CIandClaude Opus 4.8 33d0e98eb0 lotus(#6): render decoration in MediaView; fix store lifecycle
CI / Build embedded bundle (push) Successful in 43s
CI / Publish to Gitea npm registry (push) Has been skipped
Review found in-call tiles use MediaView->Avatar, not TileAvatar, so the
decoration never rendered in-call (CRITICAL). Move the overlay into
MediaView, gated on the avatar's own visibility (!(video && videoEnabled))
so it never floats over live video; revert the TileAvatar changes.
Also ref-count the io.lotus.decorations registration (one shared handler,
no double-reply) and stop clearing the map on teardown so a transient
remount doesn't drop decorations (HIGH/MED).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:45:34 -04:00
Lotus CIandClaude Opus 4.8 70358d442b lotus(#6): render avatar decorations on in-call tiles
CI / Build embedded bundle (push) Failing after 12m11s
CI / Publish to Gitea npm registry (push) Has been skipped
Adds io.lotus.decorations (toWidget): the host pushes a userId->image-URL
map and EC overlays the profile decoration on each tile avatar
(TileAvatar), keyed by userId, with a useSyncExternalStore-backed store.
Makes A6 first-class in-call instead of absent. URLs are validated
https/blob. Additive: no-op unless the host sends decorations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:38:45 -04:00
Lotus CIandClaude Opus 4.8 7d792c33bf lotus(#7): fix screenshare cap per review
- Apply the encoding patch to ALL simulcast layers, not just encodings[0]:
  screenshare publishes simulcast (VP8), so the full-res layer (the real
  bandwidth hog) was left uncapped (CRITICAL).
- Re-apply on TrackUnmuted/restart + a 500ms settle, since LiveKit's
  refreshSenderEncodings() overwrites our caps on replaceTrack (device/
  source switch, processor toggle) without firing LocalTrackPublished (HIGH).
- Clamp values to sane ranges so a typo can't brick the encoder (MED).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:38:45 -04:00
Lotus CIandClaude Opus 4.8 22be058108 lotus(#5): native transparent-background + Lotus theme hooks
CI / Build embedded bundle (push) Successful in 45s
CI / Publish to Gitea npm registry (push) Has been skipped
Adds body.lotus-transparent (lotusTransparent=1) so the host wallpaper
shows through the call natively, retiring cinny's injected
`html,body{background:none!important}` hack; and body.lotus-theme
(lotusTheme=1) as a source-level Compound-token override hook for the
Lotus/TDS palette (driven by the existing setTheme channel / URL flags).
Additive: no-op without the flags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:30:57 -04:00
Lotus CIandClaude Opus 4.8 10e6ba46e2 lotus(#3): harden audio-inject per review
CI / Build embedded bundle (push) Successful in 3m22s
CI / Publish to Gitea npm registry (push) Has been skipped
- resume() the AudioContext (host postMessage isn't a gesture) so the clip
  isn't silent; warn if it stays suspended (HIGH).
- Close the AudioContext on decode failure (no context leak) (MED).
- Abort in-flight clips on teardown (unmount/vm-change/leave) so audio
  doesn't keep blasting to peers (MED).
- Stop the cloned MediaStreamTrack when a room publish fails (MED).
- Validate url is https/blob and fetch with credentials:omit, mode:cors
  (MED security).
- Guard against NaN clip duration; fix stale enum doc comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:26:45 -04:00
Lotus CIandClaude Opus 4.8 6d739db8b8 lotus(#7): audio/screenshare quality controls via widget action
Adds io.lotus.set_quality (toWidget): caps mic audio bitrate and
screenshare bitrate/framerate via RTCRtpSender.setParameters (no
republish). Settings are sticky and re-applied on LocalTrackPublished so
they survive mute/unmute and reconnects. These encoding controls lived in
EC's module scope, unreachable from the host against the prebuilt bundle.
Additive: no-op unless the host sends the action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:24:57 -04:00
Lotus CIandClaude Opus 4.8 2ab9427c09 lotus(#4): pin camera into spotlight during screenshare (A5)
CI / Build embedded bundle (push) Successful in 51s
CI / Publish to Gitea npm registry (push) Has been skipped
Review found the manual pin only chose among cameras when no screenshare
was active; during a screenshare the screenshare won the spotlight and the
pinned camera was demoted to an ignored PiP. Apply the override at the
spotlightAndPip$ level: when a pin is explicitly set, surface that camera
in the spotlight alongside the shared screen. No manual pin = unchanged.

Note: a pin persists if the pinned user briefly leaves and rejoins; the
host clears it via focus_participant{userId:null} (by design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:21:14 -04:00
Lotus CIandClaude Opus 4.8 c73eec0781 lotus(#3): soundboard audio injection via widget action
CI / Build embedded bundle (push) Successful in 43s
CI / Publish to Gitea npm registry (push) Has been skipped
Adds io.lotus.inject_audio (toWidget): mixes a soundboard clip into the
call so other participants hear it. Publishes the clip as a separate
Unknown-source LiveKit track (rendered by MatrixAudioRenderer) rather than
splicing into the mic track, so the denoise pipeline is untouched; the
track is unpublished when the clip ends (with a 30s safety cap). This is
the real call-audio injection that was impossible against the prebuilt EC
bundle. Additive: no-op unless the host sends the action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:18:43 -04:00
Lotus CIandClaude Opus 4.8 0f90600a59 lotus(#2): address review — ack contract, rate, param precedence
CI / Build embedded bundle (push) Successful in 1m18s
CI / Publish to Gitea npm registry (push) Has been skipped
- Document that io.lotus.call_state is request/response and the host must
  ack it (cinny listenAction replies {}) to avoid 10s-timeout churn (H1).
- Throttle 150ms -> 250ms to reduce widget traffic (M1).
- lotusParam: hash fragment wins over query, matching EC's ParamParser (L1).
- Fix the misleading "opaque" id comment; id is userId:deviceId (L2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:14:42 -04:00
Lotus CIandClaude Opus 4.8 9d7784b9bc lotus(#4): focus-participant spotlight via widget action
Adds io.lotus.focus_participant (toWidget): the host can pin a participant
to the spotlight by Matrix user id (or clear with userId:null), via a
manual override injected into CallViewModel.spotlightSpeaker$. Replaces
cinny's fragile DOM .click() tile-selector focus hack. Extracts the action
enum into lotusActions.ts (no circular import) and allow-lists Lotus
toWidget actions in initializeWidget. Additive: no-op unless the host
sends the action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:10:16 -04:00
Lotus CIandClaude Opus 4.8 444af5f7ac lotus(#2): stream per-participant call state over widget API
CI / Build embedded bundle (push) Successful in 1m45s
CI / Publish to Gitea npm registry (push) Has been skipped
Opt-in (lotusCallState=1) bridge that emits io.lotus.call_state with each
participant's speaking/audio/video state, so the Lotus host can drive
speaking rings / mute badges / PiP from real events instead of scraping
EC's rendered DOM. Exposes vm.userMedia$ on the public CallViewModel.
Additive: no-op without the flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:05:20 -04:00
Lotus CIandClaude Opus 4.8 39377fb6e1 ci: drop actions/upload-artifact@v4 (incompatible with Gitea runner)
CI / Build embedded bundle (push) Successful in 1m20s
CI / Publish to Gitea npm registry (push) Has been skipped
upload-artifact@v4 needs GitHub's artifact backend, which the Gitea
act_runner doesn't implement — it failed the build job with exit 1 even
though build:embedded + smoke-check passed. The publish job rebuilds from
source, so the artifact was only a convenience.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:49:15 -04:00
Lotus CIandClaude Opus 4.8 b1fed78283 ci: stage embedded dist into embedded/web/dist before publish
CI / Build embedded bundle (push) Failing after 46s
CI / Publish to Gitea npm registry (push) Has been skipped
build:embedded outputs to repo-root dist/; the publish job and smoke-check
expected embedded/web/dist (the publish template's files entry), which was
never created in CI — so a tagged publish would fail its smoke-check or ship
an empty tarball. Copy dist -> embedded/web/dist in both jobs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:45:20 -04:00
Lotus CIandClaude Opus 4.8 229b54b3b7 Lotus fork: rename embedded package + add Gitea CI
CI / Build embedded bundle (push) Failing after 1m9s
CI / Publish to Gitea npm registry (push) Has been skipped
- embedded/web/package.json: publish as @lotusguild/element-call-embedded
  from our Gitea fork (repository URL updated).
- .gitea/workflows/ci.yml: build the embedded bundle on PR/push to lotus;
  publish to the Gitea npm registry on a v* tag. Linux-only (web bundle).

Based on upstream element-call v0.20.1 (2d74c48).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:16:45 -04:00
Robin 3837576a5f Merge pull request #4073 from element-hq/refine-scope-leak-lint
Fix some false positives flagged by ObservableScope leak lint rule
2026-06-29 14:57:50 +02:00
Johannes Marbach eb5a097999 Merge pull request #4072 from element-hq/johannes/jest
Switch to jest-style assertions to please oxlint
2026-06-29 14:36:18 +02:00
Robin db2d20ac56 Fix some false positives flagged by ObservableScope leak lint rule
The rule should only care about enclosing function/class scopes. For example if an ObservableScope is received as a parameter to a function and then simply used inside an 'if' block (technically a different scope), that's not a problem.
2026-06-29 14:33:23 +02:00
Johannes Marbach f276c51772 Switch to jest-style assertions to please oxlint
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-06-29 14:32:49 +02:00
Johannes Marbach ac2214ed77 Merge commit from fork
Restrain logging of URL properties
2026-06-29 14:21:05 +02:00
Timo 8fe7bb5329 Merge pull request #4066 from element-hq/toger5/matrixRTCMode-compatibility
Default to compatibilty mode (multi sfu) - non breaking, as multi-sfu is compatible with all versions younger 6 month
2026-06-29 19:41:23 +08:00
Timo K 8028a6aa24 Update sfu-reconnect-bug.spec.ts 2026-06-29 13:11:26 +02:00
Timo K 5b4e8865e5 Update sfu-reconnect-bug.spec.ts 2026-06-29 13:04:22 +02:00
Timo K 356d526116 Update sfu-reconnect-bug.spec.ts 2026-06-29 13:02:26 +02:00
Johannes Marbach cca69b8108 Merge pull request #4069 from element-hq/renovate/compound
Update dependency @vector-im/compound-web to v9.5.0
2026-06-29 08:00:57 +02:00
renovate[bot] 75235a7853 Update dependency @vector-im/compound-web to v9.5.0 2026-06-29 00:34:12 +00:00
Timo K 0421783ebe Update config.sample.json 2026-06-26 13:30:19 +02:00
Robin a1be4ff0f9 Merge pull request #4068 from element-hq/landscape-voice-call
Hide local tile from mobile voice calls in landscape orientation
2026-06-26 11:22:25 +02:00
Robin 8ab7f89e36 Hide local tile from mobile voice calls in landscape orientation
To match the designs. Just like in portrait, it wouldn't contribute anything meaningful.
2026-06-25 20:29:24 +02:00
Valere Fedronic ddcb0f9981 Merge pull request #4064 from element-hq/ios/default_voice_call_to_earpiece
fix(voice): Default to earpiece for voice only call
2026-06-25 16:32:59 +02:00
Timo K 6ca5ddda73 fix snapshot 2026-06-25 16:05:58 +02:00
Valere 41d0933d4d playwright: New toast 2026-06-25 15:55:08 +02:00
Johannes Marbach 5ad62c7097 Merge pull request #4053 from element-hq/johannes/oxlint
Switch from eslint to oxlint
2026-06-25 15:46:05 +02:00
Johannes Marbach 76714ac3ad Use the correct company name
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-06-25 15:29:00 +02:00
Timo K 15b40835b8 Default to compatibilty mode 2026-06-25 13:31:02 +02:00
Valere dc5b541b21 fix(voice): Default to earpiece for voice only call 2026-06-25 13:10:27 +02:00
Johannes Marbach f376ffb96b Don't redact null or undefined 2026-06-24 13:35:44 +02:00
Johannes Marbach d5ce3a955a Port over copyright rule 2026-06-24 11:08:29 +02:00
Johannes Marbach d2cb7e334e Remove duplicate paths config 2026-06-24 10:53:14 +02:00
Johannes Marbach 9d61ca13cb Use regex to test node name 2026-06-24 10:49:18 +02:00
Johannes Marbach 51bd17d335 Restrain logging of URL properties 2026-06-24 08:22:02 +02:00
Johannes Marbach 1cd2764eb2 Switch from eslint to oxlint 2026-06-23 15:13:46 +02:00
Johannes Marbach 3fb81032e0 Merge pull request #4054 from element-hq/johannes/pip-border
Add PiP border in 1on1 layouts
2026-06-23 12:38:24 +02:00
Johannes Marbach da942ea900 Fix formatting 2026-06-23 12:19:40 +02:00
Johannes Marbach d534c617d2 Eliminate noOutline constant 2026-06-23 11:42:14 +02:00
Robin ded3d0ab81 Merge pull request #4061 from element-hq/subtitle-only
Show the subtitle of the app bar on small screens
2026-06-23 10:01:55 +02:00
Robin a4b4a6b655 Remove redundant styles 2026-06-23 09:42:57 +02:00
Robin be5d8140c9 Make it clearer why the app bar elements need to be hidden individually 2026-06-23 09:42:28 +02:00