- landing: Threads row upgraded to ✓ (full side panel + unread chips); prose
sentence for the new batch (threads, KaTeX math, opt-in encrypted-search
index, session hardening, crypto diagnostics).
- README: two new rows in the Lotus Cinny custom-features table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README Custom Features table: add rows for the in-call soundboard (P5-15),
call quality controls, and room call-permissions (P5-31).
- landing: mention the soundboard, per-user quality controls, and
server-enforced room call-permissions in the feature blurb.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the client-comparison copy to reflect that calls run our self-built
Element Call fork and that the ML noise-suppression tier offers RNNoise, Speex,
DTLN, and DeepFilterNet 3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend voice-limit-guard to enforce a per-room publish-source policy
(io.lotus.room_quality allow_screenshare/allow_camera) for ALL Matrix clients,
alongside the existing participant limit.
- At token issue, re-sign the LiveKit JWT's canPublishSources to drop forbidden
sources (microphone always kept). Verifies our own secret signed the token
first and fails open on mismatch, so a secret drift can never mint a token the
SFU rejects. Limit check and source policy are independent (one's outage can't
skip the other).
- Live (mid-call) enforcement: a background reconcile loop calls LiveKit
UpdateParticipant to revoke a forbidden source from participants who joined
before the policy changed -- which unpublishes their in-progress
screenshare/camera server-side within ~3s and blocks re-publish. Only removes
sources (never grants), preserves other permission flags, fails open, and runs
as a daemon thread that cannot crash or block token issuance.
- Endpoint-specific room-id extraction (/get_token->room_id, /sfu/get->room) so
a client sending both keys can't get a different room's policy applied.
- Auto-deploy the guard on LXC 151 (py_compile-gated, backup + rollback).
- Unit tests: JWT re-sign/verify + tamper, secret-mismatch, source narrowing,
reconcile (never-grant / preserve-flags / disable-on-empty), fail-open.
Numeric bitrate/fps caps are NOT server-enforceable on an SFU (LiveKit forwards,
never transcodes) and remain a Lotus-client-cooperative setting; the
screenshare/camera permission is the hard cross-client lever.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cinny/lotus-deploy.sh (hyphen) force-deploy variant was an untracked,
redundant duplicate of the CI-gated cinny/lotus_deploy.sh (underscore) added in
c13549f. It's been removed, so its install block here referenced a file that no
longer exists. The CI-gated lotus_deploy.sh is the single source of truth for
the webhook web deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The live /usr/local/bin/lotus_deploy.sh (the `lotus-deploy` webhook target) was
never under version control and had rotted into two deploy-killing bugs that
froze chat.lotusguild.org on an old build:
1. CI gate: it waited on the WHOLE workflow run with a 15-min cap. Web CI shares
the single act_runner with the slow Tauri desktop builds, so a web run could
sit queued >15 min -> "result: timeout" -> deploy aborted. Now it gates only
on the "Build & Quality Checks" commit-status context (build + unit tests),
decoupled from "Trigger Desktop Build", and waits up to 45 min.
2. Dead element-call copy: `cp node_modules/@element-hq/element-call-embedded/...`
under `set -e` aborted every deploy after the widget was forked to
@lotusguild/element-call-embedded. The build already emits dist/public/
element-call; replaced the copy with a presence check.
Also: rsync now excludes config.json so the app deploy stops clobbering the
production runtime config (homeserver list / allowCustomHomeservers) that the
matrix repo owns. lxc106-cinny.sh now installs this script (syntax-checked).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the client supports MSC3861 OIDC login, add mozilla.org to the
homeserverList and its origins to the CSP. mozilla delegates: homeserver ->
mozilla.modular.im, OIDC issuer -> chat.mozilla.org, identity -> vector.im.
- connect-src += mozilla.org mozilla.modular.im chat.mozilla.org vector.im
- img-src += mozilla.org mozilla.modular.im
Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Federated matrix.org users load avatars/images from their own media endpoint
(matrix-client.matrix.org), which img-src still blocked — so every avatar
tripped a CSP violation. Add https://matrix.org + https://*.matrix.org to
img-src to match connect-src. (media-src already allows https: so video/audio
were fine.) Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat.lotusguild.org nginx config (LXC 106) was edited directly on the box
and never tracked — which is how its CSP drifted (kept a dead Sentry URL and
blocked matrix.org logins). Snapshot it as cinny/nginx.conf (verbatim from prod,
incl. the corrected connect-src that now allows matrix.org/*.matrix.org) and
deploy it via lxc106-cinny.sh: back up the live file, swap, `nginx -t`, and
reload only on success (auto-restore the backup if validation fails, so a bad
config can't take the site down). TLS terminates at the NPM proxy, so this is a
plain HTTP server block with no secrets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add matrix.org to homeserverList so federated friends with matrix.org accounts
can sign into chat.lotusguild.org. defaultHomeserver stays 0 (lotusguild), and
allowCustomHomeservers stays false — only the two listed servers are selectable,
so the client isn't opened up to arbitrary homeservers.
Deploys via lxc106-cinny.sh (cp -> /var/www/html/config.json); lotus-build.sh
preserves the live config across app rebuilds, so this is the authoritative copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tag the EC embed row and add a callout explaining the plan to fork
element-hq/element-call and self-build it for true ownership (decorations,
focus/screenshare, reconnect mic, theming, call-audio injection — all unfixable
against the prebuilt @element-hq/element-call-embedded bundle). Infra notes:
EC uses our LiveKit SFU (livekit/, LXC 151) + lk-jwt-service; a new build/deploy
pipeline will be needed. Full plan: LotusGuild/cinny → HANDOFF_ELEMENT_CALL_FORK.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README: correct embedded Element Call version 0.19.3 -> 0.19.4 in the
Custom Features and Tech Stack tables
- landing/index.html: add a "Noise suppression" row to the Voice & Video
comparison table (Lotus = 3 tiers incl. on-device RNNoise ML) and note
the feature in the June 2026 narrative
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 curated APNG overlay frames stored in user Matrix profile (MSC4133),
visible to other Lotus Chat users in real time across timeline, members
list, and @mention autocomplete. Includes the Lotus Flower decoration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a fail-open Python sidecar (livekit/voice-limit-guard.py) that fronts
lk-jwt-service to enforce per-room voice participant caps for ALL Matrix
clients, not just Lotus Chat:
- lk-jwt-service moved to :8071 (systemd drop-in), guard owns :8070 so NPM's
existing /sfu/get + /get_token proxy targets are unchanged
- guard reads io.lotus.voice_limit.max_users (Synapse admin API, cached),
forwards to lk-jwt-service, and on an issued token decodes the LiveKit alias
+ requester, counts distinct Matrix users via LiveKit ListParticipants, and
returns 403 when the room is full (rejoins/extra devices allowed)
- any error fails open (returns upstream response) so calls never break
- systemd/voice-limit-guard.service; README documents ports, setup, revert
Also update landing page: voice limit is now server-enforced for all clients.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Feature description paragraph: added AFK auto-mute (1–30 min voice idle
timeout) and knock-to-join admin badge (live count on Members button)
- Comparison table: new AFK auto-mute row in Voice & Video section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New table row for 5 animated CSS wallpapers (rain, stars, grid pulse,
aurora, fireflies). Feature blurb updated to mention the animated
backgrounds and the glassmorphism body-background fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds inline GIF preview and collapsible long messages rows to the
feature comparison table; extends the June 2026 feature list with all
five newly completed items (P3-5, P3-9, P5-19, P5-23, P5-26).
Includes pre-staged README additions for presence tracking, encrypted
search, privacy settings, draft persistence, and PiP persistence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update comparison table date to June 2026
- Add push-to-deafen (M key), night light filter, message length
counter, and TDS orange typing dots to also-available paragraph
- PTT row notes M = push-to-deafen
- Add Night Light row to UX & Extras comparison table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README.md:
- Replaced the stale 'Auto-revert spotlight on screenshare' entry
(that 600ms revert-to-grid code was removed — it caused fullscreen
to show avatars instead of the screenshare)
- Added accurate entries for all four features added this cycle:
Screenshare fullscreen, PiP screenshare focus, Screenshare audio
mute, Custom status message
landing/index.html:
- Updated Lotus Fork feature description paragraph to mention
screenshare fullscreen, screenshare audio mute, PTT, and custom
status messages
- Added PTT row to Voice & Video comparison table
- Updated Screenshare row for Lotus Chat to note fullscreen + audio mute
- Added 'Custom status message' row to UX & Extras section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LXC 139 NPM proxy host 49 now proxies both /sfu/get and /get_token
to lk-jwt-service (port 8070). Note that re-saving via NPM UI will
overwrite the conf and require re-adding the location blocks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document the new member list presence indicators and per-member device
sessions panel with per-device SAS verification in both the landing
page feature list and the README custom features table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The runner intermittently times out (5m) downloading the gitleaks binary
from GitHub. Add --retry 3 --retry-delay 5 --max-time 120 so transient
network blips don't fail the job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add per-message read receipts comparison row (8-col, including new official
Cinny column from dc8f588). Update Lotus Chat feature description to include
per-message read receipt avatars and chat wallpaper in calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add chat.lotusguild.org as the featured Cinny fork column and cinny.in
as a second comparison column. Adds official Cinny card in Other
Clients section and updates table colspan to 8.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OOM observed during rendering-chunks phase at 896MB and 3072MB.
6144MB heap with 8GB LXC memory is confirmed working.
Also update README rebuild command to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add !cancel command (anyone cancels own blackjack; PL50+ clears all room games)
- Add !wordlestats top-level command (wraps wordle stats function)
- Add !cleanwelcome admin command to purge stale welcome DM records
- !help now hides management section from sub-PL50 users, hides !health from non-admins
- !announce uses nio room cache for join_rule instead of an API call per room
- Fix _INVITEALL_BLOCKED comment (Commands is knock-gated, not restricted)
- welcome.py: skip duplicate DM if a pending welcome already exists for the user
- welcome.py: add clean_stale_dm_messages() helper
- welcome.py: replace no-op post_welcome_message with log_ready()
- bot.py: update import/call to match welcome.py rename
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Commands has join_rule=restricted so the join-rule filter didn't catch
it. Added _INVITEALL_BLOCKED set with the Commands room ID — any room
in that set is skipped regardless of join rule. Invite-only rooms
(Management, Cool Kids, Spam and Stuff) are still excluded by the
existing join_rule check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
!setpl now iterates every Space room via the hierarchy API and updates
the power_levels state event in each. Setting a user to the room's
users_default cleans up the explicit entry rather than leaving a
stale PL0. Rooms where the bot lacks permission are counted and
reported but don't block the rest.
!inviteall skips rooms with join_rule=invite (Management, Cool Kids,
Spam and Stuff) — only public/restricted rooms get the invite. Also
skips rooms where the target is already a member.
_get_space_room_ids() fetches the Space child list via the v1 hierarchy
API with pagination support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five new commands, all gated behind is_elevated() (power level >= 50):
!mkroom <name> — clone #general's power levels, join rules, encryption,
history visibility, and avatar into a fresh v12 room,
auto-adds it to the Lotus Guild Space, and invites
the caller.
!roominfo — show room display name, ID, member count, join rule,
encryption status, and all users with PL > 0.
!topic [text] — set or clear the current room's topic.
!invite @user — invite any Matrix user to the current room.
!setpl @user <n> — update a user's power level (0-100); cannot exceed
the caller's own level.
Also adds urllib.parse.quote and MATRIX_HOMESERVER to imports, and
adds a "Management (PL50+)" section to !help.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add is_elevated() helper that reads the room power level from the nio
client store. Users at PL50+ (Nerdy Council and above) skip the cooldown
check entirely. The timestamp is still recorded so cooldown applies
if their power level is later reduced below 50.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>