- syncDecorations.mjs tracks which slugs its regex actually removed and
exits 1 without writing if that set doesn't match the missing assets,
instead of silently no-op'ing on a reformatted catalog.
- patch-folds.mjs matches a 4-line context block, reports the installed
folds version and expected snippet when the target is missing, and
distinguishes "already patched" (exit 0) from "pattern not found"
(exit 1). Verified against all three states.
Fixes#88Fixes#55
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
- engines.node >=20.0.0 and .nvmrc mirroring .node-version (#54)
- npm audit --audit-level=high is a hard gate (tree is at 0) (#91)
- scripts/boot-check.mjs serves dist/ with vite preview and asserts /,
config.json, the entry chunk and the Element Call bundle all load (#92)
- scripts/check-bundle-size.mjs enforces gzip budgets from
scripts/bundle-budget.json (seeded +10%); fails PRs, warns on push (#96)
- check:eslint runs with --max-warnings 68 so the count can only go down;
7 unused eslint-disable directives removed to get there (#97)
Fixes#54Fixes#91Fixes#92Fixes#96Fixes#97
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
- N100: restrict <pre> classes to language-* in sanitize-html allowedClasses;
previously `class` was allowed on <pre> with no allowedClasses entry, so a
remote sender could inject arbitrary class names that activate site CSS.
- N106: OS notifications for E2EE rooms no longer carry decrypted plaintext
(which persists in the OS notification center / lock screen). Encrypted rooms
show only the sender; the in-page toast still previews while focused.
- N109: OS notification icon/badge use the static app logo instead of an
authenticated-media avatar URL the OS can't fetch (was 401 / no icon). The
in-app toast keeps the real room avatar (it can fetch via the SW).
- N119: syncDecorations.mjs distinguishes a confirmed 404 (remove) from a
network/5xx failure (abort) so a transient CDN outage can't silently wipe the
whole decoration catalog from source control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- N124: denoise shim cleanup() now disconnects the noise gate AudioWorkletNode
(var-scoped, guarded), releasing the gate processor thread instead of leaking
it on every getUserMedia within a session.
- N125: denoise-status postMessage now targets the parent origin (derived from
the parentUrl widget param via new URL(...).origin, falling back to this
frame's origin) instead of broadcasting with '*'.
- N128: patch-folds.mjs fails hard (process.exit(1)) when the patch target is
missing, so an unpatched folds can't silently ship. The idempotent
"already applied" path still exits 0 (verified by re-run).
- N120: the avatar-decoration CDN URL is now single-sourced in
avatarDecorations.ts (DECORATION_CDN); syncDecorations.mjs extracts it by
regex (can't import across the build/app boundary) and fails hard if renamed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- 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>
Prettier: auto-formatted 103 files to fix baseline. Prettier check in CI
is now a hard gate (removed continue-on-error).
Brotli: installed libnginx-mod-http-brotli-filter/static. Enabled in nginx
with brotli_static on for pre-compressed assets and comp_level 6.
Sentry releases: deploy script now exports VITE_APP_VERSION=<git-short-sha>
before building so each Sentry release maps to an exact commit.
CI also passes github.sha as VITE_APP_VERSION.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add defensive check in folds Icon component so that if src is ever
undefined or non-function (root cause unknown, possibly data-dependent),
the SVG renders empty rather than throwing and crashing the whole app.
Also adds postinstall script to re-apply the patch after npm install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: install deps related to semantic release
* chore: add husky config
* ci: add a script to update version number on new release
* ci: update ci/cd to include semantic release changes
* chore: merge dev to semantic-release