Compare commits

..
8 Commits
Author SHA1 Message Date
jaredandClaude Sonnet 5 34a3352e21 docs(readme): add per-OS desktop download table
CI / Build & Quality Checks (push) Successful in 2m48s
CI / Trigger Desktop Build (push) Successful in 6s
Replaces the single generic releases-page link with direct downloads
for Windows (.exe), Linux (AppImage/.deb/.pkg.tar.zst), plus a note on
the webkit2gtk/GStreamer WebRTC dependency needed for calls to work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 21:32:18 -04:00
jaredandClaude Sonnet 5 3cc5f0cc6a fix(room-nav): drop pointless dynamic import of setRoomNotificationPreference
RoomNavItem.tsx already statically imported getRoomNotificationModeIcon
and RoomNotificationMode from the same module, so the two
`await import('../../hooks/useRoomsNotificationPreferences')` calls
(in unmuteRoom and handleMuteFor) never achieved real code-splitting —
verified by building and grepping dist/assets: setRoomNotificationPreference
landed in the same eager entry chunk regardless, since Rolldown can't
split a module already reachable via a static import elsewhere. Just
import it statically alongside its siblings instead. No behavior
change — confirmed via 3 independent investigations before starting
and 3 independent reviews of this diff before committing.

Closes LotusGuild/cinny#5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 21:29:29 -04:00
jaredandClaude Sonnet 5 fd93339ad4 feat(explore): feature the Homelab space instead of its individual rooms
CI / Build & Quality Checks (push) Successful in 2m21s
CI / Trigger Desktop Build (push) Successful in 12s
Swaps #homelabbing/#proxmox for their parent #homelab:codestorm.net
space, so browsing it surfaces the whole space rather than two
hand-picked children.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 22:14:24 -04:00
jaredandClaude Sonnet 5 b1ecb0c46b feat(explore): feature Lotus Guild Space + favorite rooms, add matrixrooms.info directory
CI / Build & Quality Checks (push) Successful in 3m46s
CI / Trigger Desktop Build (push) Successful in 15s
Populates the previously-empty featuredCommunities block so the Explore
tab's Featured page shows the Lotus Guild Space and a few community
favorites by default, and adds matrixrooms.info as a browsable server
in the Explore sidebar (it speaks enough of the Matrix federation API
to serve as an aggregated public-room search across the network).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 22:07:41 -04:00
jared 4656f08802 Revert "ci: re-enable npm/node_modules cache — runner cache network fixed"
CI / Build & Quality Checks (push) Successful in 1m42s
CI / Trigger Desktop Build (push) Successful in 12s
This reverts commit a631e90ea2.
2026-08-02 23:23:52 -04:00
jaredandClaude Opus 4.8 a631e90ea2 ci: re-enable npm/node_modules cache — runner cache network fixed
CI / Build & Quality Checks (push) Canceled after 4m44s
CI / Trigger Desktop Build (push) Canceled after 0s
The act_runner cache server is now reachable from job containers: jobs were
landing on isolated per-job docker networks and couldn't reach the runner's
cache server on docker0 (getCacheEntry ETIMEDOUT, ~5 min wasted/build). Fixed
runner-side by putting the runner + all job containers on a shared dedicated
network (`act-cache-net`, runner at 172.30.0.2) and pointing cache.host at it —
verified a container on that network reaches the cache port.

Restores `cache: npm` on Setup Node and the actions/cache node_modules step
(restore + save-on-miss-and-success). Reverts 10270b75 now that the underlying
network issue is resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 23:18:00 -04:00
jaredandClaude Opus 4.8 10270b75ca ci: drop npm/node_modules cache — runner cache server is unreachable
CI / Build & Quality Checks (push) Successful in 1m40s
CI / Trigger Desktop Build (push) Successful in 13s
The act_runner's internal cache server (172.17.0.2:46367) can't be reached
from job containers: `setup-node` with `cache: npm` spends ~4m42s on
`getCacheEntry failed: connect ETIMEDOUT` every build, then reports "npm cache
is not found" — ~5 min of pure cost for zero caching. The `actions/cache`
node_modules steps added in 79258668 would hit the same dead server and hang
too, so they're removed here as well.

Removing the cache usage reclaims ~5 min/build with no loss (nothing was being
cached). The fast-gates-before-build reorder is kept. Re-enable caching once
the runner's cache server is reachable from job containers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 21:26:26 -04:00
jaredandClaude Opus 4.8 7925866868 ci: cache node_modules + run fast gates first; enable lint-staged hook
CI (.gitea/workflows/ci.yml):
- Cache node_modules keyed on package-lock + .node-version (actions/cache
  restore/save). An unchanged lockfile now skips `npm ci` (extraction +
  postinstall folds patch) and just restores the tree. Save runs only on a
  cache miss and only when install succeeded (`success()`), so a failed
  `npm ci` can't poison the cache. setup-node's existing `cache: npm` still
  warms the download cache on the miss path.
- Run prettier/eslint/typecheck/tests BEFORE the ~minutes-long build so a
  format/lint/type/test error fails in seconds instead of after the build.

DX (.husky/pre-commit):
- Enable the pre-commit hook (`npx lint-staged`). husky + lint-staged were
  already installed with a config (eslint + `prettier --write` on staged
  files), just commented out — so formatting kept reaching CI. It's now
  auto-applied on commit. (typecheck left out of the hook — too slow per commit.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 21:05:50 -04:00
66 changed files with 3961 additions and 2604 deletions
+34 -32
View File
@@ -30,17 +30,22 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: npm
# No npm / node_modules cache: the act_runner's internal cache server is
# unreachable from job containers (`getCacheEntry failed: connect ETIMEDOUT
# 172.17.0.2`), so every cache restore hangs ~5 min and then fails — pure
# cost, zero benefit. `cache: npm` was removed from Setup Node above for the
# same reason. Re-enable both (setup-node `cache: npm` + an actions/cache
# node_modules step) once the runner's cache server is reachable from jobs.
- name: Install dependencies - name: Install dependencies
# Harden against transient registry network failures (ECONNRESET etc.): # Harden against transient registry network failures (ECONNRESET etc.):
# raise npm's built-in fetch retries/timeouts and retry `npm ci` up to # raise npm's built-in fetch retries/timeouts and retry `npm ci` up to
# 5 times with backoff before failing the build. # 3 times with backoff before failing the build.
run: | run: |
npm config set fetch-retries 5 npm config set fetch-retries 5
npm config set fetch-retry-mintimeout 10000 npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 60000 npm config set fetch-retry-maxtimeout 120000
npm config set fetch-timeout 300000 npm config set fetch-timeout 600000
for attempt in 1 2 3; do for attempt in 1 2 3; do
echo "npm ci attempt $attempt…" echo "npm ci attempt $attempt…"
npm ci && break npm ci && break
@@ -52,39 +57,36 @@ jobs:
sleep $((attempt * 15)) sleep $((attempt * 15))
done done
# ── Critical gate — if this fails, nothing deploys ────────────────── # ── Quality gates run BEFORE the slow build so a format/lint/type/test
# error fails in seconds instead of after the ~minutes-long build. All are
# hard gates — any failure fails the job and blocks the deploy. The tree is
# held clean (prettier formatted, eslint 0 errors, typecheck 0), so these
# gate real regressions. NOTE: the lotus-build.sh upstream-merge path can
# deploy without CI; a later normal push surfaces any introduced issue here
# — fix forward (or briefly re-soften a gate) rather than deploy broken.
# eslint gates on errors only (existing no-explicit-any warnings stay
# informational — check:eslint has no --max-warnings).
- name: Prettier
run: npm run check:prettier
- name: ESLint
run: npm run check:eslint
- name: TypeScript
run: npm run typecheck
# Deterministic pure-logic tests on Node's built-in runner via tsx (no
# vitest — Vite 8 is ahead of vitest's range). A failure blocks the deploy.
- name: Unit tests
run: npm test
# ── Critical gate — if this fails, nothing deploys. Produces dist/. ──
- name: Build - name: Build
run: npm run build run: npm run build
env: env:
NODE_OPTIONS: '--max_old_space_size=4096' NODE_OPTIONS: '--max_old_space_size=4096'
VITE_APP_VERSION: ${{ github.sha }} VITE_APP_VERSION: ${{ github.sha }}
# Unit tests are a hard gate too — deterministic pure-logic tests on Node's
# built-in runner via tsx (no vitest — Vite 8 is ahead of vitest's range).
# A failure blocks the deploy.
- name: Unit tests
run: npm test
# ── Quality gates (hard — a failure fails the job and blocks deploy) ──
# The tree is held clean (typecheck 0, eslint 0 errors, prettier
# formatted), so these gate real regressions instead of relying on local
# runs. NOTE: an upstream-stable merge (the lotus-build.sh path) could
# introduce upstream type/lint/format issues; that path deploys without
# CI, but a subsequent normal push would surface the failure here — fix
# forward (or briefly re-soften a gate) rather than let it deploy broken.
# eslint gates on errors only (existing `no-explicit-any` warnings stay
# informational — `check:eslint` has no --max-warnings).
- name: TypeScript
run: npm run typecheck
- name: ESLint
run: npm run check:eslint
continue-on-error: true
- name: Prettier
run: npm run check:prettier
continue-on-error: true
# ── Security (informational — findings shouldn't block a deploy) ───── # ── Security (informational — findings shouldn't block a deploy) ─────
- name: Audit (high/critical) - name: Audit (high/critical)
run: npm audit --audit-level=high --omit=dev run: npm audit --audit-level=high --omit=dev
+1 -3
View File
@@ -1,3 +1 @@
# These are commented until we enable lint and typecheck npx lint-staged
# npx tsc -p tsconfig.json --noEmit
# npx lint-staged
+1 -15
View File
@@ -1,19 +1,5 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"js/ts.tsdk.path": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib"
"prettier.requireConfig": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
} }
+10 -1
View File
@@ -129,7 +129,16 @@ Lotus Chat has a desktop app for Windows, macOS, and Linux. It wraps the same we
### Download ### Download
Download the latest release from the [Releases page on code.lotusguild.org](https://code.lotusguild.org). Operating System | Download
---|---
Windows | [Get the installer (.exe)](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64-setup.exe)
Linux (AppImage, any distro) | [Get the AppImage](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.AppImage)
Linux (Debian/Ubuntu) | [Get the .deb](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.deb)
Linux (Arch/CachyOS/EndeavourOS) | [Get the .pkg.tar.zst](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64.pkg.tar.zst) — install with `pacman -U LotusChat-x86_64.pkg.tar.zst`
All Linux builds need `webkit2gtk-4.1` and, for calls to work, GStreamer's `good`/`bad`/`ugly`/`libav` plugin sets (the pacman package pulls these in automatically; on the AppImage/.deb, install them via your package manager if joining a call shows "browser does not support WebRTC").
See the full [Releases page on code.lotusguild.org](https://code.lotusguild.org/LotusGuild/cinny-desktop/releases) for signatures and older builds.
### SmartScreen Warning (Windows) ### SmartScreen Warning (Windows)
+40 -42
View File
@@ -19,11 +19,10 @@
* *
* Any failure falls back to the unprocessed mic so calls never break. * Any failure falls back to the unprocessed mic so calls never break.
*/ */
// TODO: MAKE THIS A TS FILE
(function () { (function () {
'use strict'; 'use strict';
let params; var params;
try { try {
params = new URLSearchParams(window.location.search); params = new URLSearchParams(window.location.search);
if (params.get('lotusDenoise') !== 'ml') return; if (params.get('lotusDenoise') !== 'ml') return;
@@ -34,31 +33,31 @@
// Derive the parent origin for postMessage targetOrigin from the parentUrl // Derive the parent origin for postMessage targetOrigin from the parentUrl
// widget param (a full URL) so denoise-status messages aren't broadcast with // widget param (a full URL) so denoise-status messages aren't broadcast with
// '*'. Fall back to this frame's own origin if parentUrl is missing/malformed. // '*'. Fall back to this frame's own origin if parentUrl is missing/malformed.
let targetOrigin; var targetOrigin;
try { try {
let parentUrl = params.get('parentUrl'); var parentUrl = params.get('parentUrl');
targetOrigin = parentUrl ? new URL(parentUrl).origin : window.location.origin; targetOrigin = parentUrl ? new URL(parentUrl).origin : window.location.origin;
} catch (e) { } catch (e) {
targetOrigin = window.location.origin; targetOrigin = window.location.origin;
} }
let md = navigator.mediaDevices; var md = navigator.mediaDevices;
if (!md || typeof md.getUserMedia !== 'function') return; if (!md || typeof md.getUserMedia !== 'function') return;
if (typeof AudioWorkletNode === 'undefined' || typeof AudioContext === 'undefined') return; if (typeof AudioWorkletNode === 'undefined' || typeof AudioContext === 'undefined') return;
let ASSET_BASE = './denoise/'; var ASSET_BASE = './denoise/';
let MODEL = params.get('lotusModel') || 'rnnoise'; var MODEL = params.get('lotusModel') || 'rnnoise';
// DTLN (@workadventure) targets 16 kHz and does not resample internally, so // DTLN (@workadventure) targets 16 kHz and does not resample internally, so
// its whole graph runs in a 16 kHz context; RNNoise/Speex (sapphi) and // its whole graph runs in a 16 kHz context; RNNoise/Speex (sapphi) and
// DeepFilterNet 3 are 48 kHz fullband. The processed MediaStreamTrack is // DeepFilterNet 3 are 48 kHz fullband. The processed MediaStreamTrack is
// published to LiveKit either way (WebRTC/Opus resamples as needed). // published to LiveKit either way (WebRTC/Opus resamples as needed).
let SAMPLE_RATE = MODEL === 'dtln' ? 16000 : 48000; var SAMPLE_RATE = MODEL === 'dtln' ? 16000 : 48000;
let USE_NATIVE_NS = params.get('lotusNativeNS') === 'true'; var USE_NATIVE_NS = params.get('lotusNativeNS') === 'true';
let USE_GATE = params.get('lotusGate') === 'true'; var USE_GATE = params.get('lotusGate') === 'true';
let GATE_THRESHOLD = parseFloat(params.get('lotusGateThreshold') || '-45'); var GATE_THRESHOLD = parseFloat(params.get('lotusGateThreshold') || '-45');
let PROCESSORS = { var PROCESSORS = {
rnnoise: { rnnoise: {
name: '@sapphi-red/web-noise-suppressor/rnnoise', name: '@sapphi-red/web-noise-suppressor/rnnoise',
script: 'rnnoiseWorklet.js', script: 'rnnoiseWorklet.js',
@@ -92,9 +91,9 @@
}, },
}; };
let origGetUserMedia = md.getUserMedia.bind(md); var origGetUserMedia = md.getUserMedia.bind(md);
let wasmPromises = {}; var wasmPromises = {};
let ctxPromise = null; var ctxPromise = null;
function checkSimd() { function checkSimd() {
try { try {
@@ -113,12 +112,12 @@
function loadWasm(modelId) { function loadWasm(modelId) {
if (wasmPromises[modelId]) return wasmPromises[modelId]; if (wasmPromises[modelId]) return wasmPromises[modelId];
let p = PROCESSORS[modelId]; var p = PROCESSORS[modelId];
if (!p || !p.wasm) return Promise.resolve(null); if (!p || !p.wasm) return Promise.resolve(null);
wasmPromises[modelId] = (modelId === 'rnnoise' ? checkSimd() : Promise.resolve(false)).then( wasmPromises[modelId] = (modelId === 'rnnoise' ? checkSimd() : Promise.resolve(false)).then(
function (simd) { function (simd) {
let file = simd && p.simdWasm ? p.simdWasm : p.wasm; var file = simd && p.simdWasm ? p.simdWasm : p.wasm;
return fetch(ASSET_BASE + file).then(function (r) { return fetch(ASSET_BASE + file).then(function (r) {
if (!r.ok) { if (!r.ok) {
if (simd && p.simdWasm) if (simd && p.simdWasm)
@@ -138,7 +137,7 @@
function getContext() { function getContext() {
if (!ctxPromise) { if (!ctxPromise) {
ctxPromise = (function () { ctxPromise = (function () {
let ctx = new AudioContext({ sampleRate: SAMPLE_RATE }); var ctx = new AudioContext({ sampleRate: SAMPLE_RATE });
if (ctx.sampleRate !== SAMPLE_RATE) { if (ctx.sampleRate !== SAMPLE_RATE) {
try { try {
ctx.close(); ctx.close();
@@ -147,7 +146,7 @@
} }
// Load worklet modules. DTLN registers its own processor via the // Load worklet modules. DTLN registers its own processor via the
// dynamic-imported helper (see buildMlNode), so it needs nothing here. // dynamic-imported helper (see buildMlNode), so it needs nothing here.
let scripts = []; var scripts = [];
if (MODEL === 'rnnoise' || MODEL === 'speex') scripts.push(PROCESSORS[MODEL].script); if (MODEL === 'rnnoise' || MODEL === 'speex') scripts.push(PROCESSORS[MODEL].script);
if (USE_GATE) scripts.push(PROCESSORS.gate.script); if (USE_GATE) scripts.push(PROCESSORS.gate.script);
@@ -170,7 +169,7 @@
return ctxPromise; return ctxPromise;
} }
let hasNotifiedActive = false; var hasNotifiedActive = false;
// Build the ML denoise AudioWorkletNode. RNNoise/Speex are flat sapphi // Build the ML denoise AudioWorkletNode. RNNoise/Speex are flat sapphi
// worklets we instantiate directly with the fetched WASM binary. DTLN comes // worklets we instantiate directly with the fetched WASM binary. DTLN comes
@@ -188,9 +187,9 @@
if (MODEL === 'deepfilternet') { if (MODEL === 'deepfilternet') {
// Resolve an absolute self-hosted base so the package's cdnUrl override // Resolve an absolute self-hosted base so the package's cdnUrl override
// fetches our vendored df_bg.wasm + ONNX model (never the upstream CDN). // fetches our vendored df_bg.wasm + ONNX model (never the upstream CDN).
let dfnBase = new URL(ASSET_BASE + 'deepfilternet', window.location.href).href; var dfnBase = new URL(ASSET_BASE + 'deepfilternet', window.location.href).href;
return import(ASSET_BASE + PROCESSORS.deepfilternet.esm).then(function (mod) { return import(ASSET_BASE + PROCESSORS.deepfilternet.esm).then(function (mod) {
let core = new mod.DeepFilterNet3Core({ var core = new mod.DeepFilterNet3Core({
sampleRate: SAMPLE_RATE, sampleRate: SAMPLE_RATE,
noiseReductionLevel: 80, noiseReductionLevel: 80,
assetConfig: { cdnUrl: dfnBase }, assetConfig: { cdnUrl: dfnBase },
@@ -213,8 +212,7 @@
}); });
}); });
} }
var node = new AudioWorkletNode(ctx, PROCESSORS[MODEL].name, {
let node = new AudioWorkletNode(ctx, PROCESSORS[MODEL].name, {
channelCount: 1, channelCount: 1,
numberOfInputs: 1, numberOfInputs: 1,
numberOfOutputs: 1, numberOfOutputs: 1,
@@ -232,21 +230,21 @@
} }
function processStream(stream) { function processStream(stream) {
let audioTracks = stream.getAudioTracks(); var audioTracks = stream.getAudioTracks();
if (audioTracks.length === 0) return Promise.resolve(stream); if (audioTracks.length === 0) return Promise.resolve(stream);
return Promise.all([loadWasm(MODEL), getContext()]) return Promise.all([loadWasm(MODEL), getContext()])
.then(function (res) { .then(function (res) {
let wasmBinary = res[0]; var wasmBinary = res[0];
let ctx = res[1]; var ctx = res[1];
let source = ctx.createMediaStreamSource(stream); var source = ctx.createMediaStreamSource(stream);
let dest = ctx.createMediaStreamDestination(); var dest = ctx.createMediaStreamDestination();
let head = source; var head = source;
// 1. Optional Noise Gate // 1. Optional Noise Gate
if (USE_GATE) { if (USE_GATE) {
let gateNode = new AudioWorkletNode(ctx, PROCESSORS.gate.name, { var gateNode = new AudioWorkletNode(ctx, PROCESSORS.gate.name, {
processorOptions: { processorOptions: {
openThreshold: GATE_THRESHOLD, openThreshold: GATE_THRESHOLD,
closeThreshold: GATE_THRESHOLD - 5, closeThreshold: GATE_THRESHOLD - 5,
@@ -260,7 +258,7 @@
// 2. ML Processor // 2. ML Processor
return buildMlNode(ctx, wasmBinary).then(function (ml) { return buildMlNode(ctx, wasmBinary).then(function (ml) {
let mlNode = ml.node; var mlNode = ml.node;
head.connect(mlNode); head.connect(mlNode);
mlNode.connect(dest); mlNode.connect(dest);
@@ -268,15 +266,15 @@
// the track handoff — audio flows via bypassUntilReady meanwhile. // the track handoff — audio flows via bypassUntilReady meanwhile.
if (ml.ready && typeof ml.ready.then === 'function') { if (ml.ready && typeof ml.ready.then === 'function') {
ml.ready.catch(function (err) { ml.ready.catch(function (err) {
let m = err instanceof Error ? err.message : String(err); var m = err instanceof Error ? err.message : String(err);
console.error('[lotus-denoise] ' + MODEL + ' init failed:', m); console.error('[lotus-denoise] ' + MODEL + ' init failed:', m);
}); });
} }
let origTrack = audioTracks[0]; var origTrack = audioTracks[0];
let processedTrack = dest.stream.getAudioTracks()[0]; var processedTrack = dest.stream.getAudioTracks()[0];
let torndown = false; var torndown = false;
function cleanup() { function cleanup() {
if (torndown) return; if (torndown) return;
torndown = true; torndown = true;
@@ -295,7 +293,7 @@
} catch (e) {} } catch (e) {}
} }
let rawStop = processedTrack.stop.bind(processedTrack); var rawStop = processedTrack.stop.bind(processedTrack);
processedTrack.stop = function () { processedTrack.stop = function () {
cleanup(); cleanup();
rawStop(); rawStop();
@@ -321,7 +319,7 @@
); );
} }
let out = new MediaStream(); var out = new MediaStream();
out.addTrack(processedTrack); out.addTrack(processedTrack);
stream.getVideoTracks().forEach(function (t) { stream.getVideoTracks().forEach(function (t) {
out.addTrack(t); out.addTrack(t);
@@ -330,7 +328,7 @@
}); });
}) })
.catch(function (e) { .catch(function (e) {
let msg = e instanceof Error ? e.message : String(e); var msg = e instanceof Error ? e.message : String(e);
console.error('[lotus-denoise] Setup failed:', msg); console.error('[lotus-denoise] Setup failed:', msg);
window.parent.postMessage( window.parent.postMessage(
{ type: 'lotus-denoise-status', active: false, error: msg }, { type: 'lotus-denoise-status', active: false, error: msg },
@@ -341,10 +339,10 @@
} }
navigator.mediaDevices.getUserMedia = function (constraints) { navigator.mediaDevices.getUserMedia = function (constraints) {
let wantsAudio = !!(constraints && constraints.audio); var wantsAudio = !!(constraints && constraints.audio);
let effective = constraints; var effective = constraints;
if (wantsAudio) { if (wantsAudio) {
let audioC = var audioC =
typeof constraints.audio === 'object' ? Object.assign({}, constraints.audio) : {}; typeof constraints.audio === 'object' ? Object.assign({}, constraints.audio) : {};
audioC.noiseSuppression = USE_NATIVE_NS; audioC.noiseSuppression = USE_NATIVE_NS;
audioC.channelCount = 1; audioC.channelCount = 1;
+3 -3
View File
@@ -4,9 +4,9 @@
"allowCustomHomeservers": true, "allowCustomHomeservers": true,
"featuredCommunities": { "featuredCommunities": {
"openAsDefault": false, "openAsDefault": false,
"spaces": [], "spaces": ["!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc", "#homelab:codestorm.net"],
"rooms": [], "rooms": ["#jellyfin:matrix.org"],
"servers": [] "servers": ["matrixrooms.info"]
}, },
"hashRouter": { "hashRouter": {
"enabled": false, "enabled": false,
+3783 -2426
View File
File diff suppressed because it is too large Load Diff
+16 -11
View File
@@ -41,21 +41,24 @@
"@eslint/eslintrc": "3.3.5", "@eslint/eslintrc": "3.3.5",
"@eslint/js": "10.0.1", "@eslint/js": "10.0.1",
"@fontsource-variable/inter": "5.2.8", "@fontsource-variable/inter": "5.2.8",
"@giphy/js-fetch-api": "5.8.0",
"@giphy/js-types": "5.1.0", "@giphy/js-types": "5.1.0",
"@giphy/js-util": "5.2.0",
"@giphy/react-components": "10.1.2", "@giphy/react-components": "10.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5", "@sapphi-red/web-noise-suppressor": "0.3.5",
"@tanstack/react-query": "5.100.13", "@tanstack/react-query": "5.100.13",
"@tanstack/react-query-devtools": "5.100.13", "@tanstack/react-query-devtools": "5.100.13",
"@tanstack/react-virtual": "3.13.25", "@tanstack/react-virtual": "3.13.25",
"@workadventure/noise-suppression": "0.1.1", "@workadventure/noise-suppression": "0.0.4",
"await-to-js": "3.0.0", "await-to-js": "3.0.0",
"badwords-list": "2.0.1-4", "badwords-list": "2.0.1-4",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"browser-encrypt-attachment": "0.3.0", "browser-encrypt-attachment": "0.3.0",
"chroma-js": "3.2.0", "chroma-js": "3.2.0",
"classnames": "2.5.1", "classnames": "2.5.1",
"dateformat": "5.0.3",
"dayjs": "1.11.20", "dayjs": "1.11.20",
"deepfilternet3-noise-filter": "1.3.0", "deepfilternet3-noise-filter": "1.2.1",
"domhandler": "6.0.1", "domhandler": "6.0.1",
"emojibase": "17.0.0", "emojibase": "17.0.0",
"emojibase-data": "17.0.0", "emojibase-data": "17.0.0",
@@ -72,13 +75,12 @@
"is-hotkey": "0.2.0", "is-hotkey": "0.2.0",
"jotai": "2.20.0", "jotai": "2.20.0",
"jsqr": "1.4.0", "jsqr": "1.4.0",
"katex": "0.16.47", "katex": "0.16.11",
"linkify-react": "4.3.3", "linkify-react": "4.3.3",
"linkifyjs": "4.3.3", "linkifyjs": "4.3.3",
"matrix-js-sdk": "41.7.0", "matrix-js-sdk": "41.7.0",
"matrix-widget-api": "1.17.0", "matrix-widget-api": "1.17.0",
"millify": "6.1.0", "millify": "6.1.0",
"oidc-client-ts": "3.5.0",
"pdfjs-dist": "5.7.284", "pdfjs-dist": "5.7.284",
"prismjs": "1.30.0", "prismjs": "1.30.0",
"qrcode": "1.5.4", "qrcode": "1.5.4",
@@ -92,8 +94,8 @@
"react-google-recaptcha": "3.1.0", "react-google-recaptcha": "3.1.0",
"react-i18next": "17.0.8", "react-i18next": "17.0.8",
"react-range": "1.10.0", "react-range": "1.10.0",
"react-router": "8.3.0", "react-router-dom": "7.15.1",
"sanitize-html": "2.17.6", "sanitize-html": "2.17.4",
"slate": "0.124.1", "slate": "0.124.1",
"slate-dom": "0.124.1", "slate-dom": "0.124.1",
"slate-history": "0.113.1", "slate-history": "0.113.1",
@@ -109,6 +111,7 @@
"@types/chroma-js": "3.1.2", "@types/chroma-js": "3.1.2",
"@types/file-saver": "2.0.7", "@types/file-saver": "2.0.7",
"@types/is-hotkey": "0.1.10", "@types/is-hotkey": "0.1.10",
"@types/katex": "0.16.8",
"@types/node": "25.9.1", "@types/node": "25.9.1",
"@types/prismjs": "1.26.6", "@types/prismjs": "1.26.6",
"@types/qrcode": "1.5.6", "@types/qrcode": "1.5.6",
@@ -116,22 +119,28 @@
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.3",
"@types/react-google-recaptcha": "2.1.9", "@types/react-google-recaptcha": "2.1.9",
"@types/sanitize-html": "2.16.1", "@types/sanitize-html": "2.16.1",
"@types/ua-parser-js": "0.7.39",
"@typescript-eslint/eslint-plugin": "8.59.4", "@typescript-eslint/eslint-plugin": "8.59.4",
"@typescript-eslint/parser": "8.59.4", "@typescript-eslint/parser": "8.59.4",
"@vanilla-extract/css": "1.20.1", "@vanilla-extract/css": "1.20.1",
"@vanilla-extract/recipes": "0.5.7", "@vanilla-extract/recipes": "0.5.7",
"@vanilla-extract/vite-plugin": "5.2.2", "@vanilla-extract/vite-plugin": "5.2.2",
"@vitejs/plugin-react": "6.0.2", "@vitejs/plugin-react": "6.0.2",
"buffer": "6.0.3",
"cz-conventional-changelog": "3.3.0",
"eslint": "9.39.4", "eslint": "9.39.4",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.5", "eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1", "eslint-plugin-react-hooks": "7.1.1",
"husky": "9.1.7", "husky": "9.1.7",
"lint-staged": "17.0.5",
"prettier": "3.8.3", "prettier": "3.8.3",
"tsx": "4.22.4", "tsx": "4.22.4",
"typescript": "6.0.3", "typescript": "6.0.3",
"vite": "8.2.0", "vite": "8.0.14",
"vite-plugin-pwa": "1.3.0", "vite-plugin-pwa": "1.3.0",
"vite-plugin-static-copy": "4.1.0" "vite-plugin-static-copy": "4.1.0"
}, },
@@ -140,9 +149,5 @@
"dompurify": ">=3.3.4" "dompurify": ">=3.3.4"
}, },
"js-cookie": ">=3.0.6" "js-cookie": ">=3.0.6"
},
"allowScripts": {
"esbuild@0.28.1": true,
"protobufjs@7.6.5": true
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactNode, useCallback } from 'react'; import { ReactNode, useCallback } from 'react';
import { matchPath, useLocation, useNavigate } from 'react-router'; import { matchPath, useLocation, useNavigate } from 'react-router-dom';
import { import {
getDirectPath, getDirectPath,
getExplorePath, getExplorePath,
+1 -1
View File
@@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { ComponentProps, forwardRef } from 'react'; import React, { ComponentProps, forwardRef } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router-dom';
import { as } from 'folds'; import { as } from 'folds';
import * as css from './styles.css'; import * as css from './styles.css';
@@ -1,5 +1,5 @@
import React, { MouseEventHandler, useCallback, useMemo, useState } from 'react'; import React, { MouseEventHandler, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { isKeyHotkey } from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { Room } from 'matrix-js-sdk'; import { Room } from 'matrix-js-sdk';
@@ -1,6 +1,6 @@
import { Box, Button, color, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds'; import { Box, Button, color, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api'; import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api';
import { AsyncState, AsyncStatus, useAsync } from '../../hooks/useAsyncCallback'; import { AsyncState, AsyncStatus, useAsync } from '../../hooks/useAsyncCallback';
import { VerificationStatus } from '../../hooks/useDeviceVerificationStatus'; import { VerificationStatus } from '../../hooks/useDeviceVerificationStatus';
+1 -1
View File
@@ -1,7 +1,7 @@
import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds'; import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds';
import React, { FormEventHandler, useCallback, useState } from 'react'; import React, { FormEventHandler, useCallback, useState } from 'react';
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk'; import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { SettingTile } from '../../components/setting-tile'; import { SettingTile } from '../../components/setting-tile';
import { SequenceCard } from '../../components/sequence-card'; import { SequenceCard } from '../../components/sequence-card';
import { addRoomIdToMDirect, isUserId } from '../../utils/matrix'; import { addRoomIdToMDirect, isUserId } from '../../utils/matrix';
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { MouseEventHandler, useCallback, useMemo, useRef, useState } from
import { Box, Chip, Icon, IconButton, Icons, Line, Scroll, Spinner, Text, config } from 'folds'; import { Box, Chip, Icon, IconButton, Icons, Line, Scroll, Spinner, Text, config } from 'folds';
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { JoinRule, RestrictedAllowType, Room } from 'matrix-js-sdk'; import { JoinRule, RestrictedAllowType, Room } from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
@@ -17,7 +17,7 @@ import {
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import { useInfiniteQuery } from '@tanstack/react-query'; import { useInfiniteQuery } from '@tanstack/react-query';
import { useSearchParams } from 'react-router'; import { useSearchParams } from 'react-router-dom';
import { EventTimeline, EventType, Room, SearchOrderBy } from 'matrix-js-sdk'; import { EventTimeline, EventType, Room, SearchOrderBy } from 'matrix-js-sdk';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types'; import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
import { PageHero, PageHeroEmpty, PageHeroSection } from '../../components/page'; import { PageHero, PageHeroEmpty, PageHeroSection } from '../../components/page';
+1 -4
View File
@@ -66,6 +66,7 @@ import { useSpaceOptionally } from '../../hooks/useSpace';
import { import {
getRoomNotificationModeIcon, getRoomNotificationModeIcon,
RoomNotificationMode, RoomNotificationMode,
setRoomNotificationPreference,
} from '../../hooks/useRoomsNotificationPreferences'; } from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCreators'; import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCreators';
@@ -298,8 +299,6 @@ export function saveMuteTimers(timers: MuteTimerEntry[]): void {
// Reverse a timed mute: restore the room's notification mode to Unset and drop // Reverse a timed mute: restore the room's notification mode to Unset and drop
// its persisted timer. Shared by the in-session timer and the boot-time restore. // its persisted timer. Shared by the in-session timer and the boot-time restore.
export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise<void> { export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise<void> {
const { setRoomNotificationPreference } =
await import('../../hooks/useRoomsNotificationPreferences');
await setRoomNotificationPreference( await setRoomNotificationPreference(
mx, mx,
roomId, roomId,
@@ -392,8 +391,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const handleMuteFor = useCallback( const handleMuteFor = useCallback(
async (durationMs: number | null) => { async (durationMs: number | null) => {
const { setRoomNotificationPreference } =
await import('../../hooks/useRoomsNotificationPreferences');
const prevMode = notificationMode ?? RoomNotificationMode.Unset; const prevMode = notificationMode ?? RoomNotificationMode.Unset;
await setRoomNotificationPreference( await setRoomNotificationPreference(
mx, mx,
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton, toRem } from 'folds'; import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton, toRem } from 'folds';
import { Page, PageHeader } from '../../components/page'; import { Page, PageHeader } from '../../components/page';
import { callChatAtom } from '../../state/callEmbed'; import { callChatAtom } from '../../state/callEmbed';
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import { Box, Line } from 'folds'; import { Box, Line } from 'folds';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { isKeyHotkey } from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { useAtomValue, useSetAtom } from 'jotai'; import { useAtomValue, useSetAtom } from 'jotai';
import { RoomView } from './RoomView'; import { RoomView } from './RoomView';
+1 -1
View File
@@ -25,7 +25,7 @@ import {
Button, Button,
} from 'folds'; } from 'folds';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { Room } from 'matrix-js-sdk'; import { Room } from 'matrix-js-sdk';
import { useStateEvent } from '../../hooks/useStateEvent'; import { useStateEvent } from '../../hooks/useStateEvent';
import { PageHeader } from '../../components/page'; import { PageHeader } from '../../components/page';
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch } from 'react-router'; import { useMatch } from 'react-router-dom';
import { getCreatePath } from '../../pages/pathUtils'; import { getCreatePath } from '../../pages/pathUtils';
export const useCreateSelected = (): boolean => { export const useCreateSelected = (): boolean => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch } from 'react-router'; import { useMatch } from 'react-router-dom';
import { getDirectCreatePath, getDirectPath } from '../../pages/pathUtils'; import { getDirectCreatePath, getDirectPath } from '../../pages/pathUtils';
export const useDirectSelected = (): boolean => { export const useDirectSelected = (): boolean => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch, useParams } from 'react-router'; import { useMatch, useParams } from 'react-router-dom';
import { getExploreFeaturedPath, getExplorePath } from '../../pages/pathUtils'; import { getExploreFeaturedPath, getExplorePath } from '../../pages/pathUtils';
export const useExploreSelected = (): boolean => { export const useExploreSelected = (): boolean => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch } from 'react-router'; import { useMatch } from 'react-router-dom';
import { import {
getHomeCreatePath, getHomeCreatePath,
getHomeJoinPath, getHomeJoinPath,
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch } from 'react-router'; import { useMatch } from 'react-router-dom';
import { import {
getInboxInvitesPath, getInboxInvitesPath,
getInboxNotificationsPath, getInboxNotificationsPath,
@@ -1,5 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useSearchParams } from 'react-router'; import { useSearchParams } from 'react-router-dom';
import { getRoomSearchParams } from '../../pages/pathSearchParam'; import { getRoomSearchParams } from '../../pages/pathSearchParam';
import { decodeSearchParamValueArray } from '../../pages/pathUtils'; import { decodeSearchParamValueArray } from '../../pages/pathUtils';
+1 -1
View File
@@ -1,4 +1,4 @@
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix'; import { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix';
import { useMatrixClient } from '../useMatrixClient'; import { useMatrixClient } from '../useMatrixClient';
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMatch, useParams } from 'react-router'; import { useMatch, useParams } from 'react-router-dom';
import { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix'; import { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix';
import { useMatrixClient } from '../useMatrixClient'; import { useMatrixClient } from '../useMatrixClient';
import { getSpaceLobbyPath, getSpaceSearchPath } from '../../pages/pathUtils'; import { getSpaceLobbyPath, getSpaceSearchPath } from '../../pages/pathUtils';
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { useMatrixClient } from './useMatrixClient'; import { useMatrixClient } from './useMatrixClient';
import { useRoomNavigate } from './useRoomNavigate'; import { useRoomNavigate } from './useRoomNavigate';
import { isRoomId } from '../utils/matrix'; import { isRoomId } from '../utils/matrix';
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactEventHandler, useCallback } from 'react'; import { ReactEventHandler, useCallback } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { useRoomNavigate } from './useRoomNavigate'; import { useRoomNavigate } from './useRoomNavigate';
import { useMatrixClient } from './useMatrixClient'; import { useMatrixClient } from './useMatrixClient';
import { isRoomId, isUserId } from '../utils/matrix'; import { isRoomId, isUserId } from '../utils/matrix';
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router-dom';
import { useNavToActivePathAtom } from '../state/hooks/navToActivePath'; import { useNavToActivePathAtom } from '../state/hooks/navToActivePath';
export const useNavToActivePathMapper = (navId: string) => { export const useNavToActivePathMapper = (navId: string) => {
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { NavigateOptions, useNavigate } from 'react-router'; import { NavigateOptions, useNavigate } from 'react-router-dom';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { getCanonicalAliasOrRoomId } from '../utils/matrix'; import { getCanonicalAliasOrRoomId } from '../utils/matrix';
import { import {
+1 -1
View File
@@ -1,4 +1,4 @@
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { MsgType } from 'matrix-js-sdk'; import { MsgType } from 'matrix-js-sdk';
import { useMatrixClient } from './useMatrixClient'; import { useMatrixClient } from './useMatrixClient';
import { useTauriEvent } from './useTauri'; import { useTauriEvent } from './useTauri';
+1 -1
View File
@@ -11,7 +11,7 @@ import {
toRem, toRem,
TooltipContainerProvider, TooltipContainerProvider,
} from 'folds'; } from 'folds';
import { RouterProvider } from 'react-router'; import { RouterProvider } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useMatch } from 'react-router'; import { useMatch } from 'react-router-dom';
import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize';
import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from './paths'; import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from './paths';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useRouteError, isRouteErrorResponse } from 'react-router'; import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
import { Box, Button, config, Text, toRem } from 'folds'; import { Box, Button, config, Text, toRem } from 'folds';
export function RouteError() { export function RouteError() {
+1 -1
View File
@@ -6,7 +6,7 @@ import {
createHashRouter, createHashRouter,
createRoutesFromElements, createRoutesFromElements,
redirect, redirect,
} from 'react-router'; } from 'react-router-dom';
import { RoomSkeleton } from '../components/RoomSkeleton'; import { RoomSkeleton } from '../components/RoomSkeleton';
import { LobbySkeleton } from '../components/LobbySkeleton'; import { LobbySkeleton } from '../components/LobbySkeleton';
import { AuthSkeleton } from '../components/AuthSkeleton'; import { AuthSkeleton } from '../components/AuthSkeleton';
+8 -1
View File
@@ -1,6 +1,13 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { Box, Header, Scroll, Spinner, Text, color } from 'folds'; import { Box, Header, Scroll, Spinner, Text, color } from 'folds';
import { Outlet, generatePath, matchPath, useLocation, useNavigate, useParams } from 'react-router'; import {
Outlet,
generatePath,
matchPath,
useLocation,
useNavigate,
useParams,
} from 'react-router-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { AuthFooter } from './AuthFooter'; import { AuthFooter } from './AuthFooter';
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Text, color } from 'folds'; import { Box, Text, color } from 'folds';
import { Link, useSearchParams } from 'react-router'; import { Link, useSearchParams } from 'react-router-dom';
import { SSOAction } from 'matrix-js-sdk'; import { SSOAction } from 'matrix-js-sdk';
import { useAuthFlows } from '../../../hooks/useAuthFlows'; import { useAuthFlows } from '../../../hooks/useAuthFlows';
import { useAuthServer } from '../../../hooks/useAuthServer'; import { useAuthServer } from '../../../hooks/useAuthServer';
@@ -18,7 +18,7 @@ import {
config, config,
} from 'folds'; } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { Link } from 'react-router'; import { Link } from 'react-router-dom';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../../utils/matrix'; import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../../utils/matrix';
import { EMAIL_REGEX } from '../../../utils/regex'; import { EMAIL_REGEX } from '../../../utils/regex';
+1 -1
View File
@@ -1,7 +1,7 @@
import to from 'await-to-js'; import to from 'await-to-js';
import { LoginRequest, LoginResponse, MatrixError, createClient } from 'matrix-js-sdk'; import { LoginRequest, LoginResponse, MatrixError, createClient } from 'matrix-js-sdk';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { ClientConfig, clientAllowedServer } from '../../../hooks/useClientConfig'; import { ClientConfig, clientAllowedServer } from '../../../hooks/useClientConfig';
import { autoDiscovery, specVersions } from '../../../cs-api'; import { autoDiscovery, specVersions } from '../../../cs-api';
import { ErrorCode } from '../../../cs-errorcode'; import { ErrorCode } from '../../../cs-errorcode';
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Text, color } from 'folds'; import { Box, Text, color } from 'folds';
import { Link, useSearchParams } from 'react-router'; import { Link, useSearchParams } from 'react-router-dom';
import { SSOAction } from 'matrix-js-sdk'; import { SSOAction } from 'matrix-js-sdk';
import { useAuthServer } from '../../../hooks/useAuthServer'; import { useAuthServer } from '../../../hooks/useAuthServer';
import { RegisterFlowStatus, useAuthFlows } from '../../../hooks/useAuthFlows'; import { RegisterFlowStatus, useAuthFlows } from '../../../hooks/useAuthFlows';
+1 -1
View File
@@ -7,7 +7,7 @@ import {
RegisterResponse, RegisterResponse,
} from 'matrix-js-sdk'; } from 'matrix-js-sdk';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { LoginPathSearchParams } from '../../paths'; import { LoginPathSearchParams } from '../../paths';
import { ErrorCode } from '../../../cs-errorcode'; import { ErrorCode } from '../../../cs-errorcode';
import { import {
@@ -12,7 +12,7 @@ import {
color, color,
config, config,
} from 'folds'; } from 'folds';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { AuthDict, AuthType, MatrixError, createClient } from 'matrix-js-sdk'; import { AuthDict, AuthType, MatrixError, createClient } from 'matrix-js-sdk';
import { useAutoDiscoveryInfo } from '../../../hooks/useAutoDiscoveryInfo'; import { useAutoDiscoveryInfo } from '../../../hooks/useAutoDiscoveryInfo';
@@ -1,6 +1,6 @@
import { Box, Text } from 'folds'; import { Box, Text } from 'folds';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router'; import { Link, useSearchParams } from 'react-router-dom';
import { getLoginPath } from '../../pathUtils'; import { getLoginPath } from '../../pathUtils';
import { useAuthServer } from '../../../hooks/useAuthServer'; import { useAuthServer } from '../../../hooks/useAuthServer';
import { PasswordResetForm } from './PasswordResetForm'; import { PasswordResetForm } from './PasswordResetForm';
+1 -5
View File
@@ -1,6 +1,6 @@
import { useAtomValue, useSetAtom } from 'jotai'; import { useAtomValue, useSetAtom } from 'jotai';
import React, { ReactNode, useCallback, useEffect, useRef } from 'react'; import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { import {
ClientEvent, ClientEvent,
ClientEventHandlerMap, ClientEventHandlerMap,
@@ -70,10 +70,6 @@ import {
THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR, THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR,
} from '../../utils/threadNotifications'; } from '../../utils/threadNotifications';
const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus.png');
const LogoUnreadSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus-unread.png');
const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus-highlight.png');
// Grace period after the initial sync settles before invite notifications arm, so // Grace period after the initial sync settles before invite notifications arm, so
// the async invite-atom population lands first and isn't mistaken for new invites. // the async invite-atom population lands first and isn't mistaken for new invites.
const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus.png'); const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus.png');
+1 -1
View File
@@ -19,7 +19,7 @@ import {
} from 'folds'; } from 'folds';
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { factoryRoomIdByActivity } from '../../../utils/sort'; import { factoryRoomIdByActivity } from '../../../utils/sort';
import { import {
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { Box, Icon, IconButton, Icons, Scroll } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll } from 'folds';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getDirectCreateSearchParams } from '../../pathSearchParam'; import { getDirectCreateSearchParams } from '../../pathSearchParam';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom'; import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { FormEventHandler, useCallback, useRef, useState } from 'react'; import React, { FormEventHandler, useCallback, useRef, useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { import {
Avatar, Avatar,
+1 -1
View File
@@ -27,7 +27,7 @@ import {
config, config,
toRem, toRem,
} from 'folds'; } from 'folds';
import { useNavigate, useParams, useSearchParams } from 'react-router'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
+1 -1
View File
@@ -6,7 +6,7 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { import {
Avatar, Avatar,
Box, Box,
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom'; import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
+1 -1
View File
@@ -12,7 +12,7 @@ import {
config, config,
toRem, toRem,
} from 'folds'; } from 'folds';
import { useSearchParams } from 'react-router'; import { useSearchParams } from 'react-router-dom';
import { import {
INotification, INotification,
INotificationsResponse, INotificationsResponse,
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { MouseEventHandler, useState } from 'react'; import React, { MouseEventHandler, useState } from 'react';
import { Box, config, Icon, Icons, Menu, PopOut, RectCords, Text } from 'folds'; import { Box, config, Icon, Icons, Menu, PopOut, RectCords, Text } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { MouseEventHandler, forwardRef, useState } from 'react'; import React, { MouseEventHandler, forwardRef, useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Icon, Icons } from 'folds'; import { Icon, Icons } from 'folds';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar';
import { useExploreSelected } from '../../../hooks/router/useExploreSelected'; import { useExploreSelected } from '../../../hooks/router/useExploreSelected';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { MouseEventHandler, forwardRef, useState } from 'react'; import React, { MouseEventHandler, forwardRef, useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { Icon, Icons } from 'folds'; import { Icon, Icons } from 'folds';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { import {
+1 -1
View File
@@ -9,7 +9,7 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { import {
Box, Box,
Icon, Icon,
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom'; import { IsDirectRoomProvider, RoomProvider } from '../../../hooks/useRoom';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router-dom';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useSpaces } from '../../../state/hooks/roomList'; import { useSpaces } from '../../../state/hooks/roomList';
import { allRoomsAtom } from '../../../state/room-list/roomList'; import { allRoomsAtom } from '../../../state/room-list/roomList';
+1 -1
View File
@@ -1,4 +1,4 @@
import { generatePath, Path } from 'react-router'; import { generatePath, Path } from 'react-router-dom';
import { import {
DIRECT_CREATE_PATH, DIRECT_CREATE_PATH,
DIRECT_PATH, DIRECT_PATH,
+1 -1
View File
@@ -2,7 +2,7 @@ import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStore } from 'jotai'; import { createStore } from 'jotai';
import { enableMapSet } from 'immer'; import { enableMapSet } from 'immer';
import type { Path } from 'react-router'; import type { Path } from 'react-router-dom';
// `makeNavToActivePathAtom(userId)` is a factory: localStorage is read when the // `makeNavToActivePathAtom(userId)` is a factory: localStorage is read when the
// returned atom is first created/accessed (not at module load), but we still // returned atom is first created/accessed (not at module load), but we still
+1 -1
View File
@@ -1,6 +1,6 @@
import { WritableAtom, atom } from 'jotai'; import { WritableAtom, atom } from 'jotai';
import { produce } from 'immer'; import { produce } from 'immer';
import { Path } from 'react-router'; import { Path } from 'react-router-dom';
import { import {
atomWithLocalStorage, atomWithLocalStorage,
getLocalStorageItem, getLocalStorageItem,
+2 -2
View File
@@ -69,7 +69,7 @@ export interface ChromeLanguageDetectorFactory {
declare global { declare global {
// eslint-disable-next-line vars-on-top // eslint-disable-next-line vars-on-top
let Translator: ChromeTranslatorFactory | undefined; var Translator: ChromeTranslatorFactory | undefined;
// eslint-disable-next-line vars-on-top // eslint-disable-next-line vars-on-top
let LanguageDetector: ChromeLanguageDetectorFactory | undefined; var LanguageDetector: ChromeLanguageDetectorFactory | undefined;
} }
+8 -6
View File
@@ -7,7 +7,7 @@ import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { VitePWA } from 'vite-plugin-pwa'; import { VitePWA } from 'vite-plugin-pwa';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import buildConfig from './build.config.ts'; import buildConfig from './build.config';
const copyFiles = { const copyFiles = {
targets: [ targets: [
@@ -227,7 +227,7 @@ const vendorChunks = (id) => {
if (id.includes('node_modules/matrix-js-sdk')) return 'matrix-sdk'; if (id.includes('node_modules/matrix-js-sdk')) return 'matrix-sdk';
if (id.includes('node_modules/react-dom')) return 'react-dom'; if (id.includes('node_modules/react-dom')) return 'react-dom';
if ( if (
id.includes('node_modules/react-router') || id.includes('node_modules/react-router-dom') ||
id.includes('node_modules/@remix-run') || id.includes('node_modules/@remix-run') ||
id.includes('node_modules/react-router/') id.includes('node_modules/react-router/')
) )
@@ -246,7 +246,7 @@ const vendorChunks = (id) => {
export default defineConfig({ export default defineConfig({
appType: 'spa', appType: 'spa',
publicDir: './public/res', publicDir: false,
base: buildConfig.base, base: buildConfig.base,
server: { server: {
port: 8080, port: 8080,
@@ -282,7 +282,7 @@ export default defineConfig({
dontCacheBustURLsMatching: /assets\//, dontCacheBustURLsMatching: /assets\//,
// Raised above the 2 MB default so the ~5.5 MB matrix-sdk crypto wasm // Raised above the 2 MB default so the ~5.5 MB matrix-sdk crypto wasm
// (hash-busted and hot on every session) is precached deliberately. // (hash-busted and hot on every session) is precached deliberately.
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, maximumFileSizeToCacheInBytes: 6 * 1024 * 1024,
// codeSplitting: false is not yet supported by vite-plugin-pwa 1.3.0; // codeSplitting: false is not yet supported by vite-plugin-pwa 1.3.0;
// the inlineDynamicImports deprecation warning from Vite is from pwa internal build // the inlineDynamicImports deprecation warning from Vite is from pwa internal build
}, },
@@ -293,8 +293,10 @@ export default defineConfig({
}), }),
], ],
optimizeDeps: { optimizeDeps: {
define: { rolldownOptions: {
global: 'globalThis', define: {
global: 'globalThis',
},
}, },
}, },
build: { build: {