diff --git a/.github/actions/prepare-tofu/action.yml b/.github/actions/prepare-tofu/action.yml index c64332e3cd..dcb95842ca 100644 --- a/.github/actions/prepare-tofu/action.yml +++ b/.github/actions/prepare-tofu/action.yml @@ -28,7 +28,7 @@ runs: SENTRY_PROJECT: ${{ env.SENTRY_PROJECT }} - name: Setup OpenTofu - uses: opentofu/setup-opentofu@9d84900f3238fab8cd84ce47d658d25dd008be2f # v1.0.8 + uses: opentofu/setup-opentofu@fc711fa910b93cba0f3fbecaafc9f42fd0c411cb # v2.0.0 with: tofu_version: ${{ inputs.opentofu-version }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9b4c9acbb3..f7d1e4180d 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -19,7 +19,7 @@ runs: using: composite steps: - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 diff --git a/.github/workflows/cloudflare-web-preview.yml b/.github/workflows/cloudflare-web-preview.yml index 8b93a4bb9b..5b9b943b4b 100644 --- a/.github/workflows/cloudflare-web-preview.yml +++ b/.github/workflows/cloudflare-web-preview.yml @@ -6,9 +6,12 @@ on: - 'src/**' - 'index.html' - 'package.json' - - 'package-lock.json' + - 'pnpm-lock.yaml' + - 'wrangler.jsonc' - 'vite.config.ts' + - 'tsconfig.web.json' - 'tsconfig.json' + - 'tsconfig.node.json' - '.github/workflows/cloudflare-web-preview.yml' - '.github/actions/setup/**' push: @@ -18,9 +21,12 @@ on: - 'src/**' - 'index.html' - 'package.json' - - 'package-lock.json' + - 'pnpm-lock.yaml' + - 'wrangler.jsonc' - 'vite.config.ts' + - 'tsconfig.web.json' - 'tsconfig.json' + - 'tsconfig.node.json' - '.github/workflows/cloudflare-web-preview.yml' - '.github/actions/setup/**' @@ -97,7 +103,7 @@ jobs: accountId: ${{ secrets.TF_VAR_ACCOUNT_ID }} command: > versions upload - -c dist/wrangler.json + -c wrangler.jsonc --preview-alias ${{ steps.alias.outputs.alias }} --message "$PREVIEW_MESSAGE" diff --git a/.github/workflows/dependency-diff-analyze.yml b/.github/workflows/dependency-diff-analyze.yml new file mode 100644 index 0000000000..d528259ade --- /dev/null +++ b/.github/workflows/dependency-diff-analyze.yml @@ -0,0 +1,31 @@ +name: Dependency diff (analyze) + +on: + pull_request: + +jobs: + dependency-diff: + name: Dependency diff + runs-on: ubuntu-latest + if: github.head_ref != 'release' + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Analyze dependencies + id: analyze + uses: e18e/action-dependency-diff@5d3c6ac2ad2de2eaca1dc120c5accfd9590764b6 # v1.5.1 + with: + mode: artifact + + - name: Upload comment artifact + if: steps.analyze.outputs.artifact-path + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: e18e-diff-result + path: ${{ steps.analyze.outputs.artifact-path }} diff --git a/.github/workflows/dependency-diff-comment.yml b/.github/workflows/dependency-diff-comment.yml new file mode 100644 index 0000000000..518ffad060 --- /dev/null +++ b/.github/workflows/dependency-diff-comment.yml @@ -0,0 +1,29 @@ +name: Dependency diff (comment) + +on: + workflow_run: + workflows: ['Dependency diff (analyze)'] + types: + - completed + +jobs: + dependency-diff-comment: + name: Dependency diff comment + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' + permissions: + actions: read + pull-requests: write + steps: + - name: Download comment artifact + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + name: e18e-diff-result + run-id: ${{ github.event.workflow_run.id }} + + - name: Post dependency diff comment + uses: e18e/action-dependency-diff@5d3c6ac2ad2de2eaca1dc120c5accfd9590764b6 # v1.5.1 + with: + artifact-path: e18e-diff-result.json + mode: comment-from-artifact diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 03a63ef99f..540ded8296 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -39,7 +39,7 @@ jobs: - name: Log in to GitHub Container Registry if: github.event_name != 'pull_request' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -64,7 +64,7 @@ jobs: - name: Extract metadata id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: | @@ -94,14 +94,14 @@ jobs: NODE_OPTIONS=--max_old_space_size=4096 pnpm run build - name: Set up QEMU - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push Docker image id: push - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index a9ac437082..b1d8f99e1d 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -97,6 +97,112 @@ jobs: - name: Run tests run: pnpm run test:run + coverage: + name: Coverage thresholds + runs-on: ubuntu-latest + if: github.head_ref != 'release' + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup app + uses: ./.github/actions/setup + + - name: Run tests with coverage + run: pnpm run test:coverage + + missing-tests: + name: Check for missing tests + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.head_ref != 'release' + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Check changed logic files for missing tests + id: check + run: | + BASE=${{ github.event.pull_request.base.sha }} + HEAD=${{ github.event.pull_request.head.sha }} + changed=$(git diff --name-only "$BASE" "$HEAD" -- 'src/**/*.ts' 'src/**/*.tsx' \ + | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '\.d\.ts' \ + | grep -v 'src/index\.tsx' | grep -v 'src/sw' | grep -v 'src/instrument' \ + | grep -v 'src/test/' || true) + missing="" + for f in $changed; do + base="${f%.*}" + if ! ls "${base}.test."* "${base}.spec."* 2>/dev/null | grep -q .; then + missing="$missing\n- $f" + fi + done + if [ -n "$missing" ]; then + echo "missing=true" >> $GITHUB_OUTPUT + printf 'files<> $GITHUB_OUTPUT + fi + + - name: Update PR comment + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + continue-on-error: true + with: + script: | + const marker = ''; + const missing = '${{ steps.check.outputs.missing }}' === 'true'; + const files = `${{ steps.check.outputs.files }}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => c.body && c.body.includes(marker)); + + if (!missing) { + if (existing) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + } + return; + } + + const body = [ + marker, + '## ⚠️ Logic changes without tests', + '', + 'The following changed files have no corresponding `.test.` file.', + 'Consider adding tests, or note in your PR description why tests are not needed for these changes.', + '', + files, + ].join('\n'); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + build: name: Build runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3314e27ba..6037b7363a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - uses: knope-dev/action@19617851f9f13ab2f27a05989c55efb18aca3675 # v2.1.2 with: - version: 0.22.3 + version: 0.22.4 - name: Create Release run: knope release --verbose diff --git a/.github/workflows/sentry-preview-issues.yml b/.github/workflows/sentry-preview-issues.yml index c81787e743..eb5077992c 100644 --- a/.github/workflows/sentry-preview-issues.yml +++ b/.github/workflows/sentry-preview-issues.yml @@ -8,7 +8,9 @@ on: - 'index.html' - 'package.json' - 'vite.config.ts' + - 'tsconfig.web.json' - 'tsconfig.json' + - 'tsconfig.node.json' workflow_dispatch: inputs: pr_number: diff --git a/.gitignore b/.gitignore index 7ec7197097..3633bdbd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ dist coverage node_modules devAssets +.tsbuildinfo +*.tsbuildinfo .DS_Store .idea diff --git a/.vscode/settings.json b/.vscode/settings.json index f2ceb43b58..4e8c7be59f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,13 +4,15 @@ }, "editor.formatOnSave": true, "editor.defaultFormatter": "oxc.oxc-vscode", - "oxc.fmt.configPath": "oxfmt.config.ts", "oxc.typeAware": true, - "typescript.tsdk": "node_modules/typescript/lib", + "js/ts.tsdk.path": "node_modules/typescript/lib", "[jsonc]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "[json]": { "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c037e31df0..d17d7a7092 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,8 @@ Also, we use [ESLint](https://eslint.org/) for clean and stylistically consisten If your change touches logic with testable behaviour, please include tests. See [docs/TESTING.md](./docs/TESTING.md) for a guide on how to write them. +For conventions on code style, naming, and project patterns, see [docs/CODE_QUALITY.md](./docs/CODE_QUALITY.md). + ## Restrictions on Generative AI Usage We expect and appreciate authentic engagement in our community. diff --git a/docs/CODE_QUALITY.md b/docs/CODE_QUALITY.md new file mode 100644 index 0000000000..4f4c9cf6e5 --- /dev/null +++ b/docs/CODE_QUALITY.md @@ -0,0 +1,161 @@ +# Code Quality Guide + +This document describes the coding conventions and standards used throughout Sable. Most rules are enforced automatically in CI or by local linting. Read this to understand the why behind them and to get the conventions right the first time. + +## Enforcement layers + +| Layer | When it runs | What it checks | +| ----------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------- | +| **CI — format / lint / typecheck / knip** | On every PR and push to `dev` | Prettier format, ESLint, TypeScript, Knip dead-code analysis | +| **CI — tests** | On every PR and push to `dev` | Runs the full Vitest suite; fails if any test fails | +| **CI — coverage thresholds** | On every PR and push to `dev` | `pnpm test:coverage`; fails if overall coverage drops below the locked baseline | +| **CI — missing tests warning** | On PRs only | Comments listing changed logic files that have no `.test.` counterpart | +| **Editor** | As you type | ESLint + Prettier via VS Code extensions | + +PRs are not merged unless all CI quality checks are green. + +To fix all violations in the repo at once: + +```sh +pnpm run lint:fix +pnpm run fmt +``` + +## TypeScript + +### Prefer `type` over `interface` + +Use `type` for all type declarations. `interface` is not used in this codebase. + +```ts +type RoomAvatarProps = { + roomId: string; + src?: string; +}; +``` + +Rule: `@typescript-eslint/consistent-type-definitions: ['error', 'type']` + +### Use `import type` for type-only imports + +When an import is used only as a type and not at runtime, annotate it with `type`. + +```ts +import { type MatrixClient, MatrixError } from '$types/matrix-sdk'; +``` + +Rule: `@typescript-eslint/consistent-type-imports` with `inline-type-imports` style. + +### Strict null checks + +The project uses `strict: true`. Do not paper over nullability with unsafe casts. Handle the null or undefined case explicitly or use optional chaining. + +### Enums + +Use string enums for sets of related constants when they improve readability and debug output. + +## Imports + +### Ordering + +Group imports in this order, with a blank line between groups: + +1. External packages. +2. Internal path aliases. +3. Relative imports. + +```ts +import { useState } from 'react'; +import { Box, Text } from 'folds'; + +import { type MatrixClient } from '$types/matrix-sdk'; +import { useMatrixClient } from '$hooks/useMatrixClient'; + +import * as css from './RoomAvatar.css'; +``` + +### Path aliases + +Prefer the `$`-prefixed aliases over deep relative traversal for anything outside the current directory. + +## Naming conventions + +| Thing | Convention | Example | +| ---------------------- | ---------------------------------------- | ------------------------------ | +| React component | `PascalCase` function | `RoomAvatar` | +| Component props type | `[ComponentName]Props` | `RoomAvatarProps` | +| Custom hook | `use` prefix, `camelCase` | `useMatrixClient` | +| Jotai atom | `camelCase` + `Atom` suffix | `settingsAtom` | +| Jotai atom family | `camelCase` + `AtomFamily` suffix | `roomIdToOpenThreadAtomFamily` | +| Utility function | `camelCase` | `getMemberDisplayName` | +| Enum | `PascalCase` type + `PascalCase` members | `AsyncStatus.Loading` | +| CSS module file | `[ComponentName].css.ts` | `RoomAvatar.css.ts` | +| File (component) | `PascalCase.tsx` | `RoomAvatar.tsx` | +| File (hook/util/state) | `camelCase.ts` | `useMatrixClient.ts` | + +## React components + +### Named exports, not default exports + +Use named exports for components, hooks, and utilities. Default exports make refactors worse. + +### Keep components focused + +- One component per file is the default. +- Derive values from props and state instead of duplicating derived state. +- Move non-trivial logic into hooks or state helpers instead of burying it in JSX. + +### localStorage access + +Direct `localStorage` access is banned in `src/app/components/**` and `src/app/features/**`. + +Use one of these patterns instead: + +- Reactive state read by JSX: use `atomWithLocalStorage` in a state file. +- Values needed before React mounts or applied directly to DOM refs: use plain helper functions in `src/app/state/`. + +Examples: + +- [src/app/state/sentryStorage.ts](../src/app/state/sentryStorage.ts) +- [src/app/state/mediaVolume.ts](../src/app/state/mediaVolume.ts) + +## Styling + +Styles live in co-located `*.css.ts` files alongside the component. + +- Avoid inline `style={{}}` for static styling that should be a class. +- Do not import global CSS from component files. + +## Testing + +See [TESTING.md](./TESTING.md) for the full guide. + +- Put tests adjacent to the code they cover, or under `src/test/` for shared fixtures. +- Use `*.test.ts` / `*.test.tsx`. +- Use `@testing-library/react` for component tests. +- If your change touches logic with clear input/output, add or update tests. + +### Coverage thresholds + +Coverage thresholds are locked in `vitest.config.ts` and enforced by CI. They should only go up, never down. + +### Missing-tests advisory + +PRs get an advisory comment when changed logic files have no corresponding test file. That job is informational only. + +## What the linter enforces automatically + +| Rule | Enforced as | +| ----------------------------------------------------------------------- | ------------------------- | +| `@typescript-eslint/consistent-type-definitions` | error | +| `@typescript-eslint/consistent-type-imports` | error | +| `@typescript-eslint/no-unused-vars` | error | +| `@typescript-eslint/no-shadow` | error | +| `react-hooks/rules-of-hooks` | error | +| `react-hooks/exhaustive-deps` | error | +| `react/no-unstable-nested-components` | error | +| No direct `localStorage` in `components/` or `features/` | error | +| Prettier formatting | error | +| Knip dead exports and unused files | error | +| Coverage thresholds (statements/functions/lines >= 1.5%, branches >= 1) | CI error | +| Logic files without a `.test.` counterpart | CI advisory comment on PR | diff --git a/infra/README.md b/infra/README.md index 3e01697cfe..ab9ea42c92 100644 --- a/infra/README.md +++ b/infra/README.md @@ -87,12 +87,12 @@ Preview builds: - `infra/web/main.tf` enables preview URL capability with `subdomain.previews_enabled = true`. - Previews are handled by Cloudflare Workers Builds, not GitHub Actions. - Connect the repo once in Cloudflare Workers Builds. -- Set the Cloudflare Builds deploy command to `npx wrangler versions upload`. +- Set the Cloudflare Builds deploy command to `pnpm run wrangler versions upload`. - This disables automatic deployments while still allowing Cloudflare to build PRs/branches and save them as preview versions. - That keeps Cloudflare from promoting `dev` commits to production. Production stays on the OpenTofu/GitHub Actions path in this repo. ```bash -npx wrangler versions upload +pnpm run wrangler versions upload ``` Production deploys: diff --git a/knip.json b/knip.json index 6cc8c8581c..6036d19417 100644 --- a/knip.json +++ b/knip.json @@ -1,13 +1,21 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", "entry": ["src/sw.ts", "scripts/normalize-imports.js"], - "ignore": ["oxlint.config.ts", "oxfmt.config.ts"], + "ignore": [ + "oxlint.config.ts", + "oxfmt.config.ts", + "scripts/migrate-matrix-sdk-imports.js", + "src/ext.d.ts", + "src/types/matrix-sdk-events.d.ts" + ], "ignoreExportsUsedInFile": { "interface": true, "type": true }, "ignoreDependencies": [ "buffer", + "@e18e/eslint-plugin", + "@sableclient/twemoji-font", "@sableclient/sable-call-embedded", "@matrix-org/matrix-sdk-crypto-wasm" ], diff --git a/oxlint.config.ts b/oxlint.config.ts index 8681734142..9ce41ecdf7 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ typeAware: true, }, plugins: ['react', 'jsx-a11y', 'typescript', 'import', 'unicorn', 'oxc', 'vitest', 'promise'], + jsPlugins: ['@e18e/eslint-plugin'], categories: { correctness: 'error', suspicious: 'warn', @@ -22,7 +23,6 @@ export default defineConfig({ 'no-console': ['error', { allow: ['warn', 'error'] }], 'react/react-in-jsx-scope': 'off', 'react/jsx-filename-extension': ['error', { extensions: ['.tsx', '.jsx'] }], - 'react/rules-of-hooks': 'error', 'react/exhaustive-deps': 'error', 'react/iframe-missing-sandbox': 'off', 'jsx-a11y/no-autofocus': 'off', @@ -35,6 +35,23 @@ export default defineConfig({ 'typescript/no-unnecessary-type-arguments': 'off', 'oxc/no-map-spread': 'off', 'promise/always-return': 'off', + 'e18e/ban-dependencies': 'error', + 'e18e/prefer-array-at': 'error', + 'e18e/prefer-array-fill': 'error', + 'e18e/prefer-array-from-map': 'error', + 'e18e/prefer-array-some': 'error', + 'e18e/prefer-array-to-reversed': 'error', + 'e18e/prefer-array-to-sorted': 'error', + 'e18e/prefer-array-to-spliced': 'error', + 'e18e/prefer-date-now': 'error', + 'e18e/prefer-includes': 'error', + 'e18e/prefer-nullish-coalescing': 'error', + 'e18e/prefer-object-has-own': 'error', + 'e18e/prefer-regex-test': 'error', + 'e18e/prefer-spread-syntax': 'error', + 'e18e/prefer-static-regex': 'off', + 'e18e/prefer-timer-args': 'error', + 'e18e/prefer-url-canparse': 'error', }, overrides: [ { diff --git a/package.json b/package.json index 47bfe902fd..bc2832caa7 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,15 @@ "lint:fix": "oxlint . --fix && oxfmt .", "fmt": "oxfmt .", "fmt:check": "oxfmt --check .", - "typecheck": "tsc", + "typecheck": "tsc -b", + "check": "pnpm run \"/^(typecheck|lint|fmt:check|knip|test:run)$/\"", "test": "vitest", "test:ui": "vitest --ui", "test:run": "vitest run", "test:coverage": "vitest run --coverage", "knip": "knip", - "tunnel": "cloudflared tunnel --url http://localhost:8080", + "tunnel": "pnpm dlx cloudflared tunnel --url http://localhost:8080", + "wrangler": "pnpm dlx --allow-build=esbuild --allow-build=sharp --allow-build=workerd wrangler", "knope": "knope", "document-change": "knope document-change", "postinstall": "node scripts/install-knope.js" @@ -32,31 +34,32 @@ "license": "AGPL-3.0-only", "dependencies": { "@arborium/arborium": "^2.16.0", - "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", + "@atlaskit/pragmatic-drag-and-drop": "^1.7.9", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^1.4.0", "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0", "@fontsource-variable/nunito": "5.2.7", "@fontsource/space-mono": "5.2.9", "@phosphor-icons/react": "^2.1.10", - "@sentry/react": "^10.43.0", - "@tanstack/react-query": "^5.90.21", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-virtual": "^3.13.19", + "@sableclient/twemoji-font": "^1.0.2", + "@sentry/react": "^10.46.0", + "@tanstack/react-query": "^5.95.2", + "@tanstack/react-query-devtools": "^5.95.2", + "@tanstack/react-virtual": "^3.13.23", "@use-gesture/react": "10.3.1", - "@vanilla-extract/css": "^1.18.0", + "@vanilla-extract/css": "^1.20.1", "@vanilla-extract/recipes": "^0.5.7", - "@vanilla-extract/vite-plugin": "^5.1.4", + "@vanilla-extract/vite-plugin": "^5.2.2", "await-to-js": "^3.0.0", "badwords-list": "^2.0.1-4", "blurhash": "^2.0.5", "browser-encrypt-attachment": "^0.3.0", "chroma-js": "^3.2.0", "classnames": "^2.5.1", - "dayjs": "^1.11.19", + "dayjs": "^1.11.20", "domhandler": "^5.0.3", "dompurify": "^3.3.3", "emojibase": "^15.3.1", - "emojibase-data": "^15.3.2", + "emojibase-data": "^17.0.0", "eventemitter3": "^5.0.4", "file-saver": "^2.0.5", "focus-trap-react": "^10.3.1", @@ -64,24 +67,25 @@ "framer-motion": "12.34.3", "html-dom-parser": "^5.1.8", "html-react-parser": "^4.2.10", - "i18next": "^25.8.13", + "i18next": "^25.10.10", "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^2.7.3", "immer": "^9.0.21", "is-hotkey": "^0.2.0", - "jotai": "^2.18.0", + "jotai": "^2.19.0", + "jotai-family": "^1.0.1", "linkify-react": "^4.3.2", "linkifyjs": "^4.3.2", "matrix-js-sdk": "^38.4.0", "matrix-widget-api": "^1.16.1", - "pdfjs-dist": "^5.4.624", + "pdfjs-dist": "^5.5.207", "react": "^18.3.1", - "react-aria": "^3.46.0", + "react-aria": "^3.47.0", "react-blurhash": "^0.3.0", "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-google-recaptcha": "^2.1.0", - "react-i18next": "^16.5.4", + "react-i18next": "^16.6.6", "react-range": "^1.10.0", "react-router-dom": "^6.30.3", "slate": "^0.123.0", @@ -93,8 +97,7 @@ "workbox-precaching": "^7.4.0" }, "devDependencies": { - "@cloudflare/vite-plugin": "^1.26.0", - "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@e18e/eslint-plugin": "^0.3.0", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-wasm": "^6.2.2", "@sableclient/sable-call-embedded": "v1.1.4", @@ -110,24 +113,22 @@ "@types/react-dom": "^18.3.7", "@types/react-google-recaptcha": "^2.1.9", "@types/ua-parser-js": "^0.7.39", - "@vitejs/plugin-react": "^5.1.4", - "@vitest/coverage-v8": "^4.1.0", - "@vitest/ui": "^4.1.0", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "^4.1.2", + "@vitest/ui": "^4.1.2", "buffer": "^6.0.3", - "cloudflared": "^0.7.1", "jsdom": "^29.0.0", "knip": "5.85.0", - "oxfmt": "^0.45.0", - "oxlint": "^1.60.0", - "oxlint-tsgolint": "^0.21.0", + "oxfmt": "^0.46.0", + "oxlint": "^1.61.0", + "oxlint-tsgolint": "^0.21.1", "typescript": "^5.9.3", - "vite": "^7.3.1", - "vite-plugin-compression2": "2.5.0", + "vite": "^8.0.3", + "vite-plugin-compression2": "2.5.3", "vite-plugin-pwa": "^1.2.0", - "vite-plugin-static-copy": "^3.2.0", + "vite-plugin-static-copy": "^4.0.0", "vite-plugin-svgr": "4.5.0", "vite-plugin-top-level-await": "^1.6.0", - "vitest": "^4.1.0", - "wrangler": "^4.70.0" + "vitest": "^4.1.2" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02f7aba34b..135fad90d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,14 +5,10 @@ settings: excludeLinksFromLockfile: false overrides: - brace-expansion: '>=1.1.12' - esbuild: '>=0.25.0' - lodash: '>=4.17.23' - minimatch: '>=10.2.3' - rollup: '>=4.59.0' - serialize-javascript: '>=7.0.3' - flatted: '>=3.4.2' - undici: '>=7.24.0' + serialize-javascript: '>=7.0.5' + picomatch: '>=4.0.4' + smol-toml: '>=1.6.1' + yaml: '>=2.8.3' importers: @@ -22,7 +18,7 @@ importers: specifier: ^2.16.0 version: 2.16.0 '@atlaskit/pragmatic-drag-and-drop': - specifier: ^1.7.7 + specifier: ^1.7.9 version: 1.7.9 '@atlaskit/pragmatic-drag-and-drop-auto-scroll': specifier: ^1.4.0 @@ -39,30 +35,33 @@ importers: '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@sableclient/twemoji-font': + specifier: ^1.0.2 + version: 1.0.2 '@sentry/react': - specifier: ^10.43.0 - version: 10.43.0(react@18.3.1) + specifier: ^10.46.0 + version: 10.46.0(react@18.3.1) '@tanstack/react-query': - specifier: ^5.90.21 - version: 5.90.21(react@18.3.1) + specifier: ^5.95.2 + version: 5.95.2(react@18.3.1) '@tanstack/react-query-devtools': - specifier: ^5.91.3 - version: 5.91.3(@tanstack/react-query@5.90.21(react@18.3.1))(react@18.3.1) + specifier: ^5.95.2 + version: 5.95.2(@tanstack/react-query@5.95.2(react@18.3.1))(react@18.3.1) '@tanstack/react-virtual': - specifier: ^3.13.19 - version: 3.13.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.13.23 + version: 3.13.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@use-gesture/react': specifier: 10.3.1 version: 10.3.1(react@18.3.1) '@vanilla-extract/css': - specifier: ^1.18.0 - version: 1.18.0 + specifier: ^1.20.1 + version: 1.20.1 '@vanilla-extract/recipes': specifier: ^0.5.7 - version: 0.5.7(@vanilla-extract/css@1.18.0) + version: 0.5.7(@vanilla-extract/css@1.20.1) '@vanilla-extract/vite-plugin': - specifier: ^5.1.4 - version: 5.1.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(yaml@2.8.2) + specifier: ^5.2.2 + version: 5.2.2(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) await-to-js: specifier: ^3.0.0 version: 3.0.0 @@ -82,8 +81,8 @@ importers: specifier: ^2.5.1 version: 2.5.1 dayjs: - specifier: ^1.11.19 - version: 1.11.19 + specifier: ^1.11.20 + version: 1.11.20 domhandler: specifier: ^5.0.3 version: 5.0.3 @@ -94,8 +93,8 @@ importers: specifier: ^15.3.1 version: 15.3.1 emojibase-data: - specifier: ^15.3.2 - version: 15.3.2(emojibase@15.3.1) + specifier: ^17.0.0 + version: 17.0.0(emojibase@15.3.1) eventemitter3: specifier: ^5.0.4 version: 5.0.4 @@ -107,7 +106,7 @@ importers: version: 10.3.1(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) folds: specifier: ^2.6.2 - version: 2.6.2(@vanilla-extract/css@1.18.0)(@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.18.0))(classnames@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.6.2(@vanilla-extract/css@1.20.1)(@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.20.1))(classnames@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) framer-motion: specifier: 12.34.3 version: 12.34.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -118,8 +117,8 @@ importers: specifier: ^4.2.10 version: 4.2.10(react@18.3.1) i18next: - specifier: ^25.8.13 - version: 25.8.17(typescript@5.9.3) + specifier: ^25.10.10 + version: 25.10.10(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 @@ -133,8 +132,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 jotai: - specifier: ^2.18.0 - version: 2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1) + specifier: ^2.19.0 + version: 2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1) + jotai-family: + specifier: ^1.0.1 + version: 1.0.1(jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1)) linkify-react: specifier: ^4.3.2 version: 4.3.2(linkifyjs@4.3.2)(react@18.3.1) @@ -148,13 +150,13 @@ importers: specifier: ^1.16.1 version: 1.17.0 pdfjs-dist: - specifier: ^5.4.624 + specifier: ^5.5.207 version: 5.5.207 react: specifier: ^18.3.1 version: 18.3.1 react-aria: - specifier: ^3.46.0 + specifier: ^3.47.0 version: 3.47.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-blurhash: specifier: ^0.3.0 @@ -169,8 +171,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(react@18.3.1) react-i18next: - specifier: ^16.5.4 - version: 16.5.7(i18next@25.8.17(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + specifier: ^16.6.6 + version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) react-range: specifier: ^1.10.0 version: 1.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -199,24 +201,21 @@ importers: specifier: ^7.4.0 version: 7.4.0 devDependencies: - '@cloudflare/vite-plugin': - specifier: ^1.26.0 - version: 1.27.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(workerd@1.20260310.1)(wrangler@4.72.0) - '@esbuild-plugins/node-globals-polyfill': - specifier: ^0.2.3 - version: 0.2.3(esbuild@0.27.3) + '@e18e/eslint-plugin': + specifier: ^0.3.0 + version: 0.3.0(eslint@10.2.1(jiti@2.6.1))(oxlint@1.61.0(oxlint-tsgolint@0.21.1)) '@rollup/plugin-inject': specifier: ^5.0.5 - version: 5.0.5(rollup@4.59.0) + version: 5.0.5(rollup@4.60.0) '@rollup/plugin-wasm': specifier: ^6.2.2 - version: 6.2.2(rollup@4.59.0) + version: 6.2.2(rollup@4.60.0) '@sableclient/sable-call-embedded': specifier: v1.1.4 version: 1.1.4 '@sentry/vite-plugin': specifier: ^5.1.1 - version: 5.1.1(rollup@4.59.0) + version: 5.1.1(rollup@4.60.0) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -251,70 +250,64 @@ importers: specifier: ^0.7.39 version: 0.7.39 '@vitejs/plugin-react': - specifier: ^5.1.4 - version: 5.1.4(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) '@vitest/coverage-v8': - specifier: ^4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: ^4.1.2 + version: 4.1.2(vitest@4.1.2) '@vitest/ui': - specifier: ^4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: ^4.1.2 + version: 4.1.2(vitest@4.1.2) buffer: specifier: ^6.0.3 version: 6.0.3 - cloudflared: - specifier: ^0.7.1 - version: 0.7.1 jsdom: specifier: ^29.0.0 - version: 29.0.0 + version: 29.0.1 knip: specifier: 5.85.0 version: 5.85.0(@types/node@24.10.13)(typescript@5.9.3) oxfmt: - specifier: ^0.45.0 - version: 0.45.0 + specifier: ^0.46.0 + version: 0.46.0 oxlint: - specifier: ^1.60.0 - version: 1.60.0(oxlint-tsgolint@0.21.0) + specifier: ^1.61.0 + version: 1.61.0(oxlint-tsgolint@0.21.1) oxlint-tsgolint: - specifier: ^0.21.0 - version: 0.21.0 + specifier: ^0.21.1 + version: 0.21.1 typescript: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.3.1 - version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + specifier: ^8.0.3 + version: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) vite-plugin-compression2: - specifier: 2.5.0 - version: 2.5.0(rollup@4.59.0) + specifier: 2.5.3 + version: 2.5.3(rollup@4.60.0) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) + version: 1.2.0(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) vite-plugin-static-copy: - specifier: ^3.2.0 - version: 3.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + specifier: ^4.0.0 + version: 4.0.0(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) vite-plugin-svgr: specifier: 4.5.0 - version: 4.5.0(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + version: 4.5.0(rollup@4.60.0)(typescript@5.9.3)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) vite-plugin-top-level-await: specifier: ^1.6.0 - version: 1.6.0(@swc/helpers@0.5.19)(rollup@4.59.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + version: 1.6.0(@swc/helpers@0.5.19)(rollup@4.60.0)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) vitest: - specifier: ^4.1.0 - version: 4.1.0(@types/node@24.10.13)(@vitest/ui@4.1.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) - wrangler: - specifier: ^4.70.0 - version: 4.72.0 + specifier: ^4.1.2 + version: 4.1.2(@types/node@24.10.13)(@vitest/ui@4.1.2)(jsdom@29.0.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@apideck/better-ajv-errors@0.3.6': - resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} + '@apideck/better-ajv-errors@0.3.7': + resolution: {integrity: sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==} engines: {node: '>=10'} peerDependencies: ajv: '>=8' @@ -450,6 +443,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -744,18 +742,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.29.0': resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} @@ -867,59 +853,6 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@cloudflare/kv-asset-handler@0.4.2': - resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} - engines: {node: '>=18.0.0'} - - '@cloudflare/unenv-preset@2.15.0': - resolution: {integrity: sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==} - peerDependencies: - unenv: 2.0.0-rc.24 - workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 - peerDependenciesMeta: - workerd: - optional: true - - '@cloudflare/vite-plugin@1.27.0': - resolution: {integrity: sha512-+s8APrIadCf2h0zYZ3WIPVeQ2CYgbXgFiR3BIrKvbpoo5klLgTeYpTq8ggxBmtjwt88PNruI4eCGZ6XNaDHMaA==} - peerDependencies: - vite: ^6.1.0 || ^7.0.0 - wrangler: ^4.72.0 - - '@cloudflare/workerd-darwin-64@1.20260310.1': - resolution: {integrity: sha512-hF2VpoWaMb1fiGCQJqCY6M8I+2QQqjkyY4LiDYdTL5D/w6C1l5v1zhc0/jrjdD1DXfpJtpcSMSmEPjHse4p9Ig==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260310.1': - resolution: {integrity: sha512-h/Vl3XrYYPI6yFDE27XO1QPq/1G1lKIM8tzZGIWYpntK3IN5XtH3Ee/sLaegpJ49aIJoqhF2mVAZ6Yw+Vk2gJw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-linux-64@1.20260310.1': - resolution: {integrity: sha512-XzQ0GZ8G5P4d74bQYOIP2Su4CLdNPpYidrInaSOuSxMw+HamsHaFrjVsrV2mPy/yk2hi6SY2yMbgKFK9YjA7vw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260310.1': - resolution: {integrity: sha512-sxv4CxnN4ZR0uQGTFVGa0V4KTqwdej/czpIc5tYS86G8FQQoGIBiAIs2VvU7b8EROPcandxYHDBPTb+D9HIMPw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-windows-64@1.20260310.1': - resolution: {integrity: sha512-+1ZTViWKJypLfgH/luAHCqkent0DEBjAjvO40iAhOMHRLYP/SPphLvr4Jpi6lb+sIocS8Q1QZL4uM5Etg1Wskg==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} @@ -956,6 +889,17 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} + '@e18e/eslint-plugin@0.3.0': + resolution: {integrity: sha512-hHgfpxsrZ2UYHcicA+tGZnmk19uJTaye9VH79O+XS8R4ona2Hx3xjhXghclNW58uXMk3xXlbYEOMr8thsoBmWg==} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + oxlint: ^1.55.0 + peerDependenciesMeta: + eslint: + optional: true + oxlint: + optional: true + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -968,167 +912,192 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - '@esbuild-plugins/node-globals-polyfill@0.2.3': - resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} - peerDependencies: - esbuild: '>=0.25.0' - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1159,158 +1128,25 @@ packages: '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [musl] + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@internationalized/date@3.12.0': resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} @@ -1347,9 +1183,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} @@ -1447,6 +1280,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -1555,276 +1391,276 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.45.0': - resolution: {integrity: sha512-A/UMxFob1fefCuMeGxQBulGfFE38g2Gm23ynr3u6b+b7fY7/ajGbNsa3ikMIkGMLJW/TRoQaMoP1kME7S+815w==} + '@oxfmt/binding-android-arm-eabi@0.46.0': + resolution: {integrity: sha512-b1doV4WRcJU+BESSlCvCjV+5CEr/T6h0frArAdV26Nir+gGNFNaylvDiiMPfF1pxeV0txZEs38ojzJaxBYg+ng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.45.0': - resolution: {integrity: sha512-L63z4uZmHjgvvqvMJD7mwff8aSBkM0+X4uFr6l6U5t6+Qc9DCLVZWIunJ7Gm4fn4zHPdSq6FFQnhu9yqqobxIg==} + '@oxfmt/binding-android-arm64@0.46.0': + resolution: {integrity: sha512-v6+HhjsoV3GO0u2u9jLSAZrvWfTraDxKofUIQ7/ktS7tzS+epVsxdHmeM+XxuNcAY/nWxxU1Sg4JcGTNRXraBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.45.0': - resolution: {integrity: sha512-UV34dd623FzqT+outIGndsCA/RBB+qgB3XVQhgmmJ9PJwa37NzPC9qzgKeOhPKxVk2HW+JKldQrVL54zs4Noww==} + '@oxfmt/binding-darwin-arm64@0.46.0': + resolution: {integrity: sha512-3eeooJGrqGIlI5MyryDZsAcKXSmKIgAD4yYtfRrRJzXZ0UTFZtiSveIur56YPrGMYZwT4XyVhHsMqrNwr1XeFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.45.0': - resolution: {integrity: sha512-pMNJv0CMa1pDefVPeNbuQxibh8ITpWDFEhMC/IBB9Zlu76EbgzYwrzI4Cb11mqX2+rIYN70UTrh3z06TM59ptQ==} + '@oxfmt/binding-darwin-x64@0.46.0': + resolution: {integrity: sha512-QG8BDM0CXWbu84k2SKmCqfEddPQPFiBicwtYnLqHRWZZl57HbtOLRMac/KTq2NO4AEc4ICCBpFxJIV9zcqYfkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.45.0': - resolution: {integrity: sha512-xTcRoxbbo61sW2+ZRPeH+vp/o9G8gkdhiVumFU+TpneiPm14c79l6GFlxPXlCE9bNWikigbsrvJw46zCVAQFfg==} + '@oxfmt/binding-freebsd-x64@0.46.0': + resolution: {integrity: sha512-9DdCqS/n2ncu/Chazvt3cpgAjAmIGQDz7hFKSrNItMApyV/Ja9mz3hD4JakIE3nS8PW9smEbPWnb389QLBY4nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.45.0': - resolution: {integrity: sha512-hWL8Hdni+3U1mPFx1UtWeGp3tNb6EhBAUHRMbKUxVkOp3WwoJbpVO2bfUVbS4PfpledviXXNHSTl1veTa6FhkQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': + resolution: {integrity: sha512-Dgs7VeE2jT0LHMhw6tPEt0xQYe54kBqHEovmWsv4FVQlegCOvlIJNx0S8n4vj8WUtpT+Z6BD2HhKJPLglLxvZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.45.0': - resolution: {integrity: sha512-6Blt/0OBT7vvfQpqYuYbpbFLPqSiaYpEJzUUWhinPEuADypDbtV1+LdjM0vYBNGPvnj85ex7lTerEX6JGcPt9w==} + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': + resolution: {integrity: sha512-Zxn3adhTH13JKnU4xXJj8FeEfF680XjXh3gSShKl57HCMBRde2tUJTgogV/1MSHA80PJEVrDa7r66TLVq3Ia7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.45.0': - resolution: {integrity: sha512-jLjoLfe+hGfjhA8hNBSdw85yCA8ePKq7ME4T+g6P9caQXvmt6IhE2X7iVjnVdkmYUWEzZrxlh4p6RkDmAMJY/A==} + '@oxfmt/binding-linux-arm64-gnu@0.46.0': + resolution: {integrity: sha512-+TWipjrgVM8D7aIdDD0tlr3teLTTvQTn7QTE5BpT10H1Fj82gfdn9X6nn2sDgx/MepuSCfSnzFNJq2paLL0OiA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.45.0': - resolution: {integrity: sha512-XQKXZIKYJC3GQJ8FnD3iMntpw69Wd9kDDK/Xt79p6xnFYlGGxSNv2vIBvRTDg5CKByWFWWZLCRDOXoP/m6YN4g==} + '@oxfmt/binding-linux-arm64-musl@0.46.0': + resolution: {integrity: sha512-aAUPBWJ1lGwwnxZUEDLJ94+Iy6MuwJwPxUgO4sCA5mEEyDk7b+cDQ+JpX1VR150Zoyd+D49gsrUzpUK5h587Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.45.0': - resolution: {integrity: sha512-+g5RiG+xOkdrCWkKodv407nTvMq4vYM18Uox2MhZBm/YoqFxxJpWKsloskFFG5NU13HGPw1wzYjjOVcyd9moCA==} + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': + resolution: {integrity: sha512-ufBCJukyFX/UDrokP/r6BGDoTInnsDs7bxyzKAgMiZlt2Qu8GPJSJ6Zm6whIiJzKk0naxA8ilwmbO1LMw6Htxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.45.0': - resolution: {integrity: sha512-V7dXKoSyEbWAkkSF4JJNtF+NJZDmJoSarSoP30WCsB3X636Rehd3CvxBj49FIJxEBFWhvcUjGSHVeU8Erck1bQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': + resolution: {integrity: sha512-eqtlC2YmPqjun76R1gVfGLuKWx7NuEnLEAudZ7n6ipSKbCZTqIKSs1b5Y8K/JHZsRpLkeSmAAjig5HOIg8fQzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.45.0': - resolution: {integrity: sha512-Vdelft1sAEYojVGgcODEFXSWYQYlIvoyIGWebKCuUibd1tvS1TjTx413xG2ZLuHpYj45CkN/ztMLMX6jrgqpgg==} + '@oxfmt/binding-linux-riscv64-musl@0.46.0': + resolution: {integrity: sha512-yccVOO2nMXkQLGgy0He3EQEwKD7NF0zEk+/OWmroznkqXyJdN6bfK0LtNnr6/14Bh3FjpYq7bP33l/VloCnxpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.45.0': - resolution: {integrity: sha512-RR7xKgNpqwENnK0aYCGYg0JycY2n93J0reNjHyes+I9Gq52dH95x+CBlnlAQHCPfz6FGnKA9HirgUl14WO6o7w==} + '@oxfmt/binding-linux-s390x-gnu@0.46.0': + resolution: {integrity: sha512-aAf7fG23OQCey6VRPj9IeCraoYtpgtx0ZyJ1CXkPyT1wjzBE7c3xtuxHe/AdHaJfVVb/SXpSk8Gl1LzyQupSqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.45.0': - resolution: {integrity: sha512-U/QQ0+BQNSHxjuXR/utvXnQ50Vu5kUuqEomZvQ1/3mhgbBiMc2WU9q5kZ5WwLp3gnFIx9ibkveoRSe2EZubkqg==} + '@oxfmt/binding-linux-x64-gnu@0.46.0': + resolution: {integrity: sha512-q0JPsTMyJNjYrBvYFDz4WbVsafNZaPCZv4RnFypRotLqpKROtBZcEaXQW4eb9YmvLU3NckVemLJnzkSZSdmOxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.45.0': - resolution: {integrity: sha512-o5TLOUCF0RWQjsIS06yVC+kFgp092/yLe6qBGSUvtnmTVw9gxjpdQSXc3VN5Cnive4K11HNstEZF8ROKHfDFSw==} + '@oxfmt/binding-linux-x64-musl@0.46.0': + resolution: {integrity: sha512-7LsLY9Cw57GPkhSR+duI3mt9baRczK/DtHYSldQ4BEU92da9igBQNl4z7Vq5U9NNPsh1FmpKvv1q9WDtiUQR1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.45.0': - resolution: {integrity: sha512-RnGcV3HgPuOjsGx/k9oyRNKmOp+NBLGzZTdPDYbc19r7NGeYPplnUU/BfU35bX2Y/O4ejvHxcfkvW2WoYL/gsg==} + '@oxfmt/binding-openharmony-arm64@0.46.0': + resolution: {integrity: sha512-lHiBOz8Duaku7JtRNLlps3j++eOaICPZSd8FCVmTDM4DFOPT71Bjn7g6iar1z7StXlKRweUKxWUs4sA+zWGDXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.45.0': - resolution: {integrity: sha512-v3Vj7iKKsUFwt9w5hsqIIoErKVoENC6LoqfDlteOQ5QMDCXihlqLoxpmviUhXnNncg4zV6U9BPwlBbwa+qm4wg==} + '@oxfmt/binding-win32-arm64-msvc@0.46.0': + resolution: {integrity: sha512-/5ktYUliP89RhgC37DBH1x20U5zPSZMy3cMEcO0j3793rbHP9MWsknBwQB6eozRzWmYrh0IFM/p20EbPvDlYlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.45.0': - resolution: {integrity: sha512-N8yotPBX6ph0H3toF4AEpdCeVPrdcSetj+8eGiZGsrLsng3bs/Q5HPu4bbSxip5GBPx5hGbGHrZwH4+rcrjhHA==} + '@oxfmt/binding-win32-ia32-msvc@0.46.0': + resolution: {integrity: sha512-3WTnoiuIr8XvV0DIY7SN+1uJSwKf4sPpcbHfobcRT9JutGcLaef/miyBB87jxd3aqH+mS0+G5lsgHuXLUwjjpQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.45.0': - resolution: {integrity: sha512-w5MMTRCK1dpQeRA+HHqXQXyN33DlG/N2LOYxJmaT4fJjcmZrbNnqw7SmIk7I2/a2493PPLZ+2E/Ar6t2iKVMug==} + '@oxfmt/binding-win32-x64-msvc@0.46.0': + resolution: {integrity: sha512-IXxiQpkYnOwNfP23vzwSfhdpxJzyiPTY7eTn6dn3DsriKddESzM8i6kfq9R7CD/PUJwCvQT22NgtygBeug3KoA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.21.0': - resolution: {integrity: sha512-P20j3MLqfwIT+94qGU3htC7dWp4pXGZW1p1p7FRUzu1aopq7c9nPCgf0W/WjktqQ57+iuTq9mbSlwWinl6+H1A==} + '@oxlint-tsgolint/darwin-arm64@0.21.1': + resolution: {integrity: sha512-7TLjyWe4wG9saJc992VWmaHq2hwKfOEEVTjheReXJXaDhavMZI4X9a6nKhbEng4IVkYtzjD2jw16vw2WFXLYLw==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.21.0': - resolution: {integrity: sha512-81TmmuBcPedEA0MwRmObuQuXnCprS1UiHQWGe7pseqNAJzUWXeAPrayqKTACX92VpruJI+yvY0XJrFp11PpcTA==} + '@oxlint-tsgolint/darwin-x64@0.21.1': + resolution: {integrity: sha512-7wf9Wf75nTzA7zpL9myhFe2RKvfuqGUOADNvUooCjEWvh7hmPz3lSEqTMh5Z/VQhzsG04mM9ACyghxhRzq7zFw==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.21.0': - resolution: {integrity: sha512-sbjBr6zDduX8rNO0PTjhf7VYLCPWqdijWiMPp8e10qu6Tam1GdaVLaLlX8QrNupTgglO1GvqqgY/jcacWL8a6g==} + '@oxlint-tsgolint/linux-arm64@0.21.1': + resolution: {integrity: sha512-IPuQN/Vd0Rjklg/cCGBbQyUuRBp2f6LQXpZYwk5ivOR6V/+CgiYsv8pn/PVY7gjeyoNvPQrXB7xMjHUO2YZbdw==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.21.0': - resolution: {integrity: sha512-jNrOcy53R5TJQfrK444Cm60bW9437xDoxPbm3AdvFSo/fhdFMllawc7uZC2Wzr+EAjTkW13K8R4QHzsUdBG9fQ==} + '@oxlint-tsgolint/linux-x64@0.21.1': + resolution: {integrity: sha512-d1niGuTbh2qiv7dR7tqkbOcM5cIR63of0lMBFdEQavL1KrJV8zuRdwdi68K7MNGdgoR+J5A9ajpGGvsHwp1bPg==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.21.0': - resolution: {integrity: sha512-xWeRxJJILDE4b9UqHEWGBxcBc1TUS6zWHhxcyxTZMwf4q3wdKeu0OHYAcwLGJzoSjEIf6FTjyfPiRNil2oqsdg==} + '@oxlint-tsgolint/win32-arm64@0.21.1': + resolution: {integrity: sha512-ICu9y2JLnFPvFqstnWPPNqBM8LK8BWw2OTeaR0UgEMm4hOSbrZAKv1/hwZYyiLqnCNjBL87AGSQIgTHCYlsipw==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.21.0': - resolution: {integrity: sha512-Ob9AA9teI8ckPo1whV1smLr5NrqwgBv/8boDbK0YZG+fKgNGRwr1hBj1ORgFWOQaUBv+5njp5A0RAfJJjQ95QQ==} + '@oxlint-tsgolint/win32-x64@0.21.1': + resolution: {integrity: sha512-cTEFCFjCj6iXfrSHcvajSPNqhEA4TxSzU3gFxbdGSAUTNXGToU99IbdhWAPSbhcucoym0XE4Zl7E41NiSkNTug==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.60.0': - resolution: {integrity: sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA==} + '@oxlint/binding-android-arm-eabi@1.61.0': + resolution: {integrity: sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.60.0': - resolution: {integrity: sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw==} + '@oxlint/binding-android-arm64@1.61.0': + resolution: {integrity: sha512-CkwLR69MUnyv5wjzebvbbtTSUwqLxM35CXE79bHqDIK+NtKmPEUpStTcLQRZMCo4MP0qRT6TXIQVpK0ZVScnMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.60.0': - resolution: {integrity: sha512-pJsgd9AfplLGBm1fIr25V6V14vMrayhx4uIQvlfH7jWs2SZwSrvi3TfgfJySB8T+hvyEH8K2zXljQiUnkgUnfQ==} + '@oxlint/binding-darwin-arm64@1.61.0': + resolution: {integrity: sha512-8JbefTkbmvqkqWjmQrHke+MdpgT2UghhD/ktM4FOQSpGeCgbMToJEKdl9zwhr/YWTl92i4QI1KiTwVExpcUN8A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.60.0': - resolution: {integrity: sha512-Ue1aXHX49ivwflKqGJc7zcd/LeLgbhaTcDCQStgx5x06AXgjEAZmvrlMuIkWd4AL4FHQe6QJ9f33z04Cg448VQ==} + '@oxlint/binding-darwin-x64@1.61.0': + resolution: {integrity: sha512-uWpoxDT47hTnDLcdEh5jVbso8rlTTu5o0zuqa9J8E0JAKmIWn7kGFEIB03Pycn2hd2vKxybPGLhjURy/9We5FQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.60.0': - resolution: {integrity: sha512-YCyQzsQtusQw+gNRW9rRTifSO+Dt/+dtCl2NHoDMZqJlRTEZ/Oht9YnuporI9yiTx7+cB+eqzX3MtHHVHGIWhg==} + '@oxlint/binding-freebsd-x64@1.61.0': + resolution: {integrity: sha512-K/o4hEyW7flfMel0iBVznmMBt7VIMHGdjADocHKpK1DUF9erpWnJ+BSSWd2W0c8K3mPtpph+CuHzRU6CI3l9jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.60.0': - resolution: {integrity: sha512-c7dxM2Zksa45Qw16i2iGY3Fti2NirJ38FrsBsKw+qcJ0OtqTsBgKJLF0xV+yLG56UH01Z8WRPgsw31e0MoRoGQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.61.0': + resolution: {integrity: sha512-P6040ZkcyweJ0Po9yEFqJCdvZnf3VNCGs1SIHgXDf8AAQNC6ID/heXQs9iSgo2FH7gKaKq32VWc59XZwL34C5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.60.0': - resolution: {integrity: sha512-ZWALoA42UYqBEP1Tbw9OWURgFGS1nWj2AAvLdY6ZcGx/Gj93qVCBKjcvwXMupZibYwFbi9s/rzqkZseb/6gVtQ==} + '@oxlint/binding-linux-arm-musleabihf@1.61.0': + resolution: {integrity: sha512-bwxrGCzTZkuB+THv2TQ1aTkVEfv5oz8sl+0XZZCpoYzErJD8OhPQOTA0ENPd1zJz8QsVdSzSrS2umKtPq4/JXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.60.0': - resolution: {integrity: sha512-tpy+1w4p9hN5CicMCxqNy6ymfRtV5ayE573vFNjp1k1TN/qhLFgflveZoE/0++RlkHikBz2vY545NWm/hp7big==} + '@oxlint/binding-linux-arm64-gnu@1.61.0': + resolution: {integrity: sha512-vkhb9/wKguMkLlrm3FoJW/Xmdv31GgYAE+x8lxxQ+7HeOxXUySI0q36a3NTVIuQUdLzxCI1zzMGsk1o37FOe3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.60.0': - resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==} + '@oxlint/binding-linux-arm64-musl@1.61.0': + resolution: {integrity: sha512-bl1dQh8LnVqsj6oOQAcxwbuOmNJkwc4p6o//HTBZhNTzJy21TLDwAviMqUFNUxDHkPGpmdKTSN4tWTjLryP8xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.60.0': - resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==} + '@oxlint/binding-linux-ppc64-gnu@1.61.0': + resolution: {integrity: sha512-QoOX6KB2IiEpyOj/HKqaxi+NQHPnOgNgnr22n9N4ANJCzXkUlj1UmeAbFb4PpqdlHIzvGDM5xZ0OKtcLq9RhiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.60.0': - resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==} + '@oxlint/binding-linux-riscv64-gnu@1.61.0': + resolution: {integrity: sha512-1TGcTerjY6p152wCof3oKElccq3xHljS/Mucp04gV/4ATpP6nO7YNnp7opEg6SHkv2a57/b4b8Ndm9znJ1/qAw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.60.0': - resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==} + '@oxlint/binding-linux-riscv64-musl@1.61.0': + resolution: {integrity: sha512-65wXEmZIrX2ADwC8i/qFL4EWLSbeuBpAm3suuX1vu4IQkKd+wLT/HU/BOl84kp91u2SxPkPDyQgu4yrqp8vwVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.60.0': - resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==} + '@oxlint/binding-linux-s390x-gnu@1.61.0': + resolution: {integrity: sha512-TVvhgMvor7Qa6COeXxCJ7ENOM+lcAOGsQ0iUdPSCv2hxb9qSHLQ4XF1h50S6RE1gBOJ0WV3rNukg4JJJP1LWRA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.60.0': - resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==} + '@oxlint/binding-linux-x64-gnu@1.61.0': + resolution: {integrity: sha512-SjpS5uYuFoDnDdZPwZE59ndF95AsY47R5MliuneTWR1pDm2CxGJaYXbKULI71t5TVfLQUWmrHEGRL9xvuq6dnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.60.0': - resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==} + '@oxlint/binding-linux-x64-musl@1.61.0': + resolution: {integrity: sha512-gGfAeGD4sNJGILZbc/yKcIimO9wQnPMoYp9swAaKeEtwsSQAbU+rsdQze5SBtIP6j0QDzeYd4XSSUCRCF+LIeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.60.0': - resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==} + '@oxlint/binding-openharmony-arm64@1.61.0': + resolution: {integrity: sha512-OlVT0LrG/ct33EVtWRyR+B/othwmDWeRxfi13wUdPeb3lAT5TgTcFDcfLfarZtzB4W1nWF/zICMgYdkggX2WmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.60.0': - resolution: {integrity: sha512-WA/yc7f7ZfCefBXVzNHn1Ztulb1EFwNBb4jMZ6pjML0zz6pHujlF3Q3jySluz3XHl/GNeMTntG1seUBWVMlMag==} + '@oxlint/binding-win32-arm64-msvc@1.61.0': + resolution: {integrity: sha512-vI//NZPJk6DToiovPtaiwD4iQ7kO1r5ReWQD0sOOyKRtP3E2f6jxin4uvwi3OvDzHA2EFfd7DcZl5dtkQh7g1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.60.0': - resolution: {integrity: sha512-33YxL1sqwYNZXtn3MD/4dno6s0xeedXOJlT1WohkVD565WvohClZUr7vwKdAk954n4xiEWJkewiCr+zLeq7AeA==} + '@oxlint/binding-win32-ia32-msvc@1.61.0': + resolution: {integrity: sha512-0ySj4/4zd2XjePs3XAQq7IigIstN4LPQZgCyigX5/ERMLjdWAJfnxcTsrtxZxuij8guJW8foXuHmhGxW0H4dDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.60.0': - resolution: {integrity: sha512-JOro4ZcfBLamJCyfURQmOQByoorgOdx3ZjAkSqnb/CyG/i+lN3KoV5LAgk5ZAW6DPq7/Cx7n23f8DuTWXTWgyQ==} + '@oxlint/binding-win32-x64-msvc@1.61.0': + resolution: {integrity: sha512-0xgSiyeqDLDZxXoe9CVJrOx3TUVsfyoOY7cNi03JbItNcC9WCZqrSNdrAbHONxhSPaVh/lzfnDcON1RqSUMhHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1839,15 +1675,6 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@poppinss/colors@4.1.6': - resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} - - '@poppinss/dumper@0.6.5': - resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} - - '@poppinss/exception@1.2.3': - resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@react-aria/breadcrumbs@3.5.32': resolution: {integrity: sha512-S61vh5DJ2PXiXUwD7gk+pvS/b4VPrc3ZJOUZ0yVRLHkVESr5LhIZH+SAVgZkm1lzKyMRG+BH+fiRH/DZRSs7SA==} peerDependencies: @@ -2388,8 +2215,106 @@ packages: resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} engines: {node: '>=14.0.0'} - '@rolldown/pluginutils@1.0.0-rc.3': - resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -2397,7 +2322,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 '@types/babel__core': ^7.1.9 - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0 peerDependenciesMeta: '@types/babel__core': optional: true @@ -2406,7 +2331,7 @@ packages: resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2415,7 +2340,7 @@ packages: resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^2.78.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2423,13 +2348,13 @@ packages: '@rollup/plugin-replace@2.4.2': resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0 || ^2.0.0 '@rollup/plugin-terser@0.4.4': resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2438,7 +2363,7 @@ packages: resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2447,7 +2372,7 @@ packages: resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2456,180 +2381,183 @@ packages: resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0 '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.59.0' + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + '@rollup/rollup-android-arm-eabi@4.60.0': + resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + '@rollup/rollup-android-arm64@4.60.0': + resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + '@rollup/rollup-darwin-arm64@4.60.0': + resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + '@rollup/rollup-darwin-x64@4.60.0': + resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + '@rollup/rollup-freebsd-arm64@4.60.0': + resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + '@rollup/rollup-freebsd-x64@4.60.0': + resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + '@rollup/rollup-linux-arm64-gnu@4.60.0': + resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + '@rollup/rollup-linux-arm64-musl@4.60.0': + resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + '@rollup/rollup-linux-loong64-gnu@4.60.0': + resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + '@rollup/rollup-linux-loong64-musl@4.60.0': + resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + '@rollup/rollup-linux-ppc64-musl@4.60.0': + resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + '@rollup/rollup-linux-riscv64-musl@4.60.0': + resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + '@rollup/rollup-linux-s390x-gnu@4.60.0': + resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + '@rollup/rollup-linux-x64-gnu@4.60.0': + resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + '@rollup/rollup-linux-x64-musl@4.60.0': + resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + '@rollup/rollup-openbsd-x64@4.60.0': + resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + '@rollup/rollup-openharmony-arm64@4.60.0': + resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + '@rollup/rollup-win32-arm64-msvc@4.60.0': + resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + '@rollup/rollup-win32-ia32-msvc@4.60.0': + resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + '@rollup/rollup-win32-x64-gnu@4.60.0': + resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + '@rollup/rollup-win32-x64-msvc@4.60.0': + resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} cpu: [x64] os: [win32] '@sableclient/sable-call-embedded@1.1.4': resolution: {integrity: sha512-XLRcbUPcn7i3QKZAPjIfUkUEXP0E4DOr0dyRoVCWMjHWj28kq+T7jeB2fRr5lB77olBwNHMjIuoTwrv02xiepQ==} - '@sentry-internal/browser-utils@10.43.0': - resolution: {integrity: sha512-8zYTnzhAPvNkVH1Irs62wl0J/c+0QcJ62TonKnzpSFUUD3V5qz8YDZbjIDGfxy+1EB9fO0sxtddKCzwTHF/MbQ==} + '@sableclient/twemoji-font@1.0.2': + resolution: {integrity: sha512-9IaGNpTrj6d0q7m+Lq6U+iYKwoHQSxyUQB1kox5OzTi9T1eU4eFJLGCqtq5Sst0SbCnNsfBqTiC4s0x85+Q2oQ==} + + '@sentry-internal/browser-utils@10.46.0': + resolution: {integrity: sha512-WB1gBT9G13V02ekZ6NpUhoI1aGHV2eNfjEPthkU2bGBvFpQKnstwzjg7waIRGR7cu+YSW2Q6UI6aQLgBeOPD1g==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.43.0': - resolution: {integrity: sha512-YoXuwluP6eOcQxTeTtaWb090++MrLyWOVsUTejzUQQ6LFL13Jwt+bDPF1kvBugMq4a7OHw/UNKQfd6//rZMn2g==} + '@sentry-internal/feedback@10.46.0': + resolution: {integrity: sha512-c4pI/z9nZCQXe9GYEw/hE/YTY9AxGBp8/wgKI+T8zylrN35SGHaXv63szzE1WbI8lacBY8lBF7rstq9bQVCaHw==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.43.0': - resolution: {integrity: sha512-ZIw1UNKOFXo1LbPCJPMAx9xv7D8TMZQusLDUgb6BsPQJj0igAuwd7KRGTkjjgnrwBp2O/sxcQFRhQhknWk7QPg==} + '@sentry-internal/replay-canvas@10.46.0': + resolution: {integrity: sha512-ub314MWUsekVCuoH0/HJbbimlI24SkV745UW2pj9xRbxOAEf1wjkmIzxKrMDbTgJGuEunug02XZVdJFJUzOcDw==} engines: {node: '>=18'} - '@sentry-internal/replay@10.43.0': - resolution: {integrity: sha512-khCXlGrlH1IU7P5zCEAJFestMeH97zDVCekj8OsNNDtN/1BmCJ46k6Xi0EqAUzdJgrOLJeLdoYdgtiIjovZ8Sg==} + '@sentry-internal/replay@10.46.0': + resolution: {integrity: sha512-JBsWeXG6bRbxBFK8GzWymWGOB9QE7Kl57BeF3jzgdHTuHSWZ2mRnAmb1K05T4LU+gVygk6yW0KmdC8Py9Qzg9A==} engines: {node: '>=18'} '@sentry/babel-plugin-component-annotate@5.1.1': resolution: {integrity: sha512-x2wEpBHwsTyTF2rWsLKJlzrRF1TTIGOfX+ngdE+Yd5DBkoS58HwQv824QOviPGQRla4/ypISqAXzjdDPL/zalg==} engines: {node: '>= 18'} - '@sentry/browser@10.43.0': - resolution: {integrity: sha512-2V3I3sXi3SMeiZpKixd9ztokSgK27cmvsD9J5oyOyjhGLTW/6QKCwHbKnluMgQMXq20nixQk5zN4wRjRUma3sg==} + '@sentry/browser@10.46.0': + resolution: {integrity: sha512-80DmGlTk5Z2/OxVOzLNxwolMyouuAYKqG8KUcoyintZqHbF6kO1RulI610HmyUt3OagKeBCqt9S7w0VIfCRL+Q==} engines: {node: '>=18'} '@sentry/bundler-plugin-core@5.1.1': @@ -2688,12 +2616,12 @@ packages: engines: {node: '>= 10'} hasBin: true - '@sentry/core@10.43.0': - resolution: {integrity: sha512-l0SszQAPiQGWl/ferw8GP3ALyHXiGiRKJaOvNmhGO+PrTQyZTZ6OYyPnGijAFRg58dE1V3RCH/zw5d2xSUIiNg==} + '@sentry/core@10.46.0': + resolution: {integrity: sha512-N3fj4zqBQOhXliS1Ne9euqIKuciHCGOJfPGQLwBoW9DNz03jF+NB8+dUKtrJ79YLoftjVgf8nbgwtADK7NR+2Q==} engines: {node: '>=18'} - '@sentry/react@10.43.0': - resolution: {integrity: sha512-shvErEpJ41i0Q3lIZl0CDWYQ7m8yHLi7ECG0gFvN8zf8pEdl5grQIOoe3t/GIUzcpCcor16F148ATmKJJypc/Q==} + '@sentry/react@10.46.0': + resolution: {integrity: sha512-Rb1S+9OuUPVwsz7GWnQ6Kgf3azbsseUymIegg3JZHNcW/fM1nPpaljzTBnuineia113DH0pgMBcdrrZDLaosFQ==} engines: {node: '>=18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x @@ -2702,19 +2630,12 @@ packages: resolution: {integrity: sha512-1d5NkdRR6aKWBP7czkY8sFFWiKnfmfRpQOj+m9bJTsyTjbMiEQJst6315w5pCVlRItPhBqpAraqAhutZFgvyVg==} engines: {node: '>= 18'} peerDependencies: - rollup: '>=4.59.0' + rollup: '>=3.2.0' '@sentry/vite-plugin@5.1.1': resolution: {integrity: sha512-i6NWUDi2SDikfSUeMJvJTRdwEKYSfTd+mvBO2Ja51S1YK+hnickBuDfD+RvPerIXLuyRu3GamgNPbNqgCGUg/Q==} engines: {node: '>= 18'} - '@sindresorhus/is@7.2.0': - resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} - engines: {node: '>=18'} - - '@speed-highlight/core@1.2.14': - resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2874,31 +2795,31 @@ packages: '@swc/wasm@1.15.18': resolution: {integrity: sha512-zeSORFArxqUwfVMTRHu8AN9k9LlfSn0CKDSzLhJDITpgLoS0xpnocxsgMjQjUcVYDgO47r9zLP49HEjH/iGsFg==} - '@tanstack/query-core@5.90.20': - resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + '@tanstack/query-core@5.95.2': + resolution: {integrity: sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==} - '@tanstack/query-devtools@5.93.0': - resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==} + '@tanstack/query-devtools@5.95.2': + resolution: {integrity: sha512-QfaoqBn9uAZ+ICkA8brd1EHj+qBF6glCFgt94U8XP5BT6ppSsDBI8IJ00BU+cAGjQzp6wcKJL2EmRYvxy0TWIg==} - '@tanstack/react-query-devtools@5.91.3': - resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==} + '@tanstack/react-query-devtools@5.95.2': + resolution: {integrity: sha512-AFQFmbznVkbtfpx8VJ2DylW17wWagQel/qLstVLkYmNRo2CmJt3SNej5hvl6EnEeljJIdC3BTB+W7HZtpsH+3g==} peerDependencies: - '@tanstack/react-query': ^5.90.20 + '@tanstack/react-query': ^5.95.2 react: ^18 || ^19 - '@tanstack/react-query@5.90.21': - resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} + '@tanstack/react-query@5.95.2': + resolution: {integrity: sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==} peerDependencies: react: ^18 || ^19 - '@tanstack/react-virtual@3.13.21': - resolution: {integrity: sha512-SYXFrmrbPgXBvf+HsOsKhFgqSe4M6B29VHOsX9Jih9TlNkNkDWx0hWMiMLUghMEzyUz772ndzdEeCEBx+3GIZw==} + '@tanstack/react-virtual@3.13.23': + resolution: {integrity: sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/virtual-core@3.13.21': - resolution: {integrity: sha512-ww+fmLHyCbPSf7JNbWZP3g7wl6SdNo3ah5Aiw+0e9FDErkVHLKprYUrwTm7dF646FtEkN/KkAKPYezxpmvOjxw==} + '@tanstack/virtual-core@3.13.23': + resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==} '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -2956,6 +2877,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} @@ -2971,6 +2895,9 @@ packages: '@types/is-hotkey@0.1.10': resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@24.10.13': resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==} @@ -3008,14 +2935,14 @@ packages: '@vanilla-extract/babel-plugin-debug-ids@1.2.2': resolution: {integrity: sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw==} - '@vanilla-extract/compiler@0.3.4': - resolution: {integrity: sha512-W9HXf9EAccpE1vEIATvSoBVj/bQnmHfYHfDJjUN8dcOHW6oMcnoGTqweDM9I66BHqlNH4d0IsaeZKSViOv7K4w==} + '@vanilla-extract/compiler@0.7.0': + resolution: {integrity: sha512-rZQ40HVmsxfGLjoflwwsaUBLfpbpKDoZC19oiDA0FHq4LdrYtyVbFkc0MfqkNo/qBCvaZfsRezCqk0QQxCqZ8w==} - '@vanilla-extract/css@1.18.0': - resolution: {integrity: sha512-/p0dwOjr0o8gE5BRQ5O9P0u/2DjUd6Zfga2JGmE4KaY7ZITWMszTzk4x4CPlM5cKkRr2ZGzbE6XkuPNfp9shSQ==} + '@vanilla-extract/css@1.20.1': + resolution: {integrity: sha512-5I9RNo5uZW9tsBnqrWzJqELegOqTHBrZyDFnES0gR9gJJHBB9dom1N0bwITM9tKwBcfKrTX4a6DHVeQdJ2ubQA==} - '@vanilla-extract/integration@8.0.7': - resolution: {integrity: sha512-ILob4F9cEHXpbWAVt3Y2iaQJpqYq/c/5TJC8Fz58C2XmX3QW2Y589krvViiyJhQfydCGK3EbwPQhVFjQaBeKfg==} + '@vanilla-extract/integration@8.0.9': + resolution: {integrity: sha512-NP+CSo5IYHDmkMMy5vAxY4R9i2+CAg4sxgvVaxuHiuY9q30i6dNUTujNNKZGW2urEkd4HVVI6NggeIyYjbGPwA==} '@vanilla-extract/private@1.0.9': resolution: {integrity: sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==} @@ -3025,59 +2952,71 @@ packages: peerDependencies: '@vanilla-extract/css': ^1.0.0 - '@vanilla-extract/vite-plugin@5.1.4': - resolution: {integrity: sha512-fTYNKUK3n4ApkUf2FEcO7mpqNKEHf9kDGg8DXlkqHtPxgwPhjuaajmDfQCSBsNgnA2SLI+CB5EO6kLQuKsw2Rw==} + '@vanilla-extract/vite-plugin@5.2.2': + resolution: {integrity: sha512-AUyB4fDR2b/Mo0lcXhhlf6RxnDPYwFMyKKopalJ4BwQNKYzZSoTwHJ1PLPO9SKhpz7lzXc0Z18GHQZOewzl3YA==} peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitejs/plugin-react@5.1.4': - resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true - '@vitest/coverage-v8@4.1.0': - resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==} + '@vitest/coverage-v8@4.1.2': + resolution: {integrity: sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==} peerDependencies: - '@vitest/browser': 4.1.0 - vitest: 4.1.0 + '@vitest/browser': 4.1.2 + vitest: 4.1.2 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.0': - resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} - '@vitest/mocker@4.1.0': - resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.1.0': - resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} - '@vitest/runner@4.1.0': - resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} - '@vitest/snapshot@4.1.0': - resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} - '@vitest/spy@4.1.0': - resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} - '@vitest/ui@4.1.0': - resolution: {integrity: sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==} + '@vitest/ui@4.1.2': + resolution: {integrity: sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.2 - '@vitest/utils@4.1.0': - resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} @@ -3088,6 +3027,9 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -3168,6 +3110,9 @@ packages: badwords-list@2.0.1-4: resolution: {integrity: sha512-FxfZUp7B9yCnesNtFQS9v6PvZdxTYa14Q60JR6vhjdQdWI4naTjJIyx22JzoER8ooeT8SAAKoHLjKfCV7XgYUQ==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -3193,14 +3138,14 @@ packages: bind-event-listener@3.0.0: resolution: {integrity: sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==} - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - blurhash@2.0.5: resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -3265,10 +3210,6 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - cloudflared@0.7.1: - resolution: {integrity: sha512-jJn1Gu9Tf4qnIu8tfiHZ25Hs8rNcRYSVf8zAd97wvYdOCzftm1CTs1S/RPhijjGi8gUT1p9yzfDi9zYlU/0RwA==} - hasBin: true - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -3293,10 +3234,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} @@ -3331,11 +3268,6 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -3355,8 +3287,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -3378,6 +3310,9 @@ packages: babel-plugin-macros: optional: true + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deep-object-diff@1.1.9: resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} @@ -3446,14 +3381,18 @@ packages: electron-to-chromium@1.5.307: resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} - emojibase-data@15.3.2: - resolution: {integrity: sha512-TpDyTDDTdqWIJixV5sTA6OQ0P0JfIIeK2tFRR3q56G9LK65ylAZ7z3KyBXokpvTTJ+mLUXQXbLNyVkjvnTLE+A==} + emojibase-data@17.0.0: + resolution: {integrity: sha512-Yvgb5AWoHViHV/gq1qr5ZAarcBip+B27/ZLRsUJkbgAEaLlZ/fof9g882LTpmEpyhBNEC0m2SEmItljHsTygjA==} peerDependencies: emojibase: '*' emojibase@15.3.1: resolution: {integrity: sha512-GNsjHnG2J3Ktg684Fs/vZR/6XpOSkZPMAv85EHrr6br2RN2cJNwdS4am/3YSK3y+/gOv2kmoK3GGdahXdMxg2g==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -3469,9 +3408,6 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - error-stack-parser-es@1.0.5: - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -3502,8 +3438,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} engines: {node: '>=18'} hasBin: true @@ -3511,6 +3447,53 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-depend@1.5.0: + resolution: {integrity: sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A==} + peerDependencies: + eslint: '>=8.40.0' + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.2.1: + resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@1.0.1: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} @@ -3549,6 +3532,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -3562,7 +3548,7 @@ packages: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} peerDependencies: - picomatch: ^3 || ^4 + picomatch: '>=4.0.4' peerDependenciesMeta: picomatch: optional: true @@ -3570,6 +3556,10 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} @@ -3584,6 +3574,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -3679,6 +3673,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} @@ -3767,10 +3765,10 @@ packages: i18next-http-backend@2.7.3: resolution: {integrity: sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==} - i18next@25.8.17: - resolution: {integrity: sha512-vWtCttyn5bpOK4hWbRAe1ZXkA+Yzcn2OcACT+WJavtfGMcxzkfvXTLMeOU8MUhRmAySKjU4VVuKlo0sSGeBokA==} + i18next@25.10.10: + resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==} peerDependencies: - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: typescript: optional: true @@ -3781,6 +3779,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} @@ -3788,6 +3790,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -3972,8 +3978,14 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jotai@2.18.1: - resolution: {integrity: sha512-e0NOzK+yRFwHo7DOp0DS0Ycq74KMEAObDWFGmfEL28PD9nLqBTt3/Ug7jf9ca72x0gC9LQZG9zH+0ISICmy3iA==} + jotai-family@1.0.1: + resolution: {integrity: sha512-Zb/79GNDhC/z82R+6qTTpeKW4l4H6ZCApfF5W8G4SH37E4mhbysU7r8DkP0KX94hWvjB/6lt/97nSr3wB+64Zg==} + engines: {node: '>=12.20.0'} + peerDependencies: + jotai: '>=2.9.0' + + jotai@2.19.0: + resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==} engines: {node: '>=12.20.0'} peerDependencies: '@babel/core': '>=7.0.0' @@ -4000,8 +4012,8 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsdom@29.0.0: - resolution: {integrity: sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==} + jsdom@29.0.1: + resolution: {integrity: sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -4014,14 +4026,20 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -4039,9 +4057,8 @@ packages: resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} engines: {node: '>=18'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} knip@5.85.0: resolution: {integrity: sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==} @@ -4055,6 +4072,84 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4154,15 +4249,14 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@4.20260310.0: - resolution: {integrity: sha512-uC5vNPenFpDSj5aUU3wGSABG6UUqMr+Xs1m4AkCrTHo37F4Z6xcQw5BXqViTfPDVT/zcYH1UgTVoXhr1l6ZMXw==} - engines: {node: '>=18.0.0'} - hasBin: true - minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4176,6 +4270,9 @@ packages: modern-ahocorasick@1.1.0: resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} + module-replacements@2.11.0: + resolution: {integrity: sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==} + motion-dom@12.35.2: resolution: {integrity: sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg==} @@ -4194,6 +4291,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -4239,6 +4339,10 @@ packages: resolution: {integrity: sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==} engines: {node: '>=18'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -4246,17 +4350,17 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} - oxfmt@0.45.0: - resolution: {integrity: sha512-0o/COoN9fY50bjVeM7PQsNgbhndKurBIeTIcspW033OumksjJJmIVDKjAk5HMwU/GHTxSOdGDdhJ6BRzGPmsHg==} + oxfmt@0.46.0: + resolution: {integrity: sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.21.0: - resolution: {integrity: sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg==} + oxlint-tsgolint@0.21.1: + resolution: {integrity: sha512-O2hxiT14C2HJkwzBU6CQBFPoagSd/IcV+Tt3e3UUaXFwbW4BO5DSDPSSboc3UM5MIDY+MLyepvtQwBQafNxWdw==} hasBin: true - oxlint@1.60.0: - resolution: {integrity: sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw==} + oxlint@1.61.0: + resolution: {integrity: sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4310,9 +4414,6 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4327,16 +4428,8 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pkg-types@1.3.1: @@ -4350,6 +4443,10 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -4415,14 +4512,14 @@ packages: peerDependencies: react: '>=16.4.1' - react-i18next@16.5.7: - resolution: {integrity: sha512-t/si6ng+hMPvgRGNgGvHAkMuVRBsIBx5mN+exm/yiBPSFL7VooQ37YYfISxSE0LjvQjG+MTe+0htKdOJY0S/vw==} + react-i18next@16.6.6: + resolution: {integrity: sha512-ZgL2HUoW34UKUkOV7uSQFE1CDnRPD+tCR3ywSuWH7u2iapnz86U8Bi3Vrs620qNDzCf1F47NxglCEkchCTDOHw==} peerDependencies: - i18next: '>= 25.6.2' + i18next: '>= 25.10.9' react: '>= 16.8.0' react-dom: '*' react-native: '*' - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: react-dom: optional: true @@ -4446,10 +4543,6 @@ packages: react: '*' react-dom: '*' - react-refresh@0.18.0: - resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} - engines: {node: '>=0.10.0'} - react-router-dom@6.30.3: resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} engines: {node: '>=14.0.0'} @@ -4521,8 +4614,18 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@2.80.0: + resolution: {integrity: sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + rollup@4.60.0: + resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4580,10 +4683,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4644,8 +4743,8 @@ packages: resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} engines: {node: '>=20.0.0'} - smol-toml@1.6.0: - resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} snake-case@3.0.4: @@ -4719,10 +4818,6 @@ packages: style-to-object@1.0.3: resolution: {integrity: sha512-xOpx7S53E0V3DpVsvt7ySvoiumRpfXiC99PUXLqGB3wiAnN9ybEIpuzlZ8LAZg+h1sl9JkEUwtSQXxcCgFqbbg==} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4810,6 +4905,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -4849,13 +4948,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@7.24.3: - resolution: {integrity: sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==} + undici@7.24.6: + resolution: {integrity: sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==} engines: {node: '>=20.18.1'} - unenv@2.0.0-rc.24: - resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - unhomoglyph@1.0.6: resolution: {integrity: sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==} @@ -4893,6 +4989,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -4931,8 +5030,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-plugin-compression2@2.5.0: - resolution: {integrity: sha512-bHxtBibPxxSn5eZSe0IAzvYucP/hg8Bz8ppjbH7lndU5kIHT+92qTkB4z9xWYfnyV0YHuir1SjOuyO0fzU4Vgg==} + vite-plugin-compression2@2.5.3: + resolution: {integrity: sha512-ItPgqQWkcnBbVw7is9OKwiZ8v6+ju9rYROl5Lp6QfQDEx/d55AwJQb/KLpsQqsU9HoigYBsZ8tK6I02UwJNvEw==} vite-plugin-pwa@1.2.0: resolution: {integrity: sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==} @@ -4946,11 +5045,11 @@ packages: '@vite-pwa/assets-generator': optional: true - vite-plugin-static-copy@3.2.0: - resolution: {integrity: sha512-g2k9z8B/1Bx7D4wnFjPLx9dyYGrqWMLTpwTtPHhcU+ElNZP2O4+4OsyaficiDClus0dzVhdGvoGFYMJxoXZ12Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-plugin-static-copy@4.0.0: + resolution: {integrity: sha512-TTf6cVTV4M2pH2Wfr3zhevdRsIQezfm2ltDkSfkjqvvdryJHYQyNoPISvuytX3r9jFZV0yVeMYyGTsAvAH2XLw==} + engines: {node: ^22.0.0 || >=24.0.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 vite-plugin-svgr@4.5.0: resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} @@ -4977,7 +5076,7 @@ packages: sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 - yaml: ^2.4.2 + yaml: '>=2.8.3' peerDependenciesMeta: '@types/node': optional: true @@ -5002,21 +5101,64 @@ packages: yaml: optional: true - vitest@4.1.0: - resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} + vite@8.0.3: + resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: '>=2.8.3' + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.0 - '@vitest/browser-preview': 4.1.0 - '@vitest/browser-webdriverio': 4.1.0 - '@vitest/ui': 4.1.0 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -5099,6 +5241,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + workbox-background-sync@7.4.0: resolution: {integrity: sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==} @@ -5148,33 +5294,6 @@ packages: workbox-window@7.4.0: resolution: {integrity: sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==} - workerd@1.20260310.1: - resolution: {integrity: sha512-yawXhypXXHtArikJj15HOMknNGikpBbSg2ZDe6lddUbqZnJXuCVSkgc/0ArUeVMG1jbbGvpst+REFtKwILvRTQ==} - engines: {node: '>=16'} - hasBin: true - - wrangler@4.72.0: - resolution: {integrity: sha512-bKkb8150JGzJZJWiNB2nu/33smVfawmfYiecA6rW4XH7xS23/jqMbgpdelM34W/7a1IhR66qeQGVqTRXROtAZg==} - engines: {node: '>=20.0.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260310.1 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -5185,21 +5304,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - youch-core@0.3.3: - resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} - - youch@4.1.0-beta.10: - resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -5207,10 +5315,9 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)': + '@apideck/better-ajv-errors@0.3.7(ajv@8.18.0)': dependencies: ajv: 8.18.0 - json-schema: 0.4.0 jsonpointer: 5.0.1 leven: 3.1.0 @@ -5246,7 +5353,7 @@ snapshots: '@atlaskit/pragmatic-drag-and-drop@1.7.9': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 bind-event-listener: 3.0.0 raf-schd: 4.0.3 @@ -5408,6 +5515,11 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + optional: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -5734,16 +5846,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -5927,46 +6029,6 @@ snapshots: dependencies: css-tree: 3.2.1 - '@cloudflare/kv-asset-handler@0.4.2': {} - - '@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260310.1)': - dependencies: - unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260310.1 - - '@cloudflare/vite-plugin@1.27.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(workerd@1.20260310.1)(wrangler@4.72.0)': - dependencies: - '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260310.1) - miniflare: 4.20260310.0 - unenv: 2.0.0-rc.24 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) - wrangler: 4.72.0 - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - workerd - - '@cloudflare/workerd-darwin-64@1.20260310.1': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20260310.1': - optional: true - - '@cloudflare/workerd-linux-64@1.20260310.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260310.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20260310.1': - optional: true - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@6.0.2': {} '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -5991,6 +6053,13 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} + '@e18e/eslint-plugin@0.3.0(eslint@10.2.1(jiti@2.6.1))(oxlint@1.61.0(oxlint-tsgolint@0.21.1))': + dependencies: + eslint-plugin-depend: 1.5.0(eslint@10.2.1(jiti@2.6.1)) + optionalDependencies: + eslint: 10.2.1(jiti@2.6.1) + oxlint: 1.61.0(oxlint-tsgolint@0.21.1) + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -6009,215 +6078,161 @@ snapshots: '@emotion/hash@0.9.2': {} - '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.27.3)': - dependencies: - esbuild: 0.27.3 - - '@esbuild/aix-ppc64@0.27.3': + '@esbuild/aix-ppc64@0.27.4': optional: true - '@esbuild/android-arm64@0.27.3': + '@esbuild/android-arm64@0.27.4': optional: true - '@esbuild/android-arm@0.27.3': + '@esbuild/android-arm@0.27.4': optional: true - '@esbuild/android-x64@0.27.3': + '@esbuild/android-x64@0.27.4': optional: true - '@esbuild/darwin-arm64@0.27.3': + '@esbuild/darwin-arm64@0.27.4': optional: true - '@esbuild/darwin-x64@0.27.3': + '@esbuild/darwin-x64@0.27.4': optional: true - '@esbuild/freebsd-arm64@0.27.3': + '@esbuild/freebsd-arm64@0.27.4': optional: true - '@esbuild/freebsd-x64@0.27.3': + '@esbuild/freebsd-x64@0.27.4': optional: true - '@esbuild/linux-arm64@0.27.3': + '@esbuild/linux-arm64@0.27.4': optional: true - '@esbuild/linux-arm@0.27.3': + '@esbuild/linux-arm@0.27.4': optional: true - '@esbuild/linux-ia32@0.27.3': + '@esbuild/linux-ia32@0.27.4': optional: true - '@esbuild/linux-loong64@0.27.3': + '@esbuild/linux-loong64@0.27.4': optional: true - '@esbuild/linux-mips64el@0.27.3': + '@esbuild/linux-mips64el@0.27.4': optional: true - '@esbuild/linux-ppc64@0.27.3': + '@esbuild/linux-ppc64@0.27.4': optional: true - '@esbuild/linux-riscv64@0.27.3': + '@esbuild/linux-riscv64@0.27.4': optional: true - '@esbuild/linux-s390x@0.27.3': + '@esbuild/linux-s390x@0.27.4': optional: true - '@esbuild/linux-x64@0.27.3': + '@esbuild/linux-x64@0.27.4': optional: true - '@esbuild/netbsd-arm64@0.27.3': + '@esbuild/netbsd-arm64@0.27.4': optional: true - '@esbuild/netbsd-x64@0.27.3': + '@esbuild/netbsd-x64@0.27.4': optional: true - '@esbuild/openbsd-arm64@0.27.3': + '@esbuild/openbsd-arm64@0.27.4': optional: true - '@esbuild/openbsd-x64@0.27.3': + '@esbuild/openbsd-x64@0.27.4': optional: true - '@esbuild/openharmony-arm64@0.27.3': + '@esbuild/openharmony-arm64@0.27.4': optional: true - '@esbuild/sunos-x64@0.27.3': + '@esbuild/sunos-x64@0.27.4': optional: true - '@esbuild/win32-arm64@0.27.3': + '@esbuild/win32-arm64@0.27.4': optional: true - '@esbuild/win32-ia32@0.27.3': + '@esbuild/win32-ia32@0.27.4': optional: true - '@esbuild/win32-x64@0.27.3': + '@esbuild/win32-x64@0.27.4': optional: true - '@exodus/bytes@1.15.0': {} - - '@fontsource-variable/nunito@5.2.7': {} - - '@fontsource/space-mono@5.2.9': {} - - '@formatjs/ecma402-abstract@2.3.6': + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': dependencies: - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.2 - decimal.js: 10.6.0 - tslib: 2.8.1 + eslint: 10.2.1(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 - '@formatjs/fast-memoize@2.2.7': - dependencies: - tslib: 2.8.1 + '@eslint-community/regexpp@4.12.2': {} - '@formatjs/icu-messageformat-parser@2.11.4': + '@eslint/config-array@0.23.5': dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/icu-skeleton-parser': 1.8.16 - tslib: 2.8.1 + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color - '@formatjs/icu-skeleton-parser@1.8.16': + '@eslint/config-helpers@0.5.5': dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - tslib: 2.8.1 + '@eslint/core': 1.2.1 - '@formatjs/intl-localematcher@0.6.2': + '@eslint/core@1.2.1': dependencies: - tslib: 2.8.1 + '@types/json-schema': 7.0.15 - '@img/colour@1.1.0': {} + '@eslint/object-schema@3.0.5': {} - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true + '@eslint/plugin-kit@0.7.1': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true + '@exodus/bytes@1.15.0': {} - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true + '@fontsource-variable/nunito@5.2.7': {} - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true + '@fontsource/space-mono@5.2.9': {} - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true + '@formatjs/ecma402-abstract@2.3.6': + dependencies: + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/intl-localematcher': 0.6.2 + decimal.js: 10.6.0 + tslib: 2.8.1 - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true + '@formatjs/fast-memoize@2.2.7': + dependencies: + tslib: 2.8.1 - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true + '@formatjs/icu-messageformat-parser@2.11.4': + dependencies: + '@formatjs/ecma402-abstract': 2.3.6 + '@formatjs/icu-skeleton-parser': 1.8.16 + tslib: 2.8.1 - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true + '@formatjs/icu-skeleton-parser@1.8.16': + dependencies: + '@formatjs/ecma402-abstract': 2.3.6 + tslib: 2.8.1 - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true + '@formatjs/intl-localematcher@0.6.2': + dependencies: + tslib: 2.8.1 - '@img/sharp-wasm32@0.34.5': + '@humanfs/core@0.19.2': dependencies: - '@emnapi/runtime': 1.8.1 - optional: true + '@humanfs/types': 0.15.0 - '@img/sharp-win32-arm64@0.34.5': - optional: true + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 - '@img/sharp-win32-ia32@0.34.5': - optional: true + '@humanfs/types@0.15.0': {} - '@img/sharp-win32-x64@0.34.5': - optional: true + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} '@internationalized/date@3.12.0': dependencies: @@ -6262,11 +6277,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@juggle/resize-observer@3.4.0': {} '@matrix-org/matrix-sdk-crypto-wasm@15.3.0': {} @@ -6338,6 +6348,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-project/types@0.122.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -6400,136 +6412,136 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true - '@oxfmt/binding-android-arm-eabi@0.45.0': + '@oxfmt/binding-android-arm-eabi@0.46.0': optional: true - '@oxfmt/binding-android-arm64@0.45.0': + '@oxfmt/binding-android-arm64@0.46.0': optional: true - '@oxfmt/binding-darwin-arm64@0.45.0': + '@oxfmt/binding-darwin-arm64@0.46.0': optional: true - '@oxfmt/binding-darwin-x64@0.45.0': + '@oxfmt/binding-darwin-x64@0.46.0': optional: true - '@oxfmt/binding-freebsd-x64@0.45.0': + '@oxfmt/binding-freebsd-x64@0.46.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.45.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.45.0': + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.45.0': + '@oxfmt/binding-linux-arm64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.45.0': + '@oxfmt/binding-linux-arm64-musl@0.46.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.45.0': + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.45.0': + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.45.0': + '@oxfmt/binding-linux-riscv64-musl@0.46.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.45.0': + '@oxfmt/binding-linux-s390x-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.45.0': + '@oxfmt/binding-linux-x64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.45.0': + '@oxfmt/binding-linux-x64-musl@0.46.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.45.0': + '@oxfmt/binding-openharmony-arm64@0.46.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.45.0': + '@oxfmt/binding-win32-arm64-msvc@0.46.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.45.0': + '@oxfmt/binding-win32-ia32-msvc@0.46.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.45.0': + '@oxfmt/binding-win32-x64-msvc@0.46.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.21.0': + '@oxlint-tsgolint/darwin-arm64@0.21.1': optional: true - '@oxlint-tsgolint/darwin-x64@0.21.0': + '@oxlint-tsgolint/darwin-x64@0.21.1': optional: true - '@oxlint-tsgolint/linux-arm64@0.21.0': + '@oxlint-tsgolint/linux-arm64@0.21.1': optional: true - '@oxlint-tsgolint/linux-x64@0.21.0': + '@oxlint-tsgolint/linux-x64@0.21.1': optional: true - '@oxlint-tsgolint/win32-arm64@0.21.0': + '@oxlint-tsgolint/win32-arm64@0.21.1': optional: true - '@oxlint-tsgolint/win32-x64@0.21.0': + '@oxlint-tsgolint/win32-x64@0.21.1': optional: true - '@oxlint/binding-android-arm-eabi@1.60.0': + '@oxlint/binding-android-arm-eabi@1.61.0': optional: true - '@oxlint/binding-android-arm64@1.60.0': + '@oxlint/binding-android-arm64@1.61.0': optional: true - '@oxlint/binding-darwin-arm64@1.60.0': + '@oxlint/binding-darwin-arm64@1.61.0': optional: true - '@oxlint/binding-darwin-x64@1.60.0': + '@oxlint/binding-darwin-x64@1.61.0': optional: true - '@oxlint/binding-freebsd-x64@1.60.0': + '@oxlint/binding-freebsd-x64@1.61.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.60.0': + '@oxlint/binding-linux-arm-gnueabihf@1.61.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.60.0': + '@oxlint/binding-linux-arm-musleabihf@1.61.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.60.0': + '@oxlint/binding-linux-arm64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.60.0': + '@oxlint/binding-linux-arm64-musl@1.61.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.60.0': + '@oxlint/binding-linux-ppc64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.60.0': + '@oxlint/binding-linux-riscv64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.60.0': + '@oxlint/binding-linux-riscv64-musl@1.61.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.60.0': + '@oxlint/binding-linux-s390x-gnu@1.61.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.60.0': + '@oxlint/binding-linux-x64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-x64-musl@1.60.0': + '@oxlint/binding-linux-x64-musl@1.61.0': optional: true - '@oxlint/binding-openharmony-arm64@1.60.0': + '@oxlint/binding-openharmony-arm64@1.61.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.60.0': + '@oxlint/binding-win32-arm64-msvc@1.61.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.60.0': + '@oxlint/binding-win32-ia32-msvc@1.61.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.60.0': + '@oxlint/binding-win32-x64-msvc@1.61.0': optional: true '@phosphor-icons/react@2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -6539,18 +6551,6 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@poppinss/colors@4.1.6': - dependencies: - kleur: 4.1.5 - - '@poppinss/dumper@0.6.5': - dependencies: - '@poppinss/colors': 4.1.6 - '@sindresorhus/is': 7.2.0 - supports-color: 10.2.2 - - '@poppinss/exception@1.2.3': {} - '@react-aria/breadcrumbs@3.5.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@react-aria/i18n': 3.12.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7514,180 +7514,239 @@ snapshots: '@remix-run/router@1.23.2': {} - '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rolldown/binding-android-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.12': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} - '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@4.59.0)': + '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.80.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@rollup/pluginutils': 3.1.0(rollup@4.59.0) - rollup: 4.59.0 + '@rollup/pluginutils': 3.1.0(rollup@2.80.0) + rollup: 2.80.0 optionalDependencies: '@types/babel__core': 7.20.5 transitivePeerDependencies: - supports-color - '@rollup/plugin-inject@5.0.5(rollup@4.59.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.60.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 4.59.0 + rollup: 4.60.0 - '@rollup/plugin-node-resolve@15.3.1(rollup@4.59.0)': + '@rollup/plugin-node-resolve@15.3.1(rollup@2.80.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@2.80.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 4.59.0 + rollup: 2.80.0 - '@rollup/plugin-replace@2.4.2(rollup@4.59.0)': + '@rollup/plugin-replace@2.4.2(rollup@2.80.0)': dependencies: - '@rollup/pluginutils': 3.1.0(rollup@4.59.0) + '@rollup/pluginutils': 3.1.0(rollup@2.80.0) magic-string: 0.25.9 - rollup: 4.59.0 + rollup: 2.80.0 - '@rollup/plugin-terser@0.4.4(rollup@4.59.0)': + '@rollup/plugin-terser@0.4.4(rollup@2.80.0)': dependencies: serialize-javascript: 7.0.5 smob: 1.6.1 terser: 5.46.1 optionalDependencies: - rollup: 4.59.0 + rollup: 2.80.0 - '@rollup/plugin-virtual@3.0.2(rollup@4.59.0)': + '@rollup/plugin-virtual@3.0.2(rollup@4.60.0)': optionalDependencies: - rollup: 4.59.0 + rollup: 4.60.0 - '@rollup/plugin-wasm@6.2.2(rollup@4.59.0)': + '@rollup/plugin-wasm@6.2.2(rollup@4.60.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) optionalDependencies: - rollup: 4.59.0 + rollup: 4.60.0 - '@rollup/pluginutils@3.1.0(rollup@4.59.0)': + '@rollup/pluginutils@3.1.0(rollup@2.80.0)': dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 - picomatch: 2.3.2 - rollup: 4.59.0 + picomatch: 4.0.4 + rollup: 2.80.0 + + '@rollup/pluginutils@5.3.0(rollup@2.80.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 2.80.0 - '@rollup/pluginutils@5.3.0(rollup@4.59.0)': + '@rollup/pluginutils@5.3.0(rollup@4.60.0)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.3 + picomatch: 4.0.4 optionalDependencies: - rollup: 4.59.0 + rollup: 4.60.0 - '@rollup/rollup-android-arm-eabi@4.59.0': + '@rollup/rollup-android-arm-eabi@4.60.0': optional: true - '@rollup/rollup-android-arm64@4.59.0': + '@rollup/rollup-android-arm64@4.60.0': optional: true - '@rollup/rollup-darwin-arm64@4.59.0': + '@rollup/rollup-darwin-arm64@4.60.0': optional: true - '@rollup/rollup-darwin-x64@4.59.0': + '@rollup/rollup-darwin-x64@4.60.0': optional: true - '@rollup/rollup-freebsd-arm64@4.59.0': + '@rollup/rollup-freebsd-arm64@4.60.0': optional: true - '@rollup/rollup-freebsd-x64@4.59.0': + '@rollup/rollup-freebsd-x64@4.60.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.59.0': + '@rollup/rollup-linux-arm-musleabihf@4.60.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.59.0': + '@rollup/rollup-linux-arm64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.59.0': + '@rollup/rollup-linux-arm64-musl@4.60.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.59.0': + '@rollup/rollup-linux-loong64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.59.0': + '@rollup/rollup-linux-loong64-musl@4.60.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.59.0': + '@rollup/rollup-linux-ppc64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.59.0': + '@rollup/rollup-linux-ppc64-musl@4.60.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.59.0': + '@rollup/rollup-linux-riscv64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.59.0': + '@rollup/rollup-linux-riscv64-musl@4.60.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.59.0': + '@rollup/rollup-linux-s390x-gnu@4.60.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.59.0': + '@rollup/rollup-linux-x64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-x64-musl@4.59.0': + '@rollup/rollup-linux-x64-musl@4.60.0': optional: true - '@rollup/rollup-openbsd-x64@4.59.0': + '@rollup/rollup-openbsd-x64@4.60.0': optional: true - '@rollup/rollup-openharmony-arm64@4.59.0': + '@rollup/rollup-openharmony-arm64@4.60.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.59.0': + '@rollup/rollup-win32-arm64-msvc@4.60.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.59.0': + '@rollup/rollup-win32-ia32-msvc@4.60.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.59.0': + '@rollup/rollup-win32-x64-gnu@4.60.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.59.0': + '@rollup/rollup-win32-x64-msvc@4.60.0': optional: true '@sableclient/sable-call-embedded@1.1.4': {} - '@sentry-internal/browser-utils@10.43.0': + '@sableclient/twemoji-font@1.0.2': {} + + '@sentry-internal/browser-utils@10.46.0': dependencies: - '@sentry/core': 10.43.0 + '@sentry/core': 10.46.0 - '@sentry-internal/feedback@10.43.0': + '@sentry-internal/feedback@10.46.0': dependencies: - '@sentry/core': 10.43.0 + '@sentry/core': 10.46.0 - '@sentry-internal/replay-canvas@10.43.0': + '@sentry-internal/replay-canvas@10.46.0': dependencies: - '@sentry-internal/replay': 10.43.0 - '@sentry/core': 10.43.0 + '@sentry-internal/replay': 10.46.0 + '@sentry/core': 10.46.0 - '@sentry-internal/replay@10.43.0': + '@sentry-internal/replay@10.46.0': dependencies: - '@sentry-internal/browser-utils': 10.43.0 - '@sentry/core': 10.43.0 + '@sentry-internal/browser-utils': 10.46.0 + '@sentry/core': 10.46.0 '@sentry/babel-plugin-component-annotate@5.1.1': {} - '@sentry/browser@10.43.0': + '@sentry/browser@10.46.0': dependencies: - '@sentry-internal/browser-utils': 10.43.0 - '@sentry-internal/feedback': 10.43.0 - '@sentry-internal/replay': 10.43.0 - '@sentry-internal/replay-canvas': 10.43.0 - '@sentry/core': 10.43.0 + '@sentry-internal/browser-utils': 10.46.0 + '@sentry-internal/feedback': 10.46.0 + '@sentry-internal/replay': 10.46.0 + '@sentry-internal/replay-canvas': 10.46.0 + '@sentry/core': 10.46.0 '@sentry/bundler-plugin-core@5.1.1': dependencies: @@ -7746,36 +7805,32 @@ snapshots: - encoding - supports-color - '@sentry/core@10.43.0': {} + '@sentry/core@10.46.0': {} - '@sentry/react@10.43.0(react@18.3.1)': + '@sentry/react@10.46.0(react@18.3.1)': dependencies: - '@sentry/browser': 10.43.0 - '@sentry/core': 10.43.0 + '@sentry/browser': 10.46.0 + '@sentry/core': 10.46.0 react: 18.3.1 - '@sentry/rollup-plugin@5.1.1(rollup@4.59.0)': + '@sentry/rollup-plugin@5.1.1(rollup@4.60.0)': dependencies: '@sentry/bundler-plugin-core': 5.1.1 magic-string: 0.30.21 - rollup: 4.59.0 + rollup: 4.60.0 transitivePeerDependencies: - encoding - supports-color - '@sentry/vite-plugin@5.1.1(rollup@4.59.0)': + '@sentry/vite-plugin@5.1.1(rollup@4.60.0)': dependencies: '@sentry/bundler-plugin-core': 5.1.1 - '@sentry/rollup-plugin': 5.1.1(rollup@4.59.0) + '@sentry/rollup-plugin': 5.1.1(rollup@4.60.0) transitivePeerDependencies: - encoding - rollup - supports-color - '@sindresorhus/is@7.2.0': {} - - '@speed-highlight/core@1.2.14': {} - '@standard-schema/spec@1.1.0': {} '@surma/rollup-plugin-off-main-thread@2.2.3': @@ -7914,28 +7969,28 @@ snapshots: '@swc/wasm@1.15.18': {} - '@tanstack/query-core@5.90.20': {} + '@tanstack/query-core@5.95.2': {} - '@tanstack/query-devtools@5.93.0': {} + '@tanstack/query-devtools@5.95.2': {} - '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.90.21(react@18.3.1))(react@18.3.1)': + '@tanstack/react-query-devtools@5.95.2(@tanstack/react-query@5.95.2(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/query-devtools': 5.93.0 - '@tanstack/react-query': 5.90.21(react@18.3.1) + '@tanstack/query-devtools': 5.95.2 + '@tanstack/react-query': 5.95.2(react@18.3.1) react: 18.3.1 - '@tanstack/react-query@5.90.21(react@18.3.1)': + '@tanstack/react-query@5.95.2(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.90.20 + '@tanstack/query-core': 5.95.2 react: 18.3.1 - '@tanstack/react-virtual@3.13.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.13.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/virtual-core': 3.13.21 + '@tanstack/virtual-core': 3.13.23 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@tanstack/virtual-core@3.13.21': {} + '@tanstack/virtual-core@3.13.23': {} '@testing-library/dom@10.4.1': dependencies: @@ -7980,24 +8035,28 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 + optional: true '@types/babel__generator@7.27.0': dependencies: '@babel/types': 7.29.0 + optional: true '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 + optional: true '@types/babel__traverse@7.28.0': dependencies: '@babel/types': 7.29.0 + optional: true '@types/chai@5.2.3': dependencies: @@ -8008,6 +8067,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@0.0.39': {} '@types/estree@1.0.8': {} @@ -8018,6 +8079,8 @@ snapshots: '@types/is-hotkey@0.1.10': {} + '@types/json-schema@7.0.15': {} + '@types/node@24.10.13': dependencies: undici-types: 7.16.0 @@ -8056,15 +8119,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@vanilla-extract/compiler@0.3.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)': + '@vanilla-extract/compiler@0.7.0(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)': dependencies: - '@vanilla-extract/css': 1.18.0 - '@vanilla-extract/integration': 8.0.7 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/integration': 8.0.9 + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) + vite-node: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1) transitivePeerDependencies: - '@types/node' + - '@vitejs/devtools' - babel-plugin-macros + - esbuild - jiti - less - lightningcss @@ -8077,12 +8142,11 @@ snapshots: - tsx - yaml - '@vanilla-extract/css@1.18.0': + '@vanilla-extract/css@1.20.1': dependencies: '@emotion/hash': 0.9.2 '@vanilla-extract/private': 1.0.9 css-what: 6.2.2 - cssesc: 3.0.0 csstype: 3.2.3 dedent: 1.7.2 deep-object-diff: 1.1.9 @@ -8094,14 +8158,14 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@vanilla-extract/integration@8.0.7': + '@vanilla-extract/integration@8.0.9': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@vanilla-extract/babel-plugin-debug-ids': 1.2.2 - '@vanilla-extract/css': 1.18.0 + '@vanilla-extract/css': 1.20.1 dedent: 1.7.2 - esbuild: 0.27.3 + esbuild: 0.27.4 eval: 0.1.8 find-up: 5.0.0 javascript-stringify: 2.1.0 @@ -8112,18 +8176,20 @@ snapshots: '@vanilla-extract/private@1.0.9': {} - '@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.18.0)': + '@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.20.1)': dependencies: - '@vanilla-extract/css': 1.18.0 + '@vanilla-extract/css': 1.20.1 - '@vanilla-extract/vite-plugin@5.1.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(yaml@2.8.2)': + '@vanilla-extract/vite-plugin@5.2.2(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1))': dependencies: - '@vanilla-extract/compiler': 0.3.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) - '@vanilla-extract/integration': 8.0.7 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + '@vanilla-extract/compiler': 0.7.0(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1) + '@vanilla-extract/integration': 8.0.9 + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) transitivePeerDependencies: - '@types/node' + - '@vitejs/devtools' - babel-plugin-macros + - esbuild - jiti - less - lightningcss @@ -8136,22 +8202,15 @@ snapshots: - tsx - yaml - '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.1(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) - '@rolldown/pluginutils': 1.0.0-rc.3 - '@types/babel__core': 7.20.5 - react-refresh: 0.18.0 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) - '@vitest/coverage-v8@4.1.0(vitest@4.1.0)': + '@vitest/coverage-v8@4.1.2(vitest@4.1.2)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -8160,60 +8219,64 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.0(@types/node@24.10.13)(@vitest/ui@4.1.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + vitest: 4.1.2(@types/node@24.10.13)(@vitest/ui@4.1.2)(jsdom@29.0.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) - '@vitest/expect@4.1.0': + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))': + '@vitest/mocker@4.1.2(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1))': dependencies: - '@vitest/spy': 4.1.0 + '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) - '@vitest/pretty-format@4.1.0': + '@vitest/pretty-format@4.1.2': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.0': + '@vitest/runner@4.1.2': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 pathe: 2.0.3 - '@vitest/snapshot@4.1.0': + '@vitest/snapshot@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.0': {} + '@vitest/spy@4.1.2': {} - '@vitest/ui@4.1.0(vitest@4.1.0)': + '@vitest/ui@4.1.2(vitest@4.1.2)': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 fflate: 0.8.2 flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.0(@types/node@24.10.13)(@vitest/ui@4.1.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) + vitest: 4.1.2(@types/node@24.10.13)(@vitest/ui@4.1.2)(jsdom@29.0.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) - '@vitest/utils@4.1.0': + '@vitest/utils@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.0 + '@vitest/pretty-format': 4.1.2 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn@8.16.0: {} agent-base@6.0.2: @@ -8222,6 +8285,13 @@ snapshots: transitivePeerDependencies: - supports-color + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -8238,7 +8308,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 4.0.4 argparse@2.0.1: {} @@ -8309,6 +8379,8 @@ snapshots: badwords-list@2.0.1-4: {} + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} base-x@5.0.1: {} @@ -8325,11 +8397,13 @@ snapshots: bind-event-listener@3.0.0: {} - blake3-wasm@2.1.5: {} - blurhash@2.0.5: {} - brace-expansion@5.0.4: + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -8401,8 +8475,6 @@ snapshots: classnames@2.5.1: {} - cloudflared@0.7.1: {} - clsx@2.1.1: {} commander@2.20.3: {} @@ -8417,8 +8489,6 @@ snapshots: convert-source-map@2.0.0: {} - cookie@1.1.1: {} - core-js-compat@3.49.0: dependencies: browserslist: 4.28.1 @@ -8455,8 +8525,6 @@ snapshots: css.escape@1.5.1: {} - cssesc@3.0.0: {} - csstype@3.2.3: {} data-urls@7.0.0: @@ -8484,7 +8552,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dayjs@1.11.19: {} + dayjs@1.11.20: {} debug@4.4.3: dependencies: @@ -8494,6 +8562,8 @@ snapshots: dedent@1.7.2: {} + deep-is@0.1.4: {} + deep-object-diff@1.1.9: {} deepmerge@4.3.1: {} @@ -8561,12 +8631,14 @@ snapshots: electron-to-chromium@1.5.307: {} - emojibase-data@15.3.2(emojibase@15.3.1): + emojibase-data@17.0.0(emojibase@15.3.1): dependencies: emojibase: 15.3.1 emojibase@15.3.1: {} + empathic@2.0.0: {} + entities@4.5.0: {} entities@6.0.1: {} @@ -8577,8 +8649,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - error-stack-parser-es@1.0.5: {} - es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -8661,37 +8731,110 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.3: + esbuild@0.27.4: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + + eslint-plugin-depend@1.5.0(eslint@10.2.1(jiti@2.6.1)): + dependencies: + empathic: 2.0.0 + eslint: 10.2.1(jiti@2.6.1) + module-replacements: 2.11.0 + semver: 7.7.4 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.2.1(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@1.0.1: {} estree-walker@2.0.2: {} @@ -8725,6 +8868,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -8735,17 +8880,21 @@ snapshots: dependencies: walk-up-path: 4.0.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 fflate@0.8.2: {} + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-saver@2.0.5: {} filelist@1.0.6: dependencies: - minimatch: 10.2.4 + minimatch: 5.1.9 fill-range@7.1.1: dependencies: @@ -8756,6 +8905,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + flatted@3.4.2: {} focus-trap-react@10.3.1(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -8770,10 +8924,10 @@ snapshots: dependencies: tabbable: 6.4.0 - folds@2.6.2(@vanilla-extract/css@1.18.0)(@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.18.0))(classnames@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + folds@2.6.2(@vanilla-extract/css@1.20.1)(@vanilla-extract/recipes@0.5.7(@vanilla-extract/css@1.20.1))(classnames@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@vanilla-extract/css': 1.18.0 - '@vanilla-extract/recipes': 0.5.7(@vanilla-extract/css@1.18.0) + '@vanilla-extract/css': 1.20.1 + '@vanilla-extract/recipes': 0.5.7(@vanilla-extract/css@1.20.1) classnames: 2.5.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -8857,6 +9011,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@11.1.0: dependencies: foreground-child: 3.3.1 @@ -8968,9 +9126,9 @@ snapshots: transitivePeerDependencies: - encoding - i18next@25.8.17(typescript@5.9.3): + i18next@25.10.10(typescript@5.9.3): dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 optionalDependencies: typescript: 5.9.3 @@ -8978,6 +9136,8 @@ snapshots: ieee754@1.2.1: {} + ignore@5.3.2: {} + immer@9.0.21: {} import-fresh@3.3.1: @@ -8985,6 +9145,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} inline-style-parser@0.2.2: {} @@ -9163,7 +9325,11 @@ snapshots: jiti@2.6.1: {} - jotai@2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1): + jotai-family@1.0.1(jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1)): + dependencies: + jotai: 2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1) + + jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1): optionalDependencies: '@babel/core': 7.29.0 '@babel/template': 7.28.6 @@ -9178,7 +9344,7 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@29.0.0: + jsdom@29.0.1: dependencies: '@asamuzakjp/css-color': 5.0.1 '@asamuzakjp/dom-selector': 7.0.3 @@ -9195,7 +9361,7 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.1 - undici: 7.24.3 + undici: 7.24.6 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -9206,11 +9372,15 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} - json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -9224,7 +9394,9 @@ snapshots: jwt-decode@4.0.0: {} - kleur@4.1.5: {} + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 knip@5.85.0(@types/node@24.10.13)(typescript@5.9.3): dependencies: @@ -9237,14 +9409,68 @@ snapshots: minimist: 1.2.8 oxc-resolver: 11.19.1 picocolors: 1.1.1 - picomatch: 4.0.3 - smol-toml: 1.6.0 + picomatch: 4.0.4 + smol-toml: 1.6.1 strip-json-comments: 5.0.3 typescript: 5.9.3 zod: 4.3.6 leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} linkify-react@4.3.2(linkifyjs@4.3.2)(react@18.3.1): @@ -9334,32 +9560,24 @@ snapshots: media-query-parser@2.0.2: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 merge2@1.4.1: {} micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 4.0.4 min-indent@1.0.1: {} - miniflare@4.20260310.0: + minimatch@10.2.4: dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.24.3 - workerd: 1.20260310.1 - ws: 8.18.0 - youch: 4.1.0-beta.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate + brace-expansion: 5.0.5 - minimatch@10.2.4: + minimatch@5.1.9: dependencies: - brace-expansion: 5.0.4 + brace-expansion: 2.0.3 minimist@1.2.8: {} @@ -9374,6 +9592,8 @@ snapshots: modern-ahocorasick@1.1.0: {} + module-replacements@2.11.0: {} + motion-dom@12.35.2: dependencies: motion-utils: 12.29.2 @@ -9386,6 +9606,8 @@ snapshots: nanoid@3.3.11: {} + natural-compare@1.4.0: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -9423,6 +9645,15 @@ snapshots: dependencies: jwt-decode: 4.0.0 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -9452,61 +9683,61 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 - oxfmt@0.45.0: + oxfmt@0.46.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.45.0 - '@oxfmt/binding-android-arm64': 0.45.0 - '@oxfmt/binding-darwin-arm64': 0.45.0 - '@oxfmt/binding-darwin-x64': 0.45.0 - '@oxfmt/binding-freebsd-x64': 0.45.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.45.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.45.0 - '@oxfmt/binding-linux-arm64-gnu': 0.45.0 - '@oxfmt/binding-linux-arm64-musl': 0.45.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.45.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.45.0 - '@oxfmt/binding-linux-riscv64-musl': 0.45.0 - '@oxfmt/binding-linux-s390x-gnu': 0.45.0 - '@oxfmt/binding-linux-x64-gnu': 0.45.0 - '@oxfmt/binding-linux-x64-musl': 0.45.0 - '@oxfmt/binding-openharmony-arm64': 0.45.0 - '@oxfmt/binding-win32-arm64-msvc': 0.45.0 - '@oxfmt/binding-win32-ia32-msvc': 0.45.0 - '@oxfmt/binding-win32-x64-msvc': 0.45.0 - - oxlint-tsgolint@0.21.0: + '@oxfmt/binding-android-arm-eabi': 0.46.0 + '@oxfmt/binding-android-arm64': 0.46.0 + '@oxfmt/binding-darwin-arm64': 0.46.0 + '@oxfmt/binding-darwin-x64': 0.46.0 + '@oxfmt/binding-freebsd-x64': 0.46.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.46.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.46.0 + '@oxfmt/binding-linux-arm64-gnu': 0.46.0 + '@oxfmt/binding-linux-arm64-musl': 0.46.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-musl': 0.46.0 + '@oxfmt/binding-linux-s390x-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-musl': 0.46.0 + '@oxfmt/binding-openharmony-arm64': 0.46.0 + '@oxfmt/binding-win32-arm64-msvc': 0.46.0 + '@oxfmt/binding-win32-ia32-msvc': 0.46.0 + '@oxfmt/binding-win32-x64-msvc': 0.46.0 + + oxlint-tsgolint@0.21.1: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.21.0 - '@oxlint-tsgolint/darwin-x64': 0.21.0 - '@oxlint-tsgolint/linux-arm64': 0.21.0 - '@oxlint-tsgolint/linux-x64': 0.21.0 - '@oxlint-tsgolint/win32-arm64': 0.21.0 - '@oxlint-tsgolint/win32-x64': 0.21.0 - - oxlint@1.60.0(oxlint-tsgolint@0.21.0): + '@oxlint-tsgolint/darwin-arm64': 0.21.1 + '@oxlint-tsgolint/darwin-x64': 0.21.1 + '@oxlint-tsgolint/linux-arm64': 0.21.1 + '@oxlint-tsgolint/linux-x64': 0.21.1 + '@oxlint-tsgolint/win32-arm64': 0.21.1 + '@oxlint-tsgolint/win32-x64': 0.21.1 + + oxlint@1.61.0(oxlint-tsgolint@0.21.1): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.60.0 - '@oxlint/binding-android-arm64': 1.60.0 - '@oxlint/binding-darwin-arm64': 1.60.0 - '@oxlint/binding-darwin-x64': 1.60.0 - '@oxlint/binding-freebsd-x64': 1.60.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.60.0 - '@oxlint/binding-linux-arm-musleabihf': 1.60.0 - '@oxlint/binding-linux-arm64-gnu': 1.60.0 - '@oxlint/binding-linux-arm64-musl': 1.60.0 - '@oxlint/binding-linux-ppc64-gnu': 1.60.0 - '@oxlint/binding-linux-riscv64-gnu': 1.60.0 - '@oxlint/binding-linux-riscv64-musl': 1.60.0 - '@oxlint/binding-linux-s390x-gnu': 1.60.0 - '@oxlint/binding-linux-x64-gnu': 1.60.0 - '@oxlint/binding-linux-x64-musl': 1.60.0 - '@oxlint/binding-openharmony-arm64': 1.60.0 - '@oxlint/binding-win32-arm64-msvc': 1.60.0 - '@oxlint/binding-win32-ia32-msvc': 1.60.0 - '@oxlint/binding-win32-x64-msvc': 1.60.0 - oxlint-tsgolint: 0.21.0 + '@oxlint/binding-android-arm-eabi': 1.61.0 + '@oxlint/binding-android-arm64': 1.61.0 + '@oxlint/binding-darwin-arm64': 1.61.0 + '@oxlint/binding-darwin-x64': 1.61.0 + '@oxlint/binding-freebsd-x64': 1.61.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.61.0 + '@oxlint/binding-linux-arm-musleabihf': 1.61.0 + '@oxlint/binding-linux-arm64-gnu': 1.61.0 + '@oxlint/binding-linux-arm64-musl': 1.61.0 + '@oxlint/binding-linux-ppc64-gnu': 1.61.0 + '@oxlint/binding-linux-riscv64-gnu': 1.61.0 + '@oxlint/binding-linux-riscv64-musl': 1.61.0 + '@oxlint/binding-linux-s390x-gnu': 1.61.0 + '@oxlint/binding-linux-x64-gnu': 1.61.0 + '@oxlint/binding-linux-x64-musl': 1.61.0 + '@oxlint/binding-openharmony-arm64': 1.61.0 + '@oxlint/binding-win32-arm64-msvc': 1.61.0 + '@oxlint/binding-win32-ia32-msvc': 1.61.0 + '@oxlint/binding-win32-x64-msvc': 1.61.0 + oxlint-tsgolint: 0.21.1 p-limit@3.1.0: dependencies: @@ -9550,8 +9781,6 @@ snapshots: lru-cache: 11.2.6 minipass: 7.1.3 - path-to-regexp@6.3.0: {} - path-type@4.0.0: {} pathe@2.0.3: {} @@ -9563,11 +9792,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.3: {} + picomatch@4.0.4: {} pkg-types@1.3.1: dependencies: @@ -9583,6 +9808,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + pretty-bytes@5.6.0: {} pretty-bytes@6.1.1: {} @@ -9684,11 +9911,11 @@ snapshots: react: 18.3.1 react-async-script: 1.2.0(react@18.3.1) - react-i18next@16.5.7(i18next@25.8.17(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3): + react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3): dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.8.17(typescript@5.9.3) + i18next: 25.10.10(typescript@5.9.3) react: 18.3.1 use-sync-external-store: 1.6.0(react@18.3.1) optionalDependencies: @@ -9706,8 +9933,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-refresh@0.18.0: {} - react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@remix-run/router': 1.23.2 @@ -9726,7 +9951,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 4.0.4 redent@3.0.0: dependencies: @@ -9788,35 +10013,60 @@ snapshots: reusify@1.1.0: {} - rollup@4.59.0: + rolldown@1.0.0-rc.12: + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + + rollup@2.80.0: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.60.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 + '@rollup/rollup-android-arm-eabi': 4.60.0 + '@rollup/rollup-android-arm64': 4.60.0 + '@rollup/rollup-darwin-arm64': 4.60.0 + '@rollup/rollup-darwin-x64': 4.60.0 + '@rollup/rollup-freebsd-arm64': 4.60.0 + '@rollup/rollup-freebsd-x64': 4.60.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 + '@rollup/rollup-linux-arm-musleabihf': 4.60.0 + '@rollup/rollup-linux-arm64-gnu': 4.60.0 + '@rollup/rollup-linux-arm64-musl': 4.60.0 + '@rollup/rollup-linux-loong64-gnu': 4.60.0 + '@rollup/rollup-linux-loong64-musl': 4.60.0 + '@rollup/rollup-linux-ppc64-gnu': 4.60.0 + '@rollup/rollup-linux-ppc64-musl': 4.60.0 + '@rollup/rollup-linux-riscv64-gnu': 4.60.0 + '@rollup/rollup-linux-riscv64-musl': 4.60.0 + '@rollup/rollup-linux-s390x-gnu': 4.60.0 + '@rollup/rollup-linux-x64-gnu': 4.60.0 + '@rollup/rollup-linux-x64-musl': 4.60.0 + '@rollup/rollup-openbsd-x64': 4.60.0 + '@rollup/rollup-openharmony-arm64': 4.60.0 + '@rollup/rollup-win32-arm64-msvc': 4.60.0 + '@rollup/rollup-win32-ia32-msvc': 4.60.0 + '@rollup/rollup-win32-x64-gnu': 4.60.0 + '@rollup/rollup-win32-x64-msvc': 4.60.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -9884,37 +10134,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.7.4 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9992,7 +10211,7 @@ snapshots: smob@1.6.1: {} - smol-toml@1.6.0: {} + smol-toml@1.6.1: {} snake-case@3.0.4: dependencies: @@ -10084,8 +10303,6 @@ snapshots: dependencies: inline-style-parser: 0.2.2 - supports-color@10.2.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -10124,8 +10341,8 @@ snapshots: tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinypool@2.1.0: {} @@ -10159,6 +10376,10 @@ snapshots: tslib@2.8.1: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.16.0: {} typed-array-buffer@1.0.3: @@ -10209,11 +10430,7 @@ snapshots: undici-types@7.16.0: {} - undici@7.24.3: {} - - unenv@2.0.0-rc.24: - dependencies: - pathe: 2.0.3 + undici@7.24.6: {} unhomoglyph@1.0.6: {} @@ -10242,6 +10459,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-sync-external-store@1.6.0(react@18.3.1): dependencies: react: 18.3.1 @@ -10255,13 +10476,13 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - vite-node@3.2.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2): + vite-node@3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1) transitivePeerDependencies: - '@types/node' - jiti @@ -10276,95 +10497,109 @@ snapshots: - tsx - yaml - vite-plugin-compression2@2.5.0(rollup@4.59.0): + vite-plugin-compression2@2.5.3(rollup@4.60.0): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) tar-mini: 0.2.0 transitivePeerDependencies: - rollup - vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): + vite-plugin-pwa@1.2.0(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) workbox-build: 7.4.0(@types/babel__core@7.20.5) workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite-plugin-static-copy@3.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)): + vite-plugin-static-copy@4.0.0(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) - vite-plugin-svgr@4.5.0(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)): + vite-plugin-svgr@4.5.0(rollup@4.60.0)(typescript@5.9.3)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite-plugin-top-level-await@1.6.0(@swc/helpers@0.5.19)(rollup@4.59.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)): + vite-plugin-top-level-await@1.6.0(@swc/helpers@0.5.19)(rollup@4.60.0)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)): dependencies: - '@rollup/plugin-virtual': 3.0.2(rollup@4.59.0) + '@rollup/plugin-virtual': 3.0.2(rollup@4.60.0) '@swc/core': 1.15.18(@swc/helpers@0.5.19) '@swc/wasm': 1.15.18 uuid: 10.0.0 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) transitivePeerDependencies: - '@swc/helpers' - rollup - vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2): + vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1): + dependencies: + esbuild: 0.27.4 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.13 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.32.0 + terser: 5.46.1 + + vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + lightningcss: 1.32.0 + picomatch: 4.0.4 postcss: 8.5.8 - rollup: 4.59.0 + rolldown: 1.0.0-rc.12 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.13 + esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 terser: 5.46.1 - yaml: 2.8.2 - vitest@4.1.0(@types/node@24.10.13)(@vitest/ui@4.1.0)(jsdom@29.0.0)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)): + vitest@4.1.2(@types/node@24.10.13)(@vitest/ui@4.1.2)(jsdom@29.0.1)(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)): dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(vite@8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.4 std-env: 4.0.0 tinybench: 2.9.0 tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.2) + vite: 8.0.3(@types/node@24.10.13)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.46.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.13 - '@vitest/ui': 4.1.0(vitest@4.1.0) - jsdom: 29.0.0 + '@vitest/ui': 4.1.2(vitest@4.1.2) + jsdom: 29.0.1 transitivePeerDependencies: - msw @@ -10453,6 +10688,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + workbox-background-sync@7.4.0: dependencies: idb: 7.1.1 @@ -10464,14 +10701,14 @@ snapshots: workbox-build@7.4.0(@types/babel__core@7.20.5): dependencies: - '@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0) + '@apideck/better-ajv-errors': 0.3.7(ajv@8.18.0) '@babel/core': 7.29.0 '@babel/preset-env': 7.29.2(@babel/core@7.29.0) '@babel/runtime': 7.29.2 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@4.59.0) - '@rollup/plugin-node-resolve': 15.3.1(rollup@4.59.0) - '@rollup/plugin-replace': 2.4.2(rollup@4.59.0) - '@rollup/plugin-terser': 0.4.4(rollup@4.59.0) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.80.0) + '@rollup/plugin-node-resolve': 15.3.1(rollup@2.80.0) + '@rollup/plugin-replace': 2.4.2(rollup@2.80.0) + '@rollup/plugin-terser': 0.4.4(rollup@2.80.0) '@surma/rollup-plugin-off-main-thread': 2.2.3 ajv: 8.18.0 common-tags: 1.8.2 @@ -10480,7 +10717,7 @@ snapshots: glob: 11.1.0 lodash: 4.17.23 pretty-bytes: 5.6.0 - rollup: 4.59.0 + rollup: 2.80.0 source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 @@ -10566,54 +10803,12 @@ snapshots: '@types/trusted-types': 2.0.7 workbox-core: 7.4.0 - workerd@1.20260310.1: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260310.1 - '@cloudflare/workerd-darwin-arm64': 1.20260310.1 - '@cloudflare/workerd-linux-64': 1.20260310.1 - '@cloudflare/workerd-linux-arm64': 1.20260310.1 - '@cloudflare/workerd-windows-64': 1.20260310.1 - - wrangler@4.72.0: - dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260310.1) - blake3-wasm: 2.1.5 - esbuild: 0.27.3 - miniflare: 4.20260310.0 - path-to-regexp: 6.3.0 - unenv: 2.0.0-rc.24 - workerd: 1.20260310.1 - optionalDependencies: - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - ws@8.18.0: {} - xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} yallist@3.1.1: {} - yaml@2.8.2: - optional: true - yocto-queue@0.1.0: {} - youch-core@0.3.3: - dependencies: - '@poppinss/exception': 1.2.3 - error-stack-parser-es: 1.0.5 - - youch@4.1.0-beta.10: - dependencies: - '@poppinss/colors': 4.1.6 - '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.14 - cookie: 1.1.1 - youch-core: 0.3.3 - zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fbcf9139d9..f8cf6ebc84 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,30 +1,26 @@ allowBuilds: '@sentry/cli': true '@swc/core': true - cloudflared: true esbuild: true sharp: true unrs-resolver: true - workerd: true engineStrict: true minimumReleaseAge: 1440 minimumReleaseAgeExclude: - '@sableclient/sable-call-embedded' + - '@sableclient/twemoji-font' overrides: - brace-expansion: '>=1.1.12' - esbuild: '>=0.25.0' - flatted: '>=3.4.2' - lodash: '>=4.17.23' - minimatch: '>=10.2.3' - rollup: '>=4.59.0' - serialize-javascript: '>=7.0.3' - undici: '>=7.24.0' + serialize-javascript: '>=7.0.5' + picomatch: '>=4.0.4' + smol-toml: '>=1.6.1' + yaml: '>=2.8.3' peerDependencyRules: allowedVersions: - 'folds>@vanilla-extract/css': '1.18.0' - 'folds>@vanilla-extract/recipes': '0.5.7' - 'folds>classnames': '2.5.1' - 'folds>react': '18.3.1' - 'folds>react-dom': '18.3.1' + 'folds>@vanilla-extract/css': '^1.20.1' + 'folds>@vanilla-extract/recipes': '^0.5.7' + 'folds>classnames': '^2.5.1' + 'folds>react': '^18.3.1' + 'folds>react-dom': '^18.3.1' + 'vite-plugin-pwa>vite': '^8.0.3' diff --git a/public/font/Twemoji.Mozilla.v15.1.0.ttf b/public/font/Twemoji.Mozilla.v15.1.0.ttf deleted file mode 100644 index efb3c89800..0000000000 Binary files a/public/font/Twemoji.Mozilla.v15.1.0.ttf and /dev/null differ diff --git a/public/font/Twemoji.Mozilla.v15.1.0.woff2 b/public/font/Twemoji.Mozilla.v15.1.0.woff2 deleted file mode 100644 index 5bfc425d66..0000000000 Binary files a/public/font/Twemoji.Mozilla.v15.1.0.woff2 and /dev/null differ diff --git a/scripts/import-rewrites.test.js b/scripts/import-rewrites.test.js new file mode 100644 index 0000000000..65348b81f0 --- /dev/null +++ b/scripts/import-rewrites.test.js @@ -0,0 +1,74 @@ +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { + getMatrixModuleSpecifierFromDeclarationFile, + loadAliasMapFromTsconfig, + rewriteSourceImports, +} from './utils/import-rewrites.js'; + +/** @type {string[]} */ +const tempDirs = []; + +async function makeTempProject() { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sable-import-rewrites-')); + tempDirs.push(dir); + return dir; +} + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); +}); + +describe('loadAliasMapFromTsconfig + rewriteSourceImports', () => { + it('rewrites relative imports using tsconfig path aliases', async () => { + const projectRoot = await makeTempProject(); + await fs.writeFile( + path.join(projectRoot, 'tsconfig.web.json'), + JSON.stringify( + { + compilerOptions: { + baseUrl: '.', + paths: { + '$components/*': ['src/app/components/*'], + '$types/*': ['src/types/*'], + }, + }, + }, + null, + 2 + ) + ); + + const aliases = await loadAliasMapFromTsconfig( + path.join(projectRoot, 'tsconfig.web.json'), + projectRoot + ); + + const filePath = path.join(projectRoot, 'src/app/pages/Home.tsx'); + const sourceCode = [ + "import { Header } from '../components/Header';", + "import { MatrixClient } from 'matrix-js-sdk/lib/client';", + '', + ].join('\n'); + + const result = rewriteSourceImports(filePath, sourceCode, aliases, projectRoot); + + expect(result.changed).toBe(true); + expect(result.updatedCode).toContain("from '$components/Header'"); + expect(result.updatedCode).toContain("from '$types/matrix-sdk'"); + }); +}); + +describe('getMatrixModuleSpecifierFromDeclarationFile', () => { + it('normalizes matrix-js-sdk declaration paths to bare module specifiers', () => { + const declarationFile = String.raw`C:\repo\node_modules\matrix-js-sdk\lib\models\room.d.ts`; + + expect(getMatrixModuleSpecifierFromDeclarationFile(declarationFile)).toBe( + 'matrix-js-sdk/lib/models/room' + ); + }); +}); diff --git a/scripts/install-knope.js b/scripts/install-knope.js index cbda39e577..66587ee198 100644 --- a/scripts/install-knope.js +++ b/scripts/install-knope.js @@ -1,5 +1,6 @@ #!/usr/bin/env node import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; import process from 'node:process'; import { chmodSync, existsSync, mkdirSync, realpathSync, writeFileSync } from 'node:fs'; import { join, dirname, resolve, sep } from 'node:path'; @@ -10,20 +11,52 @@ import { PrefixedLogger, createTextHelpers } from './utils/console-style.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const VERSION = '0.22.3'; +const VERSION = '0.22.4'; +/** + * @typedef {'linux-x64' | 'linux-arm64' | 'darwin-x64' | 'darwin-arm64' | 'win32-x64'} SupportedTargetKey + */ + +/** + * Pinned to the published release asset digests for knope. + * Source: GitHub release asset metadata at publish time. + * @type {Record} + */ const TARGETS = { - 'linux-x64': 'x86_64-unknown-linux-musl', - 'linux-arm64': 'aarch64-unknown-linux-musl', - 'darwin-x64': 'x86_64-apple-darwin', - 'darwin-arm64': 'aarch64-apple-darwin', - 'win32-x64': 'x86_64-pc-windows-msvc', + 'linux-x64': { + target: 'x86_64-unknown-linux-musl', + digest: 'sha256:45a74925ae9f4c9c2c33b51992ae50241ec4fa836bf8d2977c0b8e8172dd69cf', + }, + 'linux-arm64': { + target: 'aarch64-unknown-linux-musl', + digest: 'sha256:95e882afdb4154c5baaba91f7bbd1fb1d41cec6898363a2b30e7abad4057b83b', + }, + 'darwin-x64': { + target: 'x86_64-apple-darwin', + digest: 'sha256:010dc197bf159bbd9d60e897252248ba2b0e204beae7250ce54a9deae1ec4876', + }, + 'darwin-arm64': { + target: 'aarch64-apple-darwin', + digest: 'sha256:02131f284315c8ece8a4ef69a0aff5f658309d4df73b95cfdfbe0fbd9e9ce259', + }, + 'win32-x64': { + target: 'x86_64-pc-windows-msvc', + digest: 'sha256:09f735b2da42cd594189042d1379c0a3a350a8c0ccb741015a84c6ff334543b1', + }, }; +/** + * @param {string | null | undefined} output + * @returns {string | null} + */ function parseKnopeVersion(output) { const version = output?.trim().replace(/^knope\s+/, ''); return version || null; } +/** + * @param {string} command + * @returns {string | null} + */ function getKnopeVersion(command) { const result = spawnSync(command, ['--version'], { encoding: 'utf8' }); if (result.status !== 0) { @@ -32,17 +65,30 @@ function getKnopeVersion(command) { return parseKnopeVersion(result.stdout); } +/** + * @param {Buffer} buffer + * @returns {string} + */ function readNullTerminatedString(buffer) { const nulIndex = buffer.indexOf(0); const end = nulIndex === -1 ? buffer.length : nulIndex; return buffer.toString('utf8', 0, end); } +/** + * @param {string} entryName + * @returns {string} + */ function getTarBasename(entryName) { const segments = entryName.split('/').filter(Boolean); return segments.at(-1) ?? ''; } +/** + * @param {Buffer} tarBuffer + * @param {string} expectedBasename + * @returns {Buffer} + */ function extractRegularFileFromTar(tarBuffer, expectedBasename) { let offset = 0; const regularEntries = []; @@ -87,6 +133,9 @@ function extractRegularFileFromTar(tarBuffer, expectedBasename) { ); } +/** + * @returns {string | null} + */ function getSystemKnopePath() { const which = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['knope'], { encoding: 'utf8', @@ -102,7 +151,16 @@ function getSystemKnopePath() { ); } +/** + * @param {string} candidatePath + * @param {string} rootPath + * @returns {boolean} + */ function isPathWithin(candidatePath, rootPath) { + /** + * @param {string} value + * @returns {string} + */ const toComparablePath = (value) => { const resolved = resolve(value); return process.platform === 'win32' ? resolved.toLowerCase() : resolved; @@ -112,22 +170,34 @@ function isPathWithin(candidatePath, rootPath) { return candidate === root || candidate.startsWith(`${root}${sep}`); } +/** + * @param {Buffer} buffer + * @returns {string} + */ +function getSha256Digest(buffer) { + return `sha256:${createHash('sha256').update(buffer).digest('hex')}`; +} + const logger = new PrefixedLogger('[postinstall:knope]'); const { dim, red, green } = createTextHelpers({ useColor: logger.useColor }); if (process.env.GITHUB_ACTIONS && process.env.CI) { - logger.info(`${dim('Running in CI environment, skipping knope installation')}`); + logger.info(dim('Running in CI environment, skipping knope installation')); process.exit(0); } -const target = TARGETS[`${process.platform}-${process.arch}`]; -if (!target) { +const targetKey = `${process.platform}-${process.arch}`; +const targetConfig = Object.hasOwn(TARGETS, targetKey) + ? TARGETS[/** @type {SupportedTargetKey} */ (targetKey)] + : undefined; +if (!targetConfig) { const supported = Object.keys(TARGETS).join(', '); logger.error( `${dim('Unsupported platform: ')}${red(`${process.platform}-${process.arch}`)}${dim('. Supported targets: ')}${supported}` ); process.exit(1); } +const { target, digest: expectedDigest } = targetConfig; const bin = join( __dirname, @@ -173,7 +243,8 @@ if (systemKnopePath) { } } -const url = `https://github.com/knope-dev/knope/releases/download/knope%2Fv${VERSION}/knope-${target}.tgz`; +const assetName = `knope-${target}.tgz`; +const url = `https://github.com/knope-dev/knope/releases/download/knope%2Fv${VERSION}/${assetName}`; logger.info( `${dim('Downloading knope ')}${green(`v${VERSION}`)}${dim(' for ')}${target}${dim('...')}` ); @@ -182,6 +253,12 @@ if (!response.ok) { throw new Error(`Failed to download knope: ${response.status} ${response.statusText}`); } const gzipBytes = Buffer.from(await response.arrayBuffer()); +const actualDigest = getSha256Digest(gzipBytes); +if (actualDigest !== expectedDigest) { + throw new Error( + `Downloaded ${assetName} digest mismatch: expected ${expectedDigest}, got ${actualDigest}` + ); +} const tarBytes = gunzipSync(gzipBytes); const expectedBinaryName = process.platform === 'win32' ? 'knope.exe' : 'knope'; const knopeBinary = extractRegularFileFromTar(tarBytes, expectedBinaryName); diff --git a/scripts/migrate-matrix-sdk-imports.js b/scripts/migrate-matrix-sdk-imports.js new file mode 100644 index 0000000000..c33407980c --- /dev/null +++ b/scripts/migrate-matrix-sdk-imports.js @@ -0,0 +1,305 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; + +import ts from 'typescript'; + +import { createTextHelpers } from './utils/console-style.js'; +import { + DEFAULT_ROOTS, + applyTextReplacements, + collectSourceFiles, + getMatrixModuleSpecifierFromDeclarationFile, + renderMatrixImportGroups, + toPosix, +} from './utils/import-rewrites.js'; + +const MATRIX_BOUNDARY_SPECIFIER = '$types/matrix-sdk'; + +/** + * @typedef {{ + * write: boolean; + * roots: string[]; + * }} CliArgs + */ + +/** + * @typedef {{ + * importedName: string; + * localName: string; + * }} ImportEntry + */ + +/** + * @typedef {{ + * values: ImportEntry[]; + * types: ImportEntry[]; + * }} MatrixImportGroup + */ + +/** + * @typedef {{ + * start: number; + * end: number; + * original: string; + * value: string; + * }} Replacement + */ + +/** + * @param {string[]} argv + * @returns {CliArgs} + */ +function parseArgs(argv) { + let write = false; + const roots = []; + let index = 0; + + while (index < argv.length) { + const arg = argv[index]; + if (arg === '--write') { + write = true; + } else if (arg === '--root' && argv[index + 1]) { + roots.push(argv[index + 1]); + index += 1; + } else if (arg.startsWith('--root=')) { + roots.push(arg.slice('--root='.length)); + } else if (arg === '--help' || arg === '-h') { + console.log( + [ + 'Usage: node scripts/migrate-matrix-sdk-imports.js [--write] [--root ]', + '', + 'Default mode is dry-run.', + '--write Apply changes to files.', + '--root Root directory to scan (repeatable). Default: src', + ].join('\n') + ); + process.exit(0); + } + + index += 1; + } + + return { + write, + roots: roots.length > 0 ? roots : DEFAULT_ROOTS, + }; +} + +/** + * @param {string} projectRoot + * @returns {import('typescript').Program} + */ +function loadProgram(projectRoot) { + const tsconfigPath = path.join(projectRoot, 'tsconfig.web.json'); + const configResult = ts.readConfigFile(tsconfigPath, (filePath) => ts.sys.readFile(filePath)); + + if (configResult.error) { + const message = ts.flattenDiagnosticMessageText(configResult.error.messageText, '\n'); + throw new Error(`Failed to read tsconfig.web.json: ${message}`); + } + + const parsedConfig = ts.parseJsonConfigFileContent( + configResult.config, + ts.sys, + projectRoot, + undefined, + tsconfigPath + ); + + if (parsedConfig.errors.length > 0) { + const message = parsedConfig.errors + .map((error) => ts.flattenDiagnosticMessageText(error.messageText, '\n')) + .join('\n'); + throw new Error(`Failed to parse tsconfig.web.json:\n${message}`); + } + + return ts.createProgram({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + }); +} + +/** + * @param {string} filePath + * @param {string[]} rootPaths + * @returns {boolean} + */ +function isWithinRoots(filePath, rootPaths) { + return rootPaths.some((rootPath) => { + const relativePath = path.relative(rootPath, filePath); + return ( + relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) + ); + }); +} + +/** + * @param {import('typescript').TypeChecker} checker + * @param {import('typescript').ImportSpecifier} specifier + * @returns {string | null} + */ +function getDeclarationModuleSpecifier(checker, specifier) { + const importedSymbol = checker.getSymbolAtLocation(specifier.name); + if (!importedSymbol) return null; + + const resolvedSymbol = + importedSymbol.flags & ts.SymbolFlags.Alias + ? checker.getAliasedSymbol(importedSymbol) + : importedSymbol; + const declaration = resolvedSymbol.declarations?.[0]; + if (!declaration) return null; + + return getMatrixModuleSpecifierFromDeclarationFile(declaration.getSourceFile().fileName); +} + +/** + * @param {import('typescript').ImportSpecifier} specifier + * @returns {ImportEntry} + */ +function getImportEntry(specifier) { + return { + importedName: specifier.propertyName?.text ?? specifier.name.text, + localName: specifier.name.text, + }; +} + +/** + * @param {import('typescript').TypeChecker} checker + * @param {import('typescript').ImportDeclaration} importDeclaration + * @returns {string | null} + */ +function buildReplacementText(checker, importDeclaration) { + const importClause = importDeclaration.importClause; + if ( + !importClause || + !importClause.namedBindings || + !ts.isNamedImports(importClause.namedBindings) + ) { + return null; + } + + /** @type {Map} */ + const groups = new Map(); + + for (const specifier of importClause.namedBindings.elements) { + const moduleSpecifier = getDeclarationModuleSpecifier(checker, specifier); + if (!moduleSpecifier) { + return null; + } + + const group = groups.get(moduleSpecifier) ?? { values: [], types: [] }; + const bucket = importClause.isTypeOnly || specifier.isTypeOnly ? group.types : group.values; + bucket.push(getImportEntry(specifier)); + groups.set(moduleSpecifier, group); + } + + return renderMatrixImportGroups(groups).join('\n'); +} + +/** + * @param {import('typescript').SourceFile} sourceFile + * @param {import('typescript').TypeChecker} checker + * @returns {Replacement[]} + */ +function collectReplacements(sourceFile, checker) { + /** @type {Replacement[]} */ + const replacements = []; + + /** + * @param {import('typescript').Node} node + */ + function visit(node) { + if ( + ts.isImportDeclaration(node) && + ts.isStringLiteral(node.moduleSpecifier) && + node.moduleSpecifier.text === MATRIX_BOUNDARY_SPECIFIER + ) { + const replacementText = buildReplacementText(checker, node); + if (replacementText) { + replacements.push({ + start: node.getStart(sourceFile), + end: node.getEnd(), + original: '', + value: replacementText, + }); + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + return replacements.toSorted((left, right) => right.start - left.start); +} + +async function main() { + const projectRoot = process.cwd(); + const { write, roots } = parseArgs(process.argv.slice(2)); + const targetRoots = roots.map((root) => path.resolve(projectRoot, root)); + const sourceFiles = ( + await Promise.all( + targetRoots.map(async (root) => { + try { + const stat = await fs.stat(root); + if (!stat.isDirectory()) return []; + return collectSourceFiles(root); + } catch { + return []; + } + }) + ) + ).flat(); + + const sourceFileSet = new Set(sourceFiles.map((filePath) => path.normalize(filePath))); + const program = loadProgram(projectRoot); + const checker = program.getTypeChecker(); + const { dim, green } = createTextHelpers(); + + const changes = []; + const writePromises = []; + + for (const sourceFile of program.getSourceFiles()) { + const filePath = path.normalize(sourceFile.fileName); + if (!sourceFileSet.has(filePath) || !isWithinRoots(filePath, targetRoots)) continue; + + const replacements = collectReplacements(sourceFile, checker); + if (replacements.length === 0) continue; + + const originalCode = sourceFile.getFullText(); + const updatedCode = applyTextReplacements(originalCode, replacements); + + if (write) { + writePromises.push(fs.writeFile(filePath, updatedCode, 'utf8')); + } + + changes.push({ + file: toPosix(path.relative(projectRoot, filePath)), + replacements: replacements.length, + }); + } + + await Promise.all(writePromises); + + changes + .toSorted((left, right) => left.file.localeCompare(right.file)) + .forEach((change) => { + console.log( + `${dim(change.file)}: ${green(`${change.replacements} matrix import rewrite(s)`)}` + ); + }); + + const mode = write ? 'Applied' : 'Dry run'; + console.log(`${mode}: ${changes.length} files.`); + if (!write) { + console.log('Re-run with --write to apply changes.'); + } +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +}); diff --git a/scripts/normalize-imports.js b/scripts/normalize-imports.js index 77af442c38..d83047a075 100644 --- a/scripts/normalize-imports.js +++ b/scripts/normalize-imports.js @@ -4,21 +4,34 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; -import ts from 'typescript'; import { createTextHelpers } from './utils/console-style.js'; - -const DEFAULT_ROOTS = ['src']; -const SKIP_DIRS = new Set(['.git', '.hg', '.svn', 'node_modules', 'dist', 'coverage']); -const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs']); -const MATRIX_IMPORT_BOUNDARY_FILES = new Set([ - path.normalize('src/types/matrix-sdk.ts'), - path.normalize('src/types/matrix-sdk-events.d.ts'), -]); - -function toPosix(inputPath) { - return inputPath.split(path.sep).join('/'); -} - +import { + DEFAULT_ROOTS, + collectSourceFiles, + loadAliasMapFromTsconfig, + rewriteSourceImports, + toPosix, +} from './utils/import-rewrites.js'; + +/** + * @typedef {{ + * write: boolean; + * roots: string[]; + * }} CliArgs + */ + +/** + * @typedef {{ + * file: string; + * replacements: number; + * edits: { from: string; to: string }[]; + * }} FileResult + */ + +/** + * @param {string[]} argv + * @returns {CliArgs} + */ function parseArgs(argv) { let write = false; const roots = []; @@ -55,190 +68,17 @@ function parseArgs(argv) { }; } -async function loadAliasMap(viteConfigPath, projectRoot) { - const viteConfig = await fs.readFile(viteConfigPath, 'utf8'); - const regex = /(\$[A-Za-z0-9_]+)\s*:\s*path\.resolve\(__dirname,\s*'([^']+)'\s*\)/g; - const aliasMap = []; - let match = regex.exec(viteConfig); - - while (match) { - const alias = match[1]; - const relativePath = match[2]; - aliasMap.push({ - alias, - absolutePath: path.resolve(projectRoot, relativePath), - }); - match = regex.exec(viteConfig); - } - - aliasMap.sort((a, b) => b.absolutePath.length - a.absolutePath.length); - return aliasMap; -} - -async function collectSourceFiles(rootDir) { - const files = []; - - async function walk(currentDir) { - const entries = await fs.readdir(currentDir, { withFileTypes: true }); - await Promise.all( - entries.map(async (entry) => { - if (entry.name.startsWith('.') && entry.name !== '.eslintrc') return; - if (entry.isDirectory()) { - if (SKIP_DIRS.has(entry.name)) return; - await walk(path.join(currentDir, entry.name)); - return; - } - - if (!entry.isFile()) return; - const filePath = path.join(currentDir, entry.name); - const ext = path.extname(entry.name); - if (!SOURCE_EXTENSIONS.has(ext)) return; - files.push(filePath); - }) - ); - } - - await walk(rootDir); - return files; -} - -function splitSpecifier(specifier) { - const match = specifier.match(/^([^?#]+)([?#].*)?$/); - if (!match) { - return { bare: specifier, suffix: '' }; - } - return { - bare: match[1], - suffix: match[2] ?? '', - }; -} - -function findMatchingAlias(absoluteTargetPath, aliases) { - return aliases.find(({ absolutePath }) => { - const rel = path.relative(absolutePath, absoluteTargetPath); - return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel)); - }); -} - -function getRewrittenSpecifier(filePath, specifier, aliases, projectRoot) { - const normalizedFilePath = path.normalize(path.relative(projectRoot, filePath)); - const { bare, suffix } = splitSpecifier(specifier); - - if ( - !MATRIX_IMPORT_BOUNDARY_FILES.has(normalizedFilePath) && - (bare === 'matrix-js-sdk' || bare.startsWith('matrix-js-sdk/')) - ) { - return `$types/matrix-sdk${suffix}`; - } - - if (!/^\.\.(?:\/|$)/.test(bare)) { - return null; - } - - const absoluteTargetPath = path.resolve(path.dirname(filePath), bare); - const matchedAlias = findMatchingAlias(absoluteTargetPath, aliases); - if (!matchedAlias) return null; - - const aliasRelativePath = toPosix(path.relative(matchedAlias.absolutePath, absoluteTargetPath)); - const aliasImport = aliasRelativePath - ? `${matchedAlias.alias}/${aliasRelativePath}` - : matchedAlias.alias; - return `${aliasImport}${suffix}`; -} - -function queueReplacement(sourceFile, literalNode, replacements, aliases, filePath, projectRoot) { - const specifier = literalNode.text; - const rewrittenSpecifier = getRewrittenSpecifier(filePath, specifier, aliases, projectRoot); - if (!rewrittenSpecifier || rewrittenSpecifier === specifier) return; - - replacements.push({ - start: literalNode.getStart(sourceFile) + 1, - end: literalNode.getEnd() - 1, - original: specifier, - value: rewrittenSpecifier, - }); -} - -function rewriteFileImports(filePath, sourceCode, aliases, projectRoot) { - const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true); - const replacements = []; - - function visit(node) { - if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { - queueReplacement( - sourceFile, - node.moduleSpecifier, - replacements, - aliases, - filePath, - projectRoot - ); - } else if ( - ts.isExportDeclaration(node) && - node.moduleSpecifier && - ts.isStringLiteral(node.moduleSpecifier) - ) { - queueReplacement( - sourceFile, - node.moduleSpecifier, - replacements, - aliases, - filePath, - projectRoot - ); - } else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) { - const { literal } = node.argument; - if (ts.isStringLiteral(literal)) { - queueReplacement(sourceFile, literal, replacements, aliases, filePath, projectRoot); - } - } else if (ts.isCallExpression(node) && node.arguments.length > 0) { - const firstArg = node.arguments[0]; - if (ts.isStringLiteral(firstArg)) { - const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword; - const isRequire = ts.isIdentifier(node.expression) && node.expression.text === 'require'; - if (isDynamicImport || isRequire) { - queueReplacement(sourceFile, firstArg, replacements, aliases, filePath, projectRoot); - } - } - } - - ts.forEachChild(node, visit); - } - - visit(sourceFile); - if (replacements.length === 0) { - return { changed: false, updatedCode: sourceCode, replacements: 0 }; - } - - const uniqueReplacements = Array.from( - new Map(replacements.map((r) => [`${r.start}:${r.end}`, r])).values() - ).toSorted((a, b) => b.start - a.start); - - const updatedCode = uniqueReplacements.reduce( - (code, replacement) => - code.slice(0, replacement.start) + replacement.value + code.slice(replacement.end), - sourceCode - ); - - return { - changed: updatedCode !== sourceCode, - updatedCode, - replacements: uniqueReplacements.length, - edits: uniqueReplacements.map((replacement) => ({ - from: replacement.original, - to: replacement.value, - })), - }; -} - async function main() { const projectRoot = process.cwd(); const { write, roots } = parseArgs(process.argv.slice(2)); - const aliases = await loadAliasMap(path.join(projectRoot, 'vite.config.ts'), projectRoot); + const aliases = await loadAliasMapFromTsconfig( + path.join(projectRoot, 'tsconfig.web.json'), + projectRoot + ); const { dim, red, green } = createTextHelpers(); if (aliases.length === 0) { - throw new Error('No aliases found in vite.config.ts'); + throw new Error('No aliases found in tsconfig.web.json'); } const targetRoots = roots.map((root) => path.resolve(projectRoot, root)); @@ -259,7 +99,7 @@ async function main() { const fileResults = await Promise.all( sourceFiles.map(async (filePath) => { const sourceCode = await fs.readFile(filePath, 'utf8'); - const { changed, updatedCode, replacements, edits } = rewriteFileImports( + const { changed, updatedCode, replacements, edits } = rewriteSourceImports( filePath, sourceCode, aliases, @@ -280,6 +120,7 @@ async function main() { }) ); + /** @type {FileResult[]} */ const changedFiles = fileResults.filter((result) => result !== null); const filesChanged = changedFiles.length; const importRewrites = changedFiles.reduce((total, result) => total + result.replacements, 0); diff --git a/scripts/utils/console-style.js b/scripts/utils/console-style.js index ae7e29b792..aa246b4adf 100644 --- a/scripts/utils/console-style.js +++ b/scripts/utils/console-style.js @@ -1,6 +1,17 @@ /* oxlint-disable no-console */ import process from 'node:process'; +/** + * @typedef {object} TextHelperOptions + * @property {boolean} [useColor] + */ + +/** + * @typedef {TextHelperOptions & { + * prefixColor?: string; + * }} LoggerOptions + */ + export const ANSI = { reset: '\x1b[0m', red: '\x1b[31m', @@ -11,19 +22,36 @@ export const ANSI = { export function shouldUseColor() { if (process.env.NO_COLOR !== undefined) return false; if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') return true; - return Boolean(process.stdout.isTTY); + return process.stdout.isTTY; } +/** + * @param {string} text + * @param {string} color + * @param {boolean} enabled + * @returns {string} + */ export function styleText(text, color, enabled) { if (!enabled) return text; return `${color}${text}${ANSI.reset}`; } +/** + * @param {TextHelperOptions} [options] + */ export function createTextHelpers(options = {}) { const useColor = options.useColor ?? shouldUseColor(); + /** + * @param {string} text + * @param {string} color + * @returns {string} + */ const style = (text, color) => styleText(text, color, useColor); + /** @param {string} text */ const dim = (text) => styleText(text, ANSI.dim, useColor); + /** @param {string} text */ const red = (text) => styleText(text, ANSI.red, useColor); + /** @param {string} text */ const green = (text) => styleText(text, ANSI.green, useColor); return { useColor, @@ -35,20 +63,30 @@ export function createTextHelpers(options = {}) { } export class PrefixedLogger { + /** + * @param {string} prefix + * @param {LoggerOptions} [options] + */ constructor(prefix, options = {}) { this.prefix = prefix; this.useColor = options.useColor ?? shouldUseColor(); this.prefixColor = options.prefixColor ?? ANSI.dim; } + /** + * @param {string} message + * @returns {string} + */ withPrefix(message) { return `${styleText(this.prefix, this.prefixColor, this.useColor)} ${message}`; } + /** @param {string} message */ info(message) { console.log(this.withPrefix(message)); } + /** @param {string} message */ error(message) { console.error(this.withPrefix(message)); } diff --git a/scripts/utils/import-rewrites.js b/scripts/utils/import-rewrites.js new file mode 100644 index 0000000000..a404501d57 --- /dev/null +++ b/scripts/utils/import-rewrites.js @@ -0,0 +1,394 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import ts from 'typescript'; + +/** + * @typedef {{ + * alias: string; + * absolutePath: string; + * }} AliasEntry + */ + +/** + * @typedef {{ + * start: number; + * end: number; + * original: string; + * value: string; + * }} TextReplacement + */ + +/** + * @typedef {{ + * importedName: string; + * localName: string; + * }} MatrixImportSpecifier + */ + +/** + * @typedef {{ + * values: MatrixImportSpecifier[]; + * types: MatrixImportSpecifier[]; + * }} MatrixImportGroup + */ + +export const DEFAULT_ROOTS = ['src']; +export const SKIP_DIRS = new Set(['.git', '.hg', '.svn', 'node_modules', 'dist', 'coverage']); +export const SOURCE_EXTENSIONS = new Set([ + '.ts', + '.tsx', + '.js', + '.jsx', + '.mts', + '.cts', + '.mjs', + '.cjs', +]); +export const MATRIX_IMPORT_BOUNDARY_FILES = new Set([ + path.normalize('src/types/matrix-sdk.ts'), + path.normalize('src/types/matrix-sdk-events.d.ts'), +]); + +/** + * @param {string} inputPath + * @returns {string} + */ +export function toPosix(inputPath) { + return inputPath.replace(/\\/g, '/'); +} + +/** + * @param {string} pattern + * @returns {string} + */ +function normalizeAliasPattern(pattern) { + return pattern.replace(/\/\*$/, ''); +} + +/** + * @param {import('typescript').Diagnostic} error + * @returns {string} + */ +function getConfigErrorMessage(error) { + return ts.flattenDiagnosticMessageText(error.messageText, '\n'); +} + +/** + * @param {string} tsconfigPath + * @param {string} projectRoot + * @returns {Promise} + */ +export async function loadAliasMapFromTsconfig(tsconfigPath, projectRoot) { + const configResult = ts.readConfigFile(tsconfigPath, (filePath) => ts.sys.readFile(filePath)); + if (configResult.error) { + throw new Error( + `Failed to read ${path.basename(tsconfigPath)}: ${getConfigErrorMessage(configResult.error)}` + ); + } + + const compilerOptions = configResult.config.compilerOptions ?? {}; + const baseUrl = compilerOptions.baseUrl ?? '.'; + const paths = compilerOptions.paths ?? {}; + + /** @type {Map} */ + const aliasEntries = new Map(); + for (const [aliasPattern, targets] of Object.entries(paths)) { + if (!Array.isArray(targets) || targets.length === 0) continue; + + const alias = normalizeAliasPattern(aliasPattern); + const targetPattern = normalizeAliasPattern(targets[0]); + const absolutePath = path.resolve(projectRoot, baseUrl, targetPattern); + aliasEntries.set(`${alias}:${absolutePath}`, { alias, absolutePath }); + } + + const aliasMap = [...aliasEntries.values()]; + + aliasMap.sort((a, b) => b.absolutePath.length - a.absolutePath.length); + return aliasMap; +} + +/** + * @param {string} rootDir + * @returns {Promise} + */ +export async function collectSourceFiles(rootDir) { + /** @type {string[]} */ + const files = []; + + /** + * @param {string} currentDir + * @returns {Promise} + */ + async function walk(currentDir) { + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + await Promise.all( + entries.map(async (entry) => { + if (entry.name.startsWith('.') && entry.name !== '.eslintrc') return; + if (entry.isDirectory()) { + if (SKIP_DIRS.has(entry.name)) return; + await walk(path.join(currentDir, entry.name)); + return; + } + + if (!entry.isFile()) return; + const filePath = path.join(currentDir, entry.name); + if (!SOURCE_EXTENSIONS.has(path.extname(entry.name))) return; + files.push(filePath); + }) + ); + } + + await walk(rootDir); + return files; +} + +/** + * @param {string} specifier + * @returns {{ bare: string; suffix: string }} + */ +function splitSpecifier(specifier) { + const match = specifier.match(/^([^?#]+)([?#].*)?$/); + if (!match) { + return { bare: specifier, suffix: '' }; + } + + return { + bare: match[1], + suffix: match[2] ?? '', + }; +} + +/** + * @param {string} absoluteTargetPath + * @param {AliasEntry[]} aliases + * @returns {AliasEntry | undefined} + */ +function findMatchingAlias(absoluteTargetPath, aliases) { + return aliases.find(({ absolutePath }) => { + const rel = path.relative(absolutePath, absoluteTargetPath); + return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel)); + }); +} + +/** + * @param {string} filePath + * @param {string} specifier + * @param {AliasEntry[]} aliases + * @param {string} projectRoot + * @returns {string | null} + */ +function getRewrittenSpecifier(filePath, specifier, aliases, projectRoot) { + const normalizedFilePath = path.normalize(path.relative(projectRoot, filePath)); + const { bare, suffix } = splitSpecifier(specifier); + + if ( + !MATRIX_IMPORT_BOUNDARY_FILES.has(normalizedFilePath) && + (bare === 'matrix-js-sdk' || bare.startsWith('matrix-js-sdk/')) + ) { + return `$types/matrix-sdk${suffix}`; + } + + if (!/^\.\.(?:\/|$)/.test(bare)) { + return null; + } + + const absoluteTargetPath = path.resolve(path.dirname(filePath), bare); + const matchedAlias = findMatchingAlias(absoluteTargetPath, aliases); + if (!matchedAlias) return null; + + const aliasRelativePath = toPosix(path.relative(matchedAlias.absolutePath, absoluteTargetPath)); + const aliasImport = aliasRelativePath + ? `${matchedAlias.alias}/${aliasRelativePath}` + : matchedAlias.alias; + return `${aliasImport}${suffix}`; +} + +/** + * @param {import('typescript').SourceFile} sourceFile + * @param {import('typescript').StringLiteral} literalNode + * @param {TextReplacement[]} replacements + * @param {AliasEntry[]} aliases + * @param {string} filePath + * @param {string} projectRoot + */ +function queueReplacement(sourceFile, literalNode, replacements, aliases, filePath, projectRoot) { + const specifier = literalNode.text; + const rewrittenSpecifier = getRewrittenSpecifier(filePath, specifier, aliases, projectRoot); + if (!rewrittenSpecifier || rewrittenSpecifier === specifier) return; + + replacements.push({ + start: literalNode.getStart(sourceFile) + 1, + end: literalNode.getEnd() - 1, + original: specifier, + value: rewrittenSpecifier, + }); +} + +/** + * @param {string} sourceCode + * @param {TextReplacement[]} replacements + * @returns {string} + */ +export function applyTextReplacements(sourceCode, replacements) { + return replacements.reduce( + (code, replacement) => + code.slice(0, replacement.start) + replacement.value + code.slice(replacement.end), + sourceCode + ); +} + +/** + * @param {string} filePath + * @param {string} sourceCode + * @param {AliasEntry[]} aliases + * @param {string} projectRoot + */ +export function rewriteSourceImports(filePath, sourceCode, aliases, projectRoot) { + const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true); + /** @type {TextReplacement[]} */ + const replacements = []; + + /** + * @param {import('typescript').Node} node + */ + function visit(node) { + if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { + queueReplacement( + sourceFile, + node.moduleSpecifier, + replacements, + aliases, + filePath, + projectRoot + ); + } else if ( + ts.isExportDeclaration(node) && + node.moduleSpecifier && + ts.isStringLiteral(node.moduleSpecifier) + ) { + queueReplacement( + sourceFile, + node.moduleSpecifier, + replacements, + aliases, + filePath, + projectRoot + ); + } else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) { + const { literal } = node.argument; + if (ts.isStringLiteral(literal)) { + queueReplacement(sourceFile, literal, replacements, aliases, filePath, projectRoot); + } + } else if (ts.isCallExpression(node) && node.arguments.length > 0) { + const firstArg = node.arguments[0]; + if (ts.isStringLiteral(firstArg)) { + const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword; + const isRequire = ts.isIdentifier(node.expression) && node.expression.text === 'require'; + if (isDynamicImport || isRequire) { + queueReplacement(sourceFile, firstArg, replacements, aliases, filePath, projectRoot); + } + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + if (replacements.length === 0) { + return { changed: false, updatedCode: sourceCode, replacements: 0, edits: [] }; + } + + /** @type {TextReplacement[]} */ + const uniqueReplacements = [ + ...new Map( + replacements.map((replacement) => [`${replacement.start}:${replacement.end}`, replacement]) + ).values(), + ].toSorted((a, b) => b.start - a.start); + + const updatedCode = applyTextReplacements(sourceCode, uniqueReplacements); + + return { + changed: updatedCode !== sourceCode, + updatedCode, + replacements: uniqueReplacements.length, + edits: uniqueReplacements.map((replacement) => ({ + from: replacement.original, + to: replacement.value, + })), + }; +} + +/** + * @param {string} relativePath + * @returns {string} + */ +function stripDeclarationExtension(relativePath) { + return relativePath + .replace(/\.d\.[cm]?ts$/i, '') + .replace(/\.[cm]?tsx?$/i, '') + .replace(/\.[cm]?js$/i, ''); +} + +/** + * @param {string} declarationFile + * @returns {string | null} + */ +export function getMatrixModuleSpecifierFromDeclarationFile(declarationFile) { + const normalizedFile = toPosix(declarationFile); + const marker = '/node_modules/matrix-js-sdk/'; + const markerIndex = normalizedFile.lastIndexOf(marker); + + if (markerIndex === -1) return null; + + const relativePath = normalizedFile.slice(markerIndex + marker.length); + return `matrix-js-sdk/${stripDeclarationExtension(relativePath)}`; +} + +/** + * @param {MatrixImportSpecifier[]} specifiers + * @returns {MatrixImportSpecifier[]} + */ +function sortSpecifiers(specifiers) { + return [...specifiers].toSorted((left, right) => + left.importedName.localeCompare(right.importedName) + ); +} + +/** + * @param {MatrixImportSpecifier} specifier + * @returns {string} + */ +function formatSpecifier({ importedName, localName }) { + return importedName === localName ? importedName : `${importedName} as ${localName}`; +} + +/** + * @param {Map} groups + * @returns {string[]} + */ +export function renderMatrixImportGroups(groups) { + /** @type {string[]} */ + const lines = []; + + [...groups.entries()] + .toSorted(([left], [right]) => left.localeCompare(right)) + .forEach(([moduleSpecifier, group]) => { + const valueSpecifiers = sortSpecifiers(group.values); + const typeSpecifiers = sortSpecifiers(group.types); + + if (valueSpecifiers.length > 0) { + lines.push( + `import { ${valueSpecifiers.map(formatSpecifier).join(', ')} } from '${moduleSpecifier}';` + ); + } + + if (typeSpecifiers.length > 0) { + lines.push( + `import type { ${typeSpecifiers.map(formatSpecifier).join(', ')} } from '${moduleSpecifier}';` + ); + } + }); + + return lines; +} diff --git a/src/app/components/GlobalKeyboardShortcuts.tsx b/src/app/components/GlobalKeyboardShortcuts.tsx index 7246219a4c..0413c2787c 100644 --- a/src/app/components/GlobalKeyboardShortcuts.tsx +++ b/src/app/components/GlobalKeyboardShortcuts.tsx @@ -62,7 +62,7 @@ export function GlobalKeyboardShortcuts() { } else { const parents = roomToParents.get(roomId); if (parents && parents.size > 0) { - const spaceId = Array.from(parents)[0]; + const spaceId = [...parents].at(0); if (!spaceId) { navigate(getHomeRoomPath(roomIdOrAliasToNav)); return; @@ -84,13 +84,13 @@ export function GlobalKeyboardShortcuts() { const handleNextUnreadKeyDown = useCallback( (evt: KeyboardEvent) => { if (!isKeyHotkey('alt+n', evt)) return; - const unreadEntries = Array.from(roomToUnread.entries()) + const unreadEntries = [...roomToUnread.entries()] .filter(([id, u]) => u.total > 0 && id !== currentRoom?.roomId) .toSorted((a, b) => b[1].highlight - a[1].highlight || b[1].total - a[1].total); if (unreadEntries.length === 0) return; evt.preventDefault(); unreadIndexRef.current = 0; - const [roomId] = unreadEntries[0]!; + const [roomId] = unreadEntries[0]; navigateToRoom(roomId, unreadEntries.length - 1); }, [roomToUnread, currentRoom?.roomId, navigateToRoom] @@ -102,7 +102,7 @@ export function GlobalKeyboardShortcuts() { const isDown = isKeyHotkey('alt+shift+down', evt); const isUp = isKeyHotkey('alt+shift+up', evt); if (!isDown && !isUp) return; - const unreadEntries = Array.from(roomToUnread.entries()) + const unreadEntries = [...roomToUnread.entries()] .filter(([, u]) => u.total > 0) .toSorted((a, b) => b[1].highlight - a[1].highlight || b[1].total - a[1].total); if (unreadEntries.length === 0) return; diff --git a/src/app/components/LogoutDialog.tsx b/src/app/components/LogoutDialog.tsx index c1cf9d1ce5..96fcd59903 100644 --- a/src/app/components/LogoutDialog.tsx +++ b/src/app/components/LogoutDialog.tsx @@ -16,7 +16,7 @@ type LogoutDialogProps = { export const LogoutDialog = forwardRef( ({ handleClose }, ref) => { const mx = useMatrixClient(); - const hasEncryptedRoom = !!mx.getRooms().find((room) => room.hasEncryptionStateEvent()); + const hasEncryptedRoom = mx.getRooms().some((room) => room.hasEncryptionStateEvent()); const crossSigningActive = useCrossSigningActive(); const verificationStatus = useDeviceVerificationStatus( mx.getCrypto(), diff --git a/src/app/components/RenderMessageContent.tsx b/src/app/components/RenderMessageContent.tsx index 9b51b7aef3..a6ca4d1cf6 100644 --- a/src/app/components/RenderMessageContent.tsx +++ b/src/app/components/RenderMessageContent.tsx @@ -57,8 +57,8 @@ type RenderMessageContentProps = { const getMediaType = (url: string) => { const cleanUrl = url.toLowerCase(); - if (cleanUrl.match(/\.(mp4|webm|ogg)$/i)) return 'video'; - if (cleanUrl.match(/\.(png|jpg|jpeg|gif|webp)$/i) || cleanUrl.match(/@(jpeg|webp|png|jpg)$/i)) + if (/\.(mp4|webm|ogg)$/i.test(cleanUrl)) return 'video'; + if (/\.(png|jpg|jpeg|gif|webp)$/i.test(cleanUrl) || /@(jpeg|webp|png|jpg)$/i.test(cleanUrl)) return 'image'; return null; }; @@ -121,7 +121,7 @@ function RenderMessageContentInternal({ })); const mediaLinks = analyzed.filter((item) => item.type !== null); const previewCandidates = mediaLinks.length > 0 ? mediaLinks : analyzed; - const toRender = multiplePreviews ? previewCandidates : [previewCandidates[0]!]; + const toRender = multiplePreviews ? previewCandidates : [previewCandidates[0]]; return ( {toRender.map((item) => { diff --git a/src/app/components/SwipeableChatWrapper.tsx b/src/app/components/SwipeableChatWrapper.tsx index d4a5472989..c4b14dc0ec 100644 --- a/src/app/components/SwipeableChatWrapper.tsx +++ b/src/app/components/SwipeableChatWrapper.tsx @@ -5,12 +5,12 @@ import { useAtomValue } from 'jotai'; import { settingsAtom, RightSwipeAction } from '$state/settings'; import { mobileOrTablet } from '$utils/user-agent'; -interface SwipeableChatWrapperProps { +type SwipeableChatWrapperProps = { children: ReactNode; onOpenSidebar?: () => void; onOpenMembers?: () => void; onReply?: () => void; -} +}; export function SwipeableChatWrapper({ children, diff --git a/src/app/components/SwipeableOverlayWrapper.tsx b/src/app/components/SwipeableOverlayWrapper.tsx index a77b802f5d..d1ceb9c36b 100644 --- a/src/app/components/SwipeableOverlayWrapper.tsx +++ b/src/app/components/SwipeableOverlayWrapper.tsx @@ -5,11 +5,11 @@ import { useAtomValue } from 'jotai'; import { settingsAtom } from '$state/settings'; import { mobileOrTablet } from '$utils/user-agent'; -interface SwipeableOverlayWrapperProps { +type SwipeableOverlayWrapperProps = { children: ReactNode; onClose: () => void; direction: 'left' | 'right'; -} +}; export function SwipeableOverlayWrapper({ children, diff --git a/src/app/components/create-room/AdditionalCreatorInput.tsx b/src/app/components/create-room/AdditionalCreatorInput.tsx index d88c1e555e..2dfcefa4fd 100644 --- a/src/app/components/create-room/AdditionalCreatorInput.tsx +++ b/src/app/components/create-room/AdditionalCreatorInput.tsx @@ -40,7 +40,7 @@ export const useAdditionalCreators = (defaultCreators?: string[]) => { setAdditionalCreators((creators) => { const creatorsSet = new Set(creators); creatorsSet.add(userId); - return Array.from(creatorsSet); + return [...creatorsSet]; }); }; @@ -48,7 +48,7 @@ export const useAdditionalCreators = (defaultCreators?: string[]) => { setAdditionalCreators((creators) => { const creatorsSet = new Set(creators); creatorsSet.delete(userId); - return Array.from(creatorsSet); + return [...creatorsSet]; }); }; diff --git a/src/app/components/editor/Editor.test.tsx b/src/app/components/editor/Editor.test.tsx index ab38d73a5b..1627a38dfc 100644 --- a/src/app/components/editor/Editor.test.tsx +++ b/src/app/components/editor/Editor.test.tsx @@ -380,7 +380,7 @@ describe('CustomEditor', () => { let resizeObserverCallback: ResizeObserverCallback | undefined; const observedElements = new Set(); const flushQueuedFrames = () => { - const pendingFrames = Array.from(queuedFrames.entries()); + const pendingFrames = [...queuedFrames.entries()]; queuedFrames.clear(); pendingFrames.forEach(([, callback]) => { callback(performance.now()); @@ -421,7 +421,7 @@ describe('CustomEditor', () => { act(() => { resizeObserverCallback?.( - Array.from(observedElements).map((target) => ({ target }) as ResizeObserverEntry), + Array.from(observedElements, (target) => ({ target }) as ResizeObserverEntry), {} as ResizeObserver ); }); @@ -489,7 +489,7 @@ describe('CustomEditor', () => { const flushQueuedFrames = () => { let safetyCounter = 0; while (queuedFrames.size > 0 && safetyCounter < 10) { - const pendingFrames = Array.from(queuedFrames.entries()); + const pendingFrames = [...queuedFrames.entries()]; queuedFrames.clear(); pendingFrames.forEach(([, callback]) => { callback(performance.now()); @@ -529,7 +529,7 @@ describe('CustomEditor', () => { act(() => { resizeObserverCallback?.( - Array.from(observedElements).map((target) => ({ target }) as ResizeObserverEntry), + Array.from(observedElements, (target) => ({ target }) as ResizeObserverEntry), {} as ResizeObserver ); }); diff --git a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx index 2b4776a2d8..c6a100bf4e 100644 --- a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx @@ -100,7 +100,7 @@ export function EmoticonAutocomplete({ useKeyDown(window, (evt: KeyboardEvent) => { onTabPress(evt, () => { if (autoCompleteEmoticon.length === 0) return; - const emoticon = autoCompleteEmoticon[0]!; + const emoticon = autoCompleteEmoticon[0]; const key = 'url' in emoticon ? emoticon.url : emoticon.unicode; handleAutocomplete(key, emoticon.shortcode); }); diff --git a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx index 90789525c5..bd52bce60d 100644 --- a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx @@ -133,7 +133,7 @@ export function RoomMentionAutocomplete({ const rId = autoCompleteRoomIds[0]; const r = mx.getRoom(rId); const name = r?.name ?? rId; - handleAutocomplete(r?.getCanonicalAlias() ?? rId ?? '', name as string); + handleAutocomplete(r?.getCanonicalAlias() ?? rId ?? '', name); }); }); diff --git a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx index f8aed12847..c0f37e5eb5 100644 --- a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx @@ -140,7 +140,7 @@ export function UserMentionAutocomplete({ handleAutocomplete(userId, userId); return; } - const roomMember = autoCompleteMembers[0]!; + const roomMember = autoCompleteMembers[0]; handleAutocomplete(roomMember.userId, getName(roomMember)); }); }); diff --git a/src/app/components/editor/input.ts b/src/app/components/editor/input.ts index b2476e4377..2c134b0656 100644 --- a/src/app/components/editor/input.ts +++ b/src/app/components/editor/input.ts @@ -227,7 +227,7 @@ const parseCodeBlockNode = (node: Element): CodeBlockElement[] | ParagraphElemen type: BlockType.Paragraph, children: [{ text }], })); - const childCode = node.children[0]!; + const childCode = node.children[0]; const className = isTag(childCode) && childCode.tagName === 'code' ? (childCode.attribs.class ?? '') : ''; const prefix = { text: `${mdSequence}${className.replace('language-', '')}` }; @@ -329,7 +329,7 @@ const parseHeadingNode = ( const headingMatch = node.name.match(/^h([123456])$/); const [, g1AsLevel] = headingMatch ?? ['h3', '3']; - const level = Number.parseInt(g1AsLevel!, 10); + const level = Number.parseInt(g1AsLevel, 10); const mdSequence = node.attribs['data-md']; if (mdSequence !== undefined) { @@ -457,7 +457,7 @@ export const domToEditorInput = ( return; } - if (node.name.match(/^h[123456]$/)) { + if (/^h[123456]$/.test(node.name)) { appendLine(); children.push(parseHeadingNode(node, processText)); return; diff --git a/src/app/components/editor/keyboard.ts b/src/app/components/editor/keyboard.ts index b45e9f9abe..5061b735bb 100644 --- a/src/app/components/editor/keyboard.ts +++ b/src/app/components/editor/keyboard.ts @@ -83,7 +83,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent): bo const blockToggled = BLOCK_KEYS.find((hotkey) => { if (isKeyHotkey(hotkey, event)) { event.preventDefault(); - toggleBlock(editor, BLOCK_HOTKEYS[hotkey]!); + toggleBlock(editor, BLOCK_HOTKEYS[hotkey]); return true; } return false; @@ -107,7 +107,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent): bo : INLINE_KEYS.find((hotkey) => { if (isKeyHotkey(hotkey, event)) { event.preventDefault(); - toggleMark(editor, INLINE_HOTKEYS[hotkey]!); + toggleMark(editor, INLINE_HOTKEYS[hotkey]); return true; } return false; diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index cf65cdcbce..aef88ebc7d 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -131,8 +131,9 @@ export const toMatrixCustomHTML = ( // strip nicknames if needed if (opts.stripNickname && opts.nickNameReplacement) { - opts.nickNameReplacement?.keys().forEach((key) => { - const replacement = opts.nickNameReplacement!.get(key) ?? ''; + const { nickNameReplacement } = opts; + [...nickNameReplacement.keys()].forEach((key) => { + const replacement = nickNameReplacement.get(key) ?? ''; line = line.replaceAll(key, replacement); }); } diff --git a/src/app/components/editor/utils.ts b/src/app/components/editor/utils.ts index 254d7a5b74..d32d644b03 100644 --- a/src/app/components/editor/utils.ts +++ b/src/app/components/editor/utils.ts @@ -26,7 +26,7 @@ export const isMarkActive = (editor: Editor, format: MarkType) => { export const isAnyMarkActive = (editor: Editor) => { const marks = Editor.marks(editor); - return marks && !!ALL_MARK_TYPE.find((type) => marks[type] === true); + return marks && ALL_MARK_TYPE.some((type) => marks[type] === true); }; export const toggleMark = (editor: Editor, format: MarkType) => { @@ -220,10 +220,10 @@ export const moveCursor = (editor: Editor, withSpace?: boolean) => { Transforms.collapse(editor, { edge: 'end' }); }; -interface PointUntilCharOptions { +type PointUntilCharOptions = { match: (char: string) => boolean; reverse?: boolean; -} +}; export const getPointUntilChar = ( editor: Editor, cursorPoint: BasePoint, diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index d209b2d0a4..c5f83fe385 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -418,8 +418,8 @@ export function EmojiBoard({ const searchList = useMemo(() => { let list: Array = []; - list = list.concat(imagePacks.flatMap((pack) => pack.getImages(usage))); - if (emojiTab) list = list.concat(emojis); + list = [...list, ...imagePacks.flatMap((pack) => pack.getImages(usage))]; + if (emojiTab) list = [...list, ...emojis]; return list; }, [emojiTab, usage, imagePacks]); @@ -601,7 +601,7 @@ export function EmojiBoard({ }} > {vItems.map((vItem) => { - const group = groups[vItem.index]!; + const group = groups[vItem.index]; return ( ( const spoilerClickHandler = useSpoilerClickHandler(); const htmlReactParserOptions = useMemo( () => - getReactCustomHtmlParser(mx, mEvents[0]!.getRoomId(), { + getReactCustomHtmlParser(mx, mEvents[0].getRoomId(), { settingsLinkBaseUrl, linkifyOpts, useAuthentication, diff --git a/src/app/components/image-pack-view/ImagePackContent.tsx b/src/app/components/image-pack-view/ImagePackContent.tsx index d162f745f2..56c2b12157 100644 --- a/src/app/components/image-pack-view/ImagePackContent.tsx +++ b/src/app/components/image-pack-view/ImagePackContent.tsx @@ -33,7 +33,7 @@ export const ImagePackContent = as<'div', ImagePackContentProps>( const [savedMeta, setSavedMeta] = useState(); const currentMeta = savedMeta ?? imagePack.meta; - const images = useMemo(() => Array.from(imagePack.images.collection.values()), [imagePack]); + const images = useMemo(() => [...imagePack.images.collection.values()], [imagePack]); const [files, setFiles] = useState([]); const [uploadedImages, setUploadedImages] = useState([]); const [imagesEditing, setImagesEditing] = useState>(new Set()); @@ -44,11 +44,9 @@ export const ImagePackContent = as<'div', ImagePackContentProps>( (shortcode: string): boolean => { const hasInPack = imagePack.images.collection.has(shortcode); if (hasInPack) return true; - const hasInUploaded = - uploadedImages.find((img) => img.shortcode === shortcode) !== undefined; + const hasInUploaded = uploadedImages.some((img) => img.shortcode === shortcode); if (hasInUploaded) return true; - const hasInSaved = - Array.from(savedImages).find(([, img]) => img.shortcode === shortcode) !== undefined; + const hasInSaved = [...savedImages].some(([, img]) => img.shortcode === shortcode); return hasInSaved; }, [imagePack, savedImages, uploadedImages] diff --git a/src/app/components/media/Video.tsx b/src/app/components/media/Video.tsx index b27b4d0e80..c7c8e2a457 100644 --- a/src/app/components/media/Video.tsx +++ b/src/app/components/media/Video.tsx @@ -1,6 +1,7 @@ import type { VideoHTMLAttributes } from 'react'; import { forwardRef, useEffect, useRef } from 'react'; import classNames from 'classnames'; +import { getMediaVolume, setMediaVolume } from '$state/mediaVolume'; import * as css from './media.css'; export const Video = forwardRef>( @@ -10,8 +11,6 @@ export const Video = forwardRef(null); useEffect(() => { - const stored = localStorage.getItem(MEDIA_VOLUME_KEY); - if (innerRef.current && stored !== null) { - const parsed = parseFloat(stored); - if (!Number.isNaN(parsed)) innerRef.current.volume = parsed; - } + const volume = getMediaVolume(); + if (innerRef.current && volume !== undefined) innerRef.current.volume = volume; }, []); return ( @@ -31,7 +27,7 @@ export function PersistedVolumeVideo({ {...props} ref={innerRef} onVolumeChange={(e) => { - localStorage.setItem(MEDIA_VOLUME_KEY, String((e.target as HTMLVideoElement).volume)); + setMediaVolume((e.target as HTMLVideoElement).volume); onVolumeChange?.(e); }} /> diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index 399228cc2e..f78ea7fcac 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -2,7 +2,8 @@ import type { CSSProperties, ReactNode } from 'react'; import { useMemo } from 'react'; import { Box, Chip, Icon, Icons, Text, toRem } from 'folds'; import type { IContent, IPreviewUrlResponse } from '$types/matrix-sdk'; -import { JUMBO_EMOJI_REG, URL_REG } from '$utils/regex'; +import { URL_REG } from '$utils/regex'; +import { isJumboEmojiText } from '$utils/emojiDetection'; import { trimReplyFromBody } from '$utils/room'; import type { IAudioContent, @@ -133,7 +134,7 @@ export function MText({ ) ) return true; - if (!JUMBO_EMOJI_REG.test(trimmedBody)) return false; + if (!isJumboEmojiText(trimmedBody)) return false; if (trimmedBody.includes(':')) { const hasImage = customBody && /]*>/i.test(customBody); @@ -231,7 +232,7 @@ export function MEmote({ return ; } const trimmedBody = trimReplyFromBody(body); - const isJumbo = JUMBO_EMOJI_REG.test(trimmedBody); + const isJumbo = isJumboEmojiText(trimmedBody); let bundleContent: BundleContent[] | undefined; const urlsMatch = trimmedBody.match(URL_REG); @@ -283,7 +284,7 @@ export function MNotice({ return ; } const trimmedBody = trimReplyFromBody(body); - const isJumbo = JUMBO_EMOJI_REG.test(trimmedBody); + const isJumbo = isJumboEmojiText(trimmedBody); let bundleContent: BundleContent[] | undefined; const urlsMatch = trimmedBody.match(URL_REG); diff --git a/src/app/components/message/RenderBody.tsx b/src/app/components/message/RenderBody.tsx index 415984d136..d5de6f081e 100644 --- a/src/app/components/message/RenderBody.tsx +++ b/src/app/components/message/RenderBody.tsx @@ -74,7 +74,7 @@ function splitBodyTextByAbbreviations( } const result = segments as TextSegment[]; for (let i = 0; i < result.length; i += 1) { - result[i]!.id = `txt-${i}`; + result[i].id = `txt-${i}`; } return result; } diff --git a/src/app/components/message/content/AudioContent.tsx b/src/app/components/message/content/AudioContent.tsx index f20bbca646..898c6f4e18 100644 --- a/src/app/components/message/content/AudioContent.tsx +++ b/src/app/components/message/content/AudioContent.tsx @@ -19,7 +19,7 @@ import { useThrottle } from '$hooks/useThrottle'; import { secondsToMinutesAndSeconds } from '$utils/common'; import { decryptFile, downloadEncryptedMedia, downloadMedia, mxcUrlToHttp } from '$utils/matrix'; import { useMediaAuthentication } from '$hooks/useMediaAuthentication'; -import { MEDIA_VOLUME_KEY } from '$components/media'; +import { getMediaVolume, setMediaVolume } from '$state/mediaVolume'; const PLAY_TIME_THROTTLE_OPS = { wait: 500, @@ -63,11 +63,8 @@ export function AudioContent({ const audioRef = useRef(null); useEffect(() => { - const stored = localStorage.getItem(MEDIA_VOLUME_KEY); - if (audioRef.current && stored !== null) { - const parsed = parseFloat(stored); - if (!Number.isNaN(parsed)) audioRef.current.volume = parsed; - } + const volume = getMediaVolume(); + if (audioRef.current && volume !== undefined) audioRef.current.volume = volume; }, []); const [currentTime, setCurrentTime] = useState(0); @@ -240,7 +237,7 @@ export function AudioContent({ autoPlay ref={audioRef} onVolumeChange={(e) => { - localStorage.setItem(MEDIA_VOLUME_KEY, String((e.target as HTMLAudioElement).volume)); + setMediaVolume((e.target as HTMLAudioElement).volume); }} > {srcState.status === AsyncStatus.Success && } diff --git a/src/app/components/notification-banner/NotificationBanner.tsx b/src/app/components/notification-banner/NotificationBanner.tsx index 4476e83899..4c0cf20cbe 100644 --- a/src/app/components/notification-banner/NotificationBanner.tsx +++ b/src/app/components/notification-banner/NotificationBanner.tsx @@ -67,7 +67,7 @@ function BannerItem({ notification, onDismiss }: BannerItemProps) { if (dismissedRef.current) return; dismissedRef.current = true; setDismissing(true); - dismissAnimTimerRef.current = setTimeout(() => onDismiss(notification.id), 200); + dismissAnimTimerRef.current = setTimeout(onDismiss, 200, notification.id); }, [notification.id, onDismiss]); // Auto-dismiss timer — only runs when not paused. diff --git a/src/app/components/power/PowerIcon.tsx b/src/app/components/power/PowerIcon.tsx index f86331035e..63f9d6d8eb 100644 --- a/src/app/components/power/PowerIcon.tsx +++ b/src/app/components/power/PowerIcon.tsx @@ -1,14 +1,29 @@ -import { JUMBO_EMOJI_REG } from '$utils/regex'; +import { isJumboEmojiText } from '$utils/emojiDetection'; import * as css from './style.css'; type PowerIconProps = css.PowerIconVariants & { iconSrc: string; name?: string; }; + +const ALLOWED_ICON_PROTOCOLS = new Set(['http:', 'https:']); + +function getSafeIconUrl(iconSrc: string): string | undefined { + try { + const parsed = new URL(iconSrc); + return ALLOWED_ICON_PROTOCOLS.has(parsed.protocol) ? parsed.href : undefined; + } catch { + return undefined; + } +} + export function PowerIcon({ size, iconSrc, name }: PowerIconProps) { - return JUMBO_EMOJI_REG.test(iconSrc) ? ( - {iconSrc} - ) : ( - {name} - ); + if (isJumboEmojiText(iconSrc, 1)) { + return {iconSrc}; + } + + const safeIconUrl = getSafeIconUrl(iconSrc); + if (!safeIconUrl) return null; + + return {name}; } diff --git a/src/app/components/power/PowerSelector.tsx b/src/app/components/power/PowerSelector.tsx index 8d4d61876a..4f53661e97 100644 --- a/src/app/components/power/PowerSelector.tsx +++ b/src/app/components/power/PowerSelector.tsx @@ -36,11 +36,11 @@ export const PowerSelector = forwardRef( aria-pressed={selected} radii="300" onClick={selected ? undefined : () => onChange(power)} - before={} + before={} after={{power}} > - {tag!.name} + {tag.name} ); diff --git a/src/app/components/telemetry-consent/TelemetryConsentBanner.tsx b/src/app/components/telemetry-consent/TelemetryConsentBanner.tsx index 5c1e90a082..6335cf7af4 100644 --- a/src/app/components/telemetry-consent/TelemetryConsentBanner.tsx +++ b/src/app/components/telemetry-consent/TelemetryConsentBanner.tsx @@ -1,14 +1,11 @@ import { useEffect, useRef, useState } from 'react'; import { Box, Button, Icon, Icons, Text } from 'folds'; +import { isSentryDecided, setSentryEnabled } from '$state/sentryStorage'; import * as css from './TelemetryConsentBanner.css'; -const SENTRY_KEY = 'sable_sentry_enabled'; - export function TelemetryConsentBanner() { const isSentryConfigured = Boolean(import.meta.env.VITE_SENTRY_DSN); - const [visible, setVisible] = useState( - isSentryConfigured && localStorage.getItem(SENTRY_KEY) === null - ); + const [visible, setVisible] = useState(isSentryConfigured && !isSentryDecided()); const [dismissing, setDismissing] = useState(false); const dismissTimerRef = useRef | null>(null); @@ -22,14 +19,14 @@ export function TelemetryConsentBanner() { if (!visible) return null; const handleEnable = () => { - localStorage.setItem(SENTRY_KEY, 'true'); + setSentryEnabled(true); window.location.reload(); }; const handleDecline = () => { - localStorage.setItem(SENTRY_KEY, 'false'); + setSentryEnabled(false); setDismissing(true); - dismissTimerRef.current = setTimeout(() => setVisible(false), 220); + dismissTimerRef.current = setTimeout(setVisible, 220, false); }; return ( diff --git a/src/app/components/url-preview/ClientPreview.tsx b/src/app/components/url-preview/ClientPreview.tsx index cea5b3c6f2..79347a70e3 100644 --- a/src/app/components/url-preview/ClientPreview.tsx +++ b/src/app/components/url-preview/ClientPreview.tsx @@ -11,7 +11,7 @@ import { Image } from '../media'; import { UrlPreview } from './UrlPreview'; import { VideoContent } from '../message'; -interface OEmbed { +type OEmbed = { type: 'photo' | 'video' | 'link' | 'rich'; version: '1.0'; title?: string; @@ -27,7 +27,7 @@ interface OEmbed { html?: string; width?: number; height?: number; -} +}; async function oEmbedData(url: string): Promise { const data = await fetch(url).then((resp) => resp.json()); @@ -168,7 +168,7 @@ function parseYoutubeLink(url: Readonly): YoutubeLink | null { // new URL can throw return null; } - const urlHost = parsedURL.host; + const urlHost = parsedURL.hostname.toLowerCase(); const urlSearchParams = parsedURL.searchParams; /** @@ -204,7 +204,7 @@ function parseYoutubeLink(url: Readonly): YoutubeLink | null { videoId, timestamp, playlist, - isMusic: url.includes('music.youtube.com'), + isMusic: urlHost === 'music.youtube.com' || urlHost.endsWith('.music.youtube.com'), }; } diff --git a/src/app/components/user-profile/PowerChip.tsx b/src/app/components/user-profile/PowerChip.tsx index 5b96698477..70e956e557 100644 --- a/src/app/components/user-profile/PowerChip.tsx +++ b/src/app/components/user-profile/PowerChip.tsx @@ -252,7 +252,7 @@ export function PowerChip({ userId }: { userId: string }) { )} {getPowers(powerLevelTags).map((power) => { - const powerTag = powerLevelTags[power]!; + const powerTag = powerLevelTags[power]; const powerTagIconSrc = powerTag.icon && getPowerTagIconSrc(mx, useAuthentication, powerTag.icon); diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index 4f5c4a69a0..0e2dd7023a 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -35,6 +35,7 @@ import { useSpoilerClickHandler } from '$hooks/useSpoilerClickHandler'; import { RenderBody } from '$components/message'; import { getSettings, settingsAtom } from '$state/settings'; import { filterPronounsByLanguage } from '$utils/pronouns'; +import { extractPlainTextFromCustomHtml } from '$utils/sanitize'; import { useSetting } from '$state/hooks/settings'; import { useSettingsLinkBaseUrl } from '$features/settings/useSettingsLinkBaseUrl'; import { TextViewerContent } from '$components/text-viewer'; @@ -135,7 +136,7 @@ function UserExtendedSection({ const safetyTrim = rawBio.length > 2048 ? rawBio.slice(0, 2048) : rawBio; - const visibleText = safetyTrim.replaceAll(/<[^>]*>?/gm, ''); + const visibleText = extractPlainTextFromCustomHtml(safetyTrim); const VISIBLE_LIMIT = 1024; if (visibleText.length <= VISIBLE_LIMIT) { diff --git a/src/app/features/add-existing/AddExisting.tsx b/src/app/features/add-existing/AddExisting.tsx index 1a589ced41..25b0a73282 100644 --- a/src/app/features/add-existing/AddExisting.tsx +++ b/src/app/features/add-existing/AddExisting.tsx @@ -100,7 +100,7 @@ export function AddExistingModal({ parentId, space, requestClose }: AddExistingM return true; } - return Array.from(parentIds).some((id) => isAncestor(sourceId, id, visited)); + return [...parentIds].some((id) => isAncestor(sourceId, id, visited)); }, [roomIdToParents] ); diff --git a/src/app/features/call-status/MemberSpeaking.tsx b/src/app/features/call-status/MemberSpeaking.tsx index f513a07f56..e952287ef6 100644 --- a/src/app/features/call-status/MemberSpeaking.tsx +++ b/src/app/features/call-status/MemberSpeaking.tsx @@ -8,7 +8,8 @@ type MemberSpeakingProps = { speakers: Set; }; export function MemberSpeaking({ room, speakers }: MemberSpeakingProps) { - const speakingNames = Array.from(speakers).map( + const speakingNames = Array.from( + speakers, (userId) => getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId ); return ( diff --git a/src/app/features/call/CallMemberCard.tsx b/src/app/features/call/CallMemberCard.tsx index c6b4517285..045c7e15e9 100644 --- a/src/app/features/call/CallMemberCard.tsx +++ b/src/app/features/call/CallMemberCard.tsx @@ -12,11 +12,11 @@ import { UserAvatar } from '../../components/user-avatar'; import { getMouseEventCords } from '../../utils/dom'; import * as css from './styles.css'; -interface MemberWithMembershipData { +type MemberWithMembershipData = { membershipData?: SessionMembershipData & { 'm.call.intent': 'video' | 'audio'; }; -} +}; type CallMemberCardProps = { member: CallMembership; diff --git a/src/app/features/call/CallView.tsx b/src/app/features/call/CallView.tsx index 861af9175d..02ff9ea16e 100644 --- a/src/app/features/call/CallView.tsx +++ b/src/app/features/call/CallView.tsx @@ -148,9 +148,9 @@ function CallJoined({ joined, containerRef }: CallJoinedProps) { ); } -interface CallViewProps { +type CallViewProps = { resizable?: boolean; -} +}; export function CallView({ resizable }: CallViewProps) { const room = useRoom(); diff --git a/src/app/features/common-settings/developer-tools/DevelopTools.tsx b/src/app/features/common-settings/developer-tools/DevelopTools.tsx index d35e20da77..ce65a0749f 100644 --- a/src/app/features/common-settings/developer-tools/DevelopTools.tsx +++ b/src/app/features/common-settings/developer-tools/DevelopTools.tsx @@ -80,7 +80,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) { const userId = mx.getUserId(); const clientSyncState = mx.getSyncState(); const liveEvents = room.getLiveTimeline().getEvents(); - const latestTimelineEvent = liveEvents[liveEvents.length - 1]; + const latestTimelineEvent = liveEvents.at(-1); const latestTimelineEventId = latestTimelineEvent?.getId() ?? null; const latestMessageEvent = [...liveEvents].toReversed().find((event) => { const type = event.getType(); @@ -477,92 +477,85 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) { - {Array.from(roomState.keys()) - .toSorted() - .map((eventType) => { - const expanded = eventType === expandStateType; - const stateKeyToEvents = roomState.get(eventType); - if (!stateKeyToEvents) return null; + {[...roomState.keys()].toSorted().map((eventType) => { + const expanded = eventType === expandStateType; + const stateKeyToEvents = roomState.get(eventType); + if (!stateKeyToEvents) return null; - return ( - - - setExpandStateType(expanded ? undefined : eventType) - } - variant="Surface" - fill="None" - size="300" - radii="0" - before={ - - } - after={{stateKeyToEvents.size}} + return ( + + + setExpandStateType(expanded ? undefined : eventType) + } + variant="Surface" + fill="None" + size="300" + radii="0" + before={ + + } + after={{stateKeyToEvents.size}} + > + + + {eventType} + + + + {expanded && ( +
- - - {eventType} - - - - {expanded && ( -
+ setComposeEvent({ type: eventType, stateKey: '' }) + } + variant="Surface" + fill="None" + size="300" + radii="0" + before={} > + + + Add New + + + + {[...stateKeyToEvents.keys()].toSorted().map((stateKey) => ( - setComposeEvent({ + onClick={() => { + setOpenStateEvent({ type: eventType, - stateKey: '', - }) - } + stateKey, + }); + }} + key={stateKey} variant="Surface" fill="None" size="300" radii="0" - before={} + after={} > - Add New + {stateKey ? `"${stateKey}"` : 'Default'} - {Array.from(stateKeyToEvents.keys()) - .toSorted() - .map((stateKey) => ( - { - setOpenStateEvent({ - type: eventType, - stateKey, - }); - }} - key={stateKey} - variant="Surface" - fill="None" - size="300" - radii="0" - after={} - > - - - {stateKey ? `"${stateKey}"` : 'Default'} - - - - ))} -
- )} - - ); - })} + ))} +
+ )} +
+ ); + })}
)} @@ -617,25 +610,23 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) { - {Array.from(accountData.keys()) - .toSorted() - .map((type) => ( - } - onClick={() => setAccountDataType(type)} - > - - - {type} - - - - ))} + {[...accountData.keys()].toSorted().map((type) => ( + } + onClick={() => setAccountDataType(type)} + > + + + {type} + + + + ))} )} diff --git a/src/app/features/common-settings/general/RoomUpgrade.tsx b/src/app/features/common-settings/general/RoomUpgrade.tsx index 0c1534f4ad..098ca5341b 100644 --- a/src/app/features/common-settings/general/RoomUpgrade.tsx +++ b/src/app/features/common-settings/general/RoomUpgrade.tsx @@ -57,7 +57,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { const allowAdditionalCreators = creatorsSupported(selectedRoomVersion); const { additionalCreators, addAdditionalCreator, removeAdditionalCreator } = - useAdditionalCreators(Array.from(creators)); + useAdditionalCreators([...creators]); const [upgradeState, upgrade] = useAsyncCallback( useCallback( diff --git a/src/app/features/common-settings/members/Members.tsx b/src/app/features/common-settings/members/Members.tsx index 71ef0a74d7..74621cdc6b 100644 --- a/src/app/features/common-settings/members/Members.tsx +++ b/src/app/features/common-settings/members/Members.tsx @@ -89,7 +89,7 @@ export function Members({ requestClose }: MembersProps) { const sortedMembers = useMemo( () => - Array.from(members) + [...members] .filter(membershipFilter.filterFn) .toSorted(memberSort.sortFn) .sort(memberPowerSort), @@ -296,7 +296,7 @@ export function Members({ requestClose }: MembersProps) { gap="100" > {virtualizer.getVirtualItems().map((vItem) => { - const tagOrMember = flattenTagMembers[vItem.index]!; + const tagOrMember = flattenTagMembers[vItem.index]; if ('userId' in tagOrMember) { const server = getMxIdServer(tagOrMember.userId); diff --git a/src/app/features/common-settings/permissions/Powers.tsx b/src/app/features/common-settings/permissions/Powers.tsx index 7ad8630c4f..cb935142c0 100644 --- a/src/app/features/common-settings/permissions/Powers.tsx +++ b/src/app/features/common-settings/permissions/Powers.tsx @@ -167,7 +167,7 @@ export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) { {getPowers(powerLevelTags).map((power) => { - const tag = powerLevelTags[power]!; + const tag = powerLevelTags[power]; const tagIconSrc = tag.icon && getPowerTagIconSrc(mx, useAuthentication, tag.icon); return ( diff --git a/src/app/features/common-settings/permissions/PowersEditor.tsx b/src/app/features/common-settings/permissions/PowersEditor.tsx index be1cb7da6b..1ed9bac04a 100644 --- a/src/app/features/common-settings/permissions/PowersEditor.tsx +++ b/src/app/features/common-settings/permissions/PowersEditor.tsx @@ -314,7 +314,7 @@ export function PowersEditor({ powerLevels, requestClose }: Readonly { const up = getUsedPowers(powerLevels); - return [up, Math.max(...Array.from(up))]; + return [up, Math.max(...up)]; }, [powerLevels]); const powerLevelTags = usePowerLevelTags(room, powerLevels); @@ -437,7 +437,7 @@ export function PowersEditor({ powerLevels, requestClose }: Readonly {getPowers(powerTags).map((power) => { - const tag = powerTags[power]!; + const tag = powerTags[power]; const tagIconSrc = tag.icon && getPowerTagIconSrc(mx, useAuthentication, tag.icon); diff --git a/src/app/features/lobby/DnD.tsx b/src/app/features/lobby/DnD.tsx index c39949a099..de4717b255 100644 --- a/src/app/features/lobby/DnD.tsx +++ b/src/app/features/lobby/DnD.tsx @@ -136,7 +136,7 @@ export const useDnDMonitor = ( onDragging(undefined); const { dropTargets } = location.current; if (dropTargets.length === 0) return; - onReorder(source.data as HierarchyItem, dropTargets[0]!.data as DropContainerData); + onReorder(source.data as HierarchyItem, dropTargets[0].data as DropContainerData); }, }), autoScrollForElements({ diff --git a/src/app/features/lobby/Lobby.tsx b/src/app/features/lobby/Lobby.tsx index 8d2b826ac5..fc816bbbd1 100644 --- a/src/app/features/lobby/Lobby.tsx +++ b/src/app/features/lobby/Lobby.tsx @@ -265,7 +265,7 @@ export function Lobby() { // As a subspace can be in multiple spaces, // only return true if all parent spaces are closed. - const allClosed = !Array.from(parentParentIds).some( + const allClosed = ![...parentParentIds].some( (id) => !getInClosedCategories(spaceId, id, parentId, visited) ); visited.delete(categoryId); @@ -289,7 +289,7 @@ export function Lobby() { return false; } - return !Array.from(parentIds).some((id) => !getInClosedCategories(spaceId, id, roomId)); + return ![...parentIds].some((id) => !getInClosedCategories(spaceId, id, roomId)); }; const [subspaceHierarchyLimit] = useSetting(settingsAtom, 'subspaceHierarchyLimit'); @@ -441,9 +441,9 @@ export function Lobby() { } } - const itemSpaces = Array.from( - hierarchy?.find((i) => i.space.roomId === containerParentId)?.rooms ?? [] - ); + const itemSpaces = [ + ...(hierarchy?.find((i) => i.space.roomId === containerParentId)?.rooms ?? []), + ]; const beforeItem: HierarchyItem | undefined = 'space' in containerItem ? undefined : containerItem; diff --git a/src/app/features/lobby/SpaceHierarchyItem.tsx b/src/app/features/lobby/SpaceHierarchyItem.tsx index 8297dadeef..ffbdc3a4f2 100644 --- a/src/app/features/lobby/SpaceHierarchyItem.tsx +++ b/src/app/features/lobby/SpaceHierarchyItem.tsx @@ -98,7 +98,7 @@ export const SpaceHierarchyItem = forwardRef { - onSpacesFound(Array.from(subspaces.values())); + onSpacesFound([...subspaces.values()]); }, [subspaces, onSpacesFound]); let childItems: HierarchyItemRoom[] | undefined = roomItems?.filter( diff --git a/src/app/features/message-search/MessageSearch.tsx b/src/app/features/message-search/MessageSearch.tsx index 893ff00eb4..7a5bce9a99 100644 --- a/src/app/features/message-search/MessageSearch.tsx +++ b/src/app/features/message-search/MessageSearch.tsx @@ -115,7 +115,7 @@ export function MessageSearch({ const groups = useMemo(() => data?.pages.flatMap((result) => result.groups) ?? [], [data]); const highlights = useMemo(() => { const mixed = data?.pages.flatMap((result) => result.highlights); - return Array.from(new Set(mixed)); + return [...new Set(mixed)]; }, [data]); const virtualizer = useVirtualizer({ diff --git a/src/app/features/message-search/SearchFilters.tsx b/src/app/features/message-search/SearchFilters.tsx index 7a5706ab1f..50631ecd5b 100644 --- a/src/app/features/message-search/SearchFilters.tsx +++ b/src/app/features/message-search/SearchFilters.tsx @@ -134,7 +134,7 @@ function SelectRoomButton({ roomList, selectedRooms, onChange }: SelectRoomButto getRoomNameStr, SEARCH_OPTS ); - const rooms = Array.from(searchResult?.items ?? roomList).toSorted(factoryRoomIdByAtoZ(mx)); + const rooms = [...(searchResult?.items ?? roomList)].toSorted(factoryRoomIdByAtoZ(mx)); const virtualizer = useVirtualizer({ count: rooms.length, @@ -244,7 +244,7 @@ function SelectRoomButton({ roomList, selectedRooms, onChange }: SelectRoomButto }} > {vItems.map((vItem) => { - const roomId = rooms[vItem.index]!; + const roomId = rooms[vItem.index]; const room = mx.getRoom(roomId); if (!room) return null; const selected = localSelected?.includes(roomId); diff --git a/src/app/features/message-search/useMessageSearch.ts b/src/app/features/message-search/useMessageSearch.ts index dfe205f691..0ac84d9751 100644 --- a/src/app/features/message-search/useMessageSearch.ts +++ b/src/app/features/message-search/useMessageSearch.ts @@ -37,7 +37,7 @@ const groupSearchResult = (results: ISearchResult[]): ResultGroup[] => { context: item.context, }; - const lastAddedGroup: ResultGroup | undefined = groups[groups.length - 1]; + const lastAddedGroup: ResultGroup | undefined = groups.at(-1); if (lastAddedGroup && roomId === lastAddedGroup.roomId) { lastAddedGroup.items.push(resultItem); return; diff --git a/src/app/features/room-settings/abbreviations/RoomAbbreviations.tsx b/src/app/features/room-settings/abbreviations/RoomAbbreviations.tsx index 913e6dbafb..8093a3326b 100644 --- a/src/app/features/room-settings/abbreviations/RoomAbbreviations.tsx +++ b/src/app/features/room-settings/abbreviations/RoomAbbreviations.tsx @@ -72,7 +72,7 @@ export function RoomAbbreviations({ requestClose, isSpace }: AbbreviationsProps) type SpaceEntryGroup = { spaceId: string; spaceName: string; entries: AbbreviationEntry[] }; const ancestorGroups = useMemo((): SpaceEntryGroup[] => { void ancestorUpdateCount; - return Array.from(getAllParents(roomToParents, room.roomId)).reduce( + return [...getAllParents(roomToParents, room.roomId)].reduce( (groups, parentId) => { const parentRoom = mx.getRoom(parentId); if (!parentRoom) return groups; diff --git a/src/app/features/room/CommandAutocomplete.tsx b/src/app/features/room/CommandAutocomplete.tsx index 61a62d8bba..6df4cbe572 100644 --- a/src/app/features/room/CommandAutocomplete.tsx +++ b/src/app/features/room/CommandAutocomplete.tsx @@ -68,7 +68,7 @@ export function CommandAutocomplete({ if (autoCompleteNames.length === 0) { return; } - const cmdName = autoCompleteNames[0]!; + const cmdName = autoCompleteNames[0]; handleAutocomplete(cmdName); }); }); diff --git a/src/app/features/room/RoomCallButton.tsx b/src/app/features/room/RoomCallButton.tsx index 3c60e5935f..54d59c6b96 100644 --- a/src/app/features/room/RoomCallButton.tsx +++ b/src/app/features/room/RoomCallButton.tsx @@ -6,9 +6,9 @@ import { callEmbedAtom } from '$state/callEmbed'; import { useMatrixClient } from '$hooks/useMatrixClient'; import { useCallPreferences } from '$state/hooks/callPreferences'; -interface RoomCallButtonProps { +type RoomCallButtonProps = { room: Room; -} +}; export function RoomCallButton({ room }: RoomCallButtonProps) { const startCall = useCallStart(); diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index ed2fdb6952..a1123a8a98 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -153,7 +153,7 @@ const getLatestThreadEventId = (room: Room, threadRootId: string): string => { (ev) => ev.getId() !== threadRootId && !reactionOrEditEvent(ev) ); if (filtered.length > 0) { - return filtered[filtered.length - 1]!.getId() ?? threadRootId; + return filtered.at(-1)?.getId() ?? threadRootId; } // Fall back to the live timeline if the Thread object hasn't been registered yet const liveEvents = room @@ -165,7 +165,7 @@ const getLatestThreadEventId = (room: Room, threadRootId: string): string => { ev.threadRootId === threadRootId && ev.getId() !== threadRootId && !reactionOrEditEvent(ev) ); if (liveEvents.length > 0) { - return liveEvents[liveEvents.length - 1]!.getId() ?? threadRootId; + return liveEvents.at(-1)?.getId() ?? threadRootId; } return threadRootId; }; @@ -210,9 +210,9 @@ const getReplyContent = (replyDraft: IReplyDraft | undefined, room?: Room): IEve const log = createLogger('RoomInput'); const debugLog = createDebugLogger('RoomInput'); -interface ReplyEventContent { +type ReplyEventContent = { 'm.relates_to'?: IEventRelation; -} +}; const createUploadItemKey = () => globalThis.crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`; @@ -547,7 +547,7 @@ export const RoomInput = forwardRef( if (contents.length > 0) { const replyContent = plainText?.length === 0 ? getReplyContent(replyDraft, room) : undefined; - if (replyContent) contents[0]!['m.relates_to'] = replyContent; + if (replyContent) contents[0]['m.relates_to'] = replyContent; if (threadRootId) { setReplyDraft({ userId: mx.getUserId() ?? '', @@ -800,7 +800,7 @@ export const RoomInput = forwardRef( mentionData.users.add(replyDraft.userId); } - content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); + content['m.mentions'] = getMentionContent([...mentionData.users], mentionData.room); if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; @@ -1176,19 +1176,17 @@ export const RoomInput = forwardRef( {uploadBoard && ( - {Array.from(selectedFiles) - .toReversed() - .map((fileItem) => ( - - ))} + {[...selectedFiles].toReversed().map((fileItem) => ( + + ))} )} diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 8bc5d770cb..51c6c96c41 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -458,7 +458,7 @@ export function RoomTimeline({ if (!el) return () => {}; const observer = new ResizeObserver((entries) => { - const newHeight = entries[0]!.contentRect.height; + const newHeight = entries[0].contentRect.height; const prev = prevViewportHeightRef.current; const atBottom = atBottomRef.current; const shrank = newHeight < prev; diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index c804f54ab0..5ed2196eeb 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -103,16 +103,16 @@ async function getPinsHash(pinnedIds: string[]): Promise { const encoder = new TextEncoder(); const data = encoder.encode(sorted); const hashBuffer = await crypto.subtle.digest('SHA-256', data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashArray = [...new Uint8Array(hashBuffer)]; const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); return hashHex.slice(0, 10); } -export interface PinReadMarker { +export type PinReadMarker = { hash: string; count: number; last_seen_id: string; -} +}; type RoomMenuProps = { room: Room; diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index 3171e3ba43..2aeec93f37 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -393,7 +393,7 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra const events = currentThread.events || []; if (events.length === 0) return; - const lastEvent = events[events.length - 1]; + const lastEvent = events.at(-1); if (!lastEvent || lastEvent.isSending()) return; const userId = mx.getUserId(); @@ -631,7 +631,7 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra const isInReplies = processedEventsRef.current.some((e) => e.id === targetId); if (!isRoot && !isInReplies) return; setJumpToEventId(targetId); - setTimeout(() => setJumpToEventId(undefined), 2500); + setTimeout(setJumpToEventId, 2500, undefined); const el = drawerRef.current; if (el) { const target = el.querySelector(`[data-message-id="${targetId}"]`); diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 7bc04b46ea..7f381a9dda 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -168,7 +168,7 @@ export const MessagePinItem = as< const handlePin = () => { const eventId = mEvent.getId(); const pinContent: RoomPinnedEventsEventContent = { - pinned: Array.from(pinnedEvents).filter((id) => id !== eventId), + pinned: [...pinnedEvents].filter((id) => id !== eventId), }; if (!isPinned && eventId) { pinContent.pinned.push(eventId); diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index edcb4c4c3d..c08a6a958c 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -208,7 +208,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( mentionData.users.add(prevMentionId); }); - const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room); + const mMentions = getMentionContent([...mentionData.users], mentionData.room); newContent['m.mentions'] = mMentions; contentBody['m.mentions'] = mMentions; diff --git a/src/app/features/room/message/Reactions.tsx b/src/app/features/room/message/Reactions.tsx index e2fc884782..5145e42323 100644 --- a/src/app/features/room/message/Reactions.tsx +++ b/src/app/features/room/message/Reactions.tsx @@ -73,7 +73,7 @@ export const Reactions = as<'div', ReactionsProps>( ref={ref} > {reactions.map(([key, events]) => { - const rEvents = Array.from(events); + const rEvents = [...events]; if (rEvents.length === 0 || typeof key !== 'string') return null; const myREvent = myUserId ? rEvents.find(factoryEventSentBy(myUserId)) : undefined; const isPressed = !!myREvent?.getRelation(); diff --git a/src/app/features/room/reaction-viewer/ReactionViewer.tsx b/src/app/features/room/reaction-viewer/ReactionViewer.tsx index 2f27fe5de3..e9e5650143 100644 --- a/src/app/features/room/reaction-viewer/ReactionViewer.tsx +++ b/src/app/features/room/reaction-viewer/ReactionViewer.tsx @@ -62,7 +62,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>( const getReactionsForKey = (key: string): MatrixEvent[] => { const reactSet = reactions.find(([k]) => k === key)?.[1]; if (!reactSet) return []; - return Array.from(reactSet); + return [...reactSet]; }; const selectedReactions = getReactionsForKey(selectedKey); diff --git a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx index 6117c33498..a2f7ffd303 100644 --- a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx +++ b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx @@ -314,7 +314,7 @@ export const RoomPinMenu = forwardRef( ); const pinnedEvents = useRoomPinnedEvents(room); - const sortedPinnedEvent = useMemo(() => Array.from(pinnedEvents).reverse(), [pinnedEvents]); + const sortedPinnedEvent = useMemo(() => pinnedEvents.toReversed(), [pinnedEvents]); const useAuthentication = useMediaAuthentication(); const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); diff --git a/src/app/features/search/Search.tsx b/src/app/features/search/Search.tsx index 1a80070d54..a9d265ceb1 100644 --- a/src/app/features/search/Search.tsx +++ b/src/app/features/search/Search.tsx @@ -322,7 +322,7 @@ export function Search({ requestClose }: SearchProps) { const exactParents = roomToParents.get(roomId); const perfectParent = - exactParents && guessPerfectParent(mx, roomId, Array.from(exactParents)); + exactParents && guessPerfectParent(mx, roomId, [...exactParents]); const unread = roomToUnread.get(roomId); diff --git a/src/app/features/settings/developer-tools/AccountData.tsx b/src/app/features/settings/developer-tools/AccountData.tsx index da21691898..9ca29fe3f6 100644 --- a/src/app/features/settings/developer-tools/AccountData.tsx +++ b/src/app/features/settings/developer-tools/AccountData.tsx @@ -14,16 +14,17 @@ type AccountDataProps = { }; export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataProps) { const mx = useMatrixClient(); - const [accountDataTypes, setAccountDataKeys] = useState(() => - // TODO: tighten this once account data event typing is standardized. - Array.from(mx.store.accountData.keys()) + const [accountDataTypes, setAccountDataKeys] = useState( + () => + // TODO: tighten this once account data event typing is standardized. + [...mx.store.accountData.keys()] ); useAccountDataCallback( mx, useCallback(() => { // TODO: tighten this once account data event typing is standardized. - setAccountDataKeys(Array.from(mx.store.accountData.keys())); + setAccountDataKeys([...mx.store.accountData.keys()]); }, [mx]) ); diff --git a/src/app/features/settings/developer-tools/SentrySettings.tsx b/src/app/features/settings/developer-tools/SentrySettings.tsx index 686df77983..1fb67b86b0 100644 --- a/src/app/features/settings/developer-tools/SentrySettings.tsx +++ b/src/app/features/settings/developer-tools/SentrySettings.tsx @@ -4,6 +4,7 @@ import { SequenceCard } from '$components/sequence-card'; import { SettingTile } from '$components/setting-tile'; import { SequenceCardStyle } from '$features/settings/styles.css'; import { toSettingsFocusIdPart } from '$features/settings/settingsLink'; +import { getSentryEnabled } from '$state/sentryStorage'; import type { LogCategory } from '$utils/debugLogger'; import { getDebugLogger } from '$utils/debugLogger'; @@ -52,7 +53,7 @@ export function SentrySettings() { }; const isSentryConfigured = Boolean(import.meta.env.VITE_SENTRY_DSN); - const sentryEnabled = localStorage.getItem('sable_sentry_enabled') === 'true'; + const sentryEnabled = getSentryEnabled(); const environment = import.meta.env.VITE_SENTRY_ENVIRONMENT || import.meta.env.MODE; const isProd = environment === 'production'; const traceSampleRate = isProd ? '10%' : '100%'; diff --git a/src/app/features/settings/devices/OtherDevices.tsx b/src/app/features/settings/devices/OtherDevices.tsx index a3eb4fcaab..9918f05bab 100644 --- a/src/app/features/settings/devices/OtherDevices.tsx +++ b/src/app/features/settings/devices/OtherDevices.tsx @@ -28,7 +28,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O const authMetadata = useAuthMetadata(); const accountManagementActions = useAccountManagementActions(); - const [deleted, setDeleted] = useState(new Set()); + const [deleted, setDeleted] = useState>(new Set()); const handleDashboardOIDC = useCallback(() => { const authUrl = authMetadata?.account_management_uri ?? authMetadata?.issuer; @@ -77,7 +77,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O const deleteDevices = useAsync( useCallback( async (authDict?: AuthDict) => { - await mx.deleteMultipleDevices(Array.from(deleted) as string[], authDict); + await mx.deleteMultipleDevices([...deleted], authDict); }, [mx, deleted] ), diff --git a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx index e9faed951a..7a8371ec92 100644 --- a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx +++ b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx @@ -79,9 +79,9 @@ function GlobalPackSelector({ const addSelected = (adds: PackAddress[]) => { setSelected((addresses) => { - const newAddresses = Array.from(addresses); + const newAddresses = [...addresses]; adds.forEach((address) => { - if (newAddresses.find((addr) => packAddressEqual(addr, address))) { + if (newAddresses.some((addr) => packAddressEqual(addr, address))) { return; } newAddresses.push(address); @@ -93,7 +93,7 @@ function GlobalPackSelector({ const removeSelected = (adds: PackAddress[]) => { setSelected((addresses) => { const newAddresses = addresses.filter( - (addr) => !adds.find((address) => packAddressEqual(addr, address)) + (addr) => !adds.some((address) => packAddressEqual(addr, address)) ); return newAddresses; }); @@ -132,7 +132,7 @@ function GlobalPackSelector({ paddingRight: config.space.S100, }} > - {Array.from(roomToPacks.entries()).map(([roomId, roomPacks]) => { + {Array.from(roomToPacks.entries(), ([roomId, roomPacks]) => { const room = mx.getRoom(roomId); if (!room) return null; const roomPackAddresses = roomPacks @@ -172,7 +172,7 @@ function GlobalPackSelector({ const { address } = pack; if (!address) return null; - const added = !!selected.find((addr) => packAddressEqual(addr, address)); + const added = selected.some((addr) => packAddressEqual(addr, address)); return ( roomsImagePack.filter( - (pack) => !globalPacks.find((p) => packAddressEqual(pack.address, p.address)) + (pack) => !globalPacks.some((p) => packAddressEqual(pack.address, p.address)) ), [roomsImagePack, globalPacks] ); @@ -285,7 +285,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { const unselectedGlobalPacks = useMemo( () => nonGlobalPacks.filter( - (pack) => !selectedPacks.find((addr) => packAddressEqual(pack.address, addr)) + (pack) => !selectedPacks.some((addr) => packAddressEqual(pack.address, addr)) ), [selectedPacks, nonGlobalPacks] ); @@ -323,7 +323,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { removedPacks.forEach((addr) => { if (updatedContent.rooms?.[addr.roomId]?.[addr.stateKey]) { - delete updatedContent.rooms[addr.roomId]![addr.stateKey]; + delete updatedContent.rooms[addr.roomId][addr.stateKey]; } }); @@ -354,7 +354,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined; const { address } = pack; if (!address) return null; - const removed = !!removedPacks.find((addr) => packAddressEqual(addr, address)); + const removed = removedPacks.some((addr) => packAddressEqual(addr, address)); return ( {globalPacks.map(renderPack)} {nonGlobalPacks - .filter((pack) => !!selectedPacks.find((addr) => packAddressEqual(pack.address, addr))) + .filter((pack) => selectedPacks.some((addr) => packAddressEqual(pack.address, addr))) .map(renderPack)} {hasChanges && ( diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 061953d14c..06ea584f3e 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -41,6 +41,12 @@ import { useMessageSpacingItems } from '$hooks/useMessageSpacing'; import { useDateFormatItems } from '$hooks/useDateFormat'; import { SequenceCardStyle } from '$features/settings/styles.css'; import { sessionsAtom, activeSessionIdAtom } from '$state/sessions'; +import { + getSentryEnabled, + getSentryReplayEnabled, + setSentryEnabled as persistSentryEnabled, + setSentryReplayEnabled, +} from '$state/sentryStorage'; import { useClientConfig } from '$hooks/useClientConfig'; import { resolveSlidingEnabled } from '$client/initMatrix'; import { isKeyHotkey } from 'is-hotkey'; @@ -1306,33 +1312,21 @@ function SettingsSyncSection() { } function DiagnosticsAndPrivacy() { - const [sentryEnabled, setSentryEnabled] = useState( - localStorage.getItem('sable_sentry_enabled') === 'true' - ); - const [sessionReplayEnabled, setSessionReplayEnabled] = useState( - localStorage.getItem('sable_sentry_replay_enabled') === 'true' - ); + const [sentryEnabled, setSentryEnabled] = useState(getSentryEnabled()); + const [sessionReplayEnabled, setSessionReplayEnabled] = useState(getSentryReplayEnabled()); const [needsRefresh, setNeedsRefresh] = useState(false); const isSentryConfigured = Boolean(import.meta.env.VITE_SENTRY_DSN); const handleSentryToggle = (enabled: boolean) => { setSentryEnabled(enabled); - if (enabled) { - localStorage.setItem('sable_sentry_enabled', 'true'); - } else { - localStorage.setItem('sable_sentry_enabled', 'false'); - } + persistSentryEnabled(enabled); setNeedsRefresh(true); }; const handleReplayToggle = (enabled: boolean) => { setSessionReplayEnabled(enabled); - if (enabled) { - localStorage.setItem('sable_sentry_replay_enabled', 'true'); - } else { - localStorage.removeItem('sable_sentry_replay_enabled'); - } + setSentryReplayEnabled(enabled); setNeedsRefresh(true); }; diff --git a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx index a0b52da48e..be94a1302d 100644 --- a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx +++ b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx @@ -20,7 +20,7 @@ type ShortcutCategory = { function formatKey(key: string): string { const isMac = - typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; + typeof navigator !== 'undefined' && navigator.platform.toUpperCase().includes('MAC'); return key .replace(/\bmod\b/g, isMac ? '⌘' : 'Ctrl') .replace(/\balt\b/gi, isMac ? '⌥' : 'Alt') diff --git a/src/app/features/settings/settingsLink.ts b/src/app/features/settings/settingsLink.ts index 3ff9bae41e..8bd34d1e28 100644 --- a/src/app/features/settings/settingsLink.ts +++ b/src/app/features/settings/settingsLink.ts @@ -197,7 +197,7 @@ const parseSettingsLinkQuery = ( const params = new URLSearchParams(search); if ( - Array.from(params.entries()).some( + [...params.entries()].some( ([key, value]) => SETTINGS_LINK_FORBIDDEN_QUERY_VALUE_PATTERN.test(key) || SETTINGS_LINK_FORBIDDEN_QUERY_VALUE_PATTERN.test(value) diff --git a/src/app/features/settings/useSettingsFocus.ts b/src/app/features/settings/useSettingsFocus.ts index 0ccda14959..4fc3d8b220 100644 --- a/src/app/features/settings/useSettingsFocus.ts +++ b/src/app/features/settings/useSettingsFocus.ts @@ -7,7 +7,7 @@ const getHighlightTarget = (target: HTMLElement): HTMLElement => target.closest('[data-sequence-card="true"]') ?? target.parentElement ?? target; const getFocusTarget = (focusId: string): HTMLElement | null => document.getElementById(focusId) ?? - Array.from(document.querySelectorAll('[data-settings-focus]')).find( + [...document.querySelectorAll('[data-settings-focus]')].find( (element) => element.getAttribute('data-settings-focus') === focusId ) ?? null; diff --git a/src/app/features/widgets/IntegrationManager.tsx b/src/app/features/widgets/IntegrationManager.tsx index cd90c04636..f401b4d4b4 100644 --- a/src/app/features/widgets/IntegrationManager.tsx +++ b/src/app/features/widgets/IntegrationManager.tsx @@ -17,11 +17,11 @@ import type { Room } from '$types/matrix-sdk'; import { useIntegrationManager, buildIntegrationManagerUrl } from '$hooks/useIntegrationManager'; import * as css from './IntegrationManager.css'; -interface IntegrationManagerProps { +type IntegrationManagerProps = { room: Room; open: boolean; onClose: () => void; -} +}; export function IntegrationManager({ room, open, onClose }: IntegrationManagerProps) { const { managers, scalarToken, loading, error } = useIntegrationManager(); diff --git a/src/app/features/widgets/WidgetIframe.tsx b/src/app/features/widgets/WidgetIframe.tsx index 95da8d357c..bfddaf338d 100644 --- a/src/app/features/widgets/WidgetIframe.tsx +++ b/src/app/features/widgets/WidgetIframe.tsx @@ -10,12 +10,12 @@ import { GenericWidgetDriver } from './GenericWidgetDriver'; const log = createLogger('WidgetIframe'); -interface WidgetIframeProps { +type WidgetIframeProps = { widget: IWidget; roomId: string; mx: MatrixClient; onCapabilityRequest?: CapabilityApprovalCallback; -} +}; export function WidgetIframe({ widget, roomId, mx, onCapabilityRequest }: WidgetIframeProps) { const iframeRef = useRef(null); @@ -74,7 +74,7 @@ export function WidgetIframe({ widget, roomId, mx, onCapabilityRequest }: Widget return; } const stateEvents = state.events?.get(type); - Array.from(stateEvents?.values() ?? []).forEach((eventObject: MatrixEvent) => { + [...(stateEvents?.values() ?? [])].forEach((eventObject: MatrixEvent) => { events.push(eventObject.event); }); messaging.transport.reply(ev.detail, { events }); @@ -83,7 +83,7 @@ export function WidgetIframe({ widget, roomId, mx, onCapabilityRequest }: Widget const readUpToMap: Record = {}; mx.getRooms().forEach((room) => { const roomEvents = room.getLiveTimeline()?.getEvents() || []; - const last = roomEvents[roomEvents.length - 1]; + const last = roomEvents.at(-1); if (last) { const id = last.getId(); if (id) readUpToMap[room.roomId] = id; diff --git a/src/app/hooks/timeline/useProcessedTimeline.ts b/src/app/hooks/timeline/useProcessedTimeline.ts index 1dd6d11c55..21a85d3ad8 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.ts +++ b/src/app/hooks/timeline/useProcessedTimeline.ts @@ -8,7 +8,7 @@ import { import { reactionOrEditEvent, isMembershipChanged } from '$utils/room'; import { inSameDay, minuteDifference } from '$utils/time'; -export interface UseProcessedTimelineOptions { +export type UseProcessedTimelineOptions = { items: number[]; linkedTimelines: EventTimeline[]; ignoredUsersSet: Set; @@ -26,9 +26,9 @@ export interface UseProcessedTimelineOptions { * where every reply legitimately has `threadRootId` set to the root. */ skipThreadFilter?: boolean; -} +}; -export interface ProcessedEvent { +export type ProcessedEvent = { id: string; itemIndex: number; mEvent: MatrixEvent; @@ -37,7 +37,7 @@ export interface ProcessedEvent { collapsed: boolean; willRenderNewDivider: boolean; willRenderDayDivider: boolean; -} +}; const MESSAGE_EVENT_TYPES = new Set([ 'm.room.message', diff --git a/src/app/hooks/timeline/useTimelineActions.ts b/src/app/hooks/timeline/useTimelineActions.ts index 9b66447d58..e3f4a0ab19 100644 --- a/src/app/hooks/timeline/useTimelineActions.ts +++ b/src/app/hooks/timeline/useTimelineActions.ts @@ -10,7 +10,7 @@ import { getMxIdLocalPart, toggleReaction } from '$utils/matrix'; import { getMemberDisplayName, getEditedEvent } from '$utils/room'; import { createMentionElement, moveCursor } from '$components/editor'; -export interface UseTimelineActionsOptions { +export type UseTimelineActionsOptions = { room: Room; mx: MatrixClient; editor: Editor; @@ -31,7 +31,7 @@ export interface UseTimelineActionsOptions { setOpenThread: (threadId: string | undefined) => void; handleEdit: (editId?: string) => void; handleOpenEvent: (eventId: string) => void; -} +}; export function useTimelineActions({ room, diff --git a/src/app/hooks/timeline/useTimelineEventRenderer.tsx b/src/app/hooks/timeline/useTimelineEventRenderer.tsx index 3ceba3c9f7..de90b53ccb 100644 --- a/src/app/hooks/timeline/useTimelineEventRenderer.tsx +++ b/src/app/hooks/timeline/useTimelineEventRenderer.tsx @@ -252,7 +252,7 @@ function ThreadReplyChip({ ); } -export interface TimelineEventRendererOptions { +export type TimelineEventRendererOptions = { room: Room; mx: MatrixClient; pushProcessor: PushProcessor; @@ -306,7 +306,7 @@ export interface TimelineEventRendererOptions { getMemberPowerTag: ReturnType; parseMemberEvent: ReturnType; }; -} +}; export function useTimelineEventRenderer({ room, @@ -1084,27 +1084,21 @@ export function useTimelineEventRenderer({ `:`} {(pinsAdded || pinsRemoved) && - pinsAdded - .concat(...pinsRemoved) - .slice(0, 4) - .map((x: string) => ( - - - - - } - /> - ))} + [...pinsAdded, ...pinsRemoved].slice(0, 4).map((x: string) => ( + + + + + } + /> + ))} } /> diff --git a/src/app/hooks/timeline/useTimelineSync.ts b/src/app/hooks/timeline/useTimelineSync.ts index c10b762b80..e18f80f80b 100644 --- a/src/app/hooks/timeline/useTimelineSync.ts +++ b/src/app/hooks/timeline/useTimelineSync.ts @@ -194,9 +194,7 @@ const useTimelinePagination = ( const fetched = countAfter - countBefore; if (fetched > 0 && fetched < 5) { - const checkTimeline = backwards - ? freshLTimelines[0] - : freshLTimelines[freshLTimelines.length - 1]; + const checkTimeline = backwards ? freshLTimelines[0] : freshLTimelines.at(-1); if (!checkTimeline) return; const checkDirection = backwards ? Direction.Backward : Direction.Forward; const stillHasToken = @@ -346,7 +344,7 @@ const useThreadUpdate = (room: Room, onUpdate: () => void) => { }, [room]); }; -export interface UseTimelineSyncOptions { +export type UseTimelineSyncOptions = { room: Room; mx: MatrixClient; eventId?: string; @@ -357,7 +355,7 @@ export interface UseTimelineSyncOptions { setUnreadInfo: Dispatch>>; hideReadsRef: React.MutableRefObject; readUptoEventIdRef: React.MutableRefObject; -} +}; export function useTimelineSync({ room, diff --git a/src/app/hooks/useAsyncCallback.test.tsx b/src/app/hooks/useAsyncCallback.test.tsx index c27d06478a..bea9809e27 100644 --- a/src/app/hooks/useAsyncCallback.test.tsx +++ b/src/app/hooks/useAsyncCallback.test.tsx @@ -79,9 +79,7 @@ describe('useAsyncCallback', () => { const successStates = result.current[0]; expect(successStates.status).toBe(AsyncStatus.Success); - if (successStates.status === AsyncStatus.Success) { - expect(successStates.data).toBe('fresh'); - } + expect(successStates).toMatchObject({ data: 'fresh' }); }); it('does not call setState after the component unmounts', async () => { diff --git a/src/app/hooks/useAsyncSearch.ts b/src/app/hooks/useAsyncSearch.ts index 394d0f9d0d..0db8280703 100644 --- a/src/app/hooks/useAsyncSearch.ts +++ b/src/app/hooks/useAsyncSearch.ts @@ -50,7 +50,7 @@ export const orderSearchItems = ( getItemStr: SearchItemStrGetter, options?: UseAsyncSearchOptions ): TSearchItem[] => { - const orderedItems: TSearchItem[] = Array.from(items); + const orderedItems: TSearchItem[] = [...items]; // we will consider "_" as word boundary char. // because in more use-cases it is used. (like: emojishortcode) diff --git a/src/app/hooks/useCallSignaling.ts b/src/app/hooks/useCallSignaling.ts index 73cf864e2d..acad6eb187 100644 --- a/src/app/hooks/useCallSignaling.ts +++ b/src/app/hooks/useCallSignaling.ts @@ -14,10 +14,10 @@ const debugLog = createDebugLogger('CallSignaling'); type CallPhase = 'IDLE' | 'RINGING_OUT' | 'RINGING_IN' | 'ACTIVE' | 'ENDED'; -interface SignalState { +type SignalState = { incoming: string | null; outgoing: string | null; -} +}; export function useCallSignaling() { const mx = useMatrixClient(); @@ -100,7 +100,7 @@ export function useCallSignaling() { const myUserId = mx.getUserId(); const now = Date.now(); - const signal = Array.from(mDirects).reduce( + const signal = [...mDirects].reduce( (acc, roomId) => { if (acc.incoming || mutedRoomIdRef.current === roomId) return acc; diff --git a/src/app/hooks/useCommands.ts b/src/app/hooks/useCommands.ts index 07b987a51d..bb8a5d4cd4 100644 --- a/src/app/hooks/useCommands.ts +++ b/src/app/hooks/useCommands.ts @@ -38,6 +38,7 @@ import { settingsAtom } from '$state/settings'; import { useOpenBugReportModal } from '$state/hooks/bugReportModal'; import { createRoomEncryptionState } from '$components/create-room'; import { parsePronounsInput } from '$utils/pronouns'; +import { extractPlainTextFromCustomHtml } from '$utils/sanitize'; import { sendFeedback } from '$utils/sendFeedbackToUser'; import { PKitCommandMessageHandler } from '$plugins/pluralkit-handler/PKitCommandMessageHandler'; import { ErrorCode } from '../cs-errorcode'; @@ -139,7 +140,7 @@ export const parseTimestampFlag = (input: string): number | undefined => { return undefined; } - const value = Number.parseFloat(match[1]!); // supports decimal values + const value = Number.parseFloat(match[1]); // supports decimal values const unit = match[2]; const now = Date.now(); // in milliseconds @@ -178,13 +179,18 @@ const hslToHex = (h: number, s: number, l: number): string => { return `#${f(0)}${f(8)}${f(4)}`; }; +const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' }); + +const segmentText = (text: string): string[] => + Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment); + const getAllTextNodes = (root: Node): Node[] => root.nodeType === Node.TEXT_NODE ? [root] - : Array.from(root.childNodes).reduce( - (acc, child) => acc.concat(getAllTextNodes(child)), - [] - ); + : [...root.childNodes].reduce((acc, child) => { + acc.push(...getAllTextNodes(child)); + return acc; + }, []); export const rainbowify = (htmlInput: string): string => { const div = document.createElement('div'); @@ -192,7 +198,7 @@ export const rainbowify = (htmlInput: string): string => { const textNodes = getAllTextNodes(div); const totalTextLen = textNodes.reduce((acc, node) => { const text = node.textContent || ''; - const cleanLen = Array.from(text).filter((c) => c.trim().length > 0).length; + const cleanLen = segmentText(text).filter((c) => c.trim().length > 0).length; return acc + cleanLen; }, 0); @@ -200,7 +206,7 @@ export const rainbowify = (htmlInput: string): string => { const text = node.textContent || ''; if (!text.trim()) return currentGlobalIdx; - const chars = Array.from(text); + const chars = segmentText(text); const { html: newHtml, count: charsProcessed } = chars.reduce( (acc, char) => { @@ -340,7 +346,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { const userIds = rawIds.filter((id) => isUserId(id) && id !== mx.getSafeUserId()); if (userIds.length === 0) return; if (userIds.length === 1) { - const dmRoomId = getDMRoomFor(mx, userIds[0]!)?.roomId; + const dmRoomId = getDMRoomFor(mx, userIds[0])?.roomId; if (dmRoomId) { navigateRoom(dmRoomId); return; @@ -353,7 +359,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { preset: Preset.TrustedPrivateChat, initial_state: [createRoomEncryptionState()], }); - addRoomIdToMDirect(mx, result.room_id, userIds[0]!); + addRoomIdToMDirect(mx, result.room_id, userIds[0]); navigateRoom(result.room_id); }, }, @@ -467,7 +473,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { const rawIds = splitWithSpace(payload); const userIds = rawIds.filter((id) => isUserId(id)); if (userIds.length > 0) { - let ignoredUsers = mx.getIgnoredUsers().concat(userIds); + let ignoredUsers = [...mx.getIgnoredUsers(), ...userIds]; ignoredUsers = [...new Set(ignoredUsers)]; await mx.setIgnoredUsers(ignoredUsers); } @@ -643,7 +649,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { if (newAvatar.length === 0) { // no avatar, reset to global newAvatar = profile.avatarUrl; - } else if (!newAvatar.match(/^mxc:\/\/\S+$/)) { + } else if (!/^mxc:\/\/\S+$/.test(newAvatar)) { // bad mxc return; } @@ -853,7 +859,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { ?.getStateEvents(EventType.SpaceParent); const targetSpaceId = - parents && parents.length > 0 ? parents[0]!.getStateKey() : room.roomId; + parents && parents.length > 0 ? parents[0].getStateKey() : room.roomId; try { if (input === 'reset' || input === 'clear') { @@ -940,7 +946,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { ?.getStateEvents(EventType.SpaceParent); const targetSpaceId = - parents && parents.length > 0 ? parents[0]!.getStateKey() : room.roomId; + parents && parents.length > 0 ? parents[0].getStateKey() : room.roomId; try { if (input.toLowerCase() === 'reset' || input === '') { @@ -1084,7 +1090,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { ?.getStateEvents(EventType.SpaceParent); const targetSpaceId = - parents && parents.length > 0 ? parents[0]!.getStateKey() : room.roomId; + parents && parents.length > 0 ? parents[0].getStateKey() : room.roomId; try { if (['reset', 'clear', ''].includes(rawInput.toLowerCase())) { @@ -1371,14 +1377,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { await mx.sendMessage(room.roomId, { msgtype: MsgType.Text, - body: payload - .replaceAll('
', '\n') - .replaceAll('
  • ', '\n- ') - .replaceAll( - /(.*?))"(.*?)>(?(.*?))<\/a>/g, - '[$]($)' - ) - .replaceAll(/<[^>]*>/g, ''), + body: extractPlainTextFromCustomHtml(payload), format: 'org.matrix.custom.html', formatted_body: payload, }); diff --git a/src/app/hooks/useComposingCheck.ts b/src/app/hooks/useComposingCheck.ts index 9208fcf339..a9f508c9d0 100644 --- a/src/app/hooks/useComposingCheck.ts +++ b/src/app/hooks/useComposingCheck.ts @@ -2,9 +2,9 @@ import { useCallback, useEffect } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { lastCompositionEndAtom } from '$state/lastCompositionEnd'; -interface TimeStamped { +type TimeStamped = { readonly timeStamp: number; -} +}; export function useCompositionEndTracking(): void { const setLastCompositionEnd = useSetAtom(lastCompositionEndAtom); @@ -24,13 +24,13 @@ export function useCompositionEndTracking(): void { }); } -interface IsComposingLike { +type IsComposingLike = { readonly timeStamp: number; readonly keyCode: number; readonly nativeEvent: { readonly isComposing?: boolean; }; -} +}; export function useComposingCheck({ compositionEndThreshold = 500, diff --git a/src/app/hooks/useDebounce.ts b/src/app/hooks/useDebounce.ts index 5f33976a3e..d3ebc7b3d6 100644 --- a/src/app/hooks/useDebounce.ts +++ b/src/app/hooks/useDebounce.ts @@ -1,9 +1,9 @@ import { useCallback, useRef } from 'react'; -export interface DebounceOptions { +export type DebounceOptions = { wait?: number; immediate?: boolean; -} +}; export type DebounceCallback = (...args: T) => void; export function useDebounce( diff --git a/src/app/hooks/useImageGestures.ts b/src/app/hooks/useImageGestures.ts index eebf022c3f..8bfdf7fe90 100644 --- a/src/app/hooks/useImageGestures.ts +++ b/src/app/hooks/useImageGestures.ts @@ -1,9 +1,9 @@ import { useState, useCallback, useRef, useEffect } from 'react'; -interface Vector2 { +type Vector2 = { x: number; y: number; -} +}; // calculate pointer position relative to the image center // @@ -103,7 +103,7 @@ export const useImageGestures = (active: boolean, step = 0.2, min = 0.1, max = 5 setCursor('grabbing'); if (activePointers.current.size === 2) { - const points = Array.from(activePointers.current.values()); + const points = [...activePointers.current.values()]; initialDist.current = Math.hypot(points[0].x - points[1].x, points[0].y - points[1].y); } }, @@ -117,7 +117,7 @@ export const useImageGestures = (active: boolean, step = 0.2, min = 0.1, max = 5 activePointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY }); if (activePointers.current.size === 2) { - const points = Array.from(activePointers.current.values()); + const points = [...activePointers.current.values()]; const currentDist = Math.hypot(points[0].x - points[1].x, points[0].y - points[1].y); const delta = currentDist / initialDist.current; diff --git a/src/app/hooks/useImagePackRooms.ts b/src/app/hooks/useImagePackRooms.ts index 6dd130bbc4..22b38e1e6a 100644 --- a/src/app/hooks/useImagePackRooms.ts +++ b/src/app/hooks/useImagePackRooms.ts @@ -10,7 +10,7 @@ export const useImagePackRooms = ( const mx = useMatrixClient(); const imagePackRooms: Room[] = useMemo(() => { - const allParentSpaces = [roomId].concat(Array.from(getAllParents(roomToParents, roomId))); + const allParentSpaces = [roomId, ...getAllParents(roomToParents, roomId)]; return allParentSpaces.reduce((list, rId) => { const r = mx.getRoom(rId); if (r) list.push(r); diff --git a/src/app/hooks/useImagePacks.ts b/src/app/hooks/useImagePacks.ts index 8e568e4206..cc99d5b02b 100644 --- a/src/app/hooks/useImagePacks.ts +++ b/src/app/hooks/useImagePacks.ts @@ -24,8 +24,8 @@ import { CustomStateEvent } from '$types/matrix/room'; const imagePackEqual = (a: ImagePack | undefined, b: ImagePack | undefined): boolean => { if (!a && !b) return true; if (!a || !b) return false; - const aImages = Array.from(a.images.collection.entries()); - const bImages = Array.from(b.images.collection.entries()); + const aImages = [...a.images.collection.entries()]; + const bImages = [...b.images.collection.entries()]; if (aImages.length !== bImages.length) return false; const sameImages = aImages.every(([shortcode, image], index) => { const other = bImages[index]; @@ -134,7 +134,7 @@ export const useGlobalImagePacks = (): ImagePack[] => { typeof stateKey === 'string' ) { setGlobalPacks((prev) => { - const global = !!prev.find( + const global = prev.some( (pack) => pack.address && pack.address.roomId === roomId && pack.address.stateKey === stateKey ); @@ -269,7 +269,7 @@ export const useRoomsImagePacks = (rooms: Room[]) => { useCallback( (mEvent) => { if ( - rooms.find((room) => room.roomId === mEvent.getRoomId()) && + rooms.some((room) => room.roomId === mEvent.getRoomId()) && mEvent.getType() === (CustomStateEvent.PoniesRoomEmotes as string) ) { setRoomPacks((prev) => { @@ -294,10 +294,11 @@ export const useRelevantImagePacks = (usage: ImageUsage, rooms: Room[]): ImagePa const packs = userPack ? [userPack] : []; const globalPackIds = new Set(globalPacks.map((pack) => pack.id)); - const relPacks = packs.concat( - globalPacks, - roomsPacks.filter((pack) => !globalPackIds.has(pack.id)) - ); + const relPacks = [ + ...packs, + ...globalPacks, + ...roomsPacks.filter((pack) => !globalPackIds.has(pack.id)), + ]; return relPacks.filter((pack) => pack.getImages(usage).length > 0); }, [userPack, globalPacks, roomsPacks, usage]); diff --git a/src/app/hooks/useIntegrationManager.ts b/src/app/hooks/useIntegrationManager.ts index e9247330a9..3cedccdf13 100644 --- a/src/app/hooks/useIntegrationManager.ts +++ b/src/app/hooks/useIntegrationManager.ts @@ -2,10 +2,10 @@ import { useCallback, useEffect, useState } from 'react'; import type { MatrixClient } from '$types/matrix-sdk'; import { useMatrixClient } from './useMatrixClient'; -export interface IntegrationManager { +export type IntegrationManager = { apiUrl: string; uiUrl: string; -} +}; const DEFAULT_MANAGERS: IntegrationManager[] = [ { diff --git a/src/app/hooks/useMediaConfig.ts b/src/app/hooks/useMediaConfig.ts index 929ebd004d..d633cd6b73 100644 --- a/src/app/hooks/useMediaConfig.ts +++ b/src/app/hooks/useMediaConfig.ts @@ -1,9 +1,9 @@ import { createContext, useContext } from 'react'; -export interface MediaConfig { +export type MediaConfig = { [key: string]: unknown; 'm.upload.size'?: number; -} +}; const MediaConfigContext = createContext(null); diff --git a/src/app/hooks/useMessageEdit.ts b/src/app/hooks/useMessageEdit.ts index 120137d36e..e0a1d51b41 100644 --- a/src/app/hooks/useMessageEdit.ts +++ b/src/app/hooks/useMessageEdit.ts @@ -3,10 +3,10 @@ import type { Editor } from 'slate'; import { ReactEditor } from 'slate-react'; import { isEmptyEditor, moveCursor } from '$components/editor'; -export interface UseMessageEditOptions { +export type UseMessageEditOptions = { onReset?: () => void; alive?: () => boolean; -} +}; /** * Manages the "edit mode" state for a message composer. diff --git a/src/app/hooks/usePerMessageProfile.ts b/src/app/hooks/usePerMessageProfile.ts index af525adddc..e3464a8c70 100644 --- a/src/app/hooks/usePerMessageProfile.ts +++ b/src/app/hooks/usePerMessageProfile.ts @@ -330,7 +330,7 @@ async function getRoomsUsingProfile(mx: MatrixClient, profileId: string): Promis const content: PerMessageProfileRoomAssociationWrapper | undefined = accountData?.getContent(); const associations = getAssociationsMap(content); const roomsUsingProfile: string[] = []; - Array.from(associations.entries()).forEach(([roomId, assoc]) => { + [...associations.entries()].forEach(([roomId, assoc]) => { if (assoc?.profileId === profileId) roomsUsingProfile.push(roomId); }); return roomsUsingProfile; diff --git a/src/app/hooks/usePowerLevelTags.ts b/src/app/hooks/usePowerLevelTags.ts index 167c73b670..83eb31ee66 100644 --- a/src/app/hooks/usePowerLevelTags.ts +++ b/src/app/hooks/usePowerLevelTags.ts @@ -97,7 +97,7 @@ export const usePowerLevelTags = (room: Room, powerLevels: IPowerLevels): PowerL const powerToTags: PowerLevelTags = { ...content }; const powers = getUsedPowers(powerLevels); - Array.from(powers).forEach((power) => { + [...powers].forEach((power) => { if (powerToTags[power]?.name === undefined) { powerToTags[power] = DEFAULT_TAGS[power] ?? generateFallbackTag(DEFAULT_TAGS, power); } diff --git a/src/app/hooks/usePowerLevels.ts b/src/app/hooks/usePowerLevels.ts index 01eb781eec..d66b84fc80 100644 --- a/src/app/hooks/usePowerLevels.ts +++ b/src/app/hooks/usePowerLevels.ts @@ -52,7 +52,7 @@ const fillMissingPowers = (powerLevels: IPowerLevels): IPowerLevels => } }); if (draftPl.notifications && typeof draftPl.notifications.room !== 'number') { - draftPl.notifications.room = DEFAULT_POWER_LEVELS.notifications.room as number; + draftPl.notifications.room = DEFAULT_POWER_LEVELS.notifications.room; } return draftPl; }); @@ -109,7 +109,7 @@ export const useRoomsPowerLevels = (rooms: Room[]): Map => roomId && event.getType() === (EventType.RoomPowerLevels as string) && event.getStateKey() === '' && - rooms.find((r) => r.roomId === roomId) + rooms.some((r) => r.roomId === roomId) ) { setRoomToPowerLevels(getRoomsPowerLevels()); } diff --git a/src/app/hooks/useRoomAbbreviations.ts b/src/app/hooks/useRoomAbbreviations.ts index b6bd30ba1f..7998f1ffae 100644 --- a/src/app/hooks/useRoomAbbreviations.ts +++ b/src/app/hooks/useRoomAbbreviations.ts @@ -58,7 +58,7 @@ export const useMergedAbbreviations = (room: Room): Map => { return useMemo(() => { // `updateCount` is a cache-busting key for state-event driven recomputation. void updateCount; - const allParentIds = Array.from(getAllParents(roomToParents, room.roomId)); + const allParentIds = [...getAllParents(roomToParents, room.roomId)]; const ancestorEntries = allParentIds.flatMap((parentId) => { const parentRoom = mx.getRoom(parentId); if (!parentRoom) return []; diff --git a/src/app/hooks/useRoomAccountData.ts b/src/app/hooks/useRoomAccountData.ts index 21cf45f427..d7a6ea2be0 100644 --- a/src/app/hooks/useRoomAccountData.ts +++ b/src/app/hooks/useRoomAccountData.ts @@ -6,7 +6,7 @@ export const useRoomAccountData = (room: Room): Map => { const getAccountData = useCallback((): Map => { const accountData = new Map(); - Array.from(room.accountData.entries()).forEach(([type, mEvent]) => { + [...room.accountData.entries()].forEach(([type, mEvent]) => { const content = mEvent.getContent(); accountData.set(type, content); }); diff --git a/src/app/hooks/useRoomWidgets.ts b/src/app/hooks/useRoomWidgets.ts index 45725db4af..1ca1ace369 100644 --- a/src/app/hooks/useRoomWidgets.ts +++ b/src/app/hooks/useRoomWidgets.ts @@ -7,10 +7,10 @@ import { useStateEventCallback } from './useStateEventCallback'; import { useForceUpdate } from './useForceUpdate'; import { CustomStateEvent } from '$types/matrix/room'; -export interface RoomWidget extends IWidget { +export type RoomWidget = { eventId?: string; sender?: string; -} +} & IWidget; export const resolveWidgetUrl = ( url: string, diff --git a/src/app/hooks/useSidebarItems.ts b/src/app/hooks/useSidebarItems.ts index 13c79e17b0..a02b880518 100644 --- a/src/app/hooks/useSidebarItems.ts +++ b/src/app/hooks/useSidebarItems.ts @@ -50,13 +50,13 @@ export const parseSidebar = ( typeof item === 'object' && typeof item.id === 'string' && Array.isArray(item.content) && - !items.find((i) => (typeof i === 'string' ? false : i.id === item.id)) + !items.some((i) => (typeof i === 'string' ? false : i.id === item.id)) ) { const safeContent = item.content.filter(safeToAdd); safeContent.forEach((i) => orphans.delete(i)); items.push({ ...item, - content: Array.from(new Set(safeContent)), + content: [...new Set(safeContent)], }); } }); diff --git a/src/app/hooks/useSpaceHierarchy.ts b/src/app/hooks/useSpaceHierarchy.ts index 6c4ebcec76..c43d31005d 100644 --- a/src/app/hooks/useSpaceHierarchy.ts +++ b/src/app/hooks/useSpaceHierarchy.ts @@ -277,7 +277,7 @@ const getSpaceJoinedHierarchy = ( }; childItems.push(childItem); }); - return ([spaceItem] as HierarchyItem[]).concat(sortRoomItems(spaceItem.roomId, childItems)); + return [spaceItem, ...sortRoomItems(spaceItem.roomId, childItems)]; }); return hierarchy; diff --git a/src/app/hooks/useThrottle.ts b/src/app/hooks/useThrottle.ts index 12b249f291..3d2875fd4e 100644 --- a/src/app/hooks/useThrottle.ts +++ b/src/app/hooks/useThrottle.ts @@ -1,9 +1,9 @@ import { useCallback, useRef } from 'react'; -export interface ThrottleOptions { +export type ThrottleOptions = { wait?: number; immediate?: boolean; -} +}; export type ThrottleCallback = (...args: T) => void; diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index aec62cc867..e0665b2755 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -99,7 +99,10 @@ const getFirstSession = () => { return getFallbackSession(); }; -export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => { +export const createRouter = ( + clientConfig: ClientConfig, + screenSize: ScreenSize +): ReturnType => { const { hashRouter } = clientConfig; const mobile = screenSize === ScreenSize.Mobile; diff --git a/src/app/pages/client/BackgroundNotifications.tsx b/src/app/pages/client/BackgroundNotifications.tsx index 35c1963486..41778eaad0 100644 --- a/src/app/pages/client/BackgroundNotifications.tsx +++ b/src/app/pages/client/BackgroundNotifications.tsx @@ -145,7 +145,7 @@ export function BackgroundNotifications() { const inactiveSessionsRef = useRef(inactiveSessions); inactiveSessionsRef.current = inactiveSessions; - interface NotifyOptions { + type NotifyOptions = { /** Title shown in the notification banner. */ title: string; /** Body text. */ @@ -163,7 +163,7 @@ export function BackgroundNotifications() { /** Optional callback invoked when the user clicks the notification (window.Notification * fallback path only; the SW path routes via its own notificationclick handler). */ onClick?: () => void; - } + }; useEffect(() => { if (!shouldRunBackgroundNotifications) { diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 70ba9221e3..3033597b89 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -326,8 +326,7 @@ export function ClientRoot({ children }: ClientRootProps) { 'SHA-256', new TextEncoder().encode(matrixUserId) ); - const hashHex = Array.from(new Uint8Array(hashBuffer)) - .map((b) => b.toString(16).padStart(2, '0')) + const hashHex = Array.from(new Uint8Array(hashBuffer), (b) => b.toString(16).padStart(2, '0')) .join('') .slice(0, 16); // Include the homeserver domain as a custom attribute — it is not PII (it is the diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index e84e04daa7..b102c83f58 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -215,7 +215,7 @@ export function Direct() { const sortedDirects = useMemo(() => { void activityCounter; - const items = Array.from(directs).toSorted(factoryRoomIdByActivity(mx)); + const items = [...directs].toSorted(factoryRoomIdByActivity(mx)); const hasUnread = (roomId: string) => { const unread = roomToUnread.get(roomId); return !!unread && (unread.total > 0 || unread.highlight > 0); diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index afd4de9365..0f7b094433 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -207,7 +207,7 @@ export function Home() { const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); const sortedRooms = useMemo(() => { - const items = Array.from(rooms).toSorted( + const items = [...rooms].toSorted( closedCategories.has(DEFAULT_CATEGORY_ID) ? factoryRoomIdByActivity(mx) : factoryRoomIdByAtoZ(mx) diff --git a/src/app/pages/client/inbox/Invites.tsx b/src/app/pages/client/inbox/Invites.tsx index ffbad26e36..d92ce49279 100644 --- a/src/app/pages/client/inbox/Invites.tsx +++ b/src/app/pages/client/inbox/Invites.tsx @@ -574,7 +574,7 @@ function SpamInvites({ ); const ignoredUsers = useIgnoredUsers(); - const unignoredUsers = Array.from(new Set(invites.map((invite) => invite.senderId))).filter( + const unignoredUsers = [...new Set(invites.map((invite) => invite.senderId))].filter( (user) => !ignoredUsers.includes(user) ); const [blockAllStatus, blockAll] = useAsyncCallback( diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx index 892409da7d..10a4f454a1 100644 --- a/src/app/pages/client/inbox/Notifications.tsx +++ b/src/app/pages/client/inbox/Notifications.tsx @@ -169,7 +169,7 @@ const useNotificationTimeline = ( if (currentTimeline.nextToken === from) { return { nextToken: data.next_token, - groups: from ? currentTimeline.groups.concat(groups) : groups, + groups: from ? [...currentTimeline.groups, ...groups] : groups, }; } return currentTimeline; diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 4ffd0b1605..fb99fca981 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -669,14 +669,14 @@ export function SpaceTabs({ scrollRef }: Readonly) { if (typeof containerItem === 'string') { const folder: ISidebarFolder = { id: randomStr(), - content: [containerItem].concat(child), + content: [containerItem, ...child], }; newItems.push(folder); return; } newItems.push({ ...containerItem.folder, - content: containerItem.folder.content.concat(child), + content: [...containerItem.folder.content, ...child], }); return; } diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index c4994d829a..c4d7d82806 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -461,7 +461,7 @@ export function Space() { // As a subspace can be in multiple spaces, // only return true if all parent spaces are closed. - const allClosed = !Array.from(parentParentIds).some( + const allClosed = ![...parentParentIds].some( (id) => !getInClosedCategories(spaceId, id, parentId, visited) ); visited.delete(categoryId); @@ -493,7 +493,7 @@ export function Space() { return false; } - return Array.from(childIds).some((id) => getContainsShowRoom(id, visited)); + return [...childIds].some((id) => getContainsShowRoom(id, visited)); }, [roomToUnread, selectedRoomId, roomToChildren] ); @@ -517,9 +517,7 @@ export function Space() { return false; } - const allCollapsed = !Array.from(parentIds).some( - (id) => !getInClosedCategories(spaceId, id, roomId) - ); + const allCollapsed = ![...parentIds].some((id) => !getInClosedCategories(spaceId, id, roomId)); ancestorsCollapsedCache.current.set(categoryId, allCollapsed); return allCollapsed; }; @@ -583,7 +581,7 @@ export function Space() { connectorStack = [{ aX: Math.round(renderDepth * PADDING_LEFT_DEPTH_OFFSET), aY: 0 }]; } - const lastConnector = connectorStack[connectorStack.length - 1]; + const lastConnector = connectorStack.at(-1); if (!lastConnector) return; // aX: numeric x where the vertical connector starts diff --git a/src/app/plugins/arborium/runtime.ts b/src/app/plugins/arborium/runtime.ts index 29f34b7648..44f7be5bf1 100644 --- a/src/app/plugins/arborium/runtime.ts +++ b/src/app/plugins/arborium/runtime.ts @@ -11,11 +11,11 @@ const LANGUAGE_COMPATIBILITY: Record = { markup: 'html', }; -export interface HighlightCodeInput { +export type HighlightCodeInput = { code: string; language?: string | null; allowDetect?: boolean; -} +}; export type HighlightResult = | { mode: 'plain'; html: string; language?: string } diff --git a/src/app/plugins/bad-words.ts b/src/app/plugins/bad-words.ts index 74fcaddb7b..65c13ec02c 100644 --- a/src/app/plugins/bad-words.ts +++ b/src/app/plugins/bad-words.ts @@ -3,9 +3,10 @@ import { sanitizeForRegex } from '$utils/regex'; const additionalBadWords: string[] = ['torture', 't0rture']; -const fullBadWordList = additionalBadWords.concat( - badWords.array.filter((word) => !additionalBadWords.includes(word)) -); +const fullBadWordList = [ + ...additionalBadWords, + ...badWords.array.filter((word) => !additionalBadWords.includes(word)), +]; export const BAD_WORDS_REGEX = new RegExp( `(\\b|_)(${fullBadWordList.map((word) => sanitizeForRegex(word)).join('|')})(\\b|_)`, diff --git a/src/app/plugins/call/CallEmbed.ts b/src/app/plugins/call/CallEmbed.ts index b31323d4b1..80fc950c2d 100644 --- a/src/app/plugins/call/CallEmbed.ts +++ b/src/app/plugins/call/CallEmbed.ts @@ -223,7 +223,7 @@ export class CallEmbed { this.mx.getRooms().forEach((room) => { // Timelines are most recent last const events = room.getLiveTimeline()?.getEvents() || []; - const roomEvent = events[events.length - 1]; + const roomEvent = events.at(-1); if (!roomEvent) return; // force later code to think the room is fresh this.readUpToMap[room.roomId] = roomEvent.getId()!; }); diff --git a/src/app/plugins/call/CallWidgetDriver.ts b/src/app/plugins/call/CallWidgetDriver.ts index 2678bf5834..31f0905fc7 100644 --- a/src/app/plugins/call/CallWidgetDriver.ts +++ b/src/app/plugins/call/CallWidgetDriver.ts @@ -52,7 +52,7 @@ export class CallWidgetDriver extends WidgetDriver { } public async validateCapabilities(requested: Set): Promise> { - const allow = Array.from(requested).filter((cap) => this.allowedCapabilities.has(cap)); + const allow = [...requested].filter((cap) => this.allowedCapabilities.has(cap)); return new Set(allow); } diff --git a/src/app/plugins/custom-emoji/ImagePack.ts b/src/app/plugins/custom-emoji/ImagePack.ts index 509367501a..3685e9712b 100644 --- a/src/app/plugins/custom-emoji/ImagePack.ts +++ b/src/app/plugins/custom-emoji/ImagePack.ts @@ -54,7 +54,7 @@ export class ImagePack { return this.stickerMemo; } - const images = Array.from(this.images.collection.values()).filter((image) => { + const images = [...this.images.collection.values()].filter((image) => { const usg = image.usage ?? this.meta.usage; return usg.includes(usage); }); diff --git a/src/app/plugins/pluralkit-handler/PKitCommandMessageHandler.ts b/src/app/plugins/pluralkit-handler/PKitCommandMessageHandler.ts index 7ceb1313f1..6826758096 100644 --- a/src/app/plugins/pluralkit-handler/PKitCommandMessageHandler.ts +++ b/src/app/plugins/pluralkit-handler/PKitCommandMessageHandler.ts @@ -84,7 +84,7 @@ export class PKitCommandMessageHandler { * @async */ private async memberHandler() { - if (this.message.match(pkMemberNewRegex)) { + if (pkMemberNewRegex.test(this.message)) { // adding a new member const cmdParts = pkMemberNewRegex.exec(this.message); if (!cmdParts) { @@ -108,7 +108,7 @@ export class PKitCommandMessageHandler { this.room, this.mx.getSafeUserId() ); - } else if (this.message.match(pkMemberRenameRegex)) { + } else if (pkMemberRenameRegex.test(this.message)) { // renaming a profile based on the name const cmdParts = pkMemberRenameRegex.exec(this.message); if (!cmdParts) { diff --git a/src/app/plugins/react-custom-html-parser.test.tsx b/src/app/plugins/react-custom-html-parser.test.tsx index 979c88bb0a..4c24917f30 100644 --- a/src/app/plugins/react-custom-html-parser.test.tsx +++ b/src/app/plugins/react-custom-html-parser.test.tsx @@ -10,6 +10,7 @@ import { getReactCustomHtmlParser, makeMentionCustomProps, renderMatrixMention, + scaleSystemEmoji, } from './react-custom-html-parser'; const settingsLinkBaseUrl = 'https://app.example'; @@ -128,6 +129,19 @@ describe('getReactCustomHtmlParser code blocks', () => { }); describe('react custom html parser', () => { + it.each(['🫩', '🫪', '🫯', '🇩🇪', '🙂‍↔️'])( + 'wraps modern emoji text %s in emoticon markup', + (emoji) => { + const result = scaleSystemEmoji(emoji); + expect(result).toHaveLength(1); + expect(typeof result[0]).not.toBe('string'); + } + ); + + it('does not wrap emojis inside urls', () => { + expect(scaleSystemEmoji('https://example.com/🫩')).toEqual(['https://example.com/🫩']); + }); + it('renders same-origin raw settings links as mention-style chips through the factory link render path', () => { const renderLink = factoryRenderLinkifyWithMention( settingsLinkBaseUrl, diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index cba5cee8c4..73850c254a 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -18,7 +18,8 @@ import { } from '$utils/matrix'; import { getMemberDisplayName } from '$utils/room'; import type { Nicknames } from '$state/nicknames'; -import { EMOJI_PATTERN, sanitizeForRegex, URL_NEG_LB } from '$utils/regex'; +import { sanitizeForRegex, URL_REG } from '$utils/regex'; +import { splitEmojiText } from '$utils/emojiDetection'; import { findAndReplace } from '$utils/findAndReplace'; import { onEnterOrSpace } from '$utils/keyboard'; import { copyToClipboard } from '$utils/dom'; @@ -35,8 +36,6 @@ import { } from './matrix-to'; import { getHexcodeForEmoji, getShortcodeFor } from './emoji'; -const EMOJI_REG_G = new RegExp(`${URL_NEG_LB}(${EMOJI_PATTERN})`, 'g'); - const shouldLinkifyDomText = (domNode: DOMText): boolean => !(domNode.parent && 'name' in domNode.parent && domNode.parent.name === 'code') && !(domNode.parent && 'name' in domNode.parent && domNode.parent.name === 'a'); @@ -255,19 +254,56 @@ export const factoryRenderLinkifyWithMention = ( return renderLink; }; -export const scaleSystemEmoji = (text: string): (string | JSX.Element)[] => - findAndReplace( - text, - EMOJI_REG_G, - (match, pushIndex) => ( - - - {match[0]} +const scaleEmojiChunk = (text: string, output: (string | JSX.Element)[]) => { + splitEmojiText(text).forEach((part) => { + if (part.type === 'text') { + output.push(part.value); + return; + } + + output.push( + + + {part.value} - ), - (txt) => txt - ); + ); + }); +}; + +export const scaleSystemEmoji = (text: string): (string | JSX.Element)[] => { + const parts: (string | JSX.Element)[] = []; + const urlReg = new RegExp(URL_REG); + let lastIndex = 0; + + [...text.matchAll(urlReg)].forEach((match) => { + const start = match.index ?? 0; + scaleEmojiChunk(text.slice(lastIndex, start), parts); + parts.push(match[0]); + lastIndex = start + match[0].length; + }); + + scaleEmojiChunk(text.slice(lastIndex), parts); + + const normalized: (string | JSX.Element)[] = []; + parts.forEach((part) => { + if (typeof part !== 'string') { + normalized.push(part); + return; + } + + if (part === '') return; + const previous = normalized.at(-1); + if (typeof previous === 'string') { + normalized[normalized.length - 1] = `${previous}${part}`; + return; + } + + normalized.push(part); + }); + + return normalized.length > 0 ? normalized : ['']; +}; export const makeHighlightRegex = (highlights: string[]): RegExp | undefined => { const pattern = highlights.map(sanitizeForRegex).join('|'); @@ -631,7 +667,7 @@ export const getReactCustomHtmlParser = ( rel: ensureNoopenerRel(props.rel), }; - const content = children.find((child) => !(child instanceof DOMText)) + const content = children.some((child) => !(child instanceof DOMText)) ? undefined : children.map((c) => (c instanceof DOMText ? c.data : '')).join(); diff --git a/src/app/plugins/text-area/Operations.ts b/src/app/plugins/text-area/Operations.ts index bc0c02efe7..52fa0ebf5b 100644 --- a/src/app/plugins/text-area/Operations.ts +++ b/src/app/plugins/text-area/Operations.ts @@ -1,7 +1,7 @@ import type { Cursor } from './Cursor'; -export interface Operations { +export type Operations = { select(cursor: Cursor): void; deselect(cursor: Cursor): void; insert(cursor: Cursor, text: string): Cursor; -} +}; diff --git a/src/app/plugins/utils.ts b/src/app/plugins/utils.ts index 6a2b0598d6..8a0af6b9e8 100644 --- a/src/app/plugins/utils.ts +++ b/src/app/plugins/utils.ts @@ -13,7 +13,7 @@ export const getEmoticonSearchStr: SearchItemStrGetter const names = [shortcode, item.label]; if (Array.isArray(item.shortcodes)) { - return names.concat(item.shortcodes); + return [...names, ...item.shortcodes]; } return names; }; diff --git a/src/app/plugins/via-servers.ts b/src/app/plugins/via-servers.ts index 5832a1cfbf..04a9a41f4c 100644 --- a/src/app/plugins/via-servers.ts +++ b/src/app/plugins/via-servers.ts @@ -74,5 +74,5 @@ export const getViaServers = (room: Room): string[] => { if (firstVia && mostPop3.includes(firstVia)) { mostPop3.splice(mostPop3.indexOf(firstVia), 1); } - return via.concat(mostPop3.slice(0, 2)); + return [...via, ...mostPop3.slice(0, 2)]; }; diff --git a/src/app/plugins/voice-recorder-kit/useVoiceRecorder.ts b/src/app/plugins/voice-recorder-kit/useVoiceRecorder.ts index 3d54ff803b..7b79f6ff53 100644 --- a/src/app/plugins/voice-recorder-kit/useVoiceRecorder.ts +++ b/src/app/plugins/voice-recorder-kit/useVoiceRecorder.ts @@ -13,6 +13,8 @@ const WAVEFORM_POINT_COUNT = 100; let sharedAudioContext: AudioContext | null = null; +const createSilenceLevels = (count: number): number[] => Array(count).fill(0.15); + function getSharedAudioContext(): AudioContext { if (!sharedAudioContext || sharedAudioContext.state === 'closed') { sharedAudioContext = new AudioContext(); @@ -22,7 +24,7 @@ function getSharedAudioContext(): AudioContext { // downsample an array of samples to a target count by averaging blocks of samples together function downsampleWaveform(samples: number[], targetCount: number): number[] { - if (samples.length === 0) return Array.from({ length: targetCount }, () => 0.15); + if (samples.length === 0) return createSilenceLevels(targetCount); if (samples.length <= targetCount) { const step = (samples.length - 1) / (targetCount - 1); return Array.from({ length: targetCount }, (_, i) => { @@ -60,9 +62,7 @@ export function useVoiceRecorder(options: UseVoiceRecorderOptions = {}): UseVoic const [isPlaying, setIsPlaying] = useState(false); const [isPaused, setIsPaused] = useState(false); const [seconds, setSeconds] = useState(0); - const [levels, setLevels] = useState(() => - Array.from({ length: BAR_COUNT }, () => 0.15) - ); + const [levels, setLevels] = useState(() => createSilenceLevels(BAR_COUNT)); const [error, setError] = useState(null); const [audioUrl, setAudioUrl] = useState(null); const [audioFile, setAudioFile] = useState(null); @@ -460,7 +460,7 @@ export function useVoiceRecorder(options: UseVoiceRecorderOptions = {}): UseVoic audioContextRef.current.suspend().catch(() => {}); } - setLevels(Array.from({ length: BAR_COUNT }, () => 0.15)); + setLevels(createSilenceLevels(BAR_COUNT)); } catch { setError('Error pausing recording'); } @@ -837,7 +837,7 @@ export function useVoiceRecorder(options: UseVoiceRecorderOptions = {}): UseVoic setSeconds(0); pausedTimeRef.current = 0; startTimeRef.current = null; - setLevels(Array.from({ length: BAR_COUNT }, () => 0.15)); + setLevels(createSilenceLevels(BAR_COUNT)); previousChunksRef.current = []; chunksRef.current = []; waveformSamplesRef.current = []; @@ -879,7 +879,7 @@ export function useVoiceRecorder(options: UseVoiceRecorderOptions = {}): UseVoic setSeconds(0); pausedTimeRef.current = 0; startTimeRef.current = null; - setLevels(Array.from({ length: BAR_COUNT }, () => 0.15)); + setLevels(createSilenceLevels(BAR_COUNT)); previousChunksRef.current = []; chunksRef.current = []; isResumingRef.current = false; diff --git a/src/app/state/closedLobbyCategories.ts b/src/app/state/closedLobbyCategories.ts index cac852af45..d13519dad1 100644 --- a/src/app/state/closedLobbyCategories.ts +++ b/src/app/state/closedLobbyCategories.ts @@ -35,7 +35,7 @@ export const makeClosedLobbyCategoriesAtom = (userId: string): ClosedLobbyCatego return new Set(arrayValue); }, (key, value) => { - const arrayValue = Array.from(value); + const arrayValue = [...value]; setLocalStorageItem(key, arrayValue); } ); diff --git a/src/app/state/closedNavCategories.ts b/src/app/state/closedNavCategories.ts index 39c93b7518..26ce34468c 100644 --- a/src/app/state/closedNavCategories.ts +++ b/src/app/state/closedNavCategories.ts @@ -35,7 +35,7 @@ export const makeClosedNavCategoriesAtom = (userId: string): ClosedNavCategories return new Set(arrayValue); }, (key, value) => { - const arrayValue = Array.from(value); + const arrayValue = [...value]; setLocalStorageItem(key, arrayValue); } ); diff --git a/src/app/state/mediaVolume.ts b/src/app/state/mediaVolume.ts new file mode 100644 index 0000000000..83372b04aa --- /dev/null +++ b/src/app/state/mediaVolume.ts @@ -0,0 +1,21 @@ +/** + * Centralized access to the persisted media volume preference. + * + * Plain functions rather than a Jotai atom because the value is applied + * directly to DOM element refs, not read reactively in JSX. + */ + +const MEDIA_VOLUME_KEY = 'mediaVolume'; + +/** Returns the persisted volume (0–1), or undefined if never set. */ +export const getMediaVolume = (): number | undefined => { + const stored = localStorage.getItem(MEDIA_VOLUME_KEY); + if (stored === null) return undefined; + const parsed = parseFloat(stored); + return Number.isNaN(parsed) ? undefined : parsed; +}; + +/** Persist the current volume (0–1). */ +export const setMediaVolume = (volume: number): void => { + localStorage.setItem(MEDIA_VOLUME_KEY, String(volume)); +}; diff --git a/src/app/state/openedSidebarFolder.ts b/src/app/state/openedSidebarFolder.ts index 7a0c2b96f4..59e2e6acd2 100644 --- a/src/app/state/openedSidebarFolder.ts +++ b/src/app/state/openedSidebarFolder.ts @@ -40,7 +40,7 @@ export const makeOpenedSidebarFolderAtom = (userId: string): OpenedSidebarFolder return new Set(arrayValue); }, (key, value) => { - const arrayValue = Array.from(value); + const arrayValue = [...value]; setLocalStorageItem(key, arrayValue); } ); diff --git a/src/app/state/room-list/utils.ts b/src/app/state/room-list/utils.ts index b1184a2641..a9f8e94849 100644 --- a/src/app/state/room-list/utils.ts +++ b/src/app/state/room-list/utils.ts @@ -23,7 +23,7 @@ export const useBindRoomsWithMembershipsAtom = ( useEffect(() => { const satisfyMembership = (room: Room): boolean => - !!memberships.find((membership) => membership === room.getMyMembership()); + memberships.some((membership) => membership === room.getMyMembership()); setRoomsAtom({ type: 'INITIALIZE', rooms: mx diff --git a/src/app/state/room/roomToOpenThread.ts b/src/app/state/room/roomToOpenThread.ts index 0a60fa4a73..c177cf0b5f 100644 --- a/src/app/state/room/roomToOpenThread.ts +++ b/src/app/state/room/roomToOpenThread.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { atomFamily } from 'jotai/utils'; +import { atomFamily } from 'jotai-family'; const createOpenThreadAtom = () => atom(undefined); export type TOpenThreadAtom = ReturnType; diff --git a/src/app/state/room/roomToThreadBrowser.ts b/src/app/state/room/roomToThreadBrowser.ts index ae3c72abbd..2d85856bab 100644 --- a/src/app/state/room/roomToThreadBrowser.ts +++ b/src/app/state/room/roomToThreadBrowser.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { atomFamily } from 'jotai/utils'; +import { atomFamily } from 'jotai-family'; const createThreadBrowserAtom = () => atom(false); export type TThreadBrowserAtom = ReturnType; diff --git a/src/app/state/scheduledMessages.ts b/src/app/state/scheduledMessages.ts index 7bed185722..1a206409fd 100644 --- a/src/app/state/scheduledMessages.ts +++ b/src/app/state/scheduledMessages.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { atomFamily } from 'jotai/utils'; +import { atomFamily } from 'jotai-family'; export const delayedEventsSupportedAtom = atom(false); diff --git a/src/app/state/sentryStorage.ts b/src/app/state/sentryStorage.ts new file mode 100644 index 0000000000..223c84bd81 --- /dev/null +++ b/src/app/state/sentryStorage.ts @@ -0,0 +1,28 @@ +/** + * Centralized access to the Sentry opt-in preference stored in localStorage. + * + * These must be plain functions because src/instrument.ts reads them + * synchronously at module load time, before React and Jotai mount. + */ + +export const SENTRY_ENABLED_KEY = 'sable_sentry_enabled'; +export const SENTRY_REPLAY_ENABLED_KEY = 'sable_sentry_replay_enabled'; + +export const getSentryEnabled = (): boolean => localStorage.getItem(SENTRY_ENABLED_KEY) === 'true'; + +export const isSentryDecided = (): boolean => localStorage.getItem(SENTRY_ENABLED_KEY) !== null; + +export const setSentryEnabled = (enabled: boolean): void => { + localStorage.setItem(SENTRY_ENABLED_KEY, String(enabled)); +}; + +export const getSentryReplayEnabled = (): boolean => + localStorage.getItem(SENTRY_REPLAY_ENABLED_KEY) === 'true'; + +export const setSentryReplayEnabled = (enabled: boolean): void => { + if (enabled) { + localStorage.setItem(SENTRY_REPLAY_ENABLED_KEY, 'true'); + } else { + localStorage.removeItem(SENTRY_REPLAY_ENABLED_KEY); + } +}; diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 83532c6735..00ac6835bb 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -23,7 +23,7 @@ export enum CaptionPosition { } export type JumboEmojiSize = 'none' | 'extraSmall' | 'small' | 'normal' | 'large' | 'extraLarge'; -export interface Settings { +export type Settings = { themeId?: string; useSystemTheme: boolean; lightThemeId?: string; @@ -121,7 +121,7 @@ export interface Settings { // furry stuff renderAnimals: boolean; -} +}; const defaultSettings: Settings = { themeId: undefined, diff --git a/src/app/state/spaceRooms.ts b/src/app/state/spaceRooms.ts index 572fade3e7..ea6d1c4863 100644 --- a/src/app/state/spaceRooms.ts +++ b/src/app/state/spaceRooms.ts @@ -15,7 +15,7 @@ const baseSpaceRoomsAtom = atomWithLocalStorage>( return new Set(arrayValue); }, (key, value) => { - const arrayValue = Array.from(value); + const arrayValue = [...value]; setLocalStorageItem(key, arrayValue); } ); @@ -36,7 +36,7 @@ export const spaceRoomsAtom = atom, [SpaceRoomsAction], undefined>( const current = get(baseSpaceRoomsAtom); const { type, roomIds } = action; - if (type === 'DELETE' && roomIds.find((roomId) => current.has(roomId))) { + if (type === 'DELETE' && roomIds.some((roomId) => current.has(roomId))) { set( baseSpaceRoomsAtom, produce(current, (draft) => { diff --git a/src/app/utils/AsyncSearch.ts b/src/app/utils/AsyncSearch.ts index e198db1de8..6230a59143 100644 --- a/src/app/utils/AsyncSearch.ts +++ b/src/app/utils/AsyncSearch.ts @@ -79,7 +79,7 @@ export const AsyncSearch = ( if (findingCount !== currentFindingCount) onResult(resultList, query); searchIndex += 1; - sessionScheduleId = globalThis.setTimeout(() => find(query, thisSessionTimestamp), 1); + sessionScheduleId = globalThis.setTimeout(find, 1, query, thisSessionTimestamp); return; } } diff --git a/src/app/utils/MegolmExportEncryption.ts b/src/app/utils/MegolmExportEncryption.ts index 805734ffc0..26e8ab598e 100644 --- a/src/app/utils/MegolmExportEncryption.ts +++ b/src/app/utils/MegolmExportEncryption.ts @@ -46,7 +46,7 @@ function toArrayBufferView(data: Uint8Array): Uint8Array { function encodeBase64(uint8Array: Uint8Array): string { // Misinterpt the Uint8Array as Latin-1. // window.btoa expects a unicode string with codepoints in the range 0-255. - const latin1String = String.fromCodePoint.apply(null, Array.from(uint8Array)); + const latin1String = String.fromCodePoint(...uint8Array); // Use the builtin base64 encoder. return globalThis.btoa(latin1String); } @@ -318,7 +318,7 @@ export async function encryptMegolmKeyFile( // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary // (which would mean we wouldn't be able to decrypt on Android). The loss // of a single bit of iv is a price we have to pay. - iv[8]! &= 0x7f; + iv[8] &= 0x7f; const [aesKey, hmacKey] = await deriveKeys(salt, kdfRounds, password); const encodedData = toArrayBufferView(new TextEncoder().encode(data)); diff --git a/src/app/utils/abbreviations.ts b/src/app/utils/abbreviations.ts index bee5741658..4c64913aff 100644 --- a/src/app/utils/abbreviations.ts +++ b/src/app/utils/abbreviations.ts @@ -44,7 +44,7 @@ export const splitByAbbreviations = (text: string, abbrMap: Map) const segments: TextSegment[] = []; let lastIndex = 0; - Array.from(text.matchAll(pattern)).forEach((match) => { + [...text.matchAll(pattern)].forEach((match) => { const matchIndex = match.index; if (matchIndex > lastIndex) { segments.push({ diff --git a/src/app/utils/bgColorImg.js b/src/app/utils/bgColorImg.ts similarity index 96% rename from src/app/utils/bgColorImg.js rename to src/app/utils/bgColorImg.ts index c14326ba82..db7f397f27 100644 --- a/src/app/utils/bgColorImg.js +++ b/src/app/utils/bgColorImg.ts @@ -1,12 +1,17 @@ // Getting a dominant color from an IMG source, // and darkening it a bit afterwards for contrast -export default function bgColorImg(img) { +export default function bgColorImg(img: HTMLImageElement): string { const size = 32; const darken = 0.8; const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d', { willReadFrequently: true }); + + if (!ctx) { + return 'rgb(0, 0, 0)'; + } + ctx.drawImage(img, 0, 0, size, size); const px = ctx.getImageData(0, 0, size, size).data; diff --git a/src/app/utils/common.ts b/src/app/utils/common.ts index e3a19dc1f3..193d4b4e39 100644 --- a/src/app/utils/common.ts +++ b/src/app/utils/common.ts @@ -68,7 +68,7 @@ export const binarySearch = (items: T[], match: (item: T) => -1 | 0 | 1): T | const mid = Math.floor((start + end) / 2); - const result = match(items[mid]!); + const result = match(items[mid]); if (result === 0) return items[mid]; if (result === 1) return search(start, mid - 1); @@ -106,6 +106,7 @@ export const parseGeoUri = (location: string) => { const START_SLASHES_REG = /^\/+/g; const END_SLASHES_REG = /\/+$/g; +const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' }); export const trimLeadingSlash = (str: string): string => str.replace(START_SLASHES_REG, ''); export const trimTrailingSlash = (str: string): string => str.replace(END_SLASHES_REG, ''); @@ -113,7 +114,9 @@ export const trimSlash = (str: string): string => trimLeadingSlash(trimTrailingS export const nameInitials = (str: string | undefined | null, len = 1): string => { if (!str) return ''; - return Array.from(str).slice(0, len).join('') || ''; + return Array.from(graphemeSegmenter.segment(str), ({ segment }) => segment) + .slice(0, len) + .join(''); }; export const randomStr = (len = 12): string => { diff --git a/src/app/utils/debugLogger.ts b/src/app/utils/debugLogger.ts index 88f787328e..75ff69f632 100644 --- a/src/app/utils/debugLogger.ts +++ b/src/app/utils/debugLogger.ts @@ -20,14 +20,14 @@ export type LogCategory = | 'error' | 'general'; -export interface LogEntry { +export type LogEntry = { timestamp: number; level: LogLevel; category: LogCategory; namespace: string; message: string; data?: unknown; -} +}; type LogListener = (entry: LogEntry) => void; @@ -135,7 +135,7 @@ class DebugLoggerService { } else { this.disabledBreadcrumbCategories.add(category); } - const disabledArray = Array.from(this.disabledBreadcrumbCategories); + const disabledArray = [...this.disabledBreadcrumbCategories]; if (disabledArray.length > 0) { localStorage.setItem(BREADCRUMB_DISABLED_KEY, JSON.stringify(disabledArray)); } else { diff --git a/src/app/utils/delayedEvents.ts b/src/app/utils/delayedEvents.ts index b32f150a9b..5b534ab050 100644 --- a/src/app/utils/delayedEvents.ts +++ b/src/app/utils/delayedEvents.ts @@ -10,9 +10,9 @@ import type { } from '$types/matrix-sdk'; // Grab types needed for encryption -interface EncryptableBackend { +type EncryptableBackend = { encryptEvent(event: MatrixEvent, room: Room): Promise; -} +}; export async function supportsDelayedEvents(mx: MatrixClient): Promise { try { diff --git a/src/app/utils/dom.ts b/src/app/utils/dom.ts index 31f00cad62..9fe1c1bf6b 100644 --- a/src/app/utils/dom.ts +++ b/src/app/utils/dom.ts @@ -228,7 +228,7 @@ export const syntaxErrorPosition = (error: SyntaxError): number | undefined => { const match = error.message.match(/position\s(\d+)\s/); if (!match) return undefined; - const posStr = match[1]!; + const posStr = match[1]; const position = parseInt(posStr, 10); if (Number.isNaN(position)) return undefined; return position; diff --git a/src/app/utils/emojiDetection.test.ts b/src/app/utils/emojiDetection.test.ts new file mode 100644 index 0000000000..d3df123efc --- /dev/null +++ b/src/app/utils/emojiDetection.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { isEmojiGrapheme, isJumboEmojiText, splitEmojiText } from './emojiDetection'; + +describe('isEmojiGrapheme', () => { + it.each(['🫩', '🫪', '🫯', '🇩🇪', '🙂‍↔️', '™️'])('matches emoji grapheme %s', (emoji) => { + expect(isEmojiGrapheme(emoji)).toBe(true); + }); + + it.each(['a', '12', 'http'])('does not match plain text segment %s', (value) => { + expect(isEmojiGrapheme(value)).toBe(false); + }); +}); + +describe('splitEmojiText', () => { + it('preserves newer emoji as standalone parts', () => { + expect(splitEmojiText('a🫪b')).toEqual([ + { type: 'text', value: 'a' }, + { type: 'emoji', value: '🫪' }, + { type: 'text', value: 'b' }, + ]); + }); + + it('keeps emoji sequences whole', () => { + expect(splitEmojiText('🙂‍↔️')).toEqual([ + { type: 'text', value: '' }, + { type: 'emoji', value: '🙂‍↔️' }, + { type: 'text', value: '' }, + ]); + }); +}); + +describe('isJumboEmojiText', () => { + it.each(['🫩', '🫪', '🫯', '🇩🇪', '🙂‍↔️'])('matches modern emoji sequence %s', (emoji) => { + expect(isJumboEmojiText(emoji)).toBe(true); + }); + + it.each(['123', 'hello', 'abc 123'])('does not match non-emoji text %s', (value) => { + expect(isJumboEmojiText(value)).toBe(false); + }); + + it('still matches shortcode-only content', () => { + expect(isJumboEmojiText(':blobcat:')).toBe(true); + }); +}); diff --git a/src/app/utils/emojiDetection.ts b/src/app/utils/emojiDetection.ts new file mode 100644 index 0000000000..ba19616a74 --- /dev/null +++ b/src/app/utils/emojiDetection.ts @@ -0,0 +1,88 @@ +/** + * Emoji detection works on grapheme clusters, not raw code points. + * Intl.Segmenter keeps ZWJ sequences, flags, and keycaps intact as single user-visible units. + * Each grapheme is treated as emoji-like if it is a keycap sequence, an emoji forced by Variation Selector-16, or contains Emoji_Presentation, Extended_Pictographic, or Regional_Indicator. + * This is intentionally broader than `\p{RGI_Emoji}` because browsers can lag on that property for newer emojis like `🫪`. + * The goal here is UI rendering, so broad emoji-like detection is more useful than strict Unicode interchange validation. + */ + +const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' }); + +const SHORTCODE_TOKEN_REG = /^:[^:\s]+:/u; +const EMOJI_GRAPHEME_REG = + /[#*0-9]\uFE0F?\u20E3|\p{Emoji}\uFE0F|[\p{Emoji_Presentation}\p{Extended_Pictographic}\p{Regional_Indicator}]/u; + +export type EmojiTextPart = + | { + type: 'text'; + value: string; + } + | { + type: 'emoji'; + value: string; + }; + +export const getFirstGrapheme = (text: string): string => { + const first = graphemeSegmenter.segment(text)[Symbol.iterator]().next(); + return first.done ? '' : first.value.segment; +}; + +export const isEmojiGrapheme = (segment: string): boolean => { + if (!segment) return false; + return EMOJI_GRAPHEME_REG.test(segment); +}; + +export const splitEmojiText = (text: string): EmojiTextPart[] => { + const parts: EmojiTextPart[] = []; + let buffer = ''; + let foundEmoji = false; + + [...graphemeSegmenter.segment(text)].forEach(({ segment }) => { + if (isEmojiGrapheme(segment)) { + foundEmoji = true; + parts.push({ type: 'text', value: buffer }); + buffer = ''; + parts.push({ type: 'emoji', value: segment }); + } else { + buffer += segment; + } + }); + + if (!foundEmoji) { + return [{ type: 'text', value: buffer }]; + } + + parts.push({ type: 'text', value: buffer }); + return parts; +}; + +export const isJumboEmojiText = (text: string, maxTokens = 10): boolean => { + if (!text) return false; + + let tokenCount = 0; + let index = 0; + + while (index < text.length) { + const remainder = text.slice(index); + const whitespaceMatch = /^\s+/u.exec(remainder); + if (whitespaceMatch) { + index += whitespaceMatch[0].length; + } else { + const shortcodeMatch = SHORTCODE_TOKEN_REG.exec(remainder); + if (shortcodeMatch) { + tokenCount += 1; + if (tokenCount > maxTokens) return false; + index += shortcodeMatch[0].length; + } else { + const grapheme = getFirstGrapheme(remainder); + if (!isEmojiGrapheme(grapheme)) return false; + + tokenCount += 1; + if (tokenCount > maxTokens) return false; + index += grapheme.length; + } + } + } + + return tokenCount > 0; +}; diff --git a/src/app/utils/featureCheck.ts b/src/app/utils/featureCheck.ts index d5564e749d..c4fd1e2746 100644 --- a/src/app/utils/featureCheck.ts +++ b/src/app/utils/featureCheck.ts @@ -1,5 +1,5 @@ export const checkIndexedDBSupport = async (): Promise => { - const ts = new Date().getTime(); + const ts = Date.now(); const dbName = `checkIndexedDBSupport-${ts}`; return new Promise((resolve) => { let db; diff --git a/src/app/utils/keyboard.ts b/src/app/utils/keyboard.ts index 37ab9a3f8c..121dfc3843 100644 --- a/src/app/utils/keyboard.ts +++ b/src/app/utils/keyboard.ts @@ -1,7 +1,7 @@ import { isKeyHotkey } from 'is-hotkey'; import type { KeyboardEventHandler } from 'react'; -export interface KeyboardEventLike { +export type KeyboardEventLike = { key: string; which: number; altKey: boolean; @@ -9,7 +9,7 @@ export interface KeyboardEventLike { metaKey: boolean; shiftKey: boolean; preventDefault(): void; -} +}; export const onTabPress = (evt: KeyboardEventLike, callback: () => void) => { if (isKeyHotkey('tab', evt)) { diff --git a/src/app/utils/matrix.ts b/src/app/utils/matrix.ts index c647038c10..c461bfa5fd 100644 --- a/src/app/utils/matrix.ts +++ b/src/app/utils/matrix.ts @@ -272,7 +272,7 @@ export const addRoomIdToMDirect = async ( // remove it from the lists of any others users // (it can only be a DM room for one person) Object.keys(userIdToRoomIds).forEach((targetUserId) => { - const roomIds = userIdToRoomIds[targetUserId]!; + const roomIds = userIdToRoomIds[targetUserId]; if (targetUserId !== userId) { const indexOfRoomId = roomIds.indexOf(roomId); @@ -283,7 +283,7 @@ export const addRoomIdToMDirect = async ( }); const roomIds = userIdToRoomIds[userId] || []; - if (roomIds.indexOf(roomId) === -1) { + if (!roomIds.includes(roomId)) { roomIds.push(roomId); } userIdToRoomIds[userId] = roomIds; @@ -304,7 +304,7 @@ export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string): userIdToRoomIds = structuredClone(mDirectsEvent.getContent()); Object.keys(userIdToRoomIds).forEach((targetUserId) => { - const roomIds = userIdToRoomIds[targetUserId]!; + const roomIds = userIdToRoomIds[targetUserId]; const indexOfRoomId = roomIds.indexOf(roomId); if (indexOfRoomId > -1) { roomIds.splice(indexOfRoomId, 1); @@ -385,7 +385,7 @@ export const rateLimitedActions = async ( }; for (let i = 0; i < data.length; i += 1) { - const dataItem = data[i]!; + const dataItem = data[i]; retryCount = 0; // oxlint-disable-next-line no-await-in-loop await performAction(dataItem, i); @@ -427,7 +427,7 @@ export const toggleReaction = ( ); const allReactions = relations?.getSortedAnnotationsByKey() ?? []; const [, reactionsSet] = allReactions.find(([k]) => k === key) ?? []; - const reactions: MatrixEvent[] = reactionsSet ? Array.from(reactionsSet) : []; + const reactions: MatrixEvent[] = reactionsSet ? [...reactionsSet] : []; const myReaction = reactions.find(factoryEventSentBy(mx.getUserId()!)); if (myReaction && myReaction.isRelation?.()) { diff --git a/src/app/utils/regex.ts b/src/app/utils/regex.ts index 0b98b0e2b3..4f5bbc0014 100644 --- a/src/app/utils/regex.ts +++ b/src/app/utils/regex.ts @@ -10,17 +10,3 @@ export const URL_REG = new RegExp(HTTP_URL_PATTERN, 'g'); export const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\\"]+(\.[^<>()[\]\\.,;:\s@\\"]+)*)|(\\".+\\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - -export const URL_NEG_LB = '(? { export const getOrphanParents = (roomToParents: RoomToParents, roomId: string): string[] => { const parents = getAllParents(roomToParents, roomId); - return Array.from(parents).filter((parentRoomId) => !roomToParents.has(parentRoomId)); + return [...parents].filter((parentRoomId) => !roomToParents.has(parentRoomId)); }; export const isMutedRule = (rule: IPushRule) => @@ -263,7 +263,7 @@ export const roomHaveUnread = (mx: MatrixClient, room: Room) => { return false; } - const latestEvent = liveEvents[liveEvents.length - 1]; + const latestEvent = liveEvents.at(-1); if (latestEvent?.getSender() === userId) { return false; @@ -549,7 +549,7 @@ export const getMemberSearchStr = ( mxIdToName: (mxId: string) => string ): string[] => [ member.rawDisplayName === member.userId ? mxIdToName(member.userId) : member.rawDisplayName, - query.startsWith('@') || query.indexOf(':') > -1 ? member.userId : mxIdToName(member.userId), + query.startsWith('@') || query.includes(':') ? member.userId : mxIdToName(member.userId), ]; export const getMemberAvatarMxc = (room: Room, userId: string): string | undefined => { @@ -755,12 +755,12 @@ export const guessPerfectParent = ( if (typeof users === 'object') Object.keys(users).forEach((userId) => { - if (users[userId]! > defaultPower) { + if (users[userId] > defaultPower) { specialUsers.add(userId); } }); - return Array.from(specialUsers); + return [...specialUsers]; }; let perfectParent: string | undefined; diff --git a/src/app/utils/sanitize.test.ts b/src/app/utils/sanitize.test.ts index c1332cfc7f..fce7c1f4e1 100644 --- a/src/app/utils/sanitize.test.ts +++ b/src/app/utils/sanitize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { sanitizeCustomHtml } from './sanitize'; +import { extractPlainTextFromCustomHtml, sanitizeCustomHtml } from './sanitize'; describe('sanitizeCustomHtml', () => { it('keeps permitted Matrix v1.18 tags', () => { @@ -138,3 +138,13 @@ describe('sanitizeCustomHtml', () => { expect((result.match(/
    /g) ?? []).length).toBeLessThanOrEqual(100); }); }); + +describe('extractPlainTextFromCustomHtml', () => { + it('converts sanitized html into readable plain text', () => { + const result = extractPlainTextFromCustomHtml( + '

    Hello
    world

    • One
    • Two
    ' + ); + + expect(result).toBe('Hello\nworld\n- One\n- Two'); + }); +}); diff --git a/src/app/utils/sanitize.ts b/src/app/utils/sanitize.ts index 0e56d550c0..097eeed8cb 100644 --- a/src/app/utils/sanitize.ts +++ b/src/app/utils/sanitize.ts @@ -1,4 +1,4 @@ -import DOMPurify from 'dompurify'; +import DOMPurify, { type UponSanitizeAttributeHookEvent } from 'dompurify'; import { isMatrixHexColor } from './matrixHtml'; const MAX_TAG_NESTING = 100; @@ -70,7 +70,7 @@ const permittedTagToAttributes = { hr: ['data-md'], } as const satisfies Record; -const permittedHtmlAttributes = Array.from(new Set(Object.values(permittedTagToAttributes).flat())); +const permittedHtmlAttributes = [...new Set(Object.values(permittedTagToAttributes).flat())]; const allowedLinkSchemes = new Set(['https', 'http', 'ftp', 'mailto', 'magnet']); const forbiddenContentTags = ['mx-reply', 'script', 'style', 'textarea', 'option', 'noscript']; @@ -78,6 +78,31 @@ const forbiddenContentTags = ['mx-reply', 'script', 'style', 'textarea', 'option const codeLanguageClassRegex = /^language-[A-Za-z0-9_-]+$/; const orderedListStartRegex = /^-?\d+$/; const allowedUriRegex = /^(?:https?|ftp|mailto|magnet|mxc):/i; +const textBlockTags = new Set([ + 'blockquote', + 'caption', + 'details', + 'div', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'li', + 'ol', + 'p', + 'pre', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr', + 'ul', +]); export function sanitizeText(body: string): string { const tagsToReplace: Record = { @@ -235,7 +260,7 @@ const enforceNestingLimit = (fragment: DocumentFragment): void => { const overlyNestedElements: Array<{ depth: number; element: Element }> = []; const collect = (node: ParentNode, depth: number) => { - Array.from(node.childNodes).forEach((child) => { + [...node.childNodes].forEach((child) => { if (!(child instanceof Element)) return; const childDepth = depth + 1; @@ -253,7 +278,7 @@ const enforceNestingLimit = (fragment: DocumentFragment): void => { .toSorted((a, b) => b.depth - a.depth) .forEach(({ element }) => { if (!element.parentNode) return; - element.replaceWith(...Array.from(element.childNodes)); + element.replaceWith(...element.childNodes); }); }; @@ -273,7 +298,7 @@ const pruneInvalidEmptyElements = ( }); }; -export const sanitizeCustomHtml = (customHtml: string): string => { +export function sanitizeCustomHtml(customHtml: string): string { if (typeof window === 'undefined') { return sanitizeText(customHtml); } @@ -282,35 +307,37 @@ export const sanitizeCustomHtml = (customHtml: string): string => { const purify = DOMPurify(window); const allowedHtmlAttributes = [...permittedHtmlAttributes, INTERNAL_IMG_SRC_ATTR]; - purify.addHook('uponSanitizeAttribute', (currentNode, hookEvent) => { - const tagName = currentNode.tagName.toLowerCase(); - const attrName = hookEvent.attrName.toLowerCase(); + purify.addHook( + 'uponSanitizeAttribute', + (currentNode: Element, hookEvent: UponSanitizeAttributeHookEvent) => { + const tagName = currentNode.tagName.toLowerCase(); + const attrName = hookEvent.attrName.toLowerCase(); - if (tagName === 'img' && attrName === INTERNAL_IMG_SRC_ATTR) { - if (!protectedSources.has(hookEvent.attrValue)) { + if (tagName === 'img' && attrName === INTERNAL_IMG_SRC_ATTR) { + if (!protectedSources.has(hookEvent.attrValue)) { + hookEvent.keepAttr = false; + return; + } + + hookEvent.forceKeepAttr = true; + } + + if (!tagAllowsAttribute(tagName, attrName)) { + // DOMPurify exposes attribute decisions by mutating the hook event. hookEvent.keepAttr = false; return; } - hookEvent.forceKeepAttr = true; - return; - } - - if (!tagAllowsAttribute(tagName, attrName)) { - // DOMPurify exposes attribute decisions by mutating the hook event. - hookEvent.keepAttr = false; - return; - } + const validatedAttrValue = getValidatedAttributeValue(tagName, attrName, hookEvent.attrValue); + if (validatedAttrValue === undefined) { + hookEvent.keepAttr = false; + return; + } - const validatedAttrValue = getValidatedAttributeValue(tagName, attrName, hookEvent.attrValue); - if (validatedAttrValue === undefined) { - hookEvent.keepAttr = false; - return; + hookEvent.attrValue = validatedAttrValue; + hookEvent.forceKeepAttr = true; } - - hookEvent.attrValue = validatedAttrValue; - hookEvent.forceKeepAttr = true; - }); + ); const sanitizedNode = purify.sanitize(protectedHtml, { ALLOWED_TAGS: [...permittedHtmlTags], @@ -336,4 +363,57 @@ export const sanitizeCustomHtml = (customHtml: string): string => { const container = document.createElement('div'); container.append(fragment); return restoreProtectedImageSources(container.innerHTML, protectedSources); -}; +} + +function appendPlainTextLineBreak(parts: string[]) { + const previous = parts.at(-1); + if (previous?.endsWith('\n')) return; + parts.push('\n'); +} + +function collectPlainTextFromNode(node: Node, parts: string[]): void { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent; + if (text) parts.push(text); + return; + } + + if (!(node instanceof Element)) return; + + const tagName = node.tagName.toLowerCase(); + + if (tagName === 'br') { + appendPlainTextLineBreak(parts); + return; + } + + if (tagName === 'li') { + parts.push('- '); + } + + [...node.childNodes].forEach((child) => collectPlainTextFromNode(child, parts)); + + if (textBlockTags.has(tagName)) { + appendPlainTextLineBreak(parts); + } +} + +export function extractPlainTextFromCustomHtml(customHtml: string): string { + if (typeof DOMParser === 'undefined') { + return customHtml; + } + + const parser = new DOMParser(); + const doc = parser.parseFromString(sanitizeCustomHtml(customHtml), 'text/html'); + const parts: string[] = []; + + [...doc.body.childNodes].forEach((child) => collectPlainTextFromNode(child, parts)); + + return parts + .join('') + .replace(/\r\n?/g, '\n') + .replace(/\u00a0/g, ' ') + .replace(/[ \t]+\n/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); +} diff --git a/src/app/utils/settingsSync.test.ts b/src/app/utils/settingsSync.test.ts index 67898886f8..4409fbb45e 100644 --- a/src/app/utils/settingsSync.test.ts +++ b/src/app/utils/settingsSync.test.ts @@ -73,7 +73,7 @@ describe('serializeForSync', () => { it('strips all non-syncable keys from the payload', () => { const { settings: s } = serializeForSync(base); - Array.from(NON_SYNCABLE_KEYS).forEach((key) => { + [...NON_SYNCABLE_KEYS].forEach((key) => { expect(Object.hasOwn(s, key)).toBe(false); }); }); diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index 25249718cf..b5a20c16bf 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -238,7 +238,7 @@ export const clearMismatchedStores = async (): Promise => { allDbs.map(async ({ name }) => { if (!name) return; - const containsKnownUser = Array.from(knownUserIds).some((uid) => name.includes(uid)); + const containsKnownUser = [...knownUserIds].some((uid) => name.includes(uid)); const looksLikeUserDb = name.includes('@'); if (looksLikeUserDb && !containsKnownUser && !knownStoreNames.has(name)) { log.warn(`clearMismatchedStores: "${name}" has unknown user — deleting`); @@ -283,12 +283,12 @@ const buildClient = async (session: Session): Promise => { const storeName = getSessionStoreName(session); const indexedDBStore = new IndexedDBStore({ - indexedDB: global.indexedDB, - localStorage: global.localStorage, + indexedDB: globalThis.indexedDB, + localStorage: globalThis.localStorage, dbName: storeName.sync, }); - const legacyCryptoStore = new IndexedDBCryptoStore(global.indexedDB, storeName.crypto); + const legacyCryptoStore = new IndexedDBCryptoStore(globalThis.indexedDB, storeName.crypto); const mx = createClient({ baseUrl: session.baseUrl, diff --git a/src/client/secretStorageKeys.js b/src/client/secretStorageKeys.js deleted file mode 100644 index e58aa890a8..0000000000 --- a/src/client/secretStorageKeys.js +++ /dev/null @@ -1,37 +0,0 @@ -const secretStorageKeys = new Map(); - -export function storePrivateKey(keyId, privateKey) { - if (!(privateKey instanceof Uint8Array)) { - throw new Error('Unable to store, privateKey is invalid.'); - } - secretStorageKeys.set(keyId, privateKey); -} - -function hasPrivateKey(keyId) { - return secretStorageKeys.get(keyId) instanceof Uint8Array; -} - -function getPrivateKey(keyId) { - return secretStorageKeys.get(keyId); -} - -export function clearSecretStorageKeys() { - secretStorageKeys.clear(); -} - -async function getSecretStorageKey({ keys }) { - const keyIds = Object.keys(keys); - const keyId = keyIds.find(hasPrivateKey); - if (!keyId) return undefined; - const privateKey = getPrivateKey(keyId); - return [keyId, privateKey]; -} - -function cacheSecretStorageKey(keyId, keyInfo, privateKey) { - secretStorageKeys.set(keyId, privateKey); -} - -export const cryptoCallbacks = { - getSecretStorageKey, - cacheSecretStorageKey, -}; diff --git a/src/client/secretStorageKeys.ts b/src/client/secretStorageKeys.ts new file mode 100644 index 0000000000..a89b7f0ba2 --- /dev/null +++ b/src/client/secretStorageKeys.ts @@ -0,0 +1,49 @@ +import type { CryptoCallbacks } from '$types/matrix-sdk'; + +const secretStorageKeys = new Map(); + +export function storePrivateKey(keyId: string, privateKey: Uint8Array): void { + if (!(privateKey instanceof Uint8Array)) { + throw new Error('Unable to store, privateKey is invalid.'); + } + secretStorageKeys.set(keyId, privateKey); +} + +function hasPrivateKey(keyId: string): boolean { + return secretStorageKeys.get(keyId) instanceof Uint8Array; +} + +function getPrivateKey(keyId: string): Uint8Array | undefined { + return secretStorageKeys.get(keyId); +} + +export function clearSecretStorageKeys(): void { + secretStorageKeys.clear(); +} + +const getSecretStorageKey: NonNullable = async ({ + keys, +}) => { + const keyIds = Object.keys(keys); + const keyId = keyIds.find(hasPrivateKey); + if (!keyId) return null; + const privateKey = getPrivateKey(keyId); + if (!privateKey) return null; + return [keyId, privateKey]; +}; + +const cacheSecretStorageKey: NonNullable = ( + keyId, + _keyInfo, + privateKey +) => { + secretStorageKeys.set(keyId, privateKey); +}; + +export const cryptoCallbacks: Pick< + CryptoCallbacks, + 'getSecretStorageKey' | 'cacheSecretStorageKey' +> = { + getSecretStorageKey, + cacheSecretStorageKey, +}; diff --git a/src/client/slidingSync.ts b/src/client/slidingSync.ts index cea5b1f787..15b352982f 100644 --- a/src/client/slidingSync.ts +++ b/src/client/slidingSync.ts @@ -313,7 +313,7 @@ export class SlidingSyncManager { const defaultSubscription = buildEncryptedSubscription(roomTimelineLimit); const lists = buildLists(listPageSize, includeInviteList); - this.listKeys = Array.from(lists.keys()); + this.listKeys = [...lists.keys()]; this.slidingSync = new SlidingSync(proxyBaseUrl, lists, defaultSubscription, mx, pollTimeoutMs); // Register the presence extension so m.presence events from the server are fed diff --git a/src/index.css b/src/index.css index cde45ce4c0..821d24e415 100755 --- a/src/index.css +++ b/src/index.css @@ -1,10 +1,4 @@ -@font-face { - font-family: Twemoji; - src: - url('../public/font/Twemoji.Mozilla.v15.1.0.woff2'), - url('../public/font/Twemoji.Mozilla.v15.1.0.ttf'); - font-display: swap; -} +@import '@sableclient/twemoji-font'; :root { --tc-link: hsl(213deg 100% 45%); diff --git a/src/sw.ts b/src/sw.ts index 222156e729..59d0c266a7 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -135,7 +135,7 @@ async function cleanupDeadClients() { const activeClients = await self.clients.matchAll(); const activeIds = new Set(activeClients.map((c) => c.id)); - Array.from(sessions.keys()).forEach((id) => { + [...sessions.keys()].forEach((id) => { if (!activeIds.has(id)) { sessions.delete(id); clientToResolve.delete(id); @@ -166,7 +166,7 @@ function setSession(clientId: string, accessToken: unknown, baseUrl: unknown, us } const resolveSession = clientToResolve.get(clientId); - if (resolveSession) { + if (typeof resolveSession === 'function') { resolveSession(sessions.get(clientId)); clientToResolve.delete(clientId); clientToSessionPromise.delete(clientId); @@ -378,7 +378,7 @@ async function requestDecryptionFromClient( const eventId = rawEvent.event_id as string; // Chain clients sequentially using reduce to avoid await-in-loop and for-of. - return Array.from(windowClients).reduce( + return [...windowClients].reduce( async (prevPromise, client) => { const prev = await prevPromise; if (prev?.success) return prev; @@ -573,7 +573,7 @@ self.addEventListener('message', (event: ExtendableMessageEvent) => { const { eventId } = data as { eventId?: string }; if (typeof eventId === 'string') { const resolve = decryptionPendingMap.get(eventId); - if (resolve) { + if (typeof resolve === 'function') { decryptionPendingMap.delete(eventId); resolve(data as DecryptionResult); } diff --git a/tsconfig.json b/tsconfig.json index bcb8c62177..a41f5c3568 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,38 +1,13 @@ { - "compilerOptions": { - "sourceMap": true, - "jsx": "react-jsx", - "target": "ES2022", - "module": "ES2022", - "allowJs": true, - "strict": true, - "noImplicitAny": true, - "noUncheckedIndexedAccess": true, - "noImplicitReturns": true, - "strictNullChecks": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "paths": { - "$hooks/*": ["./src/app/hooks/*"], - "$plugins/*": ["./src/app/plugins/*"], - "$components/*": ["./src/app/components/*"], - "$features/*": ["./src/app/features/*"], - "$state/*": ["./src/app/state/*"], - "$styles/*": ["./src/app/styles/*"], - "$utils/*": ["./src/app/utils/*"], - "$pages/*": ["./src/app/pages/*"], - "$types/*": ["./src/types/*"], - "$public/*": ["./public/*"], - "$client/*": ["./src/client/*"] + "extends": "./tsconfig.web.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.web.json" }, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "noEmit": true, - "resolveJsonModule": true, - "outDir": "dist", - "skipLibCheck": true, - "lib": ["ES2022", "DOM"] - }, - "exclude": ["node_modules", "dist"], - "include": ["src", "vite.config.ts", "vitest.config.ts"] + { + "path": "./tsconfig.node.json" + } + ] } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000000..17dd0a9917 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "lib": ["ESNext"], + "types": ["node"], + "allowJs": true, + "checkJs": true, + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "moduleResolution": "NodeNext", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./.tsbuildinfo/tsconfig.node.tsbuildinfo" + }, + "include": [ + "build.config.ts", + "oxfmt.config.ts", + "oxlint.config.ts", + "vite.config.ts", + "vitest.config.ts", + "scripts/**/*.js" + ], + "exclude": ["node_modules", "dist", "coverage"] +} diff --git a/tsconfig.web.json b/tsconfig.web.json new file mode 100644 index 0000000000..4efb875d36 --- /dev/null +++ b/tsconfig.web.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "allowJs": false, + "checkJs": false, + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "composite": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "tsBuildInfoFile": "./.tsbuildinfo/tsconfig.tsbuildinfo", + "paths": { + "$hooks": ["./src/app/hooks"], + "$hooks/*": ["./src/app/hooks/*"], + "$plugins": ["./src/app/plugins"], + "$plugins/*": ["./src/app/plugins/*"], + "$components": ["./src/app/components"], + "$components/*": ["./src/app/components/*"], + "$features": ["./src/app/features"], + "$features/*": ["./src/app/features/*"], + "$state": ["./src/app/state"], + "$state/*": ["./src/app/state/*"], + "$styles": ["./src/app/styles"], + "$styles/*": ["./src/app/styles/*"], + "$utils": ["./src/app/utils"], + "$utils/*": ["./src/app/utils/*"], + "$pages": ["./src/app/pages"], + "$pages/*": ["./src/app/pages/*"], + "$generated": ["./src/app/generated"], + "$generated/*": ["./src/app/generated/*"], + "$types": ["./src/types"], + "$types/*": ["./src/types/*"], + "$public": ["./public"], + "$public/*": ["./public/*"], + "$client": ["./src/client"], + "$client/*": ["./src/client/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"], + "exclude": ["node_modules", "dist", "coverage"] +} diff --git a/vite.config.ts b/vite.config.ts index 7b482ef783..6ab88a0063 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,23 +1,28 @@ import { defineConfig } from 'vite'; import type { ViteDevServer, PluginOption } from 'vite'; -import { execSync } from 'child_process'; +import { execFileSync, execSync } from 'child_process'; +import type { RollupInjectOptions } from '@rollup/plugin-inject'; import react from '@vitejs/plugin-react'; import svgr from 'vite-plugin-svgr'; import { wasm } from '@rollup/plugin-wasm'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; -import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'; -import inject from '@rollup/plugin-inject'; -import topLevelAwait from 'vite-plugin-top-level-await'; +import * as injectModule from '@rollup/plugin-inject'; +import * as topLevelAwaitModule from 'vite-plugin-top-level-await'; +import type { Options as TopLevelAwaitOptions } from 'vite-plugin-top-level-await'; import { VitePWA } from 'vite-plugin-pwa'; import { compression, defineAlgorithm } from 'vite-plugin-compression2'; import { constants as zlibConstants } from 'zlib'; import fs from 'fs'; import path from 'path'; -import { cloudflare } from '@cloudflare/vite-plugin'; import { createRequire } from 'module'; import { sentryVitePlugin } from '@sentry/vite-plugin'; -import buildConfig from './build.config'; +import buildConfig from './build.config.ts'; + +const inject = injectModule.default as unknown as (options?: RollupInjectOptions) => PluginOption; +const topLevelAwait = topLevelAwaitModule.default as unknown as ( + options?: TopLevelAwaitOptions +) => PluginOption; const packageJson = JSON.parse( fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8') @@ -54,7 +59,10 @@ const isReleaseTag = (() => { const envVal = process.env.VITE_IS_RELEASE_TAG; if (envVal !== undefined && envVal !== '') return envVal === 'true'; try { - const tag = execSync('git describe --exact-match --tags HEAD 2>/dev/null').toString().trim(); + const tag = execFileSync('git', ['describe', '--exact-match', '--tags', 'HEAD'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); return tag.startsWith('v'); } catch { return false; @@ -133,19 +141,7 @@ export default defineConfig(({ command }) => ({ IS_RELEASE_TAG: JSON.stringify(isReleaseTag), }, resolve: { - alias: { - $hooks: path.resolve(__dirname, 'src/app/hooks'), - $plugins: path.resolve(__dirname, 'src/app/plugins'), - $components: path.resolve(__dirname, 'src/app/components'), - $features: path.resolve(__dirname, 'src/app/features'), - $state: path.resolve(__dirname, 'src/app/state'), - $styles: path.resolve(__dirname, 'src/app/styles'), - $utils: path.resolve(__dirname, 'src/app/utils'), - $pages: path.resolve(__dirname, 'src/app/pages'), - $types: path.resolve(__dirname, 'src/types'), - $public: path.resolve(__dirname, 'public'), - $client: path.resolve(__dirname, 'src/client'), - }, + tsconfigPaths: true, }, server: { port: 8080, @@ -162,7 +158,7 @@ export default defineConfig(({ command }) => ({ // The export name of top-level await promise for each chunk module promiseExportName: '__tla', // The function to generate import names of top-level await promise in each chunk module - promiseImportName: (i) => `__tla_${i}`, + promiseImportName: (i: number) => `__tla_${i}`, }), viteStaticCopy(copyFiles), vanillaExtractPlugin({ identifiers: 'debug' }), @@ -187,14 +183,6 @@ export default defineConfig(({ command }) => ({ type: 'module', }, }), - cloudflare({ - config: { - compatibility_date: '2026-03-03', - assets: { - not_found_handling: 'single-page-application', - }, - }, - }), compression({ algorithms: [ defineAlgorithm('brotliCompress', { @@ -237,25 +225,13 @@ export default defineConfig(({ command }) => ({ '@vanilla-extract/recipes/createRuntimeFn', ], needsInterop: ['matrix-widget-api'], - esbuildOptions: { - define: { - global: 'globalThis', - }, - plugins: [ - // Enable esbuild polyfill plugins - NodeGlobalsPolyfillPlugin({ - process: false, - buffer: true, - }), - ], - }, }, build: { outDir: 'dist', sourcemap: true, copyPublicDir: false, rollupOptions: { - plugins: [inject({ Buffer: ['buffer', 'Buffer'] }) as PluginOption], + plugins: [inject({ Buffer: ['buffer', 'Buffer'] })], }, }, })); diff --git a/vitest.config.ts b/vitest.config.ts index fedea11515..497c539f92 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,26 +1,13 @@ import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; -import path from 'path'; // Standalone Vitest config — intentionally excludes Cloudflare, PWA, compression, // and other production-only Vite plugins that don't apply to unit tests. export default defineConfig({ plugins: [react(), vanillaExtractPlugin()], resolve: { - alias: { - $hooks: path.resolve(__dirname, 'src/app/hooks'), - $plugins: path.resolve(__dirname, 'src/app/plugins'), - $components: path.resolve(__dirname, 'src/app/components'), - $features: path.resolve(__dirname, 'src/app/features'), - $state: path.resolve(__dirname, 'src/app/state'), - $styles: path.resolve(__dirname, 'src/app/styles'), - $utils: path.resolve(__dirname, 'src/app/utils'), - $pages: path.resolve(__dirname, 'src/app/pages'), - $types: path.resolve(__dirname, 'src/types'), - $public: path.resolve(__dirname, 'public'), - $client: path.resolve(__dirname, 'src/client'), - }, + tsconfigPaths: true, }, define: { APP_VERSION: JSON.stringify('test'), @@ -31,7 +18,7 @@ export default defineConfig({ environment: 'jsdom', globals: true, setupFiles: ['./src/test/setup.ts'], - include: ['src/**/*.{test,spec}.{ts,tsx}'], + include: ['src/**/*.{test,spec}.{ts,tsx}', 'scripts/**/*.{test,spec}.{js,ts}'], coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'], @@ -46,6 +33,14 @@ export default defineConfig({ 'src/**/*.test.{ts,tsx}', 'src/**/*.spec.{ts,tsx}', ], + // Baseline locked at current coverage. Raise these thresholds as test + // coverage improves, never lower them. + thresholds: { + statements: 1.5, + branches: 1, + functions: 1.5, + lines: 1.5, + }, }, }, }); diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000000..88329f764d --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,9 @@ +{ + "$schema": "https://www.unpkg.com/wrangler/config-schema.json", + "name": "sable", + "compatibility_date": "2026-03-03", + "assets": { + "directory": "./dist", + "not_found_handling": "single-page-application", + }, +}