diff --git a/.github/actions/prepare-tofu/action.yml b/.github/actions/prepare-tofu/action.yml index c64332e3c..dcb95842c 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 9b4c9acbb..f7d1e4180 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 8b93a4bb9..5b9b943b4 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 000000000..d528259ad --- /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 000000000..518ffad06 --- /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 03a63ef99..540ded829 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 a9ac43708..b1d8f99e1 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 d3314e27b..6037b7363 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 c81787e74..eb5077992 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 7ec719709..3633bdbd0 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 f2ceb43b5..4e8c7be59 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 c037e31df..d17d7a709 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 000000000..4f4c9cf6e --- /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 3e01697cf..ab9ea42c9 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 6cc8c8581..6036d1941 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 868173414..9ce41ecdf 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 47bfe902f..bc2832caa 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 02f7aba34..135fad90d 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 fbcf9139d..f8cf6ebc8 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 efb3c8980..000000000 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 5bfc425d6..000000000 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 000000000..65348b81f --- /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 cbda39e57..66587ee19 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 000000000..c33407980 --- /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 77af442c3..d83047a07 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 ae7e29b79..aa246b4ad 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 000000000..a404501d5 --- /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 7246219a4..0413c2787 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 c1cf9d1ce..96fcd5990 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 9b51b7aef..a6ca4d1cf 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 d4a547298..c4b14dc0e 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 a77b802f5..d1ceb9c36 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 d88c1e555..2dfcefa4f 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 ab38d73a5..1627a38df 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 2b4776a2d..c6a100bf4 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 90789525c..bd52bce60 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 f8aed1284..c0f37e5eb 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 b2476e437..2c134b065 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 b45e9f9ab..5061b735b 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 cf65cdcbc..aef88ebc7 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 254d7a5b7..d32d644b0 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 d209b2d0a..c5f83fe38 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 d162f745f..56c2b1215 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 b27b4d0e8..c7c8e2a45 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 399228cc2..f78ea7fca 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 415984d13..d5de6f081 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 f20bbca64..898c6f4e1 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 4476e8389..4c0cf20cb 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 f86331035..63f9d6d8e 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 8d4d61876..4f53661e9 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 5c1e90a08..6335cf7af 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 cea5b3c6f..79347a70e 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 5b9669847..70e956e55 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 4f5c4a69a..0e2dd7023 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 1a589ced4..25b0a7328 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 f513a07f5..e952287ef 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 c6b451728..045c7e15e 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 861af9175..02ff9ea16 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 d35e20da7..ce65a0749 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 0c1534f4a..098ca5341 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 71ef0a74d..74621cdc6 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 7ad8630c4..cb935142c 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 be1cb7da6..1ed9bac04 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 c39949a09..de4717b25 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 8d2b826ac..fc816bbbd 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 8297dadee..ffbdc3a4f 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 893ff00eb..7a5bce9a9 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 7a5706ab1..50631ecd5 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 dfe205f69..0ac84d975 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 913e6dbaf..8093a3326 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 61a62d8bb..6df4cbe57 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 3c60e5935..54d59c6b9 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 ed2fdb695..a1123a8a9 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 8bc5d770c..51c6c96c4 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 c804f54ab..5ed2196ee 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 3171e3ba4..2aeec93f3 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 7bc04b46e..7f381a9dd 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 edcb4c4c3..c08a6a958 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 e2fc88478..5145e4232 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 2f27fe5de..e9e565014 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 6117c3349..a2f7ffd30 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 1a80070d5..a9d265ceb 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 da2169189..9ca29fe3f 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 686df7798..1fb67b86b 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 a3eb4fcaa..9918f05ba 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 e9faed951..7a8371ec9 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 061953d14..06ea584f3 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 a0b52da48..be94a1302 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 3ff9bae41..8bd34d1e2 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 0ccda1495..4fc3d8b22 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 cd90c0463..f401b4d4b 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 95da8d357..bfddaf338 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 1dd6d11c5..21a85d3ad 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 9b66447d5..e3f4a0ab1 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 3ceba3c9f..de90b53cc 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 c10b762b8..e18f80f80 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 c27d06478..bea9809e2 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 394d0f9d0..0db828070 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 73cf864e2..acad6eb18 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 07b987a51..bb8a5d4cd 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 9208fcf33..a9f508c9d 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 5f33976a3..d3ebc7b3d 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 eebf022c3..8bfdf7fe9 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 6dd130bbc..22b38e1e6 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 8e568e420..cc99d5b02 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 e9247330a..3cedccdf1 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 929ebd004..d633cd6b7 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 120137d36..e0a1d51b4 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 af525addd..e3464a8c7 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 167c73b67..83eb31ee6 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 01eb781ee..d66b84fc8 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 b6bd30ba1..7998f1ffa 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 21cf45f42..d7a6ea2be 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 45725db4a..1ca1ace36 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 13c79e17b..a02b88051 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 6c4ebcec7..c43d31005 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 12b249f29..3d2875fd4 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 aec62cc86..e0665b275 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 35c196348..41778eaad 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 70ba9221e..3033597b8 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 e84e04daa..b102c83f5 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 afd4de936..0f7b09443 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 ffbad26e3..d92ce4927 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 892409da7..10a4f454a 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 4ffd0b160..fb99fca98 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 c4994d829..c4d7d8280 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 29f34b764..44f7be5bf 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 74fcaddb7..65c13ec02 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 b31323d4b..80fc950c2 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 2678bf583..31f0905fc 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 509367501..3685e9712 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 7ceb1313f..682675809 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 979c88bb0..4c24917f3 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 cba5cee8c..73850c254 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 bc0c02efe..52fa0ebf5 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 6a2b0598d..8a0af6b9e 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 5832a1cfb..04a9a41f4 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 3d54ff803..7b79f6ff5 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 cac852af4..d13519dad 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 39c93b751..26ce34468 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 000000000..83372b04a --- /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 7a0c2b96f..59e2e6acd 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 b1184a264..a9f8e9484 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 0a60fa4a7..c177cf0b5 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 ae3c72abb..2d85856ba 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 7bed18572..1a206409f 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 000000000..223c84bd8 --- /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 83532c673..00ac6835b 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 572fade3e..ea6d1c486 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 e198db1de..6230a5914 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 805734ffc..26e8ab598 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 bee574165..4c64913af 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 c14326ba8..db7f397f2 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 e3a19dc1f..193d4b4e3 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 88f787328..75ff69f63 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 b32f150a9..5b534ab05 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 31f00cad6..9fe1c1bf6 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 000000000..d3df123ef --- /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 000000000..ba19616a7 --- /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 d5564e749..c4fd1e274 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 37ab9a3f8..121dfc384 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 c647038c1..c461bfa5f 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 0b98b0e2b..4f5bbc001 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 c1332cfc7..fce7c1f4e 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 0e56d550c..097eeed8c 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 67898886f..4409fbb45 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 25249718c..b5a20c16b 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 e58aa890a..000000000 --- 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 000000000..a89b7f0ba --- /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 cea5b1f78..15b352982 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 cde45ce4c..821d24e41 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 222156e72..59d0c266a 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 bcb8c6217..a41f5c356 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 000000000..17dd0a991 --- /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 000000000..4efb875d3 --- /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 7b482ef78..6ab88a006 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 fedea1151..497c539f9 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 000000000..88329f764 --- /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", + }, +}