Picks up the fork fix where "Mute Screenshare Audio" was undone whenever the sharer stopped and re-shared (EC's own volume controls wrote volume 1 through the same setter). The mute now rides the renderer's `muted` prop like deafen does. Docs: fork feature table gains the deafen/screenshare-audio row; the soundboard + quality rows are marked verified end-to-end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
14 KiB
Lotus Chat — Engineering Reference
Repo: lotus branch at https://code.lotusguild.org/LotusGuild/cinny
Deploy: push to lotus → CI → auto-deploy to chat.lotusguild.org (~11 min)
There is no backlog in this file. All open work lives in Gitea issues — cinny, element-call, cinny-desktop, matrix (infra/CI) — grouped by milestone (
Features 2026-Q4,Desktop 2026-Q4,Manual QA backlog,Desktop QA backlog, theAudit 2026-09 · *set). Shipped features are documented in LOTUS_FEATURES.md; how to run the automated tests is in LOTUS_TESTING.md. The formerLOTUS_TODO.mdbacklog was migrated to issues on 2026-09-17 (full history in git).
This file keeps only what a contributor needs to have open while working: the two design laws, decisions already made, what the server blocks, and the operational reference.
⚠️ TDS DESIGN LAW — READ BEFORE TOUCHING ANY UI
ALL Lotus Terminal Design System (TDS) styling — colors, animations, glows, borders, fonts, spacing — MUST come exclusively from
/root/code/web_template/base.cssCSS variables. Do NOT hardcode hex values. Do NOT invent new variable names. Canonical tokens:--lt-accent-orange,--lt-accent-cyan,--lt-accent-green,--lt-glow-*,--lt-box-glow-*,--lt-border-color,--lt-font-mono. Syntax-highlight token classes:.tok-kw .tok-str .tok-num .tok-cmt .tok-fn. Reference patterns:/root/code/tinker_tickets/(markdown.js, base.js, ticket.css). Applies to every task without exception. New components must respect both TDS dark (LotusTerminalTheme) and TDS light (LotusTerminalLightTheme); non-TDS theme work uses vanilla-extract (matchsrc/lotus-terminal.css.ts).
🧩 NATIVE-CINNY LAW — EVERY FEATURE MUST FEEL LIKE STOCK CINNY
Every feature must feel native to upstream Cinny — indistinguishable from what the Cinny team would ship. Reference: https://github.com/cinnyapp/cinny.
- Use the
foldsdesign system, not bespoke UI (Button,Chip,IconButton,Menu,MenuItem,Dialog,Modal,Input,Switch,Badge,SettingTile,SequenceCard, …) and folds tokens (color.*,config.space.*,config.radii.*). Use foldsIcon/Icons, never literal emoji, in UI chrome. No hardcoded hex/rgba(), no invented CSS variables.- Match Cinny's existing patterns — find the closest existing component/flow and mirror it before adding UI.
- The ONE exception: explicit TDS features, which follow the TDS Design Law above (opt-in, only in Lotus Terminal mode).
Deferred / dropped (decided — kept for context)
- [DEFERRED] P5-51 Federated "Identity Contexts" (session isolation) — multi-sprint, touches auth/crypto/storage core; smaller intermediate step = plain multi-account switch. [DROPPED] P5-52 per-room sync governor — js-sdk can't truly per-room filter
/sync; only a cosmetic hide. [DEFERRED] P5-53 local scripting plugin — prefer a declarative automation-rules feature (no arbitrary code). [DEFERRED] Audit-3 profile banner — MSC4427 open/unmerged; revisit on merge. [WON'T FIX] P5-50 Windows HW media pipeline (WebRTC decode lives in WebView2; not injectable). [MOVED] P5-9 LFG → LotusBot!lfg.
🚫 Blocked Features (server / upstream gated)
Re-run /_matrix/client/versions + unstable_features after each Synapse upgrade. Re-checked on 1.157.1 (2026-07-23): no change — all four below are still false. The 1.156.0→1.157.1 delta unblocked nothing (it's a bugfix release; the only feature-bearing release in the gap was 1.156.0, which we were already running).
- [BLOCKED] Live Location Sharing (MSC3489 + MSC3672 both
false) — real-time GPS beacons over the existing static share. - [BLOCKED] Reaction/Relation Redaction (MSC3892
false) — remove a reaction without redacting the parent; current full-redaction fallback is acceptable. - [DONE 2026-07] Room Preview before joining (MSC3266) — the client was always built (
JoinBeforeNavigate→RoomCardviamx.getRoomSummary). The earlier "blocked" flag was a misdiagnosis: it tested/v1/rooms/{id}/summary(404), but the SDK calls the unstableim.nheko.summary/summary/{id}path, which returns 200 with name/topic/members/join_rule. Verified live after the 1.156 upgrade; also added a join-rule/encryption chip + Request-to-join for knock rooms to the preview card. - [BLOCKED] Thread Subscriptions (MSC4306
false) — "Follow thread" button (depends on the shipped Thread Panel).
📖 Reference
Server Capabilities (as of 2026-07)
- Homeserver
matrix.lotusguild.org· Synapse1.157.1+trixie1(upgraded 2026-07-23 from 1.156.0 — note the host was found on 1.156.0 while the docs claimed 1.155.0, so always verify withdpkg-query -W matrix-synapse-py3, don't trust the docs; apt package on Debian 13, LXC 151) · Matrix spec up tov1.12(Synapse still advertises v1.12; MSC features viaunstable_features). - MSC ON (re-dumped live from
/_matrix/client/versionson 1.157.1):msc4140·msc3771·msc3440.stable·msc4133.stable·simplified_msc3575·msc4222·msc3266(room summary live at unstableim.nheko.summary/summary/{id}— 200; the/v1/rooms/{id}/summarypath is still 404) ·msc3401_matrix_rtc·msc2285.stable·msc3827.stable·msc3981·msc4380.stable·msc4445·msc2659.stable·msc2666·msc2432·e2e_cross_signing·label_based_filtering. OFF/blocked:msc4306·msc3882·msc3912·msc4155·msc3489/msc3672·msc3892·msc4028·msc4069·msc4108·msc3391·msc4354(sticky events — deliberately off, see the Matrix 2.0 section above) ·msc4143(RTC foci — not a gap: LiveKit is discovered via.well-knownorg.matrix.msc4143.rtc_foci, confirmed live, not this flag). - Dead client code: Synapse 1.157.0 removed
msc3861(MAS auth delegation) entirely — the ~6msc3861/msc2965references insrc/can never activate against this homeserver (we auth via Autheliaoidc_providers). Harmless, but cleanup material. - Live endpoints: Report User (MSC4260) 200 ✅ · Report Room (MSC4151) ✅.
- Homeserver access (audits): Synapse = LXC 151 (
pct exec 151 -- bash), config/etc/matrix-synapse/homeserver.yaml. Web deploy = LXC 106. Voice guard =voice-limit-guard.pyon LXC 151. - SDK notes: no arbitrary profile-field methods (use
mx.http.authedRequest()for MSC4133); js-sdk can't per-room filter/sync; sanitizer strips<math>/MathML; SW exists atsrc/sw.ts;getMatrixToRoom()builds invite URLs; EC audio-inject unblocked via the fork'sio.lotus.inject_audio.
Key File Reference
| What | File | Lines |
|---|---|---|
| Global keydown / room nav | hooks/useKeyDown.ts · hooks/useRoomNavigate.ts |
whole / 19-72 |
| Room unread counts atom | state/room/roomToUnread.ts |
roomToUnreadAtom |
| Overlay portal provider | pages/App.tsx · index.html |
65 / 101 |
| Room settings tabs | features/room-settings/RoomSettings.tsx |
27-56 |
| State event read/write pattern | features/common-settings/general/RoomEncryption.tsx |
42-52 |
| Power levels | hooks/usePowerLevels.ts |
whole |
| Slash commands | hooks/useCommands.ts |
140-537 |
| Chat background picker/defs | features/settings/general/General.tsx · lotus/chatBackground.ts |
945-981 / whole |
| Matrix.to URL builder | plugins/matrix-to.ts |
getMatrixToRoom() |
| Media URL conversion | utils/matrix.ts |
mxcUrlToHttp() |
| Search pagination / virtual | features/message-search/{useMessageSearch,MessageSearch}.tsx |
74-121 / 234-365 |
| Call mic control | plugins/call/CallControl.ts |
206-212 |
| Knock support check | utils/matrix.ts |
376-391 |
| Notification mute push rules | hooks/useRoomsNotificationPreferences.ts |
110-150 |
Element Call fork — operational reference
Fork = LotusGuild/element-call (branch lotus, upstream base v0.25.0 since the 2026-09 sync — was v0.20.1); cinny consumes the npm package @lotusguild/element-call-embedded (built bundle copied into public/element-call/).
Toolchain (upstream-driven, accepted 2026-09): Node ≥ 22.13 (.node-version = 24) and pnpm 11, installed directly (npm i -g pnpm@<packageManager version>, currently 11.21.0) — not via corepack enable: matrix-js-sdk is a git dependency pnpm builds from source, and its own devEngines pins pnpm 11.9.0; a corepack-shimmed pnpm refuses to switch for that nested install and pnpm install fails (fork CI run #1854). pnpm 10 rejects the lockfile and Node 20 cannot build. Lint is oxlint + oxfmt (upstream dropped eslint/prettier in v0.25.0): pnpm lint (tsc + oxlint + knip) and pnpm format:check / pnpm format. matrix-js-sdk is pinned to a matrix-org/matrix-js-sdk#develop commit in the lockfile, as upstream ships it. Fork CI (.gitea/workflows/ci.yml) hard-gates lint + format + pnpm test:unit before build, with concurrency: cancel-in-progress.
Publish a new version (CI on tag push; needs the NPM_PUBLISH_TOKEN org secret): the published version is derived from the git tag — bump embedded/web/package.json (currently 0.25.0-lotus.4, published by CI; the secret is NPM_PUBLISH_TOKEN, names starting GITEA_ are reserved), push lotus, then git push lotus v0.25.0-lotus.1; the publish job builds and publishes to the Gitea registry. Always push (never delete) the annotated vX.Y.Z-lotus.N tag for every published version. Then in cinny bump the @lotusguild/element-call-embedded pin (currently 0.25.0-lotus.4) → npm install → build. Manual fallback: pnpm run build:embedded && cd embedded/web && npm version <ver> --no-git-tag-version && npm publish.
io.lotus.* widget actions (add new toWidget actions to the enum + LOTUS_TO_WIDGET_ACTIONS in src/lotus/lotusActions.ts; only send AFTER call-join or a 10s timeout fires):
| Action | Dir | Purpose | Module |
|---|---|---|---|
io.lotus.call_state |
EC→host | speaker/mute/camera stream (lotusCallState=1) |
lotusCallState.ts |
io.lotus.focus_participant |
host→EC | spotlight (works during screenshare) | lotusFocus.ts |
io.lotus.inject_audio |
host→EC | soundboard clip mixed into call (lotusAudioInject=1) |
lotusAudioInject.ts |
io.lotus.set_quality |
host→EC | audio/screenshare bitrate/fps caps | lotusQuality.ts |
io.lotus.decorations |
host→EC | in-call avatar decorations | lotusDecorations.ts |
io.lotus.set_deafen |
host→EC | LiveKit-source deafen (P6-2) | lotusDeafen.ts |
Also flag-gated: lotusTransparent/lotusTheme, lotusDenoiseSource=1 (in-source ML denoise).
CI/CD + per-feature checklist
edit → commit → git push origin lotus
→ Gitea Actions (.gitea/workflows/ci.yml): npm ci → build + npm test + tsc + eslint + prettier (ALL hard gates) → audit + bundle-size (informational)
→ lotus_deploy.sh on LXC 106 polls the "Build & Quality Checks" status → npm ci && npm run build → rsync → live (~11 min)
(a push that lands while a deploy is mid-build is queued and deployed right after — matrix@b6ea4a3; before that it was dropped)
Before marking a feature complete: npx tsc --noEmit (0 errors) · npx eslint src/ (0 new) · npx prettier --check src/ · npm test (Node runner via tsx, hard CI gate — colocated *.test.ts) · update README.md/landing/index.html for Lotus-custom features · visually verify on chat.lotusguild.org.
CI hardening (2026-07, reviewed):
- Concurrency —
cancel-in-progresson cinnyci.ymland cinny-desktoprelease.yml(386a2979/c5461ce): a superseded lotus push cancels its in-flight web CI and collapses queued ~30-min Tauri desktop builds to just the newest. Safe for deploys becauselotus_deploy.shnow follows origin/lotus HEAD each poll iteration + resets to the gated SHA (matrixc15a489) — closes the latched-SHA freeze race. - Hard quality gates — typecheck/eslint/prettier promoted from
continue-on-errorto blocking (tree held clean). eslint gates on errors only;no-explicit-anywarnings stay informational.
CI follow-ups (open):
CI follow-ups are tracked in the matrix repo: #8 dedicated desktop-linux runner, #9 debounce the desktop trigger, #10 verify Gitea concurrency. Build-once/deploy-the-artifact was considered and deferred (noted on #8).