Compare commits

...
Author SHA1 Message Date
nathan 3f37f69a13 i hate npm audit
CI / Build & Quality Checks (pull_request) Successful in 11m36s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 22:21:32 -04:00
nathan be004411c3 added another package back in
CI / Build & Quality Checks (pull_request) Successful in 10m51s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 21:58:24 -04:00
nathan f207da2344 fix ci prettier mistake
CI / Build & Quality Checks (pull_request) Successful in 10m57s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 21:39:17 -04:00
nathan b90c3b7313 Merge remote-tracking branch 'origin/lotus' into sw-fix
CI / Build & Quality Checks (pull_request) Failing after 6m7s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 21:10:35 -04:00
nathan c8475f07aa added restriction to lint only ts,js files
CI / Build & Quality Checks (pull_request) Failing after 5m57s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 21:03:43 -04:00
nathan 9b10d7d1c2 added prettier auto-fix to pipeline
CI / Build & Quality Checks (pull_request) Canceled after 1m19s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 21:02:01 -04:00
nathan a1bdaf65f6 removed unused eslint-disables, ran prettier fix
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 21:00:25 -04:00
nathan 0656e6ecb0 removed eslint unused ignore lines, config handles ignoring it now
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 20:55:42 -04:00
nathan 89ba05f462 removed warning for 'any' as a type from eslint
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 20:49:34 -04:00
jaredandClaude Opus 4.8 d5cfb663b9 chore(issues): point the issue chooser at this repo, not upstream Cinny
CI / Build & Quality Checks (push) Successful in 10m54s
CI / Trigger Desktop Build (push) Successful in 5s
The forked-in Cinny issue setup funneled reporters to the upstream project:
- config.yml disabled blank issues and linked to cinnyapp/cinny GitHub
  Discussions
- preapproved.md was Cinny's "do not open an issue, use Discussions"
  template (Gitea has no Discussions anyway)

Replace it with a Lotus setup: enable blank issues, drop the upstream
contact link, and add Bug Report + Feature Request templates. Now
/issues/new/choose offers Lotus templates and files against this repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:48:59 -04:00
nathan 2d5e90f715 linter doesnt like void
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 20:48:08 -04:00
nathan f1e4ef657c removed commitizen, added eslint package back
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 20:46:11 -04:00
nathan c1ec68e73e Merge remote-tracking branch 'origin/lotus' into sw-fix
CI / Build & Quality Checks (pull_request) Canceled after 2m37s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 20:30:14 -04:00
f2c356f288 refactor(assets): build logo/favicon URLs via withOriginBaseUrl helper
CI / Build & Quality Checks (push) Successful in 10m37s
CI / Trigger Desktop Build (push) Canceled after 0s
Swap the logo/favicon URL constants from the inline
`${trimTrailingSlash(import.meta.env.BASE_URL)}/public/res/...` form to the
repo's existing `withOriginBaseUrl(getOriginBaseUrl(), '/public/res/...')`
helper (already used here for the OIDC callback URL). Functionally equivalent —
same /public/res/ target, resolves in dev and the static-copied prod build — and
it keeps the logo URL absolute and consistent with clientUri for the OIDC
logoUri. No build-config change (publicDir stays false).

Co-authored-by: Nathan Vititoe <nathanvititoe@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:22:58 -04:00
nathan f6d66af280 Merge remote-tracking branch 'origin/image-path-changes' into sw-fix
CI / Build & Quality Checks (pull_request) Failing after 6m1s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 20:06:44 -04:00
nathan 50bb509484 fixed retarded prettier 'error'
CI / Build & Quality Checks (pull_request) Failing after 6m2s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 19:00:41 -04:00
nathan 149ea96957 removed test change
CI / Build & Quality Checks (pull_request) Failing after 6m1s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 18:37:30 -04:00
nathan 093f10db5c fix problem registering service worker when in a development environment
CI / Build & Quality Checks (pull_request) Canceled after 1m37s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
2026-08-02 18:34:54 -04:00
nathan 6a0037ecec read me updates with instructions for running a dev server
CI / Build & Quality Checks (pull_request) Failing after 5m54s
CI / Trigger Desktop Build (pull_request) Skipped
2026-08-02 18:04:25 -04:00
jaredandClaude Opus 4.8 f12e05c510 fix(assets): reference logo/favicon PNGs by URL instead of importing from public/
CI / Build & Quality Checks (push) Successful in 12m7s
CI / Trigger Desktop Build (push) Successful in 7s
Importing images from public/ (`import X from '../../public/res/*.png'`) is
fragile under Vite with `publicDir: false` and can white-screen the dev app —
Vite tries to resolve the public/ path as a module. Switch the five logo/
favicon call sites to the repo's existing BASE_URL URL pattern
(`${trimTrailingSlash(import.meta.env.BASE_URL)}/public/res/...`), matching how
config.json, locales, and the Element Call widget are referenced. Resolves
identically in dev and the static-copied prod build (public/res -> dist/public/res).

Sounds under public/sound/ stay ESM-imported: that folder is not copied to
dist/ by vite-plugin-static-copy, so a URL reference would 404.

Also add a Local Development section to the README (no-backend model, npm ci /
npm start on :8080, which homeserver to log in against, OIDC-on-localhost note).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 15:52:04 -04:00
38 changed files with 596 additions and 451 deletions
+4 -2
View File
@@ -80,8 +80,10 @@ jobs:
- name: ESLint - name: ESLint
run: npm run check:eslint run: npm run check:eslint
- name: Prettier - name: Prettier Check and Fix
run: npm run check:prettier run: |
npx prettier --write .
npm run check:prettier
# ── Security (informational — findings shouldn't block a deploy) ───── # ── Security (informational — findings shouldn't block a deploy) ─────
- name: Audit (high/critical) - name: Audit (high/critical)
+27
View File
@@ -0,0 +1,27 @@
---
name: Bug Report
about: Report something that isn't working in Lotus Chat
title: ''
labels: bug
---
**Describe the bug**
A clear and concise description of what went wrong.
**Steps to reproduce**
1. Go to '...'
2. Click on '...'
3. See error
**Expected behavior**
What you expected to happen instead.
**Client info**
- Lotus Chat version (Settings → Help & About):
- Platform: Web / Desktop (Windows / macOS / Linux)
- Browser + version (if web):
**Screenshots / logs**
If applicable, add screenshots or the browser devtools console output.
+1 -5
View File
@@ -1,5 +1 @@
blank_issues_enabled: false blank_issues_enabled: true
contact_links:
- name: Features, Bug Reports, Questions
url: https://github.com/cinnyapp/cinny/discussions/new/choose
about: Our preferred starting point if you have any questions or suggestions about features or behavior.
+15
View File
@@ -0,0 +1,15 @@
---
name: Feature Request
about: Suggest an idea or improvement for Lotus Chat
title: ''
labels: enhancement
---
**What would you like?**
A clear and concise description of the feature or change.
**Why / use case**
What problem does it solve, or what does it make better?
**Alternatives considered**
Any workarounds or other approaches you've thought about.
-9
View File
@@ -1,9 +0,0 @@
---
name: Pre-Discussed and Approved Topics
about: |-
Only for topics already discussed and approved in the GitHub Discussions section.
---
**DO NOT OPEN A NEW ISSUE. PLEASE USE THE DISCUSSIONS SECTION.**
**I DIDN'T READ THE ABOVE LINE. PLEASE CLOSE THIS ISSUE.**
+49 -1
View File
@@ -13,7 +13,38 @@ The source code is licensed under [AGPLv3](LICENSE), the same license as the ups
The Lotus Chat logo (`public/res/Lotus.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0. The Lotus Chat logo (`public/res/Lotus.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0.
--- ---
## Development Environment Setup
#### Getting correct Node version
- Ensure you have the correct version of node installed, specified in `.node-version`
- Use this command from the terminal to install nvm
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
```
- Reload your terminal shell using (for Ubuntu):
```bash
source ~/.bashrc
```
- Install the specified Node version
```bash
NODE_VERSION="$(tr -d '[:space:]' < .node-version)"
nvm install "$NODE_VERSION"
nvm use "$NODE_VERSION"
```
- verify the correct version was installed by running
```bash
node --version
```
and comparing the output to what is listed in `.node-version`
#### Install npm packages
- To install the npm packages listed in `package.json` run:
```bash
npm i
```
### Start Development Server
```bash
npm run start
```
You should now have an active development server at `localhost:8080`, where you can make changes to the code and see the UI update in real time
## Features ## Features
### Messaging ### Messaging
@@ -167,6 +198,23 @@ The source code lives in `/root/code/cinny`. All changes should be made on the `
See [LOTUS_FEATURES.md](LOTUS_FEATURES.md) for the full feature changelog and [LOTUS_TODO.md](LOTUS_TODO.md) for the work backlog. See [LOTUS_FEATURES.md](LOTUS_FEATURES.md) for the full feature changelog and [LOTUS_TODO.md](LOTUS_TODO.md) for the work backlog.
### Local Development
Lotus Chat is a **pure client — there is no backend of its own to run.** It talks directly to a Matrix homeserver (Synapse) over HTTPS, so the only thing you run locally is the Vite dev server; it connects to a real homeserver for all data. If you were looking for "the backend to pair with it," there isn't one — that's the homeserver.
**Prerequisites:** Node 20+ (CI builds on Node 24) and npm.
```bash
npm ci # deps; @lotusguild/* come from our Gitea npm registry (public read — no auth/token needed)
npm start # Vite dev server → http://localhost:8080
```
The dev server defaults to **port 8080** (`vite.config.js`); if 8080 is already in use it falls through to 8081+, so check the "Local:" URL Vite prints on startup. If it boots but the page renders blank, it's almost always a failed module/asset resolution, not a "missing backend" — open the devtools console and read the first error.
**Which homeserver / logging in:** `config.json` sets `defaultHomeserver: 0``matrix.lotusguild.org`, so you sign in with your normal `@you:matrix.lotusguild.org` account. That homeserver is **live production** — anything you send is real, so keep test traffic to a DM with yourself or a throwaway room. To develop fully isolated instead, point `config.json` at a throwaway `matrix.org` account (already in `homeserverList`) or a local Synapse.
- **SSO / OIDC works from localhost.** Login goes through Authelia via OIDC dynamic registration; the provider redirects back to `http://localhost:8080/…` and the client registers that redirect on the fly, so no server-side allow-listing is needed. After the callback you may see a `GET …/_matrix/media/v1/thumbnail/… 404` — that's just a missing avatar thumbnail, **not** a login failure.
### 🔱 Element Call fork ("Lotus Call") — LIVE ### 🔱 Element Call fork ("Lotus Call") — LIVE
Voice/video channels embed **Element Call**, which is now our **self-built fork** Voice/video channels embed **Element Call**, which is now our **self-built fork**
+1 -1
View File
@@ -109,7 +109,7 @@ export default [
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
], ],
'@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'off',
// jsx-a11y — media captions not required for this app // jsx-a11y — media captions not required for this app
'jsx-a11y/media-has-caption': 'off', 'jsx-a11y/media-has-caption': 'off',
+453 -365
View File
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -12,13 +12,12 @@
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "npm run check:eslint && npm run check:prettier", "lint": "npm run check:eslint && npm run check:prettier",
"check:eslint": "eslint src/*", "check:eslint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"check:prettier": "prettier --check .", "check:prettier": "prettier --check .",
"fix:prettier": "prettier --write .", "fix:prettier": "prettier --write .",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "node --import tsx --test $(find src -name '*.test.ts')", "test": "node --import tsx --test $(find src -name '*.test.ts')",
"prepare": "husky", "prepare": "husky",
"commit": "git-cz",
"postinstall": "node scripts/patch-folds.mjs", "postinstall": "node scripts/patch-folds.mjs",
"sync:decorations": "node scripts/syncDecorations.mjs" "sync:decorations": "node scripts/syncDecorations.mjs"
}, },
@@ -26,11 +25,6 @@
"*.{ts,tsx,js,jsx}": "eslint", "*.{ts,tsx,js,jsx}": "eslint",
"*": "prettier --ignore-unknown --write" "*": "prettier --ignore-unknown --write"
}, },
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"keywords": [], "keywords": [],
"author": "Ajay Bura", "author": "Ajay Bura",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
@@ -43,7 +37,7 @@
"@fontsource-variable/inter": "5.2.8", "@fontsource-variable/inter": "5.2.8",
"@giphy/js-fetch-api": "5.8.0", "@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/js-util": "2.0.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",
@@ -95,7 +89,7 @@
"react-i18next": "17.0.8", "react-i18next": "17.0.8",
"react-range": "1.10.0", "react-range": "1.10.0",
"react-router-dom": "7.15.1", "react-router-dom": "7.15.1",
"sanitize-html": "2.17.4", "sanitize-html": "2.17.6",
"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",
@@ -130,6 +124,7 @@
"cz-conventional-changelog": "3.3.0", "cz-conventional-changelog": "3.3.0",
"eslint": "9.39.4", "eslint": "9.39.4",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-jsx-a11y": "6.10.2",
@@ -149,5 +144,8 @@
"dompurify": ">=3.3.4" "dompurify": ">=3.3.4"
}, },
"js-cookie": ">=3.0.6" "js-cookie": ">=3.0.6"
},
"allowScripts": {
"esbuild@0.28.1": true
} }
} }
-1
View File
@@ -34,7 +34,6 @@ export default function KaTeX({ latex, displayMode = false }: KaTeXProps) {
return ( return (
<Wrapper <Wrapper
// KaTeX output is generated by our own render call (trusted-safe). // KaTeX output is generated by our own render call (trusted-safe).
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: html }} dangerouslySetInnerHTML={{ __html: html }}
/> />
); );
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react'; import React, { KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds'; import { Box, Chip, color, config, Icon, Icons, Text, toRem } from 'folds';
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations'; import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
@@ -58,7 +58,6 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
const submitAccountData: AccountDataSubmitCallback = useCallback( const submitAccountData: AccountDataSubmitCallback = useCallback(
async (type, content) => { async (type, content) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await mx.setRoomAccountData(room.roomId, type as any, content); await mx.setRoomAccountData(room.roomId, type as any, content);
}, },
[mx, room.roomId], [mx, room.roomId],
@@ -55,7 +55,6 @@ export function RoomQuality({ permissions }: RoomQualityProps) {
const [submitState, submit] = useAsyncCallback( const [submitState, submit] = useAsyncCallback(
useCallback( useCallback(
async (next: RoomQualityContent) => { async (next: RoomQualityContent) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await sendStateEvent(mx, room.roomId, StateEvent.LotusRoomQuality, next); await sendStateEvent(mx, room.roomId, StateEvent.LotusRoomQuality, next);
}, },
[mx, room.roomId], [mx, room.roomId],
@@ -31,7 +31,6 @@ export function RoomRetention({ permissions }: RoomRetentionProps) {
const content: RetentionContent = ms > 0 ? { max_lifetime: ms } : {}; const content: RetentionContent = ms > 0 ? { max_lifetime: ms } : {};
// Lotus custom-state convention: cast the type key (RoomRetention isn't a // Lotus custom-state convention: cast the type key (RoomRetention isn't a
// typed key in the SDK's StateEvents map). // typed key in the SDK's StateEvents map).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await sendStateEvent(mx, room.roomId, StateEvent.RoomRetention, content); await sendStateEvent(mx, room.roomId, StateEvent.RoomRetention, content);
}, },
[mx, room.roomId], [mx, room.roomId],
@@ -46,7 +46,6 @@ const extractText = (event: MatrixEvent): ExtractedText | null => {
const content = event.getContent(); const content = event.getContent();
if (POLL_START_TYPES.includes(evType)) { if (POLL_START_TYPES.includes(evType)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const poll = (content['m.poll'] ?? content['org.matrix.msc3381.poll.start']) as any; const poll = (content['m.poll'] ?? content['org.matrix.msc3381.poll.start']) as any;
if (!poll) return null; if (!poll) return null;
const qBody = const qBody =
@@ -57,7 +56,6 @@ const extractText = (event: MatrixEvent): ExtractedText | null => {
.map( .map(
(a) => (a) =>
((a['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ?? ((a['m.text'] as Array<{ body: string }> | undefined)?.[0]?.body ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(a['org.matrix.msc3381.poll.answer'] as any)?.body ?? (a['org.matrix.msc3381.poll.answer'] as any)?.body ??
'') as string, '') as string,
) )
@@ -104,7 +102,6 @@ const rowToResultItem = (row: SearchCacheRow): ResultItem => {
}; };
return { return {
rank: 0, rank: 0,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
event: syntheticEvent as any, event: syntheticEvent as any,
context: { events_before: [], events_after: [], profile_info: {} }, context: { events_before: [], events_after: [], profile_info: {} },
}; };
@@ -227,7 +224,6 @@ export const useLocalMessageSearch = () => {
}; };
memoryItems.push({ memoryItems.push({
rank: 0, rank: 0,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
event: syntheticEvent as any, event: syntheticEvent as any,
context: { events_before: [], events_after: [], profile_info: {} }, context: { events_before: [], events_after: [], profile_info: {} },
}); });
@@ -146,7 +146,6 @@ export const useMessageSearch = (params: MessageSearchParams) => {
...(fromTs !== undefined && { from_ts: fromTs }), ...(fromTs !== undefined && { from_ts: fromTs }),
...(toTs !== undefined && { to_ts: toTs }), ...(toTs !== undefined && { to_ts: toTs }),
...(containsUrl !== undefined && { contains_url: containsUrl }), ...(containsUrl !== undefined && { contains_url: containsUrl }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any, } as any,
include_state: false, include_state: false,
order_by: order as SearchOrderBy.Recent, order_by: order as SearchOrderBy.Recent,
@@ -341,7 +341,6 @@ export function RoomServerACL({ requestClose }: RoomServerACLProps) {
variant="Primary" variant="Primary"
/> />
<Box direction="Column" gap="0"> <Box direction="Column" gap="0">
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label <label
htmlFor="allow-ip-literals" htmlFor="allow-ip-literals"
style={{ cursor: canEdit ? 'pointer' : 'default' }} style={{ cursor: canEdit ? 'pointer' : 'default' }}
@@ -318,7 +318,6 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const results = await Promise.allSettled( const results = await Promise.allSettled(
ids.map((id) => { ids.map((id) => {
// threadId-aware overload (P3-8): explicit null = send to the main timeline. // threadId-aware overload (P3-8): explicit null = send to the main timeline.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, fwdContent); const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, fwdContent);
// Send the optional comment first so it reads as a note above the // Send the optional comment first so it reads as a note above the
// forwarded content. The room counts as failed if either send rejects. // forwarded content. The room counts as failed if either send rejects.
@@ -327,7 +326,6 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const needsComment = commentBody && !commentSentRef.current.has(id); const needsComment = commentBody && !commentSentRef.current.has(id);
const step = needsComment const step = needsComment
? mx ? mx
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.sendMessage(id, null, { msgtype: MsgType.Text, body: commentBody } as any) .sendMessage(id, null, { msgtype: MsgType.Text, body: commentBody } as any)
.then(() => { .then(() => {
commentSentRef.current.add(id); commentSentRef.current.add(id);
@@ -1390,7 +1390,6 @@ export const Message = React.memo(
after={<Icon size="100" src={Icons.Send} />} after={<Icon size="100" src={Icons.Send} />}
radii="300" radii="300"
onClick={() => { onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(mx as any).resendEvent(mEvent, room); (mx as any).resendEvent(mEvent, room);
closeMenu(); closeMenu();
}} }}
@@ -1409,7 +1408,6 @@ export const Message = React.memo(
after={<Icon size="100" src={Icons.Cross} />} after={<Icon size="100" src={Icons.Cross} />}
radii="300" radii="300"
onClick={() => { onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(mx as any).cancelPendingEvent(mEvent); (mx as any).cancelPendingEvent(mEvent);
closeMenu(); closeMenu();
}} }}
@@ -187,7 +187,6 @@ export const MessageEditor = as<'div', MessageEditorProps>(
rel_type: RelationType.Replace, rel_type: RelationType.Replace,
}, },
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return mx.sendMessage(roomId, content as any); return mx.sendMessage(roomId, content as any);
} }
@@ -236,7 +235,6 @@ export const MessageEditor = as<'div', MessageEditorProps>(
}, },
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return mx.sendMessage(roomId, content as any); return mx.sendMessage(roomId, content as any);
}, [ }, [
mx, mx,
@@ -564,7 +564,6 @@ export function ThreadTimeline({ room, thread, editor }: ThreadTimelineProps) {
mx.sendEvent( mx.sendEvent(
room.roomId, room.roomId,
thread.id, thread.id,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MessageEvent.Reaction as any, MessageEvent.Reaction as any,
getReactionContent(targetEventId, key, rShortcode), getReactionContent(targetEventId, key, rShortcode),
); );
@@ -60,7 +60,6 @@ export function RoomWidgetView({ room, widget }: RoomWidgetViewProps) {
clientApi.stop(); clientApi.stop();
iframe.remove(); iframe.remove();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mx, room.roomId, widget.id, widget.templateUrl]); }, [mx, room.roomId, widget.id, widget.templateUrl]);
if (blocked) { if (blocked) {
@@ -84,7 +84,6 @@ export function WidgetsPanel({ room, requestClose }: WidgetsPanelProps) {
data: {}, data: {},
}; };
try { try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await sendStateEvent(mx, room.roomId, StateEvent.Widget, content, id); await sendStateEvent(mx, room.roomId, StateEvent.Widget, content, id);
setAdding(false); setAdding(false);
} catch (e) { } catch (e) {
@@ -96,7 +95,6 @@ export function WidgetsPanel({ room, requestClose }: WidgetsPanelProps) {
const handleRemove = (id: string) => { const handleRemove = (id: string) => {
if (viewingId === id) setViewingId(null); if (viewingId === id) setViewingId(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendStateEvent(mx, room.roomId, StateEvent.Widget, {}, id).catch(() => undefined); sendStateEvent(mx, room.roomId, StateEvent.Widget, {}, id).catch(() => undefined);
}; };
+2 -2
View File
@@ -4,12 +4,12 @@ import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css'; import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { getOriginBaseUrl, withOriginBaseUrl } from '../../../pages/pathUtils';
import pkg from '../../../../../package.json'; import pkg from '../../../../../package.json';
import { clearCacheAndReload } from '../../../../client/initMatrix'; import { clearCacheAndReload } from '../../../../client/initMatrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getOriginBaseUrl, withOriginBaseUrl } from '../../../pages/pathUtils';
const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/Lotus.png'); const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/Lotus.png');
type MSC1929Contact = { type MSC1929Contact = {
matrix_id?: string; matrix_id?: string;
+1 -7
View File
@@ -44,13 +44,7 @@ export function getLocalRoomNamesContent(
mx: ReturnType<typeof useMatrixClient>, mx: ReturnType<typeof useMatrixClient>,
): LocalRoomNamesContent { ): LocalRoomNamesContent {
const raw: unknown = getAccountData<unknown>(mx, LOCAL_ROOM_NAMES_KEY); const raw: unknown = getAccountData<unknown>(mx, LOCAL_ROOM_NAMES_KEY);
if ( if (raw && typeof raw === 'object' && 'rooms' in raw && typeof (raw as any).rooms === 'object') {
raw &&
typeof raw === 'object' &&
'rooms' in raw &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof (raw as any).rooms === 'object'
) {
return raw as LocalRoomNamesContent; return raw as LocalRoomNamesContent;
} }
return { rooms: {} }; return { rooms: {} };
+2 -2
View File
@@ -20,6 +20,7 @@ import {
} from '../../hooks/useClientConfig'; } from '../../hooks/useClientConfig';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { LOGIN_PATH, REGISTER_PATH, RESET_PASSWORD_PATH } from '../paths'; import { LOGIN_PATH, REGISTER_PATH, RESET_PASSWORD_PATH } from '../paths';
import { getOriginBaseUrl, withOriginBaseUrl } from '../pathUtils';
import { ServerPicker } from './ServerPicker'; import { ServerPicker } from './ServerPicker';
import { AutoDiscoveryAction, autoDiscovery } from '../../cs-api'; import { AutoDiscoveryAction, autoDiscovery } from '../../cs-api';
import { SpecVersionsLoader } from '../../components/SpecVersionsLoader'; import { SpecVersionsLoader } from '../../components/SpecVersionsLoader';
@@ -29,9 +30,8 @@ import { AuthFlowsLoader } from '../../components/AuthFlowsLoader';
import { AuthFlowsProvider } from '../../hooks/useAuthFlows'; import { AuthFlowsProvider } from '../../hooks/useAuthFlows';
import { AuthServerProvider } from '../../hooks/useAuthServer'; import { AuthServerProvider } from '../../hooks/useAuthServer';
import { tryDecodeURIComponent } from '../../utils/dom'; import { tryDecodeURIComponent } from '../../utils/dom';
import { getOriginBaseUrl, withOriginBaseUrl } from '../pathUtils';
const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/Lotus.png'); const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/Lotus.png');
const currentAuthPath = (pathname: string): string => { const currentAuthPath = (pathname: string): string => {
if (matchPath(LOGIN_PATH, pathname)) { if (matchPath(LOGIN_PATH, pathname)) {
+1 -1
View File
@@ -2,7 +2,7 @@ import type { OidcRegistrationClientMetadata } from 'matrix-js-sdk';
import { OIDC_CALLBACK_PATH } from '../../paths'; import { OIDC_CALLBACK_PATH } from '../../paths';
import { getOriginBaseUrl, withOriginBaseUrl } from '../../pathUtils'; import { getOriginBaseUrl, withOriginBaseUrl } from '../../pathUtils';
const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/Lotus.png'); const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/Lotus.png');
/** /**
* Absolute URL the OIDC provider redirects back to after authorization. * Absolute URL the OIDC provider redirects back to after authorization.
+3 -3
View File
@@ -70,9 +70,9 @@ import {
THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR, THREAD_NOTIFICATIONS_FALLBACK_BEHAVIOR,
} from '../../utils/threadNotifications'; } from '../../utils/threadNotifications';
const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus.png'); const LogoSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus.png');
const LogoUnreadSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus-unread.png'); const LogoUnreadSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-unread.png');
const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/lotus-highlight.png'); const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/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.
+2 -2
View File
@@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { Box, Button, Icon, Icons, Text, config, toRem } from 'folds'; import { Box, Button, Icon, Icons, Text, config, toRem } from 'folds';
import { Page, PageHero, PageHeroSection } from '../../components/page'; import { Page, PageHero, PageHeroSection } from '../../components/page';
import pkg from '../../../../package.json';
import { getOriginBaseUrl, withOriginBaseUrl } from '../pathUtils'; import { getOriginBaseUrl, withOriginBaseUrl } from '../pathUtils';
import pkg from '../../../../package.json';
const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/Lotus.png'); const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/Lotus.png');
export function WelcomePage() { export function WelcomePage() {
return ( return (
-2
View File
@@ -25,10 +25,8 @@ export const setMarkedUnread = (
unread: boolean, unread: boolean,
): Promise<unknown> => ): Promise<unknown> =>
Promise.all([ Promise.all([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mx.setRoomAccountData(roomId, AccountDataEvent.MarkedUnread as any, { unread }), mx.setRoomAccountData(roomId, AccountDataEvent.MarkedUnread as any, { unread }),
// Best-effort mirror for older servers; never fail the primary write on it. // Best-effort mirror for older servers; never fail the primary write on it.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mx.setRoomAccountData(roomId, UNSTABLE_MARKED_UNREAD as any, { unread }).catch(() => undefined), mx.setRoomAccountData(roomId, UNSTABLE_MARKED_UNREAD as any, { unread }).catch(() => undefined),
]); ]);
-2
View File
@@ -13,7 +13,6 @@ export function getAccountData<T>(
mx: MatrixClient, mx: MatrixClient,
eventType: AccountDataEvent | string, eventType: AccountDataEvent | string,
): T | undefined { ): T | undefined {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const event = (mx as any).getAccountData(eventType) as MatrixEvent | undefined; const event = (mx as any).getAccountData(eventType) as MatrixEvent | undefined;
return event?.getContent() as T | undefined; return event?.getContent() as T | undefined;
} }
@@ -23,6 +22,5 @@ export function setAccountData<T>(
eventType: AccountDataEvent | string, eventType: AccountDataEvent | string,
content: T, content: T,
): Promise<void> { ): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (mx as any).setAccountData(eventType, content); return (mx as any).setAccountData(eventType, content);
} }
-2
View File
@@ -70,7 +70,6 @@ export async function buildModelNode(
model: DenoiseModelId, model: DenoiseModelId,
): Promise<DenoiseNode> { ): Promise<DenoiseNode> {
if (model === 'dtln') { if (model === 'dtln') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mod: any = await import(/* @vite-ignore */ `${BASE}workadventure/audio-worklet.js`); const mod: any = await import(/* @vite-ignore */ `${BASE}workadventure/audio-worklet.js`);
const handle = await mod.createNoiseSuppressionAudioWorklet(ctx, { bypassUntilReady: true }); const handle = await mod.createNoiseSuppressionAudioWorklet(ctx, { bypassUntilReady: true });
return { node: handle.node, dispose: () => handle.dispose() }; return { node: handle.node, dispose: () => handle.dispose() };
@@ -81,7 +80,6 @@ export async function buildModelNode(
// deepfilternet/v2/... Override its cdnUrl to our absolute base so nothing // deepfilternet/v2/... Override its cdnUrl to our absolute base so nothing
// hits the upstream CDN. DeepFilterNet3Core builds the worklet node directly. // hits the upstream CDN. DeepFilterNet3Core builds the worklet node directly.
const dfnBase = new URL(`${BASE}deepfilternet`, window.location.href).href; const dfnBase = new URL(`${BASE}deepfilternet`, window.location.href).href;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mod: any = await import(/* @vite-ignore */ `${BASE}deepfilternet/index.esm.js`); const mod: any = await import(/* @vite-ignore */ `${BASE}deepfilternet/index.esm.js`);
const core = new mod.DeepFilterNet3Core({ const core = new mod.DeepFilterNet3Core({
sampleRate: sampleRateFor(model), sampleRate: sampleRateFor(model),
-5
View File
@@ -44,12 +44,10 @@ test('onTabPress fires only on Tab', () => {
test('preventScrollWithArrowKey prevents default only on arrows', () => { test('preventScrollWithArrowKey prevents default only on arrows', () => {
const up = evt('ArrowUp', 38); const up = evt('ArrowUp', 38);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
preventScrollWithArrowKey(up as any); preventScrollWithArrowKey(up as any);
assert.equal(up.prevented, true); assert.equal(up.prevented, true);
const a = evt('a', 65); const a = evt('a', 65);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
preventScrollWithArrowKey(a as any); preventScrollWithArrowKey(a as any);
assert.equal(a.prevented, false); assert.equal(a.prevented, false);
}); });
@@ -95,14 +93,12 @@ test('stopPropagation: stops unless an editable element is focused', () => {
// nothing focused → stops, returns true // nothing focused → stops, returns true
withActive(null); withActive(null);
let k = makeKeyEvt(); let k = makeKeyEvt();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assert.equal(stopPropagation(k.ev as any), true); assert.equal(stopPropagation(k.ev as any), true);
assert.equal(k.wasStopped(), true); assert.equal(k.wasStopped(), true);
// input focused → does not stop, returns false // input focused → does not stop, returns false
withActive({ nodeName: 'INPUT', getAttribute: () => null }); withActive({ nodeName: 'INPUT', getAttribute: () => null });
k = makeKeyEvt(); k = makeKeyEvt();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assert.equal(stopPropagation(k.ev as any), false); assert.equal(stopPropagation(k.ev as any), false);
assert.equal(k.wasStopped(), false); assert.equal(k.wasStopped(), false);
@@ -112,6 +108,5 @@ test('stopPropagation: stops unless an editable element is focused', () => {
getAttribute: (a: string) => (a === 'contenteditable' ? 'true' : null), getAttribute: (a: string) => (a === 'contenteditable' ? 'true' : null),
}); });
k = makeKeyEvt(); k = makeKeyEvt();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assert.equal(stopPropagation(k.ev as any), false); assert.equal(stopPropagation(k.ev as any), false);
}); });
@@ -22,7 +22,6 @@ const makeMx = (
if (opts.forgetRejects) throw new Error('forget failed'); if (opts.forgetRejects) throw new Error('forget failed');
return {}; return {};
}, },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any; } as any;
return { mx, calls }; return { mx, calls };
}; };
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { M_POLL_KIND_DISCLOSED } from 'matrix-js-sdk'; import { M_POLL_KIND_DISCLOSED } from 'matrix-js-sdk';
// Pure helpers for poll display. matrix-js-sdk 41.7.0's PollStartEvent / // Pure helpers for poll display. matrix-js-sdk 41.7.0's PollStartEvent /
-1
View File
@@ -58,7 +58,6 @@ export function sendStateEvent<T extends object>(
content: T, content: T,
stateKey = '', stateKey = '',
): Promise<ISendEventResponse> { ): Promise<ISendEventResponse> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return mx.sendStateEvent(roomId, eventType as any, content, stateKey); return mx.sendStateEvent(roomId, eventType as any, content, stateKey);
} }
+28 -6
View File
@@ -22,18 +22,40 @@ document.body.classList.add(configClass, varsClass);
// Register Service Worker // Register Service Worker
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
const swUrl = const isProduction = import.meta.env.PROD;
import.meta.env.MODE === 'production' const swUrl = isProduction
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js` ? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
: `/dev-sw.js?dev-sw`; : `/dev-sw.js?dev-sw`;
const sendSessionToSW = () => { const sendSessionToSW = () => {
const session = getFallbackSession(); const session = getFallbackSession();
pushSessionToSW(session?.baseUrl, session?.accessToken); pushSessionToSW(session?.baseUrl, session?.accessToken);
}; };
navigator.serviceWorker.register(swUrl).then(sendSessionToSW); const registerServiceWorker = async () => {
navigator.serviceWorker.ready.then(sendSessionToSW); try {
const registration = await navigator.serviceWorker.register(
swUrl,
isProduction
? undefined
: {
type: 'module',
scope: '/',
},
);
sendSessionToSW();
await navigator.serviceWorker.ready;
sendSessionToSW();
console.info('Service worker registered:', registration.scope);
} catch (error) {
console.error('Service worker registration failed:', error);
}
};
registerServiceWorker();
navigator.serviceWorker.addEventListener('message', (ev) => { navigator.serviceWorker.addEventListener('message', (ev) => {
const { type } = ev.data ?? {}; const { type } = ev.data ?? {};
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { globalStyle, keyframes, style } from '@vanilla-extract/css'; import { globalStyle, keyframes, style } from '@vanilla-extract/css';
const glitch1 = keyframes({ const glitch1 = keyframes({