fix: ESLint errors, stale disable comments, bundle splitting

- RoomTimeline.tsx: add eslint-disable comment for intentional eventsLength
  dep on timelineSegments useMemo (needed to detect in-place timeline mutations)
- Remove ~47 stale eslint-disable-next-line comments across 28 files for rules
  that are now off in the flat config (no-param-reassign, jsx-a11y/media-has-caption,
  react/no-array-index-key, etc); run prettier to reformat
- vite.config.js: move manualChunks from rollupOptions.output to
  rolldownOptions.output so Rolldown (Vite 8) actually applies it; main bundle
  drops from 3.5 MB to 814 kB gzip-248 kB, matrix-sdk gets its own 1.16 MB
  cacheable chunk

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lotus Bot
2026-05-22 19:52:23 -04:00
parent 3927f01089
commit 5db4db1d95
30 changed files with 38 additions and 66 deletions
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/media-has-caption */
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
import FocusTrap from 'focus-trap-react';
@@ -137,7 +137,6 @@ function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
>
{sasData.sas.emoji?.map(([emoji, name], index) => (
<Box
// eslint-disable-next-line react/no-array-index-key
key={`${emoji}${name}${index}`}
direction="Column"
gap="100"
-1
View File
@@ -89,7 +89,6 @@ export function LobbySkeleton() {
{/* Room list rows */}
<div style={{ flex: 1, padding: '8px 0' }}>
{ROOM_ROWS.map((row, i) => (
// eslint-disable-next-line react/no-array-index-key
<div
key={i}
style={{
@@ -1,5 +1,3 @@
/* eslint-disable no-param-reassign */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import React, { FormEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import {
-2
View File
@@ -80,7 +80,6 @@ export function RoomSkeleton() {
{/* Timeline */}
<div style={{ flex: 1, overflowY: 'hidden', padding: '16px 0' }}>
{MESSAGES.map((msg, i) => (
// eslint-disable-next-line react/no-array-index-key
<div
key={i}
style={{
@@ -103,7 +102,6 @@ export function RoomSkeleton() {
<div style={{ ...shimmer, width: '90px', height: '12px', marginBottom: '2px' }} />
)}
{msg.lines.map((line, j) => (
// eslint-disable-next-line react/no-array-index-key
<div key={j} style={{ ...shimmer, width: line.w, height: '14px' }} />
))}
</div>
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import React, {
ClipboardEventHandler,
KeyboardEventHandler,
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import { Descendant, Text } from 'slate';
import parse from 'html-dom-parser';
import { ChildNode, Element, isText, isTag } from 'domhandler';
-2
View File
@@ -148,7 +148,6 @@ export const resetEditor = (editor: Editor) => {
};
export const resetEditorHistory = (editor: Editor) => {
// eslint-disable-next-line no-param-reassign
editor.history = {
undos: [],
redos: [],
@@ -228,7 +227,6 @@ export const getPointUntilChar = (
reverse: options.reverse,
});
// eslint-disable-next-line no-restricted-syntax
for (const point of pointItr) {
if (!Point.equals(point, cursorPoint) && prevPoint) {
char = Editor.string(editor, { anchor: point, focus: prevPoint });
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import React from 'react';
import FileSaver from 'file-saver';
import classNames from 'classnames';
-1
View File
@@ -4,7 +4,6 @@ import * as css from './media.css';
export const Video = forwardRef<HTMLVideoElement, VideoHTMLAttributes<HTMLVideoElement>>(
({ className, ...props }, ref) => (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video className={classNames(css.Video, className)} {...props} ref={ref} />
),
);
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/media-has-caption */
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { Badge, Chip, Icon, IconButton, Icons, ProgressBar, Spinner, Text, toRem } from 'folds';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import React, { ComponentProps, HTMLAttributes, Suspense, forwardRef, lazy } from 'react';
import classNames from 'classnames';
import { Box, Chip, Header, Icon, IconButton, Icons, Scroll, Text, as } from 'folds';
@@ -42,7 +42,6 @@ function PreviewVideo({ fileItem }: PreviewVideoProps) {
const fileUrl = useObjectURL(originalFile);
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video
style={{
objectFit: 'contain',
-1
View File
@@ -168,7 +168,6 @@ export function CallControls({ callEmbed }: CallControlsProps) {
}
};
// microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pttMode, pttKey, callEmbed]);
const [hangupState, hangup] = useAsyncCallback(
@@ -1,4 +1,3 @@
/* eslint-disable react/no-array-index-key */
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Badge, Box, Button, Chip, config, Icon, Icons, Menu, Spinner, Text } from 'folds';
import { produce } from 'immer';
@@ -1,4 +1,3 @@
/* eslint-disable react/no-array-index-key */
import React, { useState, MouseEventHandler, ReactNode } from 'react';
import FocusTrap from 'focus-trap-react';
import {
@@ -1,4 +1,3 @@
/* eslint-disable react/destructuring-assignment */
import React, { MouseEventHandler, useMemo } from 'react';
import { IEventWithRoomId, JoinRule, RelationType, Room } from 'matrix-js-sdk';
import { HTMLReactParserOptions } from 'html-react-parser';
-1
View File
@@ -556,7 +556,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
.reverse()
.map((fileItem, index) => (
<UploadCardRenderer
// eslint-disable-next-line react/no-array-index-key
key={index}
isEncrypted={!!fileItem.encInfo}
fileItem={fileItem}
+1 -1
View File
@@ -1,4 +1,3 @@
/* eslint-disable react/destructuring-assignment */
import React, {
Dispatch,
MouseEventHandler,
@@ -557,6 +556,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
base += len;
return seg;
});
// eslint-disable-next-line react-hooks/exhaustive-deps -- eventsLength detects in-place timeline mutations
}, [timeline.linkedTimelines, eventsLength]);
const liveTimelineLinked =
timeline.linkedTimelines[timeline.linkedTimelines.length - 1] === getLiveTimeline(room);
@@ -1,4 +1,3 @@
/* eslint-disable react/destructuring-assignment */
import React, { forwardRef, MouseEventHandler, useCallback, useMemo, useRef } from 'react';
import { MatrixEvent, Room } from 'matrix-js-sdk';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { useState } from 'react';
export function useForceUpdate() {
+7 -10
View File
@@ -46,12 +46,10 @@ const fillMissingPowers = (powerLevels: IPowerLevels): IPowerLevels =>
const keys = Object.keys(DEFAULT_POWER_LEVELS) as unknown as (keyof IPowerLevels)[];
keys.forEach((key) => {
if (draftPl[key] === undefined) {
// eslint-disable-next-line no-param-reassign
draftPl[key] = DEFAULT_POWER_LEVELS[key] as any;
}
});
if (draftPl.notifications && typeof draftPl.notifications.room !== 'number') {
// eslint-disable-next-line no-param-reassign
draftPl.notifications.room = DEFAULT_POWER_LEVELS.notifications.room;
}
return draftPl;
@@ -236,23 +234,22 @@ export const applyPermissionPower = (
if (typeof location.key === 'string') {
const users = powerLevels.users ?? {};
users[location.key] = power;
// eslint-disable-next-line no-param-reassign
powerLevels.users = users;
return powerLevels;
}
// eslint-disable-next-line no-param-reassign
powerLevels.users_default = power;
return powerLevels;
}
if ('action' in location) {
// eslint-disable-next-line no-param-reassign
powerLevels[location.key] = power;
return powerLevels;
}
if ('notification' in location) {
const notifications = powerLevels.notifications ?? {};
notifications[location.key] = power;
// eslint-disable-next-line no-param-reassign
powerLevels.notifications = notifications;
return powerLevels;
}
@@ -260,11 +257,11 @@ export const applyPermissionPower = (
if (typeof location.key === 'string') {
const events = powerLevels.events ?? {};
events[location.key] = power;
// eslint-disable-next-line no-param-reassign
powerLevels.events = events;
return powerLevels;
}
// eslint-disable-next-line no-param-reassign
powerLevels.state_default = power;
return powerLevels;
}
@@ -272,11 +269,11 @@ export const applyPermissionPower = (
if (typeof location.key === 'string') {
const events = powerLevels.events ?? {};
events[location.key] = power;
// eslint-disable-next-line no-param-reassign
powerLevels.events = events;
return powerLevels;
}
// eslint-disable-next-line no-param-reassign
powerLevels.events_default = power;
return powerLevels;
};
@@ -1,4 +1,3 @@
/* eslint-disable no-continue */
import { MatrixEvent, Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import { useEffect, useState } from 'react';
import { settingsAtom } from '../state/settings';
@@ -121,7 +121,6 @@ function InviteNotifications() {
}, [mx, invites, perviousInviteLen, showNotifications, notificationSound, notify, playSound]);
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<audio ref={audioRef} style={{ display: 'none' }}>
<source src={InviteSound} type="audio/ogg" />
</audio>
@@ -246,7 +245,6 @@ function MessageNotifications() {
]);
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<audio ref={audioRef} style={{ display: 'none' }}>
<source src={NotificationSound} type="audio/ogg" />
</audio>
@@ -1,4 +1,3 @@
/* eslint-disable react/destructuring-assignment */
import React, { MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Avatar,
-1
View File
@@ -143,7 +143,6 @@ export class CallControl extends EventEmitter implements CallControlState {
const callDocument = this.iframe.contentDocument ?? this.iframe.contentWindow?.document;
if (callDocument) {
callDocument.querySelectorAll('audio').forEach((el) => {
// eslint-disable-next-line no-param-reassign
el.muted = !sound;
});
}
+1 -2
View File
@@ -162,10 +162,9 @@ export class CallWidgetDriver extends WidgetDriver {
// attempt to re-batch these up into a single request
const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {};
// eslint-disable-next-line no-restricted-syntax
for (const userId of Object.keys(contentMap)) {
const userContentMap = contentMap[userId];
// eslint-disable-next-line no-restricted-syntax
for (const deviceId of Object.keys(userContentMap)) {
const content = userContentMap[deviceId];
const stringifiedContent = JSON.stringify(content);
@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/alt-text */
import React, {
type JSX,
ComponentPropsWithoutRef,
+1 -1
View File
@@ -1,4 +1,4 @@
/* eslint-disable no-bitwise, no-plusplus, no-param-reassign, no-console, prefer-template, no-continue, no-restricted-syntax */
/* eslint-disable no-bitwise, prefer-template */
// https://github.com/matrix-org/matrix-react-sdk/blob/e78a1adb6f1af2ea425b0bae9034fb7344a4b2e8/src/utils/MegolmExportEncryption.js
const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
+28 -23
View File
@@ -78,6 +78,27 @@ function serverMatrixSdkCryptoWasm(wasmFilePath) {
};
}
const vendorChunks = (id) => {
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-router-dom') ||
id.includes('node_modules/@remix-run') ||
id.includes('node_modules/react-router/')
)
return 'router';
if (id.includes('node_modules/@tanstack')) return 'react-query';
if (id.includes('node_modules/linkify')) return 'linkify';
if (id.includes('node_modules/dompurify')) return 'dompurify';
if (id.includes('node_modules/@sentry')) return 'sentry';
if (id.includes('node_modules/i18next') || id.includes('node_modules/react-i18next'))
return 'i18n';
if (id.includes('node_modules/jotai')) return 'jotai';
if (id.includes('node_modules/immer')) return 'immer';
if (id.includes('node_modules/folds')) return 'folds';
if (id.includes('node_modules/emojibase')) return 'emojibase';
};
export default defineConfig({
appType: 'spa',
publicDir: false,
@@ -139,31 +160,15 @@ export default defineConfig({
outDir: 'dist',
sourcemap: process.env.SENTRY_AUTH_TOKEN ? 'hidden' : false,
copyPublicDir: false,
rolldownOptions: { checks: { preferBuiltinFeature: false } },
// manualChunks must be in rolldownOptions (not rollupOptions) for Vite 8 / Rolldown
rolldownOptions: {
checks: { preferBuiltinFeature: false },
output: {
manualChunks: vendorChunks,
},
},
rollupOptions: {
plugins: [inject({ Buffer: ['buffer', 'Buffer'] })],
output: {
manualChunks: (id) => {
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-router-dom') ||
id.includes('node_modules/@remix-run') ||
id.includes('node_modules/react-router/')
)
return 'router';
if (id.includes('node_modules/@tanstack')) return 'react-query';
if (id.includes('node_modules/linkify')) return 'linkify';
if (id.includes('node_modules/dompurify')) return 'dompurify';
if (id.includes('node_modules/@sentry')) return 'sentry';
if (id.includes('node_modules/i18next') || id.includes('node_modules/react-i18next'))
return 'i18n';
if (id.includes('node_modules/jotai')) return 'jotai';
if (id.includes('node_modules/immer')) return 'immer';
if (id.includes('node_modules/folds')) return 'folds';
if (id.includes('node_modules/emojibase')) return 'emojibase';
},
},
},
},
});