{"owner":"agalwood","repo":"Motrix","hasSkills":true,"totalSkillsCount":28,"totalTokensCount":15563,"categories":["root-instruction","anthropic-skill","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md",".claude/rules/code-style.md","CLAUDE.md",".claude/rules/architecture.md",".claude/rules/commit-and-quality.md",".claude/rules/domain-model.md",".claude/rules/electron-vite.md",".claude/rules/git-workflow.md",".claude/rules/i18n.md",".claude/rules/bridge.md",".claude/rules/panel-layout.md",".claude/rules/language-and-docs.md",".claude/rules/plugin-registry.md",".claude/rules/plugins.md",".claude/rules/renderer.md","packages/native-host/package.json","packages/native-host/README.md","packages/native-host/README.zh-CN.md","tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json","tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json","tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json","tests/fixtures/plugins/test.resolve-band/motrix-plugin.json","tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json","tests/fixtures/plugins/test.enrich-band/motrix-plugin.json","tests/fixtures/plugins/test.echo/motrix-plugin.json","tests/fixtures/plugins/test.audit-band/motrix-plugin.json","tests/fixtures/plugins/test.allcaps/motrix-plugin.json","tests/fixtures/plugins/test.allcaps/locales/en-US.json"],"skills":{"AGENTS.md":"# AGENTS.md\n\nClaude Code rules are the canonical agent guidance for this repository. Codex\ninherits them instead of maintaining a second copy.\n\nBefore inspecting or changing files, read:\n\n1. `CLAUDE.md`.\n2. Every `.claude/rules/*.md` file without `paths` frontmatter.\n3. Every rule whose `paths` patterns match the files being inspected or\n   changed.\n\nIf the scope expands, load the newly matching rules before continuing. Do not\nload unrelated path-scoped rules by default.\n\nConflicts resolve in this order: current user instructions, this file,\n`CLAUDE.md`, matching `.claude/rules/*.md`, then default agent behavior.\nUpdate the canonical Claude rules rather than duplicating guidance here.\n",".claude/rules/code-style.md":"---\ndescription: TypeScript naming, import, alias, and schema conventions\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.css\", \"*.config.ts\"]\n---\n\n# Code Style\n\nBiome owns formatting and general lint rules; do not duplicate its settings\nhere. The conventions below cover repository-specific boundaries.\n\n## Naming\n\n- JavaScript, TypeScript, TSX, and stylesheet files use `kebab-case`.\n- Conventional qualifiers remain lowercase: `.test`, `.spec`, `.e2e`,\n  `.integration`, `.darwin`, and `.win32`.\n- Exported classes and React components remain PascalCase even though their\n  filenames are kebab-case.\n- Rust and Python use `snake_case`; Cargo binary entrypoints may use\n  kebab-case to match the executable name.\n- Run `pnpm run check:file-names` after adding or renaming files.\n\n## Imports and Aliases\n\n- Use `import type` for type-only imports and `node:` for Node builtins.\n- Prefer the configured alias over a deep relative import; local sibling or\n  parent imports may remain relative.\n- Alias availability is target-specific. Application builds expose\n  `@shared`, `@core`, `@renderer`, and `@main` as appropriate; server targets\n  may expose `@server`, and tests may expose `@test-utils`.\n- Do not assume aliases cross targets: Vitest intentionally has no `@main`,\n  and the QuickJS worker exposes only `@shared` and `@core`. Check the target's\n  TypeScript and Vite configuration before using an alias.\n\n## Shared Runtime Contracts\n\nUse Zod from `zod` for untrusted external data and cross-layer payloads. A\nshared schema is the source of truth for its runtime constraints, inferred\ntype, and defaults; do not create parallel interfaces or hand-written\nvalidators for the same contract. Pure cross-layer schemas belong in\n`src/shared/schemas/`; host-specific validation stays in its owning layer.\n\n## Comments Describe What Exists\n\nA comment in the present tense is a claim about the current code, and readers\nact on it. Describe an intended-but-unwritten consumer in the conditional, or\nas a `TODO`, and say plainly that nothing does it yet.\n\nThis matters most for comments that assert a safety property. Two distinct\nbugs shipped behind such comments: a writer documented a \"backstop\" that\nreported failures which nothing reported, and a socket wedged silently\nforever; and a getter pair was documented as being read by a wiring layer that\ndecided when to reconnect, when the only readers were tests. In both cases the\ncomment was the reason no one looked.\n\nWhen a guard's justification depends on a case, check that it covers every\ncase the code now reaches — a rationale that was true when written and is\nfalse for a later-added branch argues for the bug.\n","CLAUDE.md":"# CLAUDE.md\n\n## Project\n\nMotrix Turbo is an Electron and Node/Web download manager. Keep `src/core/`\nhost-neutral so it can be reused by both shells and replaced independently.\n\nDevelopment happens on **`main`**. The frozen **`master`** branch is the legacy\nElectron 22/Vue 2 application. Branch from and target PRs at `main`, never\n`master`.\n\n## Core Commands\n\n```bash\npnpm start                         # Electron development runner\npnpm start:server                  # Built Node/Web server\npnpm test                          # Vitest suite\npnpm exec vitest run <test-path>   # Focused test\npnpm run lint                      # Biome over the repository\npnpm exec tsc --noEmit             # Type-check\npnpm build                         # Desktop production build\npnpm build:server                  # Node/Web production build\npnpm test:e2e                      # Playwright suite\n```\n\nUse `pnpm exec`, not `npx`. The authoritative commit gate is\n`.claude/rules/commit-and-quality.md`.\n\n## Architecture Boundaries\n\n1. `src/core/` never imports `electron` or `src/main/`.\n2. `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`;\n   use `@renderer/lib/transport` with shared protocol constants.\n3. `src/shared/` contains pure cross-layer contracts and descriptive runtime\n   data only: no IO, timers, network, Electron, or Node-specific APIs.\n4. Use `Commands`, `Queries`, `Events`, and their `Bridge*` counterparts from\n   `src/shared/protocol/`; never use raw transport channel strings.\n\n## Rule Routing\n\nRules without `paths` frontmatter are global. Load path-scoped rules only when\ntheir patterns match the files under inspection or modification.\n\n| Rule | Scope |\n|---|---|\n| `commit-and-quality.md` | Required checks and conditional validation |\n| `git-workflow.md` | Commits, branches, PRs, and releases |\n| `language-and-docs.md` | Language and public/private documentation |\n| `browser-artifacts.md` | Browser test output paths and temporary UI verification files |\n| `architecture.md` | Layer boundaries and transport flow |\n| `electron-vite.md` | Build, packaging, native ABI, and pnpm |\n| `code-style.md` | TypeScript, React, CSS, and file naming |\n| `renderer.md` | Renderer state, components, forms, and transport |\n| `icons.md` | Semantic icon imports, library isolation, and tree shaking |\n| `panel-layout.md` | Viewport height and scrolling layout |\n| `i18n.md` | Locale catalogs and user-visible strings |\n| `domain-model.md` | Shared domain types, validation, and errors |\n| `plugins.md` | Plugin sandbox, capabilities, and builtins |\n| `plugin-registry.md` | Registry compatibility and install integrity |\n| `bridge.md` | MDXP pairing, dispatch, and transports |\n",".claude/rules/architecture.md":"---\ndescription: Architecture boundaries for the shared desktop and server app\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"scripts/check-boundaries.mjs\"]\n---\n\n# Architecture\n\nMotrix keeps its product core host-neutral so it can run behind either the\nElectron shell or the Node server and remain replaceable by a future engine.\n\n## Layer Matrix\n\n| Directory | Role | Allowed dependencies |\n|-----------|------|----------------------|\n| `src/renderer/` | Electron/browser frontend | `@shared/`, renderer-local modules |\n| `src/core/` | Host-neutral product core | `@shared/`, host-neutral Node/external libraries |\n| `src/main/` | Electron shell and IPC | `@core/`, `@shared/`, Electron |\n| `src/preload/` | Electron bridge | pure `@shared/` protocol values/types, Electron |\n| `src/server/` | Node/Docker shell | `@core/`, `@shared/`, server libraries |\n| `src/shared/` | Cross-layer contracts | pure schemas, constants, data, and utilities only |\n\n## Hard Boundaries\n\n- `src/core/` never imports Electron or `src/main/`.\n- `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`.\n- `src/server/` never imports Electron or `src/main/`.\n- `src/shared/` never imports an application layer and contains no IO, timers,\n  network access, Electron APIs, or Node-specific APIs.\n- Production code never imports `src/test-utils/`; generated builtin plugin\n  output is never used as source.\n\n`pnpm run check:boundaries` is the automated baseline, but changed imports\nmust also be reviewed against the matrix because not every exception is\nmachine-enforced.\n\n## Dual Transport Contract\n\nThe same renderer-facing transport contract serves both hosts:\n\n```text\nElectron: renderer -> ElectronTransport -> preload -> main IPC -> core\nBrowser:  renderer -> HttpWsTransport -> server RPC/events -> core\n```\n\nRenderer command/query/event traffic uses `@renderer/lib/transport`. Direct\n`window.motrix` access is limited to the Electron transport and narrowly scoped\nElectron platform adapters; feature code stays behind those abstractions.\nEvents return through the selected shell and transport, so renderer state must\nnot depend on a host-specific channel.\n\nAll channel names come from `src/shared/protocol/`. Use `Commands`, `Queries`,\n`Events`, and their `Bridge*` counterparts instead of raw strings.\n\n## Engine Adapter\n\nProduct-level code targets `EngineAdapter` in\n`src/core/engine/engine-adapter.ts`, never aria2 RPC types directly. Concrete\nengines translate at the adapter boundary. `EngineSupervisor` is the sole\nowner of engine start, stop, and restart lifecycle.\n",".claude/rules/commit-and-quality.md":"---\ndescription: Required commit gate and change-specific validation\n---\n\n# Commit & Quality Gate\n\n## Required for Every Commit\n\nRun all three commands and fix failures before committing:\n\n```bash\npnpm run check:boundaries\npnpm run lint\npnpm exec tsc --noEmit\n```\n\n`pnpm run lint` (`biome check .`, scoped by biome.json) is the same command\nCI runs — do not substitute a narrower path list, and never pipe it in a way\nthat discards its exit code.\n\n`check:boundaries` is an automated baseline, not a complete architecture\nproof. Review changed imports against `architecture.md` as well.\n\nStage only the intended files or hunks, then inspect `git diff --staged` and\nrun `git diff --cached --check`. Do not hide failures because a CI job is\nnon-blocking.\n\n## Change-Specific Checks\n\nRun every check that matches the change:\n\n- Behavior or logic: focused tests with `pnpm exec vitest run <test-path>`;\n  use `pnpm test` for broad cross-cutting changes.\n- Browser/Electron user flows: `pnpm test:e2e` when the affected flow has E2E\n  coverage.\n- Locale resources or i18n behavior: `pnpm run check:i18n`.\n- Added or renamed files: `pnpm run check:file-names`.\n- Plugin manifest contracts or `@motrix/plugin-manifest-schema`:\n  `pnpm run check:schema-parity`.\n- Dependencies, bundled assets, or license metadata:\n  `pnpm run check:third-party-notices`.\n- Native host Rust:\n  ```bash\n  cargo fmt --manifest-path packages/native-host/Cargo.toml --all -- --check\n  cargo clippy --manifest-path packages/native-host/Cargo.toml --all-targets --locked -- -D warnings\n  cargo test --manifest-path packages/native-host/Cargo.toml --locked --all-targets\n  ```\n- Packaging or release code: run the focused tests under `tests/scripts/` and\n  the relevant verifier. Follow workflow-supplied arguments for checks such as\n  `check:update-artifacts`.\n\nUse `pnpm exec biome check --write .` only for reviewed, auto-fixable\nissues; re-run the required gate afterward.\n",".claude/rules/domain-model.md":"---\ndescription: Engine-neutral domain aggregates, validation, defaults, and errors\npaths: [\"src/shared/**\", \"src/core/**\"]\n---\n\n# Domain Model\n\n## Engine-Neutral Aggregates\n\nProduct-level code never exposes aria2 raw types or naming. Aria2 values are\ntranslated at the engine adapter boundary into the aggregates in\n`src/shared/types/`:\n\n- `DownloadTask`, `TaskStatus`, and `TaskType` describe task identity and\n  lifecycle; `BtExtension` carries BitTorrent-only details.\n- `EngineState` and `EngineFeatureReport` describe engine lifecycle and\n  detected capabilities without binding consumers to an RPC implementation.\n- `AppSettings` is the persisted aggregate; `EngineSettings` and\n  `MotrixAppSettings` separate engine-facing and application-facing concerns.\n- `GlobalStats` is the cross-engine aggregate used by UI and protocol layers.\n\nKeep managers and stores engine-neutral (`<Domain>Manager`, `<Domain>Store`).\nEngine-specific conversion and RPC fields stay under the concrete adapter.\n\n## Schemas and Defaults\n\n- Cross-layer runtime schemas live in `src/shared/schemas/`; core may compose\n  them but must not duplicate their constraints.\n- Settings schemas use `.catch(safeDefault)` for recoverable persisted values.\n- Derive aggregate defaults from the schema, for example\n  `const DEFAULTS = schema.parse({})`, so validation and defaults cannot drift.\n- Error codes used by settings validation follow\n  `settings.{namespace}.{field}` for localization.\n\n## Errors\n\nUse `AppError` and `ErrorCode` from `@shared/errors` for domain failures that\ncross a service, transport, or UI boundary. Raw `Error` remains appropriate\nfor low-level implementation failures that are caught and translated before\nleaving their owning layer.\n",".claude/rules/electron-vite.md":"---\ndescription: Build outputs, Electron loading policy, pnpm scripts, and native ABI boundaries\npaths: [\"electron-builder.json\", \"pnpm-workspace.yaml\", \"package.json\", \"Dockerfile\", \"vite.*.config.ts\", \"scripts/dev.mjs\", \"scripts/postinstall.mjs\", \"scripts/ensure-electron-runtime.mjs\", \"scripts/ensure-native-abi.mjs\", \"scripts/native-binary-target.mjs\", \"scripts/stage-*-app.mjs\", \"scripts/verify-*-package.mjs\", \"scripts/before-*-*.mjs\", \"src/main/index.ts\", \"src/main/window/renderer-url-policy*\", \"src/main/window/window-manager*\", \"src/preload/**\"]\n---\n\n# Electron + Vite\n\n## Required Artifacts\n\n| Target | Output |\n|--------|--------|\n| Electron main | `dist/main/index.cjs` |\n| preload | `dist/preload/preload.cjs` |\n| QuickJS worker | `dist/core/plugin/host/quick-js-worker.cjs` |\n| Electron renderer | `dist/renderer/` |\n| Node server and CLI | `dist/server/index.mjs`, `dist/server/motrix-admin.mjs` |\n| browser renderer | `dist/renderer-web/` |\n\nBecause `package.json` declares `\"type\": \"module\"`, main, preload, and worker\noutputs must remain `.cjs`; server outputs remain `.mjs`. The package `main`\nfield must match the main artifact. Build entries sharing an output directory\nmust have unique basenames, and every consuming `build:*` flow must produce\nthe entries it packages.\n\n## Preload and Renderer URL Policy\n\nAt runtime `__dirname` is `dist/main/`, so the preload path is:\n\n```ts\npath.join(__dirname, '../preload/preload.cjs')\n```\n\nRenaming or moving either artifact requires updating this path. Pass\n`VITE_DEV_SERVER_URL` once to `initializeRendererUrlPolicy()` and load every\nrenderer window through `rendererUrlPolicy.loadWindow(win, route)`. Do not add\nfeature-local `loadURL()` or `loadFile()` calls that bypass its loopback-origin\nand packaged-file checks.\n\n## pnpm and Native ABI\n\n- Project pnpm settings belong in `pnpm-workspace.yaml`. Keep `nodeLinker:\n  hoisted` unless the Electron packaging smoke job proves isolated linking\n  works.\n- Install-script permission is controlled by pnpm 12 `allowBuilds`. Keep\n  `electron` allowed for compatibility, but do not rely on `pnpm install` to\n  hydrate Electron: it exposes `install.js` as a package bin without a\n  `postinstall` script. Local workflows that consume Electron or its licenses\n  must run `pnpm run ensure:electron-runtime` first; that command validates the\n  entire payload and repairs partial installs safely. CI/container workflows\n  may invoke `install.js` directly when they immediately validate the result.\n- Native modules such as `better-sqlite3` must match the active ABI. Tests use\n  the Node ABI; Electron and E2E use the Electron ABI. Preserve the\n  `ensure-native-abi.mjs` hooks when changing test or start scripts.\n- `postinstall.mjs` independently controls Electron rebuild and aria2 fetch\n  through `MOTRIX_SKIP_ELECTRON_REBUILD` and `MOTRIX_SKIP_ENGINE_FETCH`.\n  Skipping one must never imply skipping the other.\n- If Electron or a native dependency changes, rebuild for the intended target\n  and exercise the corresponding Node and Electron paths.\n\n## Server Docker Boundary\n\nThe server image uses system aria2 and skips both Electron rebuild and engine\nfetch. `stage-server-app.mjs` selects one target-platform `better-sqlite3`\nprebuild, and `verify-server-package.mjs` verifies the staged payload. The\nruntime image intentionally has no pnpm or build toolchain; never rely on a\nruntime native rebuild.\n",".claude/rules/git-workflow.md":"---\ndescription: Git commit, branch, PR, merge, and release policy\n---\n\n# Git Workflow\n\n## Commits\n\nUse Conventional Commits in English:\n\n```text\n<type>(<optional-scope>): <imperative summary>\n```\n\nAllowed types are `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `chore`,\n`ci`, and `style`. Keep the summary lowercase, without a period, and under 72\ncharacters. Add a body for non-obvious rationale and a\n`BREAKING CHANGE: ...` footer when applicable.\n\nDo not add AI attribution or co-author trailers automatically. Preserve\ntrailers explicitly supplied by a contributor or required by legal policy.\nFollow `commit-and-quality.md` before creating a commit.\n\n## Branches and Pull Requests\n\n- `main` is the protected active branch; `master` is frozen legacy code. Never\n  target or merge Turbo work into `master`.\n- Branch from current `main`. Use\n  `<type>/<snake_case_topic>_<YYYYMMDD>`, optionally placing an issue number\n  before the topic.\n- Never push directly or force-push to `main`. All changes use PRs.\n- Rebase a private feature branch onto `main` before review. Never rebase\n  `main` or a branch shared with other contributors.\n- After a rebase, update only your own feature branch with\n  `git push --force-with-lease`; never use an unconditional force push.\n- Keep a PR focused. Its title follows Conventional Commits; its description\n  explains what, why, and how the change was verified.\n- Squash merge feature PRs by default. Use a regular merge only when a release,\n  hotfix, or intentionally structured refactor needs commit-level history.\n- Delete merged branches.\n\n## Release Safety\n\nThe checked-in workflows and scripts are the release authority; do not replace\nthem with local publishing steps.\n\n- Set `package.json` to strict SemVer, then create a protected\n  `v<package-version>` tag whose commit is on `main`. The tag and package\n  versions must match; only stable and `beta` channels are supported.\n- A tag push triggers `.github/workflows/release.yml`. Publishing is allowed\n  only after its platform builds, isolated signing/finalization jobs,\n  required signature checks, package verification, artifact assembly, and\n  update-artifact validation succeed. macOS releases require signing and\n  notarization; Windows releases may be explicitly finalized unsigned when\n  both Authenticode secrets are absent. Disclose unsigned Windows artifacts in\n  the public release notes. Manual dispatch validates the build path but does\n  not sign or publish.\n- Do not manually upload release files, reuse unverified artifacts, bypass\n  protected environments, weaken signing-input isolation, or overwrite an\n  existing immutable container tag.\n- Protected stable and beta tags publish one verified amd64/arm64 Snap build\n  set to `latest/edge`. Promote those exact revisions with `snap-promote.yml`\n  from protected `main`; never rebuild during promotion. Stable rejects\n  prereleases.\n- If a release gate fails, fix the source or workflow and run the complete\n  gated path again. Do not publish a partial platform set.\n",".claude/rules/i18n.md":"---\ndescription: Internationalization contracts for renderer/main strings and locale resources\npaths: [\"src/renderer/**/*.ts\", \"src/renderer/**/*.tsx\", \"src/main/**/*.ts\", \"src/core/i18n/**\", \"src/core/plugin/plugin-registry*\", \"src/core/plugin/capabilities/i18n*\", \"src/core/plugin/manifest/i18n-resolve*\", \"src/server/index.ts\", \"src/server/operator-admin*.ts\", \"src/shared/constants/locales*.ts\", \"src/shared/schemas/locale*.ts\", \"src/shared/i18n-resources.ts\", \"src/shared/locales/*.json\", \"scripts/check-i18n.mjs\", \"tests/scripts/check-i18n.test.ts\"]\n---\n\n# Internationalization\n\nEvery user-visible application or operator string must use i18next; do not\nhardcode UI labels, messages, notifications, dialogs, or CLI output. Renderer\ncomponents use `react-i18next`; Electron main uses the initialized instance\nfrom `src/main/lib/i18n.ts`; the server operator CLI creates an isolated\ninstance from shared resources. Keep `t` in React hook dependency arrays.\nRoute host-wide locale changes through `LocaleCoordinator`: serialize changes,\nskip stale revisions, reconcile late targets, and publish `LocaleChanged` only\nafter the current apply succeeds. Keep plugin-registry and capability-host\nlocale changes transactional, including rollback on failure.\n\n## Resource contract\n\n- `src/shared/locales/en-US.json` is the key-shape source of truth. Add every\n  logical key to every locale registered in `SUPPORTED_LOCALES`; never update\n  only a subset.\n- `src/shared/i18n-resources.ts` statically and exhaustively registers loaders for those\n  locale files. Only English and Simplified Chinese are eager; await resource loading before\n  committing a host or renderer language change. New languages also require a catalog entry in\n  `src/shared/constants/locales.ts` and a matching JSON resource.\n- The server operator CLI uses `src/server/operator-admin-locales.ts` to\n  import only the `operatorAdmin` namespace from every catalog. Keep its locale\n  map exhaustive; importing full application catalogs into the CLI duplicates\n  Web UI resources in the Server package and can exceed its size budget.\n- Every translation leaf is a string. Keep readable locale files as UTF-8;\n  avoid unnecessary `\\uXXXX` escapes.\n- English user-visible prose, including matching test fixtures and assertions,\n  uses the typographic apostrophe `’` for contractions and possessives; do not\n  substitute the ASCII apostrophe `'`. Keep `'` only where technical syntax or\n  an external data contract requires that exact character.\n- Preserve the exact `{{placeholder}}` set across every locale and every plural\n  variant.\n- Plural keys use i18next category suffixes. Supply every cardinal category\n  returned by `Intl.PluralRules(locale)`; `_zero` is the only permitted extra\n  category because i18next treats it as an exact-zero override.\n\n## Namespaces\n\nKeys follow the feature that owns them and use camelCase leaves; descriptions\nnormally pair `name` with `nameDesc`.\n\n- Settings grid cards: `settings.cards.<card>.*`\n- Settings dialogs/pages: `settings.<dialog>.*`\n- BitTorrent settings and tracker defaults: `settings.bittorrent.*`\n- Tracker management: `trackers.effective.*`, `trackers.blacklist.*`,\n  `trackers.combobox.*`, and `trackers.sync.*`\n\nExtend the relevant namespace rather than creating a parallel spelling or\nplacing tracker UI under settings merely because trackers are configurable.\n\n## Marketplace locale independence\n\nApplication locales and registry listing locales are separate contracts.\n`SupportedLocale` must not constrain `listing.defaultLocale` or keys in the\nregistry's open BCP 47 `listing.localizations` map. Resolve marketplace display\nand search text with the vendored registry resolver; do not use\n`resolveSupportedLocale` or infer sibling regions such as `zh-TW -> zh-CN`.\n\n## Verification\n\nRun `pnpm run check:i18n` after any locale, catalog, placeholder, plural, or\nresource-registration change. It verifies file/catalog parity, logical key\ncoverage, plural categories, string leaves, and placeholder parity. Its tests\nmust use temporary fixtures and never rewrite real locale files. Tests that\nrender translated components load the real renderer initialization with:\n\n```ts\nimport '@renderer/lib/i18n'\n```\n",".claude/rules/bridge.md":"---\ndescription: MDXP bridge lifecycle, authorization, transports, and protocol boundaries\npaths: [\"src/core/bridge/**\", \"src/core/bridge-receiver/**\", \"src/main/bridge/**\", \"src/server/bridge/**\", \"src/shared/protocol/bridge.ts\", \"src/shared/schemas/bridge-settings.ts\", \"src/shared/config/native-messaging-extensions.json\", \"packages/native-host/**\", \"docs/bridge-pairing-protocol.md\", \"package.json\"]\n---\n\n# Bridge (MDXP / JSON-RPC 2.0)\n\nThe bridge uses JSON-RPC 2.0 over WebSocket and HTTP. The\n[`@motrix/mdxp`](https://github.com/motrixapp/mdxp) package is the source of\ntruth for wire schemas, method constants, types, errors, and connection\nbehavior; its version comes from `package.json`. Do not recreate MDXP schemas,\nnumeric error codes, or the removed legacy frame protocol in this repository.\nImport `ErrorCodes` and `makeMdxpError` from the package.\n\n## Pairing and session lifecycle\n\n- Extension WebSockets speak MBP1 (`docs/bridge-pairing-protocol.md`), which\n  authenticates *below* MDXP. `/pair?nonce=...` runs the §6 first-pair\n  handshake and `/v1` the §8 challenge–response; `/v1` carries **no**\n  credential in its URL and every query, including `?token=`, is rejected by\n  the raw-route gate. Only once a route\n  authenticates does MDXP start, inside the §10 AEAD envelope, and\n  `motrix/initialize` is the client's first request there.\n- `WebSocketBridgeServer` always owns and registers `motrix/initialize`.\n  Shell-provided download handlers are registered separately and must be wired\n  before `server.start()`; never expose a listener with incomplete handlers.\n- Authorization and readiness are different states. Every extension WebSocket\n  session is authorized **at the transport**: `adoptAuthenticatedSession` marks\n  the connection before registering a handler, and `motrix/initialize` is a\n  capabilities exchange that grants nothing and never returns a `pairToken`.\n  The `authorizedDispatch` gate over control-plane and download methods stays\n  as defence in depth. Unary HTTP separately requires its bearer-token and\n  agent-facing gates.\n- The six MBP1 `BridgeServerOptions` (`instanceId`, `serverGeneration`,\n  `appVersion`, `credentials`, `isOfficialId`, `queueMbp1Dialog`) resolve as a\n  unit. A server missing any one of them has no extension WebSocket surface at\n  all: both upgrades are refused rather than falling back to an\n  unauthenticated one.\n- A connection becomes ready only after the peer sends the\n  `motrix/initialized` notification. Providers passed to\n  `UrlResolutionService` must filter to ready connections before sending\n  server-initiated `url/probe` or `url/resolve` requests.\n- The connection close handler removes its own current session and disposes the\n  connection. Do not mutate the server's session map from feature code.\n\nApproval is proven by typing the pairing code, never by clicking an Allow\nbutton. The dialog shows an 8-symbol Crockford-alphabet code grouped\n`XXXX-XXXX` (`mbp1/pairing-code.ts`), lives 120 seconds, and *is* the SPAKE2\npassword: it never travels on the Extension connection and MUST NOT be logged\nat any level (§7.1). Server operator snapshots/events and an explicitly selected\ninteractive `motrix-admin extension pair` view follow §4.4. The terminal uses\nfixed loopback RPC with the operator Bearer credential, rejects redirected streams\nand JSON, revalidates the exact session, and never renews the code lifetime. A pairing session resolves to one of three identities —\n`official`, `attested-non-official`, `unverified` — and `official` is decided\nby the immutable allowlist only, never the NM manifest set and never the\nuser-added trusted-extension registry (§5).\n\nPair nonces are one-shot, expire after 60 seconds, and are consumed only by\n`/pair` — before the route does anything else, so a refused upgrade never\nreaches a session or a dialog. Never persist them. `endpoint.json` contains\ndiscovery information and the local CLI bearer token, `localToken`, which is\nnow dual-purpose: the native-messaging host also derives its §9.2 ticket-MAC\nkey from it (`ticketKey = HKDF(UTF8(localToken), \"MBP1/nm-ticket/v1\", \"mac\",\n32)`) but MUST NOT expose it to the extension. `localToken` persists across\nbridge restarts — only `serverGeneration` rotates on every start\n(`loadOrCreateBridgeIdentity`) — so an NM ticket minted before a restart takes\nthe §9.2 semantic-downgrade path instead of aborting on a stale-generation MAC\nmismatch: the ticket then contributes nothing, and identity falls back to what\nthe verified origin alone establishes (§9.2 only-raise — a stale ticket is\nnever worse than presenting none). The nonce route is now `POST /nonce` and requires\n`X-Motrix-Bridge: 1`; the former `GET /nonce` is gone and 404s.\n\nWhile bound to a loopback host, every route and upgrade rejects a `Host` header\nthat is not `127.0.0.1`, `localhost`, or `[::1]` with the bound port — the §4.3\nDNS-rebinding guard. It is inert for a non-loopback bind, which keeps its\nexisting token + reverse-proxy model.\n\nThe Server's documented Docker path deliberately binds MDXP to `0.0.0.0`\ninside the container so Docker can publish port 16801. Remote Extension routes\nmay use that non-loopback listener only behind a parser-issued, all-or-nothing\nremote configuration; the WS/WSS public URL is required, while the operator URL\nis optional. Without an HTTPS operator URL or explicit HTTP consent, code-bearing\noperator snapshots/events are restricted to the authenticated local Bearer path.\nBoth HTTP snapshots and event broadcasts must enforce the delivery policy from\nthe same parser-issued configuration. `RemoteExtensionSurfacePolicy` enforces the exact\npublic Host and raw route prefix plus the admission limits before MBP1 runs.\nDo not reintroduce a loopback-only bootstrap guard: container loopback is not\nreachable through Docker's published-port path.\n\n`GET /discovery` additionally reports unauthenticated compatibility hints:\n`runtime`, `extensionPairing: { protocol: 'mbp1', versions: [1] }`, and\n`applicationProtocols: { mdxp: ['1.0'] }`. They may drive an upgrade message,\nbut never trust, downgrade, or a port pin; authenticated initialization remains\nauthoritative. The WebSocket parser caps messages at the largest valid active\nenvelope (1 MiB plaintext + 24 bytes framing), while the state machines retain\ntheir stricter 16 KiB pre-authentication check.\n\nNothing SPAKE2-adjacent may be logged at any level: the pairing code, `w`, any\nPAKE intermediate, derived keys, MACs, or NM tickets (§11). A `pairError` frame\ncarries only its nine-code vocabulary (`unsupportedVersion`, `busy`,\n`rateLimited`, `codeMismatch`, `expired`, `aborted`, `authFailed`,\n`protocolViolation`, `pairingFailed`) and never a step-identifying detail.\n\nThe bridge tries the candidate port range 16802–16806 in order and falls back\nto an ephemeral port, reported as `degraded`, once every candidate is taken.\n`bridge.fixedPort` pins a single port instead and is applied by restarting the\nruntime. `endpoint.json` stays the authoritative discovery record for both the\nCLI and the Native Messaging host regardless of which port was chosen. The\nserver shell deliberately keeps its own explicit configured host and port and\nnever walks the candidate range.\n\n## MBP1 module map\n\n`src/core/bridge/mbp1/` implements `docs/bridge-pairing-protocol.md`:\n`canonical.ts` (§2 encoding primitives), `scrypt-w.ts` (§6.2 password-to-scalar\nderivation), `pairing-code.ts` (§7.1 code generation/display/normalization),\n`spake2-core.ts` (§6.3–6.6 the generic SPAKE2 core), `transcript.ts` (§6.4\ntranscript construction), `reconnect-mac.ts` (§8 reconnect MAC),\n`reconnect-rate-limit.ts` (§8 per-origin and global reconnect rate limiting,\ndeliberately separate from `flood-control.ts`'s §7.3 counters),\n`envelope.ts` + `envelope-message-stream.ts` (§10 AEAD framing),\n`ticket-verify.ts` (§9 NM ticket verification), `frames.ts` (§6.1/§11 wire\nframe schemas and the `pairError` vocabulary), `nonce-service.ts` (§4.2\none-shot pairing nonces), `flood-control.ts` (§7.3 pairing admission limits),\n`pre-auth-table.ts` (§4 pre-authentication bookkeeping), `pair-session.ts`\n(§6 the `/pair` state machine), `reconnect-session.ts` (§8 the `/v1` state\nmachine). Alongside it: `../credential-store.ts` (§6.7 durable MBP1\ncredentials), `../bridge-identity.ts` (§9.2 the persistent `localToken` /\nrotating `serverGeneration` loader), and `../endpoint-file-writer.ts` (the\n`endpoint.json` writer).\n\n## Envelope close codes\n\nOnce §10 AEAD framing is active, envelope faults are classified by a\nthree-state discriminated union — `peer-violation`, `usage-limit`, `internal`\n— and by direction. The direction matters: the same `EnvelopeViolationError`\nfrom inbound `open()` means the peer sent something §10 forbids, but from\noutbound `seal()` it means our code tried to seal a frame over the 1 MiB\nplaintext cap, so it is `internal` rather than the peer's fault.\n\nThe close codes are:\n\n- **`1002` (protocol error)**: a §11 protocol violation (bad frame, tampering,\n  replay, a post-activation text frame). The peer sent something §10 forbids.\n- **`4001` (usage limit)**: a direction reached its §10 frame- or block-count\n  usage bound (2^24 frames or 2^30 encrypted AES blocks per direction). This\n  is neither side's fault — §10 requires the closure before either bound is\n  exceeded, and the remedy — reconnect and derive fresh keys (§8) — is the\n  same whichever direction tripped it. RFC 6455 §7.4.2 reserves 4000–4999 for\n  exactly this: private use \"by prior agreement\" between applications. `1002`\n  and `1011` cannot be reused: `1002` would accuse the peer of a violation when\n  none occurred, `1011` would claim an internal crash for a routine,\n  spec-mandated transition. A conforming client reconnects on any established\n  channel close (regardless of code) via §8 and derives fresh keys.\n- **`1011` (internal error)**: this process is broken (a bug, not a protocol\n  event), including an outbound attempt to seal a plaintext over the 1 MiB\n  limit. Such a refused write closes the session rather than leaving\n  application state half-delivered.\n\nThe extension client cannot mirror this table: a browser's `WebSocket.close`\nrefuses every code outside 1000/3000–4999, so the client sends `4001` for a\nusage bound (either direction) and a bare close for everything else (§11).\n\nAn explicit extension revoke first marks the live `BridgeConnection`\nunauthorized and not-ready, then durably removes every committed/provisional\ncredential for its verified Origin before updating `PairingService`. The\nsame Origin's pending `/pair` and `/v1` sessions are synchronously cancelled,\nand new upgrades are refused for the whole durable-revocation critical section.\nThe best-effort `PairRevoked` notification may drain briefly, but dispatch is\nalready denied; the underlying WebSocket is then closed and its session-map\nentry removed.\n\nOn-disk state lives under `<userData>/bridge/`: `pairing.json`,\n`registry.json`, `endpoint.json` (mode 0600), `local-token` (mode 0600), and\n`mbp1-credentials.json`. The writers request 0600 on every platform, but\nNode's `chmod` on Windows manipulates only the read-only bit — see the\nattestation-root passage under **Native Messaging host** for what that means\nfor what the host can attest there.\n\n## Dispatch and transport exposure\n\n`MdxpDispatcher` owns request-schema `safeParse` validation and returns\n`InvalidParams`; handlers and `BridgeReceiver` receive typed data and enforce\nbusiness rules only. Keep `@core/bridge/` transport-neutral: the only production\nfile there allowed to import `ws` is `web-socket-bridge-server.ts`. Other files\nuse `WebSocketLike` from `web-socket-message-stream.ts`.\n\nFor every new MDXP method:\n\n1. Define and publish its schema and method constant in the MDXP repository,\n   then bump the published dependency here. Never commit a machine-local\n   `link:` or `file:` dependency.\n2. Register the schema and typed handler in the dispatcher. Use\n   `setHandlers()` only for shell-supplied extension methods; shared\n   control-plane methods belong in `registerReadHandlers` or\n   `registerWriteHandlers`.\n3. Choose and test exposure explicitly: unary HTTP, extension WebSocket, or\n   both. Do not assume dispatcher registration exposes every transport.\n4. For client notifications, register `conn.onNotification`. For outbound\n   notifications, publish through `BridgeEventBus` and let the shell call\n   `conn.sendNotification(Notifications.*, params)`. For outbound requests,\n   use a domain service and pass a `CancellationToken` when work is cancellable.\n\nPaired extensions may use the WebSocket control plane for `task/list`,\n`task/get`, `task/pause`, `task/resume`, `task/remove`, `stats/get`, and\n`engine/status`, plus the user-gesture-only `task/reveal`; keep the\n`dispatcher.has()` and authorization gates. `task/reveal` accepts only a\npublic task id, derives the path inside the Electron shell, is not agent-facing,\nand must never be exposed by unary `/mdxp`. The headless server does not\nregister it, and `motrix/initialize.capabilities.taskReveal` must be derived\nfrom the dispatcher's actual registration so older/headless peers report it as\nunsupported rather than advertising a dead method.\n`download/add` is unary-only, while extensions submit through\n`download/submit`. Renderer-only plural task commands must not enter MDXP.\nUnary `/mdxp` admits only methods marked agent-facing; dispatcher registration\nmust not make extension handshake or submit methods reachable over HTTP.\nAuthenticated SSE streams must close when their pairing token is revoked.\n\nRenderer-initiated URL resolution enters main through `BridgeQueries.*`; main\nthen sends MDXP `url/probe` / `url/resolve` requests to ready extensions. Use\nthe shared channel constants. For cancellation, renderer supplies a request ID,\nmain owns its `CancellationTokenSource`, and the matching cancel query aborts it.\n\n## Native Messaging host\n\n`packages/native-host/` is a standalone Rust executable spawned by Chromium or\nFirefox. It reads `endpoint.json`, probes `GET /discovery` for liveness\n(§4.1), and fetches exactly one `POST /nonce` with `X-Motrix-Bridge: 1`\n(§4.2) once liveness succeeds — the launch-poll loop probes liveness every\n~200ms but never fetches more than one nonce per resolution attempt. A\nsuccessful liveness probe settles the resolution either way: if the nonce\nfetch then fails, `resolve_endpoint` reports `NotRunning` rather than\nfalling through to a launch, because `fetch_nonce` answers `None` for any\nnon-2xx — including the 429/503 the §4.2 outstanding-nonce cap and issuance\nrate limit return — and retrying there would amplify exactly the load that\ncaused the refusal.\n\nThe **broker** path cannot yet reach one nonce per resolution. `NotRunning`\nis all the versioned stdio contract can say, so a live bridge that refuses a\nnonce is indistinguishable to the companion from one that is down: it\nlaunches and calls `WaitForEndpoint`, fetching a second nonce. Bounded at two\nper resolution and never a loop, but real — separating the two states needs a\nbroker-protocol response the current contract cannot express without\nbreaking older companions. It\nmust not expose the local CLI token itself to the extension or depend on\nsystem Node.js or Electron. Production extension IDs come from\n`src/shared/config/native-messaging-extensions.json`; development IDs may\nbe added only through `MOTRIX_DEV_TRUSTED_EXTENSIONS` and must never ship as\nproduction defaults.\n\nFor a `bootstrap` request, the host also mints a §9.2 NM attestation ticket\n(`mint_ticket_for_bootstrap` in `main.rs`), returned as `nmTicket` in\n`{ action: 'requestPair', protocolVersion: 1, port, nonce, nmTicket? }` —\n`protocolVersion` is always emitted, by both `request_pair` and\n`request_pair_with_ticket`, and §9.1 makes it normative — whenever every trusted\ninput is available: an argv-extracted caller identity, an owner-checked\n`localToken` and ASCII `generation` from `endpoint.json`, and the\nextension's `bindingPub`. A missing input degrades to a ticketless reply\nrather than fabricating one — ticketless resolves to `unverified` on the\nserver, the same outcome as presenting no ticket at all, which is safer\nthan a structurally broken ticket (§9.2 aborts on that). `localToken`\nitself never reaches the wire, only the MAC key it derives.\n\n`endpoint.json`'s `localToken`/`generation` are trusted for minting only\nwhen the file passes `is_owner_only` on the already-open handle (§9.1); a\nfile that fails it still yields a port, but those two fields are dropped. On\nUnix that is a 0600 owner-and-mode check. On Windows it is the deliberately\nweaker analogue documented on `is_owner_only` itself: owner = current process\nuser, and every DACL entry either a deny-family ACE or a plain allow ACE for\nthat user, `LocalSystem`, or `BUILTIN\\Administrators` — any other ACE type\n(the conditional/object allow variants included) fails the check closed and\ndrops the fields. SYSTEM and Administrators are admitted because they can\nalready rewrite anything the user owns, which means an administrator can\n*read* `localToken` on Windows — do not read the ticket-minting path above\nas attesting anything stronger than that there.\n\nOn Flatpak, only the **broker** (`motrix-native-host-broker`) speaks HTTP:\n`probe_bridge` reaches `probe.rs` through `resolve_endpoint`, so the broker\ninherits the `POST /nonce` / `X-Motrix-Bridge: 1` migration. The companion\n(`motrix-flatpak-native-host`) makes **no** HTTP calls of its own — it has no\nreference to `probe_liveness` or `fetch_nonce` — and reaches the bridge only by\nspawning the broker over stdio. Both are ticketless by design; neither calls\n`mint_ticket`. That scope decision stands.\n\n**Do not \"simplify\" this by giving the companion a direct probe call.** The\nindirection is the sandbox boundary: the companion runs outside the Flatpak\nsandbox and the broker inside it, and routing every bridge request through the\nstdio contract is what keeps that boundary a single, auditable seam. A direct\ncall would read as removing a pointless hop.\n\n## Verification\n\nRun the global gate in `commit-and-quality.md`, then the scoped bridge checks;\ndo not encode a fixed test count:\n\n```bash\npnpm exec biome check src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\npnpm exec vitest run src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\n```\n",".claude/rules/panel-layout.md":"---\ndescription: Height-chain and sticky-scroll rules for panels inside SidebarInset\npaths: [\"src/renderer/routes/**/*.tsx\", \"src/renderer/layouts/app-layout*.tsx\", \"src/renderer/components/ui/sidebar*.tsx\", \"src/renderer/components/desktop-kit/panel/**\", \"src/renderer/components/settings-kit/**/*.tsx\"]\n---\n\n# Panel Layout\n\nPages inside `SidebarInset` stay within the viewport. The document and panel\nshell do not scroll; the innermost data region owns scrolling.\n\n## Height chain\n\nEvery flex ancestor between the viewport clamp and the data region must allow\nits child to shrink:\n\n```text\nSidebarProvider  h-svh overflow-hidden\n  SidebarInset   flex-1 min-h-0 overflow-hidden\n    PanelShell   h-full flex flex-col\n      content    flex-1 min-h-0 flex flex-col\n        Tabs     flex-1 min-h-0 flex flex-col\n          list   shrink-0\n          content flex min-h-0 flex-1\n            panel flex min-h-0 flex-1 flex-col\n              fixed regions shrink-0\n              data region flex min-h-0 flex-1 overflow-auto\n```\n\n`flex-1` without `min-h-0` is insufficient because the default intrinsic\nminimum height lets content grow the page.\n\n## Scrolling and sticky content\n\n- The chain has exactly one `overflow-auto`: the leaf data region. Ancestors\n  use `overflow-hidden` where a clamp is needed; do not make `PanelShell`,\n  `Tabs`, or `TabsContent` a competing scroller.\n- Non-scrolling headers, filters, summaries, tab lists, and footers use\n  `shrink-0`.\n- A sticky column header is the first child inside the scroll region so\n  `sticky top-0` resolves against the intended scroller.\n- Sticky content needs an opaque background, `z-10`, and `border-b`; a separate\n  separator sibling scrolls away and does not preserve the boundary.\n- Under tabs, keep `TabsContent` as `flex min-h-0 flex-1` and its panel as\n  `flex min-h-0 flex-1 flex-col` so both axes remain bounded.\n\nWhen changing a panel, verify that the page has no document scrollbar, only the\ndata region scrolls, sticky headers remain readable, and the inset/footer stay\nvisible at short viewport heights.\n",".claude/rules/language-and-docs.md":"---\ndescription: Language, bilingual public docs, and private-context boundaries\n---\n\n# Language & Documentation\n\n## Language\n\n- Code, comments, commits, PR titles, branches, file names, and identifiers are\n  English.\n- Respond to users in the language they use.\n\n## Public Documentation\n\n- New end-user guides and documents with an existing translated counterpart\n  ship as English `<name>.md` and Chinese `<name>.zh-CN.md` pairs.\n- Governance files, generated artifacts, and subtree/developer READMEs may stay\n  single-language unless maintainers establish a pair.\n- When editing one file in a pair, update the other in the same change. Keep\n  headings, commands, paths, and code samples aligned while writing idiomatic\n  prose in each language.\n- `docs/` contains reviewed public artifacts, not private design material.\n\n## Public/Private Boundary\n\n- Machine-specific agent context belongs in ignored `CLAUDE.local.md`.\n- Never commit private plans, prompts, handoffs, credentials, private\n  application or vault names, or absolute local paths.\n- Public contribution workflows must work from a normal checkout without a\n  private account, application, or adjacent repository.\n- Rewrite private source material as a standalone public artifact and review\n  it before committing.\n\n## Maintainer Documentation\n\n- Maintainer operation manuals, deployment runbooks, internal specifications,\n  and implementation plans belong in Obsidian by default. A request to prepare\n  one does not authorize adding it to the repository, committing it, or\n  pushing it. Put it in the repository only when the user explicitly requests\n  a public repository document.\n- Use the project's `pnpm docs` gateway for these documents. Invoke it as\n  `pnpm run docs -- <command>` (or the `docs:*` scripts), because bare\n  `pnpm docs` can resolve to pnpm's built-in package-documentation command.\n  Start with `pnpm run docs -- help` and `pnpm run docs -- doctor`.\n- Resolve the destination from the ignored `obsidian-docs.config.json`. In a\n  new worktree, use `--config <existing-local-config>` when a configured\n  checkout is available. Keep vault names and machine paths out of tracked\n  rules and public documentation.\n- Create documents through `pnpm run docs -- create <directory> <name.md>\n  --from <temporary-file>` using a source file outside the repository. Check\n  existing notes before replacing content; use `--overwrite` only for the\n  intended update. Do not substitute raw vault filesystem writes or UI\n  automation for this gateway.\n- Read back saved content and check code blocks and literal escape sequences;\n  a successful create command alone does not prove the manual is intact.\n- If the gateway or configuration is unavailable, report the specific blocker\n  and retain a temporary copy outside the repository. Do not fall back to\n  placing the manual in `docs/` or publishing it in a PR.\n",".claude/rules/plugin-registry.md":"# Rules Skill\n\nPath: `.claude/rules/plugin-registry.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugin-registry.md)",".claude/rules/plugins.md":"# Rules Skill\n\nPath: `.claude/rules/plugins.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugins.md)",".claude/rules/renderer.md":"# Rules Skill\n\nPath: `.claude/rules/renderer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/renderer.md)","packages/native-host/package.json":"# Native-host Skill\n\nPath: `packages/native-host/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/package.json)","packages/native-host/README.md":"# native-host Documentation\n\nPath: `packages/native-host/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.md)","packages/native-host/README.zh-CN.md":"# Native-host Skill\n\nPath: `packages/native-host/README.zh-CN.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.zh-CN.md)","tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json":"# test.toplevel-register-only Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json)","tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json":"# test.toplevel-http Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json)","tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json":"# test.resolve-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json)","tests/fixtures/plugins/test.resolve-band/motrix-plugin.json":"# test.resolve-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band/motrix-plugin.json)","tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json":"# test.enrich-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json)","tests/fixtures/plugins/test.enrich-band/motrix-plugin.json":"# test.enrich-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band/motrix-plugin.json)","tests/fixtures/plugins/test.echo/motrix-plugin.json":"# test.echo Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.echo/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.echo/motrix-plugin.json)","tests/fixtures/plugins/test.audit-band/motrix-plugin.json":"# test.audit-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.audit-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.audit-band/motrix-plugin.json)","tests/fixtures/plugins/test.allcaps/motrix-plugin.json":"# test.allcaps Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.allcaps/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/motrix-plugin.json)","tests/fixtures/plugins/test.allcaps/locales/en-US.json":"# Locales Skill\n\nPath: `tests/fixtures/plugins/test.allcaps/locales/en-US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/locales/en-US.json)"},"files":{"AGENTS.md":"# AGENTS.md\n\nClaude Code rules are the canonical agent guidance for this repository. Codex\ninherits them instead of maintaining a second copy.\n\nBefore inspecting or changing files, read:\n\n1. `CLAUDE.md`.\n2. Every `.claude/rules/*.md` file without `paths` frontmatter.\n3. Every rule whose `paths` patterns match the files being inspected or\n   changed.\n\nIf the scope expands, load the newly matching rules before continuing. Do not\nload unrelated path-scoped rules by default.\n\nConflicts resolve in this order: current user instructions, this file,\n`CLAUDE.md`, matching `.claude/rules/*.md`, then default agent behavior.\nUpdate the canonical Claude rules rather than duplicating guidance here.\n",".claude/rules/code-style.md":"---\ndescription: TypeScript naming, import, alias, and schema conventions\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.css\", \"*.config.ts\"]\n---\n\n# Code Style\n\nBiome owns formatting and general lint rules; do not duplicate its settings\nhere. The conventions below cover repository-specific boundaries.\n\n## Naming\n\n- JavaScript, TypeScript, TSX, and stylesheet files use `kebab-case`.\n- Conventional qualifiers remain lowercase: `.test`, `.spec`, `.e2e`,\n  `.integration`, `.darwin`, and `.win32`.\n- Exported classes and React components remain PascalCase even though their\n  filenames are kebab-case.\n- Rust and Python use `snake_case`; Cargo binary entrypoints may use\n  kebab-case to match the executable name.\n- Run `pnpm run check:file-names` after adding or renaming files.\n\n## Imports and Aliases\n\n- Use `import type` for type-only imports and `node:` for Node builtins.\n- Prefer the configured alias over a deep relative import; local sibling or\n  parent imports may remain relative.\n- Alias availability is target-specific. Application builds expose\n  `@shared`, `@core`, `@renderer`, and `@main` as appropriate; server targets\n  may expose `@server`, and tests may expose `@test-utils`.\n- Do not assume aliases cross targets: Vitest intentionally has no `@main`,\n  and the QuickJS worker exposes only `@shared` and `@core`. Check the target's\n  TypeScript and Vite configuration before using an alias.\n\n## Shared Runtime Contracts\n\nUse Zod from `zod` for untrusted external data and cross-layer payloads. A\nshared schema is the source of truth for its runtime constraints, inferred\ntype, and defaults; do not create parallel interfaces or hand-written\nvalidators for the same contract. Pure cross-layer schemas belong in\n`src/shared/schemas/`; host-specific validation stays in its owning layer.\n\n## Comments Describe What Exists\n\nA comment in the present tense is a claim about the current code, and readers\nact on it. Describe an intended-but-unwritten consumer in the conditional, or\nas a `TODO`, and say plainly that nothing does it yet.\n\nThis matters most for comments that assert a safety property. Two distinct\nbugs shipped behind such comments: a writer documented a \"backstop\" that\nreported failures which nothing reported, and a socket wedged silently\nforever; and a getter pair was documented as being read by a wiring layer that\ndecided when to reconnect, when the only readers were tests. In both cases the\ncomment was the reason no one looked.\n\nWhen a guard's justification depends on a case, check that it covers every\ncase the code now reaches — a rationale that was true when written and is\nfalse for a later-added branch argues for the bug.\n","CLAUDE.md":"# CLAUDE.md\n\n## Project\n\nMotrix Turbo is an Electron and Node/Web download manager. Keep `src/core/`\nhost-neutral so it can be reused by both shells and replaced independently.\n\nDevelopment happens on **`main`**. The frozen **`master`** branch is the legacy\nElectron 22/Vue 2 application. Branch from and target PRs at `main`, never\n`master`.\n\n## Core Commands\n\n```bash\npnpm start                         # Electron development runner\npnpm start:server                  # Built Node/Web server\npnpm test                          # Vitest suite\npnpm exec vitest run <test-path>   # Focused test\npnpm run lint                      # Biome over the repository\npnpm exec tsc --noEmit             # Type-check\npnpm build                         # Desktop production build\npnpm build:server                  # Node/Web production build\npnpm test:e2e                      # Playwright suite\n```\n\nUse `pnpm exec`, not `npx`. The authoritative commit gate is\n`.claude/rules/commit-and-quality.md`.\n\n## Architecture Boundaries\n\n1. `src/core/` never imports `electron` or `src/main/`.\n2. `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`;\n   use `@renderer/lib/transport` with shared protocol constants.\n3. `src/shared/` contains pure cross-layer contracts and descriptive runtime\n   data only: no IO, timers, network, Electron, or Node-specific APIs.\n4. Use `Commands`, `Queries`, `Events`, and their `Bridge*` counterparts from\n   `src/shared/protocol/`; never use raw transport channel strings.\n\n## Rule Routing\n\nRules without `paths` frontmatter are global. Load path-scoped rules only when\ntheir patterns match the files under inspection or modification.\n\n| Rule | Scope |\n|---|---|\n| `commit-and-quality.md` | Required checks and conditional validation |\n| `git-workflow.md` | Commits, branches, PRs, and releases |\n| `language-and-docs.md` | Language and public/private documentation |\n| `browser-artifacts.md` | Browser test output paths and temporary UI verification files |\n| `architecture.md` | Layer boundaries and transport flow |\n| `electron-vite.md` | Build, packaging, native ABI, and pnpm |\n| `code-style.md` | TypeScript, React, CSS, and file naming |\n| `renderer.md` | Renderer state, components, forms, and transport |\n| `icons.md` | Semantic icon imports, library isolation, and tree shaking |\n| `panel-layout.md` | Viewport height and scrolling layout |\n| `i18n.md` | Locale catalogs and user-visible strings |\n| `domain-model.md` | Shared domain types, validation, and errors |\n| `plugins.md` | Plugin sandbox, capabilities, and builtins |\n| `plugin-registry.md` | Registry compatibility and install integrity |\n| `bridge.md` | MDXP pairing, dispatch, and transports |\n",".claude/rules/architecture.md":"---\ndescription: Architecture boundaries for the shared desktop and server app\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"scripts/check-boundaries.mjs\"]\n---\n\n# Architecture\n\nMotrix keeps its product core host-neutral so it can run behind either the\nElectron shell or the Node server and remain replaceable by a future engine.\n\n## Layer Matrix\n\n| Directory | Role | Allowed dependencies |\n|-----------|------|----------------------|\n| `src/renderer/` | Electron/browser frontend | `@shared/`, renderer-local modules |\n| `src/core/` | Host-neutral product core | `@shared/`, host-neutral Node/external libraries |\n| `src/main/` | Electron shell and IPC | `@core/`, `@shared/`, Electron |\n| `src/preload/` | Electron bridge | pure `@shared/` protocol values/types, Electron |\n| `src/server/` | Node/Docker shell | `@core/`, `@shared/`, server libraries |\n| `src/shared/` | Cross-layer contracts | pure schemas, constants, data, and utilities only |\n\n## Hard Boundaries\n\n- `src/core/` never imports Electron or `src/main/`.\n- `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`.\n- `src/server/` never imports Electron or `src/main/`.\n- `src/shared/` never imports an application layer and contains no IO, timers,\n  network access, Electron APIs, or Node-specific APIs.\n- Production code never imports `src/test-utils/`; generated builtin plugin\n  output is never used as source.\n\n`pnpm run check:boundaries` is the automated baseline, but changed imports\nmust also be reviewed against the matrix because not every exception is\nmachine-enforced.\n\n## Dual Transport Contract\n\nThe same renderer-facing transport contract serves both hosts:\n\n```text\nElectron: renderer -> ElectronTransport -> preload -> main IPC -> core\nBrowser:  renderer -> HttpWsTransport -> server RPC/events -> core\n```\n\nRenderer command/query/event traffic uses `@renderer/lib/transport`. Direct\n`window.motrix` access is limited to the Electron transport and narrowly scoped\nElectron platform adapters; feature code stays behind those abstractions.\nEvents return through the selected shell and transport, so renderer state must\nnot depend on a host-specific channel.\n\nAll channel names come from `src/shared/protocol/`. Use `Commands`, `Queries`,\n`Events`, and their `Bridge*` counterparts instead of raw strings.\n\n## Engine Adapter\n\nProduct-level code targets `EngineAdapter` in\n`src/core/engine/engine-adapter.ts`, never aria2 RPC types directly. Concrete\nengines translate at the adapter boundary. `EngineSupervisor` is the sole\nowner of engine start, stop, and restart lifecycle.\n",".claude/rules/commit-and-quality.md":"---\ndescription: Required commit gate and change-specific validation\n---\n\n# Commit & Quality Gate\n\n## Required for Every Commit\n\nRun all three commands and fix failures before committing:\n\n```bash\npnpm run check:boundaries\npnpm run lint\npnpm exec tsc --noEmit\n```\n\n`pnpm run lint` (`biome check .`, scoped by biome.json) is the same command\nCI runs — do not substitute a narrower path list, and never pipe it in a way\nthat discards its exit code.\n\n`check:boundaries` is an automated baseline, not a complete architecture\nproof. Review changed imports against `architecture.md` as well.\n\nStage only the intended files or hunks, then inspect `git diff --staged` and\nrun `git diff --cached --check`. Do not hide failures because a CI job is\nnon-blocking.\n\n## Change-Specific Checks\n\nRun every check that matches the change:\n\n- Behavior or logic: focused tests with `pnpm exec vitest run <test-path>`;\n  use `pnpm test` for broad cross-cutting changes.\n- Browser/Electron user flows: `pnpm test:e2e` when the affected flow has E2E\n  coverage.\n- Locale resources or i18n behavior: `pnpm run check:i18n`.\n- Added or renamed files: `pnpm run check:file-names`.\n- Plugin manifest contracts or `@motrix/plugin-manifest-schema`:\n  `pnpm run check:schema-parity`.\n- Dependencies, bundled assets, or license metadata:\n  `pnpm run check:third-party-notices`.\n- Native host Rust:\n  ```bash\n  cargo fmt --manifest-path packages/native-host/Cargo.toml --all -- --check\n  cargo clippy --manifest-path packages/native-host/Cargo.toml --all-targets --locked -- -D warnings\n  cargo test --manifest-path packages/native-host/Cargo.toml --locked --all-targets\n  ```\n- Packaging or release code: run the focused tests under `tests/scripts/` and\n  the relevant verifier. Follow workflow-supplied arguments for checks such as\n  `check:update-artifacts`.\n\nUse `pnpm exec biome check --write .` only for reviewed, auto-fixable\nissues; re-run the required gate afterward.\n",".claude/rules/domain-model.md":"---\ndescription: Engine-neutral domain aggregates, validation, defaults, and errors\npaths: [\"src/shared/**\", \"src/core/**\"]\n---\n\n# Domain Model\n\n## Engine-Neutral Aggregates\n\nProduct-level code never exposes aria2 raw types or naming. Aria2 values are\ntranslated at the engine adapter boundary into the aggregates in\n`src/shared/types/`:\n\n- `DownloadTask`, `TaskStatus`, and `TaskType` describe task identity and\n  lifecycle; `BtExtension` carries BitTorrent-only details.\n- `EngineState` and `EngineFeatureReport` describe engine lifecycle and\n  detected capabilities without binding consumers to an RPC implementation.\n- `AppSettings` is the persisted aggregate; `EngineSettings` and\n  `MotrixAppSettings` separate engine-facing and application-facing concerns.\n- `GlobalStats` is the cross-engine aggregate used by UI and protocol layers.\n\nKeep managers and stores engine-neutral (`<Domain>Manager`, `<Domain>Store`).\nEngine-specific conversion and RPC fields stay under the concrete adapter.\n\n## Schemas and Defaults\n\n- Cross-layer runtime schemas live in `src/shared/schemas/`; core may compose\n  them but must not duplicate their constraints.\n- Settings schemas use `.catch(safeDefault)` for recoverable persisted values.\n- Derive aggregate defaults from the schema, for example\n  `const DEFAULTS = schema.parse({})`, so validation and defaults cannot drift.\n- Error codes used by settings validation follow\n  `settings.{namespace}.{field}` for localization.\n\n## Errors\n\nUse `AppError` and `ErrorCode` from `@shared/errors` for domain failures that\ncross a service, transport, or UI boundary. Raw `Error` remains appropriate\nfor low-level implementation failures that are caught and translated before\nleaving their owning layer.\n",".claude/rules/electron-vite.md":"---\ndescription: Build outputs, Electron loading policy, pnpm scripts, and native ABI boundaries\npaths: [\"electron-builder.json\", \"pnpm-workspace.yaml\", \"package.json\", \"Dockerfile\", \"vite.*.config.ts\", \"scripts/dev.mjs\", \"scripts/postinstall.mjs\", \"scripts/ensure-electron-runtime.mjs\", \"scripts/ensure-native-abi.mjs\", \"scripts/native-binary-target.mjs\", \"scripts/stage-*-app.mjs\", \"scripts/verify-*-package.mjs\", \"scripts/before-*-*.mjs\", \"src/main/index.ts\", \"src/main/window/renderer-url-policy*\", \"src/main/window/window-manager*\", \"src/preload/**\"]\n---\n\n# Electron + Vite\n\n## Required Artifacts\n\n| Target | Output |\n|--------|--------|\n| Electron main | `dist/main/index.cjs` |\n| preload | `dist/preload/preload.cjs` |\n| QuickJS worker | `dist/core/plugin/host/quick-js-worker.cjs` |\n| Electron renderer | `dist/renderer/` |\n| Node server and CLI | `dist/server/index.mjs`, `dist/server/motrix-admin.mjs` |\n| browser renderer | `dist/renderer-web/` |\n\nBecause `package.json` declares `\"type\": \"module\"`, main, preload, and worker\noutputs must remain `.cjs`; server outputs remain `.mjs`. The package `main`\nfield must match the main artifact. Build entries sharing an output directory\nmust have unique basenames, and every consuming `build:*` flow must produce\nthe entries it packages.\n\n## Preload and Renderer URL Policy\n\nAt runtime `__dirname` is `dist/main/`, so the preload path is:\n\n```ts\npath.join(__dirname, '../preload/preload.cjs')\n```\n\nRenaming or moving either artifact requires updating this path. Pass\n`VITE_DEV_SERVER_URL` once to `initializeRendererUrlPolicy()` and load every\nrenderer window through `rendererUrlPolicy.loadWindow(win, route)`. Do not add\nfeature-local `loadURL()` or `loadFile()` calls that bypass its loopback-origin\nand packaged-file checks.\n\n## pnpm and Native ABI\n\n- Project pnpm settings belong in `pnpm-workspace.yaml`. Keep `nodeLinker:\n  hoisted` unless the Electron packaging smoke job proves isolated linking\n  works.\n- Install-script permission is controlled by pnpm 12 `allowBuilds`. Keep\n  `electron` allowed for compatibility, but do not rely on `pnpm install` to\n  hydrate Electron: it exposes `install.js` as a package bin without a\n  `postinstall` script. Local workflows that consume Electron or its licenses\n  must run `pnpm run ensure:electron-runtime` first; that command validates the\n  entire payload and repairs partial installs safely. CI/container workflows\n  may invoke `install.js` directly when they immediately validate the result.\n- Native modules such as `better-sqlite3` must match the active ABI. Tests use\n  the Node ABI; Electron and E2E use the Electron ABI. Preserve the\n  `ensure-native-abi.mjs` hooks when changing test or start scripts.\n- `postinstall.mjs` independently controls Electron rebuild and aria2 fetch\n  through `MOTRIX_SKIP_ELECTRON_REBUILD` and `MOTRIX_SKIP_ENGINE_FETCH`.\n  Skipping one must never imply skipping the other.\n- If Electron or a native dependency changes, rebuild for the intended target\n  and exercise the corresponding Node and Electron paths.\n\n## Server Docker Boundary\n\nThe server image uses system aria2 and skips both Electron rebuild and engine\nfetch. `stage-server-app.mjs` selects one target-platform `better-sqlite3`\nprebuild, and `verify-server-package.mjs` verifies the staged payload. The\nruntime image intentionally has no pnpm or build toolchain; never rely on a\nruntime native rebuild.\n",".claude/rules/git-workflow.md":"---\ndescription: Git commit, branch, PR, merge, and release policy\n---\n\n# Git Workflow\n\n## Commits\n\nUse Conventional Commits in English:\n\n```text\n<type>(<optional-scope>): <imperative summary>\n```\n\nAllowed types are `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `chore`,\n`ci`, and `style`. Keep the summary lowercase, without a period, and under 72\ncharacters. Add a body for non-obvious rationale and a\n`BREAKING CHANGE: ...` footer when applicable.\n\nDo not add AI attribution or co-author trailers automatically. Preserve\ntrailers explicitly supplied by a contributor or required by legal policy.\nFollow `commit-and-quality.md` before creating a commit.\n\n## Branches and Pull Requests\n\n- `main` is the protected active branch; `master` is frozen legacy code. Never\n  target or merge Turbo work into `master`.\n- Branch from current `main`. Use\n  `<type>/<snake_case_topic>_<YYYYMMDD>`, optionally placing an issue number\n  before the topic.\n- Never push directly or force-push to `main`. All changes use PRs.\n- Rebase a private feature branch onto `main` before review. Never rebase\n  `main` or a branch shared with other contributors.\n- After a rebase, update only your own feature branch with\n  `git push --force-with-lease`; never use an unconditional force push.\n- Keep a PR focused. Its title follows Conventional Commits; its description\n  explains what, why, and how the change was verified.\n- Squash merge feature PRs by default. Use a regular merge only when a release,\n  hotfix, or intentionally structured refactor needs commit-level history.\n- Delete merged branches.\n\n## Release Safety\n\nThe checked-in workflows and scripts are the release authority; do not replace\nthem with local publishing steps.\n\n- Set `package.json` to strict SemVer, then create a protected\n  `v<package-version>` tag whose commit is on `main`. The tag and package\n  versions must match; only stable and `beta` channels are supported.\n- A tag push triggers `.github/workflows/release.yml`. Publishing is allowed\n  only after its platform builds, isolated signing/finalization jobs,\n  required signature checks, package verification, artifact assembly, and\n  update-artifact validation succeed. macOS releases require signing and\n  notarization; Windows releases may be explicitly finalized unsigned when\n  both Authenticode secrets are absent. Disclose unsigned Windows artifacts in\n  the public release notes. Manual dispatch validates the build path but does\n  not sign or publish.\n- Do not manually upload release files, reuse unverified artifacts, bypass\n  protected environments, weaken signing-input isolation, or overwrite an\n  existing immutable container tag.\n- Protected stable and beta tags publish one verified amd64/arm64 Snap build\n  set to `latest/edge`. Promote those exact revisions with `snap-promote.yml`\n  from protected `main`; never rebuild during promotion. Stable rejects\n  prereleases.\n- If a release gate fails, fix the source or workflow and run the complete\n  gated path again. Do not publish a partial platform set.\n",".claude/rules/i18n.md":"---\ndescription: Internationalization contracts for renderer/main strings and locale resources\npaths: [\"src/renderer/**/*.ts\", \"src/renderer/**/*.tsx\", \"src/main/**/*.ts\", \"src/core/i18n/**\", \"src/core/plugin/plugin-registry*\", \"src/core/plugin/capabilities/i18n*\", \"src/core/plugin/manifest/i18n-resolve*\", \"src/server/index.ts\", \"src/server/operator-admin*.ts\", \"src/shared/constants/locales*.ts\", \"src/shared/schemas/locale*.ts\", \"src/shared/i18n-resources.ts\", \"src/shared/locales/*.json\", \"scripts/check-i18n.mjs\", \"tests/scripts/check-i18n.test.ts\"]\n---\n\n# Internationalization\n\nEvery user-visible application or operator string must use i18next; do not\nhardcode UI labels, messages, notifications, dialogs, or CLI output. Renderer\ncomponents use `react-i18next`; Electron main uses the initialized instance\nfrom `src/main/lib/i18n.ts`; the server operator CLI creates an isolated\ninstance from shared resources. Keep `t` in React hook dependency arrays.\nRoute host-wide locale changes through `LocaleCoordinator`: serialize changes,\nskip stale revisions, reconcile late targets, and publish `LocaleChanged` only\nafter the current apply succeeds. Keep plugin-registry and capability-host\nlocale changes transactional, including rollback on failure.\n\n## Resource contract\n\n- `src/shared/locales/en-US.json` is the key-shape source of truth. Add every\n  logical key to every locale registered in `SUPPORTED_LOCALES`; never update\n  only a subset.\n- `src/shared/i18n-resources.ts` statically and exhaustively registers loaders for those\n  locale files. Only English and Simplified Chinese are eager; await resource loading before\n  committing a host or renderer language change. New languages also require a catalog entry in\n  `src/shared/constants/locales.ts` and a matching JSON resource.\n- The server operator CLI uses `src/server/operator-admin-locales.ts` to\n  import only the `operatorAdmin` namespace from every catalog. Keep its locale\n  map exhaustive; importing full application catalogs into the CLI duplicates\n  Web UI resources in the Server package and can exceed its size budget.\n- Every translation leaf is a string. Keep readable locale files as UTF-8;\n  avoid unnecessary `\\uXXXX` escapes.\n- English user-visible prose, including matching test fixtures and assertions,\n  uses the typographic apostrophe `’` for contractions and possessives; do not\n  substitute the ASCII apostrophe `'`. Keep `'` only where technical syntax or\n  an external data contract requires that exact character.\n- Preserve the exact `{{placeholder}}` set across every locale and every plural\n  variant.\n- Plural keys use i18next category suffixes. Supply every cardinal category\n  returned by `Intl.PluralRules(locale)`; `_zero` is the only permitted extra\n  category because i18next treats it as an exact-zero override.\n\n## Namespaces\n\nKeys follow the feature that owns them and use camelCase leaves; descriptions\nnormally pair `name` with `nameDesc`.\n\n- Settings grid cards: `settings.cards.<card>.*`\n- Settings dialogs/pages: `settings.<dialog>.*`\n- BitTorrent settings and tracker defaults: `settings.bittorrent.*`\n- Tracker management: `trackers.effective.*`, `trackers.blacklist.*`,\n  `trackers.combobox.*`, and `trackers.sync.*`\n\nExtend the relevant namespace rather than creating a parallel spelling or\nplacing tracker UI under settings merely because trackers are configurable.\n\n## Marketplace locale independence\n\nApplication locales and registry listing locales are separate contracts.\n`SupportedLocale` must not constrain `listing.defaultLocale` or keys in the\nregistry's open BCP 47 `listing.localizations` map. Resolve marketplace display\nand search text with the vendored registry resolver; do not use\n`resolveSupportedLocale` or infer sibling regions such as `zh-TW -> zh-CN`.\n\n## Verification\n\nRun `pnpm run check:i18n` after any locale, catalog, placeholder, plural, or\nresource-registration change. It verifies file/catalog parity, logical key\ncoverage, plural categories, string leaves, and placeholder parity. Its tests\nmust use temporary fixtures and never rewrite real locale files. Tests that\nrender translated components load the real renderer initialization with:\n\n```ts\nimport '@renderer/lib/i18n'\n```\n",".claude/rules/bridge.md":"---\ndescription: MDXP bridge lifecycle, authorization, transports, and protocol boundaries\npaths: [\"src/core/bridge/**\", \"src/core/bridge-receiver/**\", \"src/main/bridge/**\", \"src/server/bridge/**\", \"src/shared/protocol/bridge.ts\", \"src/shared/schemas/bridge-settings.ts\", \"src/shared/config/native-messaging-extensions.json\", \"packages/native-host/**\", \"docs/bridge-pairing-protocol.md\", \"package.json\"]\n---\n\n# Bridge (MDXP / JSON-RPC 2.0)\n\nThe bridge uses JSON-RPC 2.0 over WebSocket and HTTP. The\n[`@motrix/mdxp`](https://github.com/motrixapp/mdxp) package is the source of\ntruth for wire schemas, method constants, types, errors, and connection\nbehavior; its version comes from `package.json`. Do not recreate MDXP schemas,\nnumeric error codes, or the removed legacy frame protocol in this repository.\nImport `ErrorCodes` and `makeMdxpError` from the package.\n\n## Pairing and session lifecycle\n\n- Extension WebSockets speak MBP1 (`docs/bridge-pairing-protocol.md`), which\n  authenticates *below* MDXP. `/pair?nonce=...` runs the §6 first-pair\n  handshake and `/v1` the §8 challenge–response; `/v1` carries **no**\n  credential in its URL and every query, including `?token=`, is rejected by\n  the raw-route gate. Only once a route\n  authenticates does MDXP start, inside the §10 AEAD envelope, and\n  `motrix/initialize` is the client's first request there.\n- `WebSocketBridgeServer` always owns and registers `motrix/initialize`.\n  Shell-provided download handlers are registered separately and must be wired\n  before `server.start()`; never expose a listener with incomplete handlers.\n- Authorization and readiness are different states. Every extension WebSocket\n  session is authorized **at the transport**: `adoptAuthenticatedSession` marks\n  the connection before registering a handler, and `motrix/initialize` is a\n  capabilities exchange that grants nothing and never returns a `pairToken`.\n  The `authorizedDispatch` gate over control-plane and download methods stays\n  as defence in depth. Unary HTTP separately requires its bearer-token and\n  agent-facing gates.\n- The six MBP1 `BridgeServerOptions` (`instanceId`, `serverGeneration`,\n  `appVersion`, `credentials`, `isOfficialId`, `queueMbp1Dialog`) resolve as a\n  unit. A server missing any one of them has no extension WebSocket surface at\n  all: both upgrades are refused rather than falling back to an\n  unauthenticated one.\n- A connection becomes ready only after the peer sends the\n  `motrix/initialized` notification. Providers passed to\n  `UrlResolutionService` must filter to ready connections before sending\n  server-initiated `url/probe` or `url/resolve` requests.\n- The connection close handler removes its own current session and disposes the\n  connection. Do not mutate the server's session map from feature code.\n\nApproval is proven by typing the pairing code, never by clicking an Allow\nbutton. The dialog shows an 8-symbol Crockford-alphabet code grouped\n`XXXX-XXXX` (`mbp1/pairing-code.ts`), lives 120 seconds, and *is* the SPAKE2\npassword: it never travels on the Extension connection and MUST NOT be logged\nat any level (§7.1). Server operator snapshots/events and an explicitly selected\ninteractive `motrix-admin extension pair` view follow §4.4. The terminal uses\nfixed loopback RPC with the operator Bearer credential, rejects redirected streams\nand JSON, revalidates the exact session, and never renews the code lifetime. A pairing session resolves to one of three identities —\n`official`, `attested-non-official`, `unverified` — and `official` is decided\nby the immutable allowlist only, never the NM manifest set and never the\nuser-added trusted-extension registry (§5).\n\nPair nonces are one-shot, expire after 60 seconds, and are consumed only by\n`/pair` — before the route does anything else, so a refused upgrade never\nreaches a session or a dialog. Never persist them. `endpoint.json` contains\ndiscovery information and the local CLI bearer token, `localToken`, which is\nnow dual-purpose: the native-messaging host also derives its §9.2 ticket-MAC\nkey from it (`ticketKey = HKDF(UTF8(localToken), \"MBP1/nm-ticket/v1\", \"mac\",\n32)`) but MUST NOT expose it to the extension. `localToken` persists across\nbridge restarts — only `serverGeneration` rotates on every start\n(`loadOrCreateBridgeIdentity`) — so an NM ticket minted before a restart takes\nthe §9.2 semantic-downgrade path instead of aborting on a stale-generation MAC\nmismatch: the ticket then contributes nothing, and identity falls back to what\nthe verified origin alone establishes (§9.2 only-raise — a stale ticket is\nnever worse than presenting none). The nonce route is now `POST /nonce` and requires\n`X-Motrix-Bridge: 1`; the former `GET /nonce` is gone and 404s.\n\nWhile bound to a loopback host, every route and upgrade rejects a `Host` header\nthat is not `127.0.0.1`, `localhost`, or `[::1]` with the bound port — the §4.3\nDNS-rebinding guard. It is inert for a non-loopback bind, which keeps its\nexisting token + reverse-proxy model.\n\nThe Server's documented Docker path deliberately binds MDXP to `0.0.0.0`\ninside the container so Docker can publish port 16801. Remote Extension routes\nmay use that non-loopback listener only behind a parser-issued, all-or-nothing\nremote configuration; the WS/WSS public URL is required, while the operator URL\nis optional. Without an HTTPS operator URL or explicit HTTP consent, code-bearing\noperator snapshots/events are restricted to the authenticated local Bearer path.\nBoth HTTP snapshots and event broadcasts must enforce the delivery policy from\nthe same parser-issued configuration. `RemoteExtensionSurfacePolicy` enforces the exact\npublic Host and raw route prefix plus the admission limits before MBP1 runs.\nDo not reintroduce a loopback-only bootstrap guard: container loopback is not\nreachable through Docker's published-port path.\n\n`GET /discovery` additionally reports unauthenticated compatibility hints:\n`runtime`, `extensionPairing: { protocol: 'mbp1', versions: [1] }`, and\n`applicationProtocols: { mdxp: ['1.0'] }`. They may drive an upgrade message,\nbut never trust, downgrade, or a port pin; authenticated initialization remains\nauthoritative. The WebSocket parser caps messages at the largest valid active\nenvelope (1 MiB plaintext + 24 bytes framing), while the state machines retain\ntheir stricter 16 KiB pre-authentication check.\n\nNothing SPAKE2-adjacent may be logged at any level: the pairing code, `w`, any\nPAKE intermediate, derived keys, MACs, or NM tickets (§11). A `pairError` frame\ncarries only its nine-code vocabulary (`unsupportedVersion`, `busy`,\n`rateLimited`, `codeMismatch`, `expired`, `aborted`, `authFailed`,\n`protocolViolation`, `pairingFailed`) and never a step-identifying detail.\n\nThe bridge tries the candidate port range 16802–16806 in order and falls back\nto an ephemeral port, reported as `degraded`, once every candidate is taken.\n`bridge.fixedPort` pins a single port instead and is applied by restarting the\nruntime. `endpoint.json` stays the authoritative discovery record for both the\nCLI and the Native Messaging host regardless of which port was chosen. The\nserver shell deliberately keeps its own explicit configured host and port and\nnever walks the candidate range.\n\n## MBP1 module map\n\n`src/core/bridge/mbp1/` implements `docs/bridge-pairing-protocol.md`:\n`canonical.ts` (§2 encoding primitives), `scrypt-w.ts` (§6.2 password-to-scalar\nderivation), `pairing-code.ts` (§7.1 code generation/display/normalization),\n`spake2-core.ts` (§6.3–6.6 the generic SPAKE2 core), `transcript.ts` (§6.4\ntranscript construction), `reconnect-mac.ts` (§8 reconnect MAC),\n`reconnect-rate-limit.ts` (§8 per-origin and global reconnect rate limiting,\ndeliberately separate from `flood-control.ts`'s §7.3 counters),\n`envelope.ts` + `envelope-message-stream.ts` (§10 AEAD framing),\n`ticket-verify.ts` (§9 NM ticket verification), `frames.ts` (§6.1/§11 wire\nframe schemas and the `pairError` vocabulary), `nonce-service.ts` (§4.2\none-shot pairing nonces), `flood-control.ts` (§7.3 pairing admission limits),\n`pre-auth-table.ts` (§4 pre-authentication bookkeeping), `pair-session.ts`\n(§6 the `/pair` state machine), `reconnect-session.ts` (§8 the `/v1` state\nmachine). Alongside it: `../credential-store.ts` (§6.7 durable MBP1\ncredentials), `../bridge-identity.ts` (§9.2 the persistent `localToken` /\nrotating `serverGeneration` loader), and `../endpoint-file-writer.ts` (the\n`endpoint.json` writer).\n\n## Envelope close codes\n\nOnce §10 AEAD framing is active, envelope faults are classified by a\nthree-state discriminated union — `peer-violation`, `usage-limit`, `internal`\n— and by direction. The direction matters: the same `EnvelopeViolationError`\nfrom inbound `open()` means the peer sent something §10 forbids, but from\noutbound `seal()` it means our code tried to seal a frame over the 1 MiB\nplaintext cap, so it is `internal` rather than the peer's fault.\n\nThe close codes are:\n\n- **`1002` (protocol error)**: a §11 protocol violation (bad frame, tampering,\n  replay, a post-activation text frame). The peer sent something §10 forbids.\n- **`4001` (usage limit)**: a direction reached its §10 frame- or block-count\n  usage bound (2^24 frames or 2^30 encrypted AES blocks per direction). This\n  is neither side's fault — §10 requires the closure before either bound is\n  exceeded, and the remedy — reconnect and derive fresh keys (§8) — is the\n  same whichever direction tripped it. RFC 6455 §7.4.2 reserves 4000–4999 for\n  exactly this: private use \"by prior agreement\" between applications. `1002`\n  and `1011` cannot be reused: `1002` would accuse the peer of a violation when\n  none occurred, `1011` would claim an internal crash for a routine,\n  spec-mandated transition. A conforming client reconnects on any established\n  channel close (regardless of code) via §8 and derives fresh keys.\n- **`1011` (internal error)**: this process is broken (a bug, not a protocol\n  event), including an outbound attempt to seal a plaintext over the 1 MiB\n  limit. Such a refused write closes the session rather than leaving\n  application state half-delivered.\n\nThe extension client cannot mirror this table: a browser's `WebSocket.close`\nrefuses every code outside 1000/3000–4999, so the client sends `4001` for a\nusage bound (either direction) and a bare close for everything else (§11).\n\nAn explicit extension revoke first marks the live `BridgeConnection`\nunauthorized and not-ready, then durably removes every committed/provisional\ncredential for its verified Origin before updating `PairingService`. The\nsame Origin's pending `/pair` and `/v1` sessions are synchronously cancelled,\nand new upgrades are refused for the whole durable-revocation critical section.\nThe best-effort `PairRevoked` notification may drain briefly, but dispatch is\nalready denied; the underlying WebSocket is then closed and its session-map\nentry removed.\n\nOn-disk state lives under `<userData>/bridge/`: `pairing.json`,\n`registry.json`, `endpoint.json` (mode 0600), `local-token` (mode 0600), and\n`mbp1-credentials.json`. The writers request 0600 on every platform, but\nNode's `chmod` on Windows manipulates only the read-only bit — see the\nattestation-root passage under **Native Messaging host** for what that means\nfor what the host can attest there.\n\n## Dispatch and transport exposure\n\n`MdxpDispatcher` owns request-schema `safeParse` validation and returns\n`InvalidParams`; handlers and `BridgeReceiver` receive typed data and enforce\nbusiness rules only. Keep `@core/bridge/` transport-neutral: the only production\nfile there allowed to import `ws` is `web-socket-bridge-server.ts`. Other files\nuse `WebSocketLike` from `web-socket-message-stream.ts`.\n\nFor every new MDXP method:\n\n1. Define and publish its schema and method constant in the MDXP repository,\n   then bump the published dependency here. Never commit a machine-local\n   `link:` or `file:` dependency.\n2. Register the schema and typed handler in the dispatcher. Use\n   `setHandlers()` only for shell-supplied extension methods; shared\n   control-plane methods belong in `registerReadHandlers` or\n   `registerWriteHandlers`.\n3. Choose and test exposure explicitly: unary HTTP, extension WebSocket, or\n   both. Do not assume dispatcher registration exposes every transport.\n4. For client notifications, register `conn.onNotification`. For outbound\n   notifications, publish through `BridgeEventBus` and let the shell call\n   `conn.sendNotification(Notifications.*, params)`. For outbound requests,\n   use a domain service and pass a `CancellationToken` when work is cancellable.\n\nPaired extensions may use the WebSocket control plane for `task/list`,\n`task/get`, `task/pause`, `task/resume`, `task/remove`, `stats/get`, and\n`engine/status`, plus the user-gesture-only `task/reveal`; keep the\n`dispatcher.has()` and authorization gates. `task/reveal` accepts only a\npublic task id, derives the path inside the Electron shell, is not agent-facing,\nand must never be exposed by unary `/mdxp`. The headless server does not\nregister it, and `motrix/initialize.capabilities.taskReveal` must be derived\nfrom the dispatcher's actual registration so older/headless peers report it as\nunsupported rather than advertising a dead method.\n`download/add` is unary-only, while extensions submit through\n`download/submit`. Renderer-only plural task commands must not enter MDXP.\nUnary `/mdxp` admits only methods marked agent-facing; dispatcher registration\nmust not make extension handshake or submit methods reachable over HTTP.\nAuthenticated SSE streams must close when their pairing token is revoked.\n\nRenderer-initiated URL resolution enters main through `BridgeQueries.*`; main\nthen sends MDXP `url/probe` / `url/resolve` requests to ready extensions. Use\nthe shared channel constants. For cancellation, renderer supplies a request ID,\nmain owns its `CancellationTokenSource`, and the matching cancel query aborts it.\n\n## Native Messaging host\n\n`packages/native-host/` is a standalone Rust executable spawned by Chromium or\nFirefox. It reads `endpoint.json`, probes `GET /discovery` for liveness\n(§4.1), and fetches exactly one `POST /nonce` with `X-Motrix-Bridge: 1`\n(§4.2) once liveness succeeds — the launch-poll loop probes liveness every\n~200ms but never fetches more than one nonce per resolution attempt. A\nsuccessful liveness probe settles the resolution either way: if the nonce\nfetch then fails, `resolve_endpoint` reports `NotRunning` rather than\nfalling through to a launch, because `fetch_nonce` answers `None` for any\nnon-2xx — including the 429/503 the §4.2 outstanding-nonce cap and issuance\nrate limit return — and retrying there would amplify exactly the load that\ncaused the refusal.\n\nThe **broker** path cannot yet reach one nonce per resolution. `NotRunning`\nis all the versioned stdio contract can say, so a live bridge that refuses a\nnonce is indistinguishable to the companion from one that is down: it\nlaunches and calls `WaitForEndpoint`, fetching a second nonce. Bounded at two\nper resolution and never a loop, but real — separating the two states needs a\nbroker-protocol response the current contract cannot express without\nbreaking older companions. It\nmust not expose the local CLI token itself to the extension or depend on\nsystem Node.js or Electron. Production extension IDs come from\n`src/shared/config/native-messaging-extensions.json`; development IDs may\nbe added only through `MOTRIX_DEV_TRUSTED_EXTENSIONS` and must never ship as\nproduction defaults.\n\nFor a `bootstrap` request, the host also mints a §9.2 NM attestation ticket\n(`mint_ticket_for_bootstrap` in `main.rs`), returned as `nmTicket` in\n`{ action: 'requestPair', protocolVersion: 1, port, nonce, nmTicket? }` —\n`protocolVersion` is always emitted, by both `request_pair` and\n`request_pair_with_ticket`, and §9.1 makes it normative — whenever every trusted\ninput is available: an argv-extracted caller identity, an owner-checked\n`localToken` and ASCII `generation` from `endpoint.json`, and the\nextension's `bindingPub`. A missing input degrades to a ticketless reply\nrather than fabricating one — ticketless resolves to `unverified` on the\nserver, the same outcome as presenting no ticket at all, which is safer\nthan a structurally broken ticket (§9.2 aborts on that). `localToken`\nitself never reaches the wire, only the MAC key it derives.\n\n`endpoint.json`'s `localToken`/`generation` are trusted for minting only\nwhen the file passes `is_owner_only` on the already-open handle (§9.1); a\nfile that fails it still yields a port, but those two fields are dropped. On\nUnix that is a 0600 owner-and-mode check. On Windows it is the deliberately\nweaker analogue documented on `is_owner_only` itself: owner = current process\nuser, and every DACL entry either a deny-family ACE or a plain allow ACE for\nthat user, `LocalSystem`, or `BUILTIN\\Administrators` — any other ACE type\n(the conditional/object allow variants included) fails the check closed and\ndrops the fields. SYSTEM and Administrators are admitted because they can\nalready rewrite anything the user owns, which means an administrator can\n*read* `localToken` on Windows — do not read the ticket-minting path above\nas attesting anything stronger than that there.\n\nOn Flatpak, only the **broker** (`motrix-native-host-broker`) speaks HTTP:\n`probe_bridge` reaches `probe.rs` through `resolve_endpoint`, so the broker\ninherits the `POST /nonce` / `X-Motrix-Bridge: 1` migration. The companion\n(`motrix-flatpak-native-host`) makes **no** HTTP calls of its own — it has no\nreference to `probe_liveness` or `fetch_nonce` — and reaches the bridge only by\nspawning the broker over stdio. Both are ticketless by design; neither calls\n`mint_ticket`. That scope decision stands.\n\n**Do not \"simplify\" this by giving the companion a direct probe call.** The\nindirection is the sandbox boundary: the companion runs outside the Flatpak\nsandbox and the broker inside it, and routing every bridge request through the\nstdio contract is what keeps that boundary a single, auditable seam. A direct\ncall would read as removing a pointless hop.\n\n## Verification\n\nRun the global gate in `commit-and-quality.md`, then the scoped bridge checks;\ndo not encode a fixed test count:\n\n```bash\npnpm exec biome check src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\npnpm exec vitest run src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\n```\n",".claude/rules/panel-layout.md":"---\ndescription: Height-chain and sticky-scroll rules for panels inside SidebarInset\npaths: [\"src/renderer/routes/**/*.tsx\", \"src/renderer/layouts/app-layout*.tsx\", \"src/renderer/components/ui/sidebar*.tsx\", \"src/renderer/components/desktop-kit/panel/**\", \"src/renderer/components/settings-kit/**/*.tsx\"]\n---\n\n# Panel Layout\n\nPages inside `SidebarInset` stay within the viewport. The document and panel\nshell do not scroll; the innermost data region owns scrolling.\n\n## Height chain\n\nEvery flex ancestor between the viewport clamp and the data region must allow\nits child to shrink:\n\n```text\nSidebarProvider  h-svh overflow-hidden\n  SidebarInset   flex-1 min-h-0 overflow-hidden\n    PanelShell   h-full flex flex-col\n      content    flex-1 min-h-0 flex flex-col\n        Tabs     flex-1 min-h-0 flex flex-col\n          list   shrink-0\n          content flex min-h-0 flex-1\n            panel flex min-h-0 flex-1 flex-col\n              fixed regions shrink-0\n              data region flex min-h-0 flex-1 overflow-auto\n```\n\n`flex-1` without `min-h-0` is insufficient because the default intrinsic\nminimum height lets content grow the page.\n\n## Scrolling and sticky content\n\n- The chain has exactly one `overflow-auto`: the leaf data region. Ancestors\n  use `overflow-hidden` where a clamp is needed; do not make `PanelShell`,\n  `Tabs`, or `TabsContent` a competing scroller.\n- Non-scrolling headers, filters, summaries, tab lists, and footers use\n  `shrink-0`.\n- A sticky column header is the first child inside the scroll region so\n  `sticky top-0` resolves against the intended scroller.\n- Sticky content needs an opaque background, `z-10`, and `border-b`; a separate\n  separator sibling scrolls away and does not preserve the boundary.\n- Under tabs, keep `TabsContent` as `flex min-h-0 flex-1` and its panel as\n  `flex min-h-0 flex-1 flex-col` so both axes remain bounded.\n\nWhen changing a panel, verify that the page has no document scrollbar, only the\ndata region scrolls, sticky headers remain readable, and the inset/footer stay\nvisible at short viewport heights.\n",".claude/rules/language-and-docs.md":"---\ndescription: Language, bilingual public docs, and private-context boundaries\n---\n\n# Language & Documentation\n\n## Language\n\n- Code, comments, commits, PR titles, branches, file names, and identifiers are\n  English.\n- Respond to users in the language they use.\n\n## Public Documentation\n\n- New end-user guides and documents with an existing translated counterpart\n  ship as English `<name>.md` and Chinese `<name>.zh-CN.md` pairs.\n- Governance files, generated artifacts, and subtree/developer READMEs may stay\n  single-language unless maintainers establish a pair.\n- When editing one file in a pair, update the other in the same change. Keep\n  headings, commands, paths, and code samples aligned while writing idiomatic\n  prose in each language.\n- `docs/` contains reviewed public artifacts, not private design material.\n\n## Public/Private Boundary\n\n- Machine-specific agent context belongs in ignored `CLAUDE.local.md`.\n- Never commit private plans, prompts, handoffs, credentials, private\n  application or vault names, or absolute local paths.\n- Public contribution workflows must work from a normal checkout without a\n  private account, application, or adjacent repository.\n- Rewrite private source material as a standalone public artifact and review\n  it before committing.\n\n## Maintainer Documentation\n\n- Maintainer operation manuals, deployment runbooks, internal specifications,\n  and implementation plans belong in Obsidian by default. A request to prepare\n  one does not authorize adding it to the repository, committing it, or\n  pushing it. Put it in the repository only when the user explicitly requests\n  a public repository document.\n- Use the project's `pnpm docs` gateway for these documents. Invoke it as\n  `pnpm run docs -- <command>` (or the `docs:*` scripts), because bare\n  `pnpm docs` can resolve to pnpm's built-in package-documentation command.\n  Start with `pnpm run docs -- help` and `pnpm run docs -- doctor`.\n- Resolve the destination from the ignored `obsidian-docs.config.json`. In a\n  new worktree, use `--config <existing-local-config>` when a configured\n  checkout is available. Keep vault names and machine paths out of tracked\n  rules and public documentation.\n- Create documents through `pnpm run docs -- create <directory> <name.md>\n  --from <temporary-file>` using a source file outside the repository. Check\n  existing notes before replacing content; use `--overwrite` only for the\n  intended update. Do not substitute raw vault filesystem writes or UI\n  automation for this gateway.\n- Read back saved content and check code blocks and literal escape sequences;\n  a successful create command alone does not prove the manual is intact.\n- If the gateway or configuration is unavailable, report the specific blocker\n  and retain a temporary copy outside the repository. Do not fall back to\n  placing the manual in `docs/` or publishing it in a PR.\n",".claude/rules/plugin-registry.md":"# Rules Skill\n\nPath: `.claude/rules/plugin-registry.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugin-registry.md)",".claude/rules/plugins.md":"# Rules Skill\n\nPath: `.claude/rules/plugins.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugins.md)",".claude/rules/renderer.md":"# Rules Skill\n\nPath: `.claude/rules/renderer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/renderer.md)","packages/native-host/package.json":"# Native-host Skill\n\nPath: `packages/native-host/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/package.json)","packages/native-host/README.md":"# native-host Documentation\n\nPath: `packages/native-host/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.md)","packages/native-host/README.zh-CN.md":"# Native-host Skill\n\nPath: `packages/native-host/README.zh-CN.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.zh-CN.md)","tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json":"# test.toplevel-register-only Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json)","tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json":"# test.toplevel-http Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json)","tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json":"# test.resolve-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json)","tests/fixtures/plugins/test.resolve-band/motrix-plugin.json":"# test.resolve-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band/motrix-plugin.json)","tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json":"# test.enrich-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json)","tests/fixtures/plugins/test.enrich-band/motrix-plugin.json":"# test.enrich-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band/motrix-plugin.json)","tests/fixtures/plugins/test.echo/motrix-plugin.json":"# test.echo Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.echo/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.echo/motrix-plugin.json)","tests/fixtures/plugins/test.audit-band/motrix-plugin.json":"# test.audit-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.audit-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.audit-band/motrix-plugin.json)","tests/fixtures/plugins/test.allcaps/motrix-plugin.json":"# test.allcaps Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.allcaps/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/motrix-plugin.json)","tests/fixtures/plugins/test.allcaps/locales/en-US.json":"# Locales Skill\n\nPath: `tests/fixtures/plugins/test.allcaps/locales/en-US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/locales/en-US.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nClaude Code rules are the canonical agent guidance for this repository. Codex\ninherits them instead of maintaining a second copy.\n\nBefore inspecting or changing files, read:\n\n1. `CLAUDE.md`.\n2. Every `.claude/rules/*.md` file without `paths` frontmatter.\n3. Every rule whose `paths` patterns match the files being inspected or\n   changed.\n\nIf the scope expands, load the newly matching rules before continuing. Do not\nload unrelated path-scoped rules by default.\n\nConflicts resolve in this order: current user instructions, this file,\n`CLAUDE.md`, matching `.claude/rules/*.md`, then default agent behavior.\nUpdate the canonical Claude rules rather than duplicating guidance here.\n","isInternal":false,"tokens":154,"sizeBytes":694},{"name":"architecture.md","path":".claude/rules/architecture.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/architecture.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Architecture boundaries for the shared desktop and server app\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"scripts/check-boundaries.mjs\"]\n---\n\n# Architecture\n\nMotrix keeps its product core host-neutral so it can run behind either the\nElectron shell or the Node server and remain replaceable by a future engine.\n\n## Layer Matrix\n\n| Directory | Role | Allowed dependencies |\n|-----------|------|----------------------|\n| `src/renderer/` | Electron/browser frontend | `@shared/`, renderer-local modules |\n| `src/core/` | Host-neutral product core | `@shared/`, host-neutral Node/external libraries |\n| `src/main/` | Electron shell and IPC | `@core/`, `@shared/`, Electron |\n| `src/preload/` | Electron bridge | pure `@shared/` protocol values/types, Electron |\n| `src/server/` | Node/Docker shell | `@core/`, `@shared/`, server libraries |\n| `src/shared/` | Cross-layer contracts | pure schemas, constants, data, and utilities only |\n\n## Hard Boundaries\n\n- `src/core/` never imports Electron or `src/main/`.\n- `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`.\n- `src/server/` never imports Electron or `src/main/`.\n- `src/shared/` never imports an application layer and contains no IO, timers,\n  network access, Electron APIs, or Node-specific APIs.\n- Production code never imports `src/test-utils/`; generated builtin plugin\n  output is never used as source.\n\n`pnpm run check:boundaries` is the automated baseline, but changed imports\nmust also be reviewed against the matrix because not every exception is\nmachine-enforced.\n\n## Dual Transport Contract\n\nThe same renderer-facing transport contract serves both hosts:\n\n```text\nElectron: renderer -> ElectronTransport -> preload -> main IPC -> core\nBrowser:  renderer -> HttpWsTransport -> server RPC/events -> core\n```\n\nRenderer command/query/event traffic uses `@renderer/lib/transport`. Direct\n`window.motrix` access is limited to the Electron transport and narrowly scoped\nElectron platform adapters; feature code stays behind those abstractions.\nEvents return through the selected shell and transport, so renderer state must\nnot depend on a host-specific channel.\n\nAll channel names come from `src/shared/protocol/`. Use `Commands`, `Queries`,\n`Events`, and their `Bridge*` counterparts instead of raw strings.\n\n## Engine Adapter\n\nProduct-level code targets `EngineAdapter` in\n`src/core/engine/engine-adapter.ts`, never aria2 RPC types directly. Concrete\nengines translate at the adapter boundary. `EngineSupervisor` is the sole\nowner of engine start, stop, and restart lifecycle.\n","frontmatter":{"description":"Architecture boundaries for the shared desktop and server app","paths":["src/**/*.ts","src/**/*.tsx","scripts/check-boundaries.mjs"]},"isInternal":false,"tokens":585,"sizeBytes":2568},{"name":"bridge.md","path":".claude/rules/bridge.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/bridge.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: MDXP bridge lifecycle, authorization, transports, and protocol boundaries\npaths: [\"src/core/bridge/**\", \"src/core/bridge-receiver/**\", \"src/main/bridge/**\", \"src/server/bridge/**\", \"src/shared/protocol/bridge.ts\", \"src/shared/schemas/bridge-settings.ts\", \"src/shared/config/native-messaging-extensions.json\", \"packages/native-host/**\", \"docs/bridge-pairing-protocol.md\", \"package.json\"]\n---\n\n# Bridge (MDXP / JSON-RPC 2.0)\n\nThe bridge uses JSON-RPC 2.0 over WebSocket and HTTP. The\n[`@motrix/mdxp`](https://github.com/motrixapp/mdxp) package is the source of\ntruth for wire schemas, method constants, types, errors, and connection\nbehavior; its version comes from `package.json`. Do not recreate MDXP schemas,\nnumeric error codes, or the removed legacy frame protocol in this repository.\nImport `ErrorCodes` and `makeMdxpError` from the package.\n\n## Pairing and session lifecycle\n\n- Extension WebSockets speak MBP1 (`docs/bridge-pairing-protocol.md`), which\n  authenticates *below* MDXP. `/pair?nonce=...` runs the §6 first-pair\n  handshake and `/v1` the §8 challenge–response; `/v1` carries **no**\n  credential in its URL and every query, including `?token=`, is rejected by\n  the raw-route gate. Only once a route\n  authenticates does MDXP start, inside the §10 AEAD envelope, and\n  `motrix/initialize` is the client's first request there.\n- `WebSocketBridgeServer` always owns and registers `motrix/initialize`.\n  Shell-provided download handlers are registered separately and must be wired\n  before `server.start()`; never expose a listener with incomplete handlers.\n- Authorization and readiness are different states. Every extension WebSocket\n  session is authorized **at the transport**: `adoptAuthenticatedSession` marks\n  the connection before registering a handler, and `motrix/initialize` is a\n  capabilities exchange that grants nothing and never returns a `pairToken`.\n  The `authorizedDispatch` gate over control-plane and download methods stays\n  as defence in depth. Unary HTTP separately requires its bearer-token and\n  agent-facing gates.\n- The six MBP1 `BridgeServerOptions` (`instanceId`, `serverGeneration`,\n  `appVersion`, `credentials`, `isOfficialId`, `queueMbp1Dialog`) resolve as a\n  unit. A server missing any one of them has no extension WebSocket surface at\n  all: both upgrades are refused rather than falling back to an\n  unauthenticated one.\n- A connection becomes ready only after the peer sends the\n  `motrix/initialized` notification. Providers passed to\n  `UrlResolutionService` must filter to ready connections before sending\n  server-initiated `url/probe` or `url/resolve` requests.\n- The connection close handler removes its own current session and disposes the\n  connection. Do not mutate the server's session map from feature code.\n\nApproval is proven by typing the pairing code, never by clicking an Allow\nbutton. The dialog shows an 8-symbol Crockford-alphabet code grouped\n`XXXX-XXXX` (`mbp1/pairing-code.ts`), lives 120 seconds, and *is* the SPAKE2\npassword: it never travels on the Extension connection and MUST NOT be logged\nat any level (§7.1). Server operator snapshots/events and an explicitly selected\ninteractive `motrix-admin extension pair` view follow §4.4. The terminal uses\nfixed loopback RPC with the operator Bearer credential, rejects redirected streams\nand JSON, revalidates the exact session, and never renews the code lifetime. A pairing session resolves to one of three identities —\n`official`, `attested-non-official`, `unverified` — and `official` is decided\nby the immutable allowlist only, never the NM manifest set and never the\nuser-added trusted-extension registry (§5).\n\nPair nonces are one-shot, expire after 60 seconds, and are consumed only by\n`/pair` — before the route does anything else, so a refused upgrade never\nreaches a session or a dialog. Never persist them. `endpoint.json` contains\ndiscovery information and the local CLI bearer token, `localToken`, which is\nnow dual-purpose: the native-messaging host also derives its §9.2 ticket-MAC\nkey from it (`ticketKey = HKDF(UTF8(localToken), \"MBP1/nm-ticket/v1\", \"mac\",\n32)`) but MUST NOT expose it to the extension. `localToken` persists across\nbridge restarts — only `serverGeneration` rotates on every start\n(`loadOrCreateBridgeIdentity`) — so an NM ticket minted before a restart takes\nthe §9.2 semantic-downgrade path instead of aborting on a stale-generation MAC\nmismatch: the ticket then contributes nothing, and identity falls back to what\nthe verified origin alone establishes (§9.2 only-raise — a stale ticket is\nnever worse than presenting none). The nonce route is now `POST /nonce` and requires\n`X-Motrix-Bridge: 1`; the former `GET /nonce` is gone and 404s.\n\nWhile bound to a loopback host, every route and upgrade rejects a `Host` header\nthat is not `127.0.0.1`, `localhost`, or `[::1]` with the bound port — the §4.3\nDNS-rebinding guard. It is inert for a non-loopback bind, which keeps its\nexisting token + reverse-proxy model.\n\nThe Server's documented Docker path deliberately binds MDXP to `0.0.0.0`\ninside the container so Docker can publish port 16801. Remote Extension routes\nmay use that non-loopback listener only behind a parser-issued, all-or-nothing\nremote configuration; the WS/WSS public URL is required, while the operator URL\nis optional. Without an HTTPS operator URL or explicit HTTP consent, code-bearing\noperator snapshots/events are restricted to the authenticated local Bearer path.\nBoth HTTP snapshots and event broadcasts must enforce the delivery policy from\nthe same parser-issued configuration. `RemoteExtensionSurfacePolicy` enforces the exact\npublic Host and raw route prefix plus the admission limits before MBP1 runs.\nDo not reintroduce a loopback-only bootstrap guard: container loopback is not\nreachable through Docker's published-port path.\n\n`GET /discovery` additionally reports unauthenticated compatibility hints:\n`runtime`, `extensionPairing: { protocol: 'mbp1', versions: [1] }`, and\n`applicationProtocols: { mdxp: ['1.0'] }`. They may drive an upgrade message,\nbut never trust, downgrade, or a port pin; authenticated initialization remains\nauthoritative. The WebSocket parser caps messages at the largest valid active\nenvelope (1 MiB plaintext + 24 bytes framing), while the state machines retain\ntheir stricter 16 KiB pre-authentication check.\n\nNothing SPAKE2-adjacent may be logged at any level: the pairing code, `w`, any\nPAKE intermediate, derived keys, MACs, or NM tickets (§11). A `pairError` frame\ncarries only its nine-code vocabulary (`unsupportedVersion`, `busy`,\n`rateLimited`, `codeMismatch`, `expired`, `aborted`, `authFailed`,\n`protocolViolation`, `pairingFailed`) and never a step-identifying detail.\n\nThe bridge tries the candidate port range 16802–16806 in order and falls back\nto an ephemeral port, reported as `degraded`, once every candidate is taken.\n`bridge.fixedPort` pins a single port instead and is applied by restarting the\nruntime. `endpoint.json` stays the authoritative discovery record for both the\nCLI and the Native Messaging host regardless of which port was chosen. The\nserver shell deliberately keeps its own explicit configured host and port and\nnever walks the candidate range.\n\n## MBP1 module map\n\n`src/core/bridge/mbp1/` implements `docs/bridge-pairing-protocol.md`:\n`canonical.ts` (§2 encoding primitives), `scrypt-w.ts` (§6.2 password-to-scalar\nderivation), `pairing-code.ts` (§7.1 code generation/display/normalization),\n`spake2-core.ts` (§6.3–6.6 the generic SPAKE2 core), `transcript.ts` (§6.4\ntranscript construction), `reconnect-mac.ts` (§8 reconnect MAC),\n`reconnect-rate-limit.ts` (§8 per-origin and global reconnect rate limiting,\ndeliberately separate from `flood-control.ts`'s §7.3 counters),\n`envelope.ts` + `envelope-message-stream.ts` (§10 AEAD framing),\n`ticket-verify.ts` (§9 NM ticket verification), `frames.ts` (§6.1/§11 wire\nframe schemas and the `pairError` vocabulary), `nonce-service.ts` (§4.2\none-shot pairing nonces), `flood-control.ts` (§7.3 pairing admission limits),\n`pre-auth-table.ts` (§4 pre-authentication bookkeeping), `pair-session.ts`\n(§6 the `/pair` state machine), `reconnect-session.ts` (§8 the `/v1` state\nmachine). Alongside it: `../credential-store.ts` (§6.7 durable MBP1\ncredentials), `../bridge-identity.ts` (§9.2 the persistent `localToken` /\nrotating `serverGeneration` loader), and `../endpoint-file-writer.ts` (the\n`endpoint.json` writer).\n\n## Envelope close codes\n\nOnce §10 AEAD framing is active, envelope faults are classified by a\nthree-state discriminated union — `peer-violation`, `usage-limit`, `internal`\n— and by direction. The direction matters: the same `EnvelopeViolationError`\nfrom inbound `open()` means the peer sent something §10 forbids, but from\noutbound `seal()` it means our code tried to seal a frame over the 1 MiB\nplaintext cap, so it is `internal` rather than the peer's fault.\n\nThe close codes are:\n\n- **`1002` (protocol error)**: a §11 protocol violation (bad frame, tampering,\n  replay, a post-activation text frame). The peer sent something §10 forbids.\n- **`4001` (usage limit)**: a direction reached its §10 frame- or block-count\n  usage bound (2^24 frames or 2^30 encrypted AES blocks per direction). This\n  is neither side's fault — §10 requires the closure before either bound is\n  exceeded, and the remedy — reconnect and derive fresh keys (§8) — is the\n  same whichever direction tripped it. RFC 6455 §7.4.2 reserves 4000–4999 for\n  exactly this: private use \"by prior agreement\" between applications. `1002`\n  and `1011` cannot be reused: `1002` would accuse the peer of a violation when\n  none occurred, `1011` would claim an internal crash for a routine,\n  spec-mandated transition. A conforming client reconnects on any established\n  channel close (regardless of code) via §8 and derives fresh keys.\n- **`1011` (internal error)**: this process is broken (a bug, not a protocol\n  event), including an outbound attempt to seal a plaintext over the 1 MiB\n  limit. Such a refused write closes the session rather than leaving\n  application state half-delivered.\n\nThe extension client cannot mirror this table: a browser's `WebSocket.close`\nrefuses every code outside 1000/3000–4999, so the client sends `4001` for a\nusage bound (either direction) and a bare close for everything else (§11).\n\nAn explicit extension revoke first marks the live `BridgeConnection`\nunauthorized and not-ready, then durably removes every committed/provisional\ncredential for its verified Origin before updating `PairingService`. The\nsame Origin's pending `/pair` and `/v1` sessions are synchronously cancelled,\nand new upgrades are refused for the whole durable-revocation critical section.\nThe best-effort `PairRevoked` notification may drain briefly, but dispatch is\nalready denied; the underlying WebSocket is then closed and its session-map\nentry removed.\n\nOn-disk state lives under `<userData>/bridge/`: `pairing.json`,\n`registry.json`, `endpoint.json` (mode 0600), `local-token` (mode 0600), and\n`mbp1-credentials.json`. The writers request 0600 on every platform, but\nNode's `chmod` on Windows manipulates only the read-only bit — see the\nattestation-root passage under **Native Messaging host** for what that means\nfor what the host can attest there.\n\n## Dispatch and transport exposure\n\n`MdxpDispatcher` owns request-schema `safeParse` validation and returns\n`InvalidParams`; handlers and `BridgeReceiver` receive typed data and enforce\nbusiness rules only. Keep `@core/bridge/` transport-neutral: the only production\nfile there allowed to import `ws` is `web-socket-bridge-server.ts`. Other files\nuse `WebSocketLike` from `web-socket-message-stream.ts`.\n\nFor every new MDXP method:\n\n1. Define and publish its schema and method constant in the MDXP repository,\n   then bump the published dependency here. Never commit a machine-local\n   `link:` or `file:` dependency.\n2. Register the schema and typed handler in the dispatcher. Use\n   `setHandlers()` only for shell-supplied extension methods; shared\n   control-plane methods belong in `registerReadHandlers` or\n   `registerWriteHandlers`.\n3. Choose and test exposure explicitly: unary HTTP, extension WebSocket, or\n   both. Do not assume dispatcher registration exposes every transport.\n4. For client notifications, register `conn.onNotification`. For outbound\n   notifications, publish through `BridgeEventBus` and let the shell call\n   `conn.sendNotification(Notifications.*, params)`. For outbound requests,\n   use a domain service and pass a `CancellationToken` when work is cancellable.\n\nPaired extensions may use the WebSocket control plane for `task/list`,\n`task/get`, `task/pause`, `task/resume`, `task/remove`, `stats/get`, and\n`engine/status`, plus the user-gesture-only `task/reveal`; keep the\n`dispatcher.has()` and authorization gates. `task/reveal` accepts only a\npublic task id, derives the path inside the Electron shell, is not agent-facing,\nand must never be exposed by unary `/mdxp`. The headless server does not\nregister it, and `motrix/initialize.capabilities.taskReveal` must be derived\nfrom the dispatcher's actual registration so older/headless peers report it as\nunsupported rather than advertising a dead method.\n`download/add` is unary-only, while extensions submit through\n`download/submit`. Renderer-only plural task commands must not enter MDXP.\nUnary `/mdxp` admits only methods marked agent-facing; dispatcher registration\nmust not make extension handshake or submit methods reachable over HTTP.\nAuthenticated SSE streams must close when their pairing token is revoked.\n\nRenderer-initiated URL resolution enters main through `BridgeQueries.*`; main\nthen sends MDXP `url/probe` / `url/resolve` requests to ready extensions. Use\nthe shared channel constants. For cancellation, renderer supplies a request ID,\nmain owns its `CancellationTokenSource`, and the matching cancel query aborts it.\n\n## Native Messaging host\n\n`packages/native-host/` is a standalone Rust executable spawned by Chromium or\nFirefox. It reads `endpoint.json`, probes `GET /discovery` for liveness\n(§4.1), and fetches exactly one `POST /nonce` with `X-Motrix-Bridge: 1`\n(§4.2) once liveness succeeds — the launch-poll loop probes liveness every\n~200ms but never fetches more than one nonce per resolution attempt. A\nsuccessful liveness probe settles the resolution either way: if the nonce\nfetch then fails, `resolve_endpoint` reports `NotRunning` rather than\nfalling through to a launch, because `fetch_nonce` answers `None` for any\nnon-2xx — including the 429/503 the §4.2 outstanding-nonce cap and issuance\nrate limit return — and retrying there would amplify exactly the load that\ncaused the refusal.\n\nThe **broker** path cannot yet reach one nonce per resolution. `NotRunning`\nis all the versioned stdio contract can say, so a live bridge that refuses a\nnonce is indistinguishable to the companion from one that is down: it\nlaunches and calls `WaitForEndpoint`, fetching a second nonce. Bounded at two\nper resolution and never a loop, but real — separating the two states needs a\nbroker-protocol response the current contract cannot express without\nbreaking older companions. It\nmust not expose the local CLI token itself to the extension or depend on\nsystem Node.js or Electron. Production extension IDs come from\n`src/shared/config/native-messaging-extensions.json`; development IDs may\nbe added only through `MOTRIX_DEV_TRUSTED_EXTENSIONS` and must never ship as\nproduction defaults.\n\nFor a `bootstrap` request, the host also mints a §9.2 NM attestation ticket\n(`mint_ticket_for_bootstrap` in `main.rs`), returned as `nmTicket` in\n`{ action: 'requestPair', protocolVersion: 1, port, nonce, nmTicket? }` —\n`protocolVersion` is always emitted, by both `request_pair` and\n`request_pair_with_ticket`, and §9.1 makes it normative — whenever every trusted\ninput is available: an argv-extracted caller identity, an owner-checked\n`localToken` and ASCII `generation` from `endpoint.json`, and the\nextension's `bindingPub`. A missing input degrades to a ticketless reply\nrather than fabricating one — ticketless resolves to `unverified` on the\nserver, the same outcome as presenting no ticket at all, which is safer\nthan a structurally broken ticket (§9.2 aborts on that). `localToken`\nitself never reaches the wire, only the MAC key it derives.\n\n`endpoint.json`'s `localToken`/`generation` are trusted for minting only\nwhen the file passes `is_owner_only` on the already-open handle (§9.1); a\nfile that fails it still yields a port, but those two fields are dropped. On\nUnix that is a 0600 owner-and-mode check. On Windows it is the deliberately\nweaker analogue documented on `is_owner_only` itself: owner = current process\nuser, and every DACL entry either a deny-family ACE or a plain allow ACE for\nthat user, `LocalSystem`, or `BUILTIN\\Administrators` — any other ACE type\n(the conditional/object allow variants included) fails the check closed and\ndrops the fields. SYSTEM and Administrators are admitted because they can\nalready rewrite anything the user owns, which means an administrator can\n*read* `localToken` on Windows — do not read the ticket-minting path above\nas attesting anything stronger than that there.\n\nOn Flatpak, only the **broker** (`motrix-native-host-broker`) speaks HTTP:\n`probe_bridge` reaches `probe.rs` through `resolve_endpoint`, so the broker\ninherits the `POST /nonce` / `X-Motrix-Bridge: 1` migration. The companion\n(`motrix-flatpak-native-host`) makes **no** HTTP calls of its own — it has no\nreference to `probe_liveness` or `fetch_nonce` — and reaches the bridge only by\nspawning the broker over stdio. Both are ticketless by design; neither calls\n`mint_ticket`. That scope decision stands.\n\n**Do not \"simplify\" this by giving the companion a direct probe call.** The\nindirection is the sandbox boundary: the companion runs outside the Flatpak\nsandbox and the broker inside it, and routing every bridge request through the\nstdio contract is what keeps that boundary a single, auditable seam. A direct\ncall would read as removing a pointless hop.\n\n## Verification\n\nRun the global gate in `commit-and-quality.md`, then the scoped bridge checks;\ndo not encode a fixed test count:\n\n```bash\npnpm exec biome check src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\npnpm exec vitest run src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\n```\n","frontmatter":{"description":"MDXP bridge lifecycle, authorization, transports, and protocol boundaries","paths":["src/core/bridge/**","src/core/bridge-receiver/**","src/main/bridge/**","src/server/bridge/**","src/shared/protocol/bridge.ts","src/shared/schemas/bridge-settings.ts","src/shared/config/native-messaging-extensions.json","packages/native-host/**","docs/bridge-pairing-protocol.md","package.json"]},"isInternal":false,"tokens":4425,"sizeBytes":18494},{"name":"code-style.md","path":".claude/rules/code-style.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/code-style.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: TypeScript naming, import, alias, and schema conventions\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.css\", \"*.config.ts\"]\n---\n\n# Code Style\n\nBiome owns formatting and general lint rules; do not duplicate its settings\nhere. The conventions below cover repository-specific boundaries.\n\n## Naming\n\n- JavaScript, TypeScript, TSX, and stylesheet files use `kebab-case`.\n- Conventional qualifiers remain lowercase: `.test`, `.spec`, `.e2e`,\n  `.integration`, `.darwin`, and `.win32`.\n- Exported classes and React components remain PascalCase even though their\n  filenames are kebab-case.\n- Rust and Python use `snake_case`; Cargo binary entrypoints may use\n  kebab-case to match the executable name.\n- Run `pnpm run check:file-names` after adding or renaming files.\n\n## Imports and Aliases\n\n- Use `import type` for type-only imports and `node:` for Node builtins.\n- Prefer the configured alias over a deep relative import; local sibling or\n  parent imports may remain relative.\n- Alias availability is target-specific. Application builds expose\n  `@shared`, `@core`, `@renderer`, and `@main` as appropriate; server targets\n  may expose `@server`, and tests may expose `@test-utils`.\n- Do not assume aliases cross targets: Vitest intentionally has no `@main`,\n  and the QuickJS worker exposes only `@shared` and `@core`. Check the target's\n  TypeScript and Vite configuration before using an alias.\n\n## Shared Runtime Contracts\n\nUse Zod from `zod` for untrusted external data and cross-layer payloads. A\nshared schema is the source of truth for its runtime constraints, inferred\ntype, and defaults; do not create parallel interfaces or hand-written\nvalidators for the same contract. Pure cross-layer schemas belong in\n`src/shared/schemas/`; host-specific validation stays in its owning layer.\n\n## Comments Describe What Exists\n\nA comment in the present tense is a claim about the current code, and readers\nact on it. Describe an intended-but-unwritten consumer in the conditional, or\nas a `TODO`, and say plainly that nothing does it yet.\n\nThis matters most for comments that assert a safety property. Two distinct\nbugs shipped behind such comments: a writer documented a \"backstop\" that\nreported failures which nothing reported, and a socket wedged silently\nforever; and a getter pair was documented as being read by a wiring layer that\ndecided when to reconnect, when the only readers were tests. In both cases the\ncomment was the reason no one looked.\n\nWhen a guard's justification depends on a case, check that it covers every\ncase the code now reaches — a rationale that was true when written and is\nfalse for a later-added branch argues for the bug.\n","frontmatter":{"description":"TypeScript naming, import, alias, and schema conventions","paths":["src/**/*.ts","src/**/*.tsx","src/**/*.css","*.config.ts"]},"isInternal":false,"tokens":593,"sizeBytes":2673},{"name":"commit-and-quality.md","path":".claude/rules/commit-and-quality.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/commit-and-quality.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Required commit gate and change-specific validation\n---\n\n# Commit & Quality Gate\n\n## Required for Every Commit\n\nRun all three commands and fix failures before committing:\n\n```bash\npnpm run check:boundaries\npnpm run lint\npnpm exec tsc --noEmit\n```\n\n`pnpm run lint` (`biome check .`, scoped by biome.json) is the same command\nCI runs — do not substitute a narrower path list, and never pipe it in a way\nthat discards its exit code.\n\n`check:boundaries` is an automated baseline, not a complete architecture\nproof. Review changed imports against `architecture.md` as well.\n\nStage only the intended files or hunks, then inspect `git diff --staged` and\nrun `git diff --cached --check`. Do not hide failures because a CI job is\nnon-blocking.\n\n## Change-Specific Checks\n\nRun every check that matches the change:\n\n- Behavior or logic: focused tests with `pnpm exec vitest run <test-path>`;\n  use `pnpm test` for broad cross-cutting changes.\n- Browser/Electron user flows: `pnpm test:e2e` when the affected flow has E2E\n  coverage.\n- Locale resources or i18n behavior: `pnpm run check:i18n`.\n- Added or renamed files: `pnpm run check:file-names`.\n- Plugin manifest contracts or `@motrix/plugin-manifest-schema`:\n  `pnpm run check:schema-parity`.\n- Dependencies, bundled assets, or license metadata:\n  `pnpm run check:third-party-notices`.\n- Native host Rust:\n  ```bash\n  cargo fmt --manifest-path packages/native-host/Cargo.toml --all -- --check\n  cargo clippy --manifest-path packages/native-host/Cargo.toml --all-targets --locked -- -D warnings\n  cargo test --manifest-path packages/native-host/Cargo.toml --locked --all-targets\n  ```\n- Packaging or release code: run the focused tests under `tests/scripts/` and\n  the relevant verifier. Follow workflow-supplied arguments for checks such as\n  `check:update-artifacts`.\n\nUse `pnpm exec biome check --write .` only for reviewed, auto-fixable\nissues; re-run the required gate afterward.\n","frontmatter":{"description":"Required commit gate and change-specific validation"},"isInternal":false,"tokens":479,"sizeBytes":1946},{"name":"domain-model.md","path":".claude/rules/domain-model.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/domain-model.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Engine-neutral domain aggregates, validation, defaults, and errors\npaths: [\"src/shared/**\", \"src/core/**\"]\n---\n\n# Domain Model\n\n## Engine-Neutral Aggregates\n\nProduct-level code never exposes aria2 raw types or naming. Aria2 values are\ntranslated at the engine adapter boundary into the aggregates in\n`src/shared/types/`:\n\n- `DownloadTask`, `TaskStatus`, and `TaskType` describe task identity and\n  lifecycle; `BtExtension` carries BitTorrent-only details.\n- `EngineState` and `EngineFeatureReport` describe engine lifecycle and\n  detected capabilities without binding consumers to an RPC implementation.\n- `AppSettings` is the persisted aggregate; `EngineSettings` and\n  `MotrixAppSettings` separate engine-facing and application-facing concerns.\n- `GlobalStats` is the cross-engine aggregate used by UI and protocol layers.\n\nKeep managers and stores engine-neutral (`<Domain>Manager`, `<Domain>Store`).\nEngine-specific conversion and RPC fields stay under the concrete adapter.\n\n## Schemas and Defaults\n\n- Cross-layer runtime schemas live in `src/shared/schemas/`; core may compose\n  them but must not duplicate their constraints.\n- Settings schemas use `.catch(safeDefault)` for recoverable persisted values.\n- Derive aggregate defaults from the schema, for example\n  `const DEFAULTS = schema.parse({})`, so validation and defaults cannot drift.\n- Error codes used by settings validation follow\n  `settings.{namespace}.{field}` for localization.\n\n## Errors\n\nUse `AppError` and `ErrorCode` from `@shared/errors` for domain failures that\ncross a service, transport, or UI boundary. Raw `Error` remains appropriate\nfor low-level implementation failures that are caught and translated before\nleaving their owning layer.\n","frontmatter":{"description":"Engine-neutral domain aggregates, validation, defaults, and errors","paths":["src/shared/**","src/core/**"]},"isInternal":false,"tokens":371,"sizeBytes":1735},{"name":"electron-vite.md","path":".claude/rules/electron-vite.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/electron-vite.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Build outputs, Electron loading policy, pnpm scripts, and native ABI boundaries\npaths: [\"electron-builder.json\", \"pnpm-workspace.yaml\", \"package.json\", \"Dockerfile\", \"vite.*.config.ts\", \"scripts/dev.mjs\", \"scripts/postinstall.mjs\", \"scripts/ensure-electron-runtime.mjs\", \"scripts/ensure-native-abi.mjs\", \"scripts/native-binary-target.mjs\", \"scripts/stage-*-app.mjs\", \"scripts/verify-*-package.mjs\", \"scripts/before-*-*.mjs\", \"src/main/index.ts\", \"src/main/window/renderer-url-policy*\", \"src/main/window/window-manager*\", \"src/preload/**\"]\n---\n\n# Electron + Vite\n\n## Required Artifacts\n\n| Target | Output |\n|--------|--------|\n| Electron main | `dist/main/index.cjs` |\n| preload | `dist/preload/preload.cjs` |\n| QuickJS worker | `dist/core/plugin/host/quick-js-worker.cjs` |\n| Electron renderer | `dist/renderer/` |\n| Node server and CLI | `dist/server/index.mjs`, `dist/server/motrix-admin.mjs` |\n| browser renderer | `dist/renderer-web/` |\n\nBecause `package.json` declares `\"type\": \"module\"`, main, preload, and worker\noutputs must remain `.cjs`; server outputs remain `.mjs`. The package `main`\nfield must match the main artifact. Build entries sharing an output directory\nmust have unique basenames, and every consuming `build:*` flow must produce\nthe entries it packages.\n\n## Preload and Renderer URL Policy\n\nAt runtime `__dirname` is `dist/main/`, so the preload path is:\n\n```ts\npath.join(__dirname, '../preload/preload.cjs')\n```\n\nRenaming or moving either artifact requires updating this path. Pass\n`VITE_DEV_SERVER_URL` once to `initializeRendererUrlPolicy()` and load every\nrenderer window through `rendererUrlPolicy.loadWindow(win, route)`. Do not add\nfeature-local `loadURL()` or `loadFile()` calls that bypass its loopback-origin\nand packaged-file checks.\n\n## pnpm and Native ABI\n\n- Project pnpm settings belong in `pnpm-workspace.yaml`. Keep `nodeLinker:\n  hoisted` unless the Electron packaging smoke job proves isolated linking\n  works.\n- Install-script permission is controlled by pnpm 12 `allowBuilds`. Keep\n  `electron` allowed for compatibility, but do not rely on `pnpm install` to\n  hydrate Electron: it exposes `install.js` as a package bin without a\n  `postinstall` script. Local workflows that consume Electron or its licenses\n  must run `pnpm run ensure:electron-runtime` first; that command validates the\n  entire payload and repairs partial installs safely. CI/container workflows\n  may invoke `install.js` directly when they immediately validate the result.\n- Native modules such as `better-sqlite3` must match the active ABI. Tests use\n  the Node ABI; Electron and E2E use the Electron ABI. Preserve the\n  `ensure-native-abi.mjs` hooks when changing test or start scripts.\n- `postinstall.mjs` independently controls Electron rebuild and aria2 fetch\n  through `MOTRIX_SKIP_ELECTRON_REBUILD` and `MOTRIX_SKIP_ENGINE_FETCH`.\n  Skipping one must never imply skipping the other.\n- If Electron or a native dependency changes, rebuild for the intended target\n  and exercise the corresponding Node and Electron paths.\n\n## Server Docker Boundary\n\nThe server image uses system aria2 and skips both Electron rebuild and engine\nfetch. `stage-server-app.mjs` selects one target-platform `better-sqlite3`\nprebuild, and `verify-server-package.mjs` verifies the staged payload. The\nruntime image intentionally has no pnpm or build toolchain; never rely on a\nruntime native rebuild.\n","frontmatter":{"description":"Build outputs, Electron loading policy, pnpm scripts, and native ABI boundaries","paths":["electron-builder.json","pnpm-workspace.yaml","package.json","Dockerfile","vite.*.config.ts","scripts/dev.mjs","scripts/postinstall.mjs","scripts/ensure-electron-runtime.mjs","scripts/ensure-native-abi.mjs","scripts/native-binary-target.mjs","scripts/stage-*-app.mjs","scripts/verify-*-package.mjs","scripts/before-*-*.mjs","src/main/index.ts","src/main/window/renderer-url-policy*","src/main/window/window-manager*","src/preload/**"]},"isInternal":false,"tokens":818,"sizeBytes":3411},{"name":"git-workflow.md","path":".claude/rules/git-workflow.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/git-workflow.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Git commit, branch, PR, merge, and release policy\n---\n\n# Git Workflow\n\n## Commits\n\nUse Conventional Commits in English:\n\n```text\n<type>(<optional-scope>): <imperative summary>\n```\n\nAllowed types are `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `chore`,\n`ci`, and `style`. Keep the summary lowercase, without a period, and under 72\ncharacters. Add a body for non-obvious rationale and a\n`BREAKING CHANGE: ...` footer when applicable.\n\nDo not add AI attribution or co-author trailers automatically. Preserve\ntrailers explicitly supplied by a contributor or required by legal policy.\nFollow `commit-and-quality.md` before creating a commit.\n\n## Branches and Pull Requests\n\n- `main` is the protected active branch; `master` is frozen legacy code. Never\n  target or merge Turbo work into `master`.\n- Branch from current `main`. Use\n  `<type>/<snake_case_topic>_<YYYYMMDD>`, optionally placing an issue number\n  before the topic.\n- Never push directly or force-push to `main`. All changes use PRs.\n- Rebase a private feature branch onto `main` before review. Never rebase\n  `main` or a branch shared with other contributors.\n- After a rebase, update only your own feature branch with\n  `git push --force-with-lease`; never use an unconditional force push.\n- Keep a PR focused. Its title follows Conventional Commits; its description\n  explains what, why, and how the change was verified.\n- Squash merge feature PRs by default. Use a regular merge only when a release,\n  hotfix, or intentionally structured refactor needs commit-level history.\n- Delete merged branches.\n\n## Release Safety\n\nThe checked-in workflows and scripts are the release authority; do not replace\nthem with local publishing steps.\n\n- Set `package.json` to strict SemVer, then create a protected\n  `v<package-version>` tag whose commit is on `main`. The tag and package\n  versions must match; only stable and `beta` channels are supported.\n- A tag push triggers `.github/workflows/release.yml`. Publishing is allowed\n  only after its platform builds, isolated signing/finalization jobs,\n  required signature checks, package verification, artifact assembly, and\n  update-artifact validation succeed. macOS releases require signing and\n  notarization; Windows releases may be explicitly finalized unsigned when\n  both Authenticode secrets are absent. Disclose unsigned Windows artifacts in\n  the public release notes. Manual dispatch validates the build path but does\n  not sign or publish.\n- Do not manually upload release files, reuse unverified artifacts, bypass\n  protected environments, weaken signing-input isolation, or overwrite an\n  existing immutable container tag.\n- Protected stable and beta tags publish one verified amd64/arm64 Snap build\n  set to `latest/edge`. Promote those exact revisions with `snap-promote.yml`\n  from protected `main`; never rebuild during promotion. Stable rejects\n  prereleases.\n- If a release gate fails, fix the source or workflow and run the complete\n  gated path again. Do not publish a partial platform set.\n","frontmatter":{"description":"Git commit, branch, PR, merge, and release policy"},"isInternal":false,"tokens":677,"sizeBytes":3037},{"name":"i18n.md","path":".claude/rules/i18n.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/i18n.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Internationalization contracts for renderer/main strings and locale resources\npaths: [\"src/renderer/**/*.ts\", \"src/renderer/**/*.tsx\", \"src/main/**/*.ts\", \"src/core/i18n/**\", \"src/core/plugin/plugin-registry*\", \"src/core/plugin/capabilities/i18n*\", \"src/core/plugin/manifest/i18n-resolve*\", \"src/server/index.ts\", \"src/server/operator-admin*.ts\", \"src/shared/constants/locales*.ts\", \"src/shared/schemas/locale*.ts\", \"src/shared/i18n-resources.ts\", \"src/shared/locales/*.json\", \"scripts/check-i18n.mjs\", \"tests/scripts/check-i18n.test.ts\"]\n---\n\n# Internationalization\n\nEvery user-visible application or operator string must use i18next; do not\nhardcode UI labels, messages, notifications, dialogs, or CLI output. Renderer\ncomponents use `react-i18next`; Electron main uses the initialized instance\nfrom `src/main/lib/i18n.ts`; the server operator CLI creates an isolated\ninstance from shared resources. Keep `t` in React hook dependency arrays.\nRoute host-wide locale changes through `LocaleCoordinator`: serialize changes,\nskip stale revisions, reconcile late targets, and publish `LocaleChanged` only\nafter the current apply succeeds. Keep plugin-registry and capability-host\nlocale changes transactional, including rollback on failure.\n\n## Resource contract\n\n- `src/shared/locales/en-US.json` is the key-shape source of truth. Add every\n  logical key to every locale registered in `SUPPORTED_LOCALES`; never update\n  only a subset.\n- `src/shared/i18n-resources.ts` statically and exhaustively registers loaders for those\n  locale files. Only English and Simplified Chinese are eager; await resource loading before\n  committing a host or renderer language change. New languages also require a catalog entry in\n  `src/shared/constants/locales.ts` and a matching JSON resource.\n- The server operator CLI uses `src/server/operator-admin-locales.ts` to\n  import only the `operatorAdmin` namespace from every catalog. Keep its locale\n  map exhaustive; importing full application catalogs into the CLI duplicates\n  Web UI resources in the Server package and can exceed its size budget.\n- Every translation leaf is a string. Keep readable locale files as UTF-8;\n  avoid unnecessary `\\uXXXX` escapes.\n- English user-visible prose, including matching test fixtures and assertions,\n  uses the typographic apostrophe `’` for contractions and possessives; do not\n  substitute the ASCII apostrophe `'`. Keep `'` only where technical syntax or\n  an external data contract requires that exact character.\n- Preserve the exact `{{placeholder}}` set across every locale and every plural\n  variant.\n- Plural keys use i18next category suffixes. Supply every cardinal category\n  returned by `Intl.PluralRules(locale)`; `_zero` is the only permitted extra\n  category because i18next treats it as an exact-zero override.\n\n## Namespaces\n\nKeys follow the feature that owns them and use camelCase leaves; descriptions\nnormally pair `name` with `nameDesc`.\n\n- Settings grid cards: `settings.cards.<card>.*`\n- Settings dialogs/pages: `settings.<dialog>.*`\n- BitTorrent settings and tracker defaults: `settings.bittorrent.*`\n- Tracker management: `trackers.effective.*`, `trackers.blacklist.*`,\n  `trackers.combobox.*`, and `trackers.sync.*`\n\nExtend the relevant namespace rather than creating a parallel spelling or\nplacing tracker UI under settings merely because trackers are configurable.\n\n## Marketplace locale independence\n\nApplication locales and registry listing locales are separate contracts.\n`SupportedLocale` must not constrain `listing.defaultLocale` or keys in the\nregistry's open BCP 47 `listing.localizations` map. Resolve marketplace display\nand search text with the vendored registry resolver; do not use\n`resolveSupportedLocale` or infer sibling regions such as `zh-TW -> zh-CN`.\n\n## Verification\n\nRun `pnpm run check:i18n` after any locale, catalog, placeholder, plural, or\nresource-registration change. It verifies file/catalog parity, logical key\ncoverage, plural categories, string leaves, and placeholder parity. Its tests\nmust use temporary fixtures and never rewrite real locale files. Tests that\nrender translated components load the real renderer initialization with:\n\n```ts\nimport '@renderer/lib/i18n'\n```\n","frontmatter":{"description":"Internationalization contracts for renderer/main strings and locale resources","paths":["src/renderer/**/*.ts","src/renderer/**/*.tsx","src/main/**/*.ts","src/core/i18n/**","src/core/plugin/plugin-registry*","src/core/plugin/capabilities/i18n*","src/core/plugin/manifest/i18n-resolve*","src/server/index.ts","src/server/operator-admin*.ts","src/shared/constants/locales*.ts","src/shared/schemas/locale*.ts","src/shared/i18n-resources.ts","src/shared/locales/*.json","scripts/check-i18n.mjs","tests/scripts/check-i18n.test.ts"]},"isInternal":false,"tokens":917,"sizeBytes":4226},{"name":"language-and-docs.md","path":".claude/rules/language-and-docs.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/language-and-docs.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Language, bilingual public docs, and private-context boundaries\n---\n\n# Language & Documentation\n\n## Language\n\n- Code, comments, commits, PR titles, branches, file names, and identifiers are\n  English.\n- Respond to users in the language they use.\n\n## Public Documentation\n\n- New end-user guides and documents with an existing translated counterpart\n  ship as English `<name>.md` and Chinese `<name>.zh-CN.md` pairs.\n- Governance files, generated artifacts, and subtree/developer READMEs may stay\n  single-language unless maintainers establish a pair.\n- When editing one file in a pair, update the other in the same change. Keep\n  headings, commands, paths, and code samples aligned while writing idiomatic\n  prose in each language.\n- `docs/` contains reviewed public artifacts, not private design material.\n\n## Public/Private Boundary\n\n- Machine-specific agent context belongs in ignored `CLAUDE.local.md`.\n- Never commit private plans, prompts, handoffs, credentials, private\n  application or vault names, or absolute local paths.\n- Public contribution workflows must work from a normal checkout without a\n  private account, application, or adjacent repository.\n- Rewrite private source material as a standalone public artifact and review\n  it before committing.\n\n## Maintainer Documentation\n\n- Maintainer operation manuals, deployment runbooks, internal specifications,\n  and implementation plans belong in Obsidian by default. A request to prepare\n  one does not authorize adding it to the repository, committing it, or\n  pushing it. Put it in the repository only when the user explicitly requests\n  a public repository document.\n- Use the project's `pnpm docs` gateway for these documents. Invoke it as\n  `pnpm run docs -- <command>` (or the `docs:*` scripts), because bare\n  `pnpm docs` can resolve to pnpm's built-in package-documentation command.\n  Start with `pnpm run docs -- help` and `pnpm run docs -- doctor`.\n- Resolve the destination from the ignored `obsidian-docs.config.json`. In a\n  new worktree, use `--config <existing-local-config>` when a configured\n  checkout is available. Keep vault names and machine paths out of tracked\n  rules and public documentation.\n- Create documents through `pnpm run docs -- create <directory> <name.md>\n  --from <temporary-file>` using a source file outside the repository. Check\n  existing notes before replacing content; use `--overwrite` only for the\n  intended update. Do not substitute raw vault filesystem writes or UI\n  automation for this gateway.\n- Read back saved content and check code blocks and literal escape sequences;\n  a successful create command alone does not prove the manual is intact.\n- If the gateway or configuration is unavailable, report the specific blocker\n  and retain a temporary copy outside the repository. Do not fall back to\n  placing the manual in `docs/` or publishing it in a PR.\n","frontmatter":{"description":"Language, bilingual public docs, and private-context boundaries"},"isInternal":false,"tokens":615,"sizeBytes":2885},{"name":"panel-layout.md","path":".claude/rules/panel-layout.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/panel-layout.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ndescription: Height-chain and sticky-scroll rules for panels inside SidebarInset\npaths: [\"src/renderer/routes/**/*.tsx\", \"src/renderer/layouts/app-layout*.tsx\", \"src/renderer/components/ui/sidebar*.tsx\", \"src/renderer/components/desktop-kit/panel/**\", \"src/renderer/components/settings-kit/**/*.tsx\"]\n---\n\n# Panel Layout\n\nPages inside `SidebarInset` stay within the viewport. The document and panel\nshell do not scroll; the innermost data region owns scrolling.\n\n## Height chain\n\nEvery flex ancestor between the viewport clamp and the data region must allow\nits child to shrink:\n\n```text\nSidebarProvider  h-svh overflow-hidden\n  SidebarInset   flex-1 min-h-0 overflow-hidden\n    PanelShell   h-full flex flex-col\n      content    flex-1 min-h-0 flex flex-col\n        Tabs     flex-1 min-h-0 flex flex-col\n          list   shrink-0\n          content flex min-h-0 flex-1\n            panel flex min-h-0 flex-1 flex-col\n              fixed regions shrink-0\n              data region flex min-h-0 flex-1 overflow-auto\n```\n\n`flex-1` without `min-h-0` is insufficient because the default intrinsic\nminimum height lets content grow the page.\n\n## Scrolling and sticky content\n\n- The chain has exactly one `overflow-auto`: the leaf data region. Ancestors\n  use `overflow-hidden` where a clamp is needed; do not make `PanelShell`,\n  `Tabs`, or `TabsContent` a competing scroller.\n- Non-scrolling headers, filters, summaries, tab lists, and footers use\n  `shrink-0`.\n- A sticky column header is the first child inside the scroll region so\n  `sticky top-0` resolves against the intended scroller.\n- Sticky content needs an opaque background, `z-10`, and `border-b`; a separate\n  separator sibling scrolls away and does not preserve the boundary.\n- Under tabs, keep `TabsContent` as `flex min-h-0 flex-1` and its panel as\n  `flex min-h-0 flex-1 flex-col` so both axes remain bounded.\n\nWhen changing a panel, verify that the page has no document scrollbar, only the\ndata region scrolls, sticky headers remain readable, and the inset/footer stay\nvisible at short viewport heights.\n","frontmatter":{"description":"Height-chain and sticky-scroll rules for panels inside SidebarInset","paths":["src/renderer/routes/**/*.tsx","src/renderer/layouts/app-layout*.tsx","src/renderer/components/ui/sidebar*.tsx","src/renderer/components/desktop-kit/panel/**","src/renderer/components/settings-kit/**/*.tsx"]},"isInternal":false,"tokens":491,"sizeBytes":2069},{"name":"plugin-registry.md","path":".claude/rules/plugin-registry.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugin-registry.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/rules/plugin-registry.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugin-registry.md)","tokens":300,"sizeBytes":174},{"name":"plugins.md","path":".claude/rules/plugins.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugins.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/rules/plugins.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugins.md)","tokens":300,"sizeBytes":158},{"name":"renderer.md","path":".claude/rules/renderer.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/renderer.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/rules/renderer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/renderer.md)","tokens":300,"sizeBytes":160},{"name":"package.json","path":"packages/native-host/package.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/package.json","title":"Native-host Skill","category":"anthropic-skill","format":"json","content":"# Native-host Skill\n\nPath: `packages/native-host/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/package.json)","tokens":300,"sizeBytes":182},{"name":"README.zh-CN.md","path":"packages/native-host/README.zh-CN.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.zh-CN.md","title":"Native-host Skill","category":"anthropic-skill","format":"markdown","content":"# Native-host Skill\n\nPath: `packages/native-host/README.zh-CN.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.zh-CN.md)","tokens":300,"sizeBytes":188},{"name":"en-US.json","path":"tests/fixtures/plugins/test.allcaps/locales/en-US.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/locales/en-US.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `tests/fixtures/plugins/test.allcaps/locales/en-US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/locales/en-US.json)","tokens":300,"sizeBytes":220},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\n## Project\n\nMotrix Turbo is an Electron and Node/Web download manager. Keep `src/core/`\nhost-neutral so it can be reused by both shells and replaced independently.\n\nDevelopment happens on **`main`**. The frozen **`master`** branch is the legacy\nElectron 22/Vue 2 application. Branch from and target PRs at `main`, never\n`master`.\n\n## Core Commands\n\n```bash\npnpm start                         # Electron development runner\npnpm start:server                  # Built Node/Web server\npnpm test                          # Vitest suite\npnpm exec vitest run <test-path>   # Focused test\npnpm run lint                      # Biome over the repository\npnpm exec tsc --noEmit             # Type-check\npnpm build                         # Desktop production build\npnpm build:server                  # Node/Web production build\npnpm test:e2e                      # Playwright suite\n```\n\nUse `pnpm exec`, not `npx`. The authoritative commit gate is\n`.claude/rules/commit-and-quality.md`.\n\n## Architecture Boundaries\n\n1. `src/core/` never imports `electron` or `src/main/`.\n2. `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`;\n   use `@renderer/lib/transport` with shared protocol constants.\n3. `src/shared/` contains pure cross-layer contracts and descriptive runtime\n   data only: no IO, timers, network, Electron, or Node-specific APIs.\n4. Use `Commands`, `Queries`, `Events`, and their `Bridge*` counterparts from\n   `src/shared/protocol/`; never use raw transport channel strings.\n\n## Rule Routing\n\nRules without `paths` frontmatter are global. Load path-scoped rules only when\ntheir patterns match the files under inspection or modification.\n\n| Rule | Scope |\n|---|---|\n| `commit-and-quality.md` | Required checks and conditional validation |\n| `git-workflow.md` | Commits, branches, PRs, and releases |\n| `language-and-docs.md` | Language and public/private documentation |\n| `browser-artifacts.md` | Browser test output paths and temporary UI verification files |\n| `architecture.md` | Layer boundaries and transport flow |\n| `electron-vite.md` | Build, packaging, native ABI, and pnpm |\n| `code-style.md` | TypeScript, React, CSS, and file naming |\n| `renderer.md` | Renderer state, components, forms, and transport |\n| `icons.md` | Semantic icon imports, library isolation, and tree shaking |\n| `panel-layout.md` | Viewport height and scrolling layout |\n| `i18n.md` | Locale catalogs and user-visible strings |\n| `domain-model.md` | Shared domain types, validation, and errors |\n| `plugins.md` | Plugin sandbox, capabilities, and builtins |\n| `plugin-registry.md` | Registry compatibility and install integrity |\n| `bridge.md` | MDXP pairing, dispatch, and transports |\n","isInternal":false,"tokens":638,"sizeBytes":2704},{"name":"README.md","path":"packages/native-host/README.md","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.md","title":"native-host Documentation","category":"plugin-manifest","format":"markdown","content":"# native-host Documentation\n\nPath: `packages/native-host/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.md)","tokens":300,"sizeBytes":184},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.allcaps/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/motrix-plugin.json","title":"test.allcaps Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.allcaps Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.allcaps/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/motrix-plugin.json)","tokens":300,"sizeBytes":235},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.audit-band/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.audit-band/motrix-plugin.json","title":"test.audit-band Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.audit-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.audit-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.audit-band/motrix-plugin.json)","tokens":300,"sizeBytes":244},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.echo/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.echo/motrix-plugin.json","title":"test.echo Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.echo Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.echo/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.echo/motrix-plugin.json)","tokens":300,"sizeBytes":226},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json","title":"test.enrich-band-throws Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.enrich-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json)","tokens":300,"sizeBytes":268},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.enrich-band/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band/motrix-plugin.json","title":"test.enrich-band Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.enrich-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band/motrix-plugin.json)","tokens":300,"sizeBytes":247},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json","title":"test.resolve-band-throws Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.resolve-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json)","tokens":300,"sizeBytes":271},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.resolve-band/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band/motrix-plugin.json","title":"test.resolve-band Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.resolve-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band/motrix-plugin.json)","tokens":300,"sizeBytes":250},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json","title":"test.toplevel-http Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.toplevel-http Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json)","tokens":300,"sizeBytes":253},{"name":"motrix-plugin.json","path":"tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json","rawUrl":"https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json","title":"test.toplevel-register-only Plugin Manifest","category":"plugin-manifest","format":"json","content":"# test.toplevel-register-only Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json)","tokens":300,"sizeBytes":280}],"systemPromptSnippet":"<yakaai_skills repo=\"agalwood/Motrix\">\n<!-- File: AGENTS.md (Tokens: ~154 | Category: root-instruction) -->\n# AGENTS.md\n\nClaude Code rules are the canonical agent guidance for this repository. Codex\ninherits them instead of maintaining a second copy.\n\nBefore inspecting or changing files, read:\n\n1. `CLAUDE.md`.\n2. Every `.claude/rules/*.md` file without `paths` frontmatter.\n3. Every rule whose `paths` patterns match the files being inspected or\n   changed.\n\nIf the scope expands, load the newly matching rules before continuing. Do not\nload unrelated path-scoped rules by default.\n\nConflicts resolve in this order: current user instructions, this file,\n`CLAUDE.md`, matching `.claude/rules/*.md`, then default agent behavior.\nUpdate the canonical Claude rules rather than duplicating guidance here.\n\n<!-- File: .claude/rules/architecture.md (Tokens: ~585 | Category: anthropic-skill) -->\n---\ndescription: Architecture boundaries for the shared desktop and server app\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"scripts/check-boundaries.mjs\"]\n---\n\n# Architecture\n\nMotrix keeps its product core host-neutral so it can run behind either the\nElectron shell or the Node server and remain replaceable by a future engine.\n\n## Layer Matrix\n\n| Directory | Role | Allowed dependencies |\n|-----------|------|----------------------|\n| `src/renderer/` | Electron/browser frontend | `@shared/`, renderer-local modules |\n| `src/core/` | Host-neutral product core | `@shared/`, host-neutral Node/external libraries |\n| `src/main/` | Electron shell and IPC | `@core/`, `@shared/`, Electron |\n| `src/preload/` | Electron bridge | pure `@shared/` protocol values/types, Electron |\n| `src/server/` | Node/Docker shell | `@core/`, `@shared/`, server libraries |\n| `src/shared/` | Cross-layer contracts | pure schemas, constants, data, and utilities only |\n\n## Hard Boundaries\n\n- `src/core/` never imports Electron or `src/main/`.\n- `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`.\n- `src/server/` never imports Electron or `src/main/`.\n- `src/shared/` never imports an application layer and contains no IO, timers,\n  network access, Electron APIs, or Node-specific APIs.\n- Production code never imports `src/test-utils/`; generated builtin plugin\n  output is never used as source.\n\n`pnpm run check:boundaries` is the automated baseline, but changed imports\nmust also be reviewed against the matrix because not every exception is\nmachine-enforced.\n\n## Dual Transport Contract\n\nThe same renderer-facing transport contract serves both hosts:\n\n```text\nElectron: renderer -> ElectronTransport -> preload -> main IPC -> core\nBrowser:  renderer -> HttpWsTransport -> server RPC/events -> core\n```\n\nRenderer command/query/event traffic uses `@renderer/lib/transport`. Direct\n`window.motrix` access is limited to the Electron transport and narrowly scoped\nElectron platform adapters; feature code stays behind those abstractions.\nEvents return through the selected shell and transport, so renderer state must\nnot depend on a host-specific channel.\n\nAll channel names come from `src/shared/protocol/`. Use `Commands`, `Queries`,\n`Events`, and their `Bridge*` counterparts instead of raw strings.\n\n## Engine Adapter\n\nProduct-level code targets `EngineAdapter` in\n`src/core/engine/engine-adapter.ts`, never aria2 RPC types directly. Concrete\nengines translate at the adapter boundary. `EngineSupervisor` is the sole\nowner of engine start, stop, and restart lifecycle.\n\n<!-- File: .claude/rules/bridge.md (Tokens: ~4425 | Category: anthropic-skill) -->\n---\ndescription: MDXP bridge lifecycle, authorization, transports, and protocol boundaries\npaths: [\"src/core/bridge/**\", \"src/core/bridge-receiver/**\", \"src/main/bridge/**\", \"src/server/bridge/**\", \"src/shared/protocol/bridge.ts\", \"src/shared/schemas/bridge-settings.ts\", \"src/shared/config/native-messaging-extensions.json\", \"packages/native-host/**\", \"docs/bridge-pairing-protocol.md\", \"package.json\"]\n---\n\n# Bridge (MDXP / JSON-RPC 2.0)\n\nThe bridge uses JSON-RPC 2.0 over WebSocket and HTTP. The\n[`@motrix/mdxp`](https://github.com/motrixapp/mdxp) package is the source of\ntruth for wire schemas, method constants, types, errors, and connection\nbehavior; its version comes from `package.json`. Do not recreate MDXP schemas,\nnumeric error codes, or the removed legacy frame protocol in this repository.\nImport `ErrorCodes` and `makeMdxpError` from the package.\n\n## Pairing and session lifecycle\n\n- Extension WebSockets speak MBP1 (`docs/bridge-pairing-protocol.md`), which\n  authenticates *below* MDXP. `/pair?nonce=...` runs the §6 first-pair\n  handshake and `/v1` the §8 challenge–response; `/v1` carries **no**\n  credential in its URL and every query, including `?token=`, is rejected by\n  the raw-route gate. Only once a route\n  authenticates does MDXP start, inside the §10 AEAD envelope, and\n  `motrix/initialize` is the client's first request there.\n- `WebSocketBridgeServer` always owns and registers `motrix/initialize`.\n  Shell-provided download handlers are registered separately and must be wired\n  before `server.start()`; never expose a listener with incomplete handlers.\n- Authorization and readiness are different states. Every extension WebSocket\n  session is authorized **at the transport**: `adoptAuthenticatedSession` marks\n  the connection before registering a handler, and `motrix/initialize` is a\n  capabilities exchange that grants nothing and never returns a `pairToken`.\n  The `authorizedDispatch` gate over control-plane and download methods stays\n  as defence in depth. Unary HTTP separately requires its bearer-token and\n  agent-facing gates.\n- The six MBP1 `BridgeServerOptions` (`instanceId`, `serverGeneration`,\n  `appVersion`, `credentials`, `isOfficialId`, `queueMbp1Dialog`) resolve as a\n  unit. A server missing any one of them has no extension WebSocket surface at\n  all: both upgrades are refused rather than falling back to an\n  unauthenticated one.\n- A connection becomes ready only after the peer sends the\n  `motrix/initialized` notification. Providers passed to\n  `UrlResolutionService` must filter to ready connections before sending\n  server-initiated `url/probe` or `url/resolve` requests.\n- The connection close handler removes its own current session and disposes the\n  connection. Do not mutate the server's session map from feature code.\n\nApproval is proven by typing the pairing code, never by clicking an Allow\nbutton. The dialog shows an 8-symbol Crockford-alphabet code grouped\n`XXXX-XXXX` (`mbp1/pairing-code.ts`), lives 120 seconds, and *is* the SPAKE2\npassword: it never travels on the Extension connection and MUST NOT be logged\nat any level (§7.1). Server operator snapshots/events and an explicitly selected\ninteractive `motrix-admin extension pair` view follow §4.4. The terminal uses\nfixed loopback RPC with the operator Bearer credential, rejects redirected streams\nand JSON, revalidates the exact session, and never renews the code lifetime. A pairing session resolves to one of three identities —\n`official`, `attested-non-official`, `unverified` — and `official` is decided\nby the immutable allowlist only, never the NM manifest set and never the\nuser-added trusted-extension registry (§5).\n\nPair nonces are one-shot, expire after 60 seconds, and are consumed only by\n`/pair` — before the route does anything else, so a refused upgrade never\nreaches a session or a dialog. Never persist them. `endpoint.json` contains\ndiscovery information and the local CLI bearer token, `localToken`, which is\nnow dual-purpose: the native-messaging host also derives its §9.2 ticket-MAC\nkey from it (`ticketKey = HKDF(UTF8(localToken), \"MBP1/nm-ticket/v1\", \"mac\",\n32)`) but MUST NOT expose it to the extension. `localToken` persists across\nbridge restarts — only `serverGeneration` rotates on every start\n(`loadOrCreateBridgeIdentity`) — so an NM ticket minted before a restart takes\nthe §9.2 semantic-downgrade path instead of aborting on a stale-generation MAC\nmismatch: the ticket then contributes nothing, and identity falls back to what\nthe verified origin alone establishes (§9.2 only-raise — a stale ticket is\nnever worse than presenting none). The nonce route is now `POST /nonce` and requires\n`X-Motrix-Bridge: 1`; the former `GET /nonce` is gone and 404s.\n\nWhile bound to a loopback host, every route and upgrade rejects a `Host` header\nthat is not `127.0.0.1`, `localhost`, or `[::1]` with the bound port — the §4.3\nDNS-rebinding guard. It is inert for a non-loopback bind, which keeps its\nexisting token + reverse-proxy model.\n\nThe Server's documented Docker path deliberately binds MDXP to `0.0.0.0`\ninside the container so Docker can publish port 16801. Remote Extension routes\nmay use that non-loopback listener only behind a parser-issued, all-or-nothing\nremote configuration; the WS/WSS public URL is required, while the operator URL\nis optional. Without an HTTPS operator URL or explicit HTTP consent, code-bearing\noperator snapshots/events are restricted to the authenticated local Bearer path.\nBoth HTTP snapshots and event broadcasts must enforce the delivery policy from\nthe same parser-issued configuration. `RemoteExtensionSurfacePolicy` enforces the exact\npublic Host and raw route prefix plus the admission limits before MBP1 runs.\nDo not reintroduce a loopback-only bootstrap guard: container loopback is not\nreachable through Docker's published-port path.\n\n`GET /discovery` additionally reports unauthenticated compatibility hints:\n`runtime`, `extensionPairing: { protocol: 'mbp1', versions: [1] }`, and\n`applicationProtocols: { mdxp: ['1.0'] }`. They may drive an upgrade message,\nbut never trust, downgrade, or a port pin; authenticated initialization remains\nauthoritative. The WebSocket parser caps messages at the largest valid active\nenvelope (1 MiB plaintext + 24 bytes framing), while the state machines retain\ntheir stricter 16 KiB pre-authentication check.\n\nNothing SPAKE2-adjacent may be logged at any level: the pairing code, `w`, any\nPAKE intermediate, derived keys, MACs, or NM tickets (§11). A `pairError` frame\ncarries only its nine-code vocabulary (`unsupportedVersion`, `busy`,\n`rateLimited`, `codeMismatch`, `expired`, `aborted`, `authFailed`,\n`protocolViolation`, `pairingFailed`) and never a step-identifying detail.\n\nThe bridge tries the candidate port range 16802–16806 in order and falls back\nto an ephemeral port, reported as `degraded`, once every candidate is taken.\n`bridge.fixedPort` pins a single port instead and is applied by restarting the\nruntime. `endpoint.json` stays the authoritative discovery record for both the\nCLI and the Native Messaging host regardless of which port was chosen. The\nserver shell deliberately keeps its own explicit configured host and port and\nnever walks the candidate range.\n\n## MBP1 module map\n\n`src/core/bridge/mbp1/` implements `docs/bridge-pairing-protocol.md`:\n`canonical.ts` (§2 encoding primitives), `scrypt-w.ts` (§6.2 password-to-scalar\nderivation), `pairing-code.ts` (§7.1 code generation/display/normalization),\n`spake2-core.ts` (§6.3–6.6 the generic SPAKE2 core), `transcript.ts` (§6.4\ntranscript construction), `reconnect-mac.ts` (§8 reconnect MAC),\n`reconnect-rate-limit.ts` (§8 per-origin and global reconnect rate limiting,\ndeliberately separate from `flood-control.ts`'s §7.3 counters),\n`envelope.ts` + `envelope-message-stream.ts` (§10 AEAD framing),\n`ticket-verify.ts` (§9 NM ticket verification), `frames.ts` (§6.1/§11 wire\nframe schemas and the `pairError` vocabulary), `nonce-service.ts` (§4.2\none-shot pairing nonces), `flood-control.ts` (§7.3 pairing admission limits),\n`pre-auth-table.ts` (§4 pre-authentication bookkeeping), `pair-session.ts`\n(§6 the `/pair` state machine), `reconnect-session.ts` (§8 the `/v1` state\nmachine). Alongside it: `../credential-store.ts` (§6.7 durable MBP1\ncredentials), `../bridge-identity.ts` (§9.2 the persistent `localToken` /\nrotating `serverGeneration` loader), and `../endpoint-file-writer.ts` (the\n`endpoint.json` writer).\n\n## Envelope close codes\n\nOnce §10 AEAD framing is active, envelope faults are classified by a\nthree-state discriminated union — `peer-violation`, `usage-limit`, `internal`\n— and by direction. The direction matters: the same `EnvelopeViolationError`\nfrom inbound `open()` means the peer sent something §10 forbids, but from\noutbound `seal()` it means our code tried to seal a frame over the 1 MiB\nplaintext cap, so it is `internal` rather than the peer's fault.\n\nThe close codes are:\n\n- **`1002` (protocol error)**: a §11 protocol violation (bad frame, tampering,\n  replay, a post-activation text frame). The peer sent something §10 forbids.\n- **`4001` (usage limit)**: a direction reached its §10 frame- or block-count\n  usage bound (2^24 frames or 2^30 encrypted AES blocks per direction). This\n  is neither side's fault — §10 requires the closure before either bound is\n  exceeded, and the remedy — reconnect and derive fresh keys (§8) — is the\n  same whichever direction tripped it. RFC 6455 §7.4.2 reserves 4000–4999 for\n  exactly this: private use \"by prior agreement\" between applications. `1002`\n  and `1011` cannot be reused: `1002` would accuse the peer of a violation when\n  none occurred, `1011` would claim an internal crash for a routine,\n  spec-mandated transition. A conforming client reconnects on any established\n  channel close (regardless of code) via §8 and derives fresh keys.\n- **`1011` (internal error)**: this process is broken (a bug, not a protocol\n  event), including an outbound attempt to seal a plaintext over the 1 MiB\n  limit. Such a refused write closes the session rather than leaving\n  application state half-delivered.\n\nThe extension client cannot mirror this table: a browser's `WebSocket.close`\nrefuses every code outside 1000/3000–4999, so the client sends `4001` for a\nusage bound (either direction) and a bare close for everything else (§11).\n\nAn explicit extension revoke first marks the live `BridgeConnection`\nunauthorized and not-ready, then durably removes every committed/provisional\ncredential for its verified Origin before updating `PairingService`. The\nsame Origin's pending `/pair` and `/v1` sessions are synchronously cancelled,\nand new upgrades are refused for the whole durable-revocation critical section.\nThe best-effort `PairRevoked` notification may drain briefly, but dispatch is\nalready denied; the underlying WebSocket is then closed and its session-map\nentry removed.\n\nOn-disk state lives under `<userData>/bridge/`: `pairing.json`,\n`registry.json`, `endpoint.json` (mode 0600), `local-token` (mode 0600), and\n`mbp1-credentials.json`. The writers request 0600 on every platform, but\nNode's `chmod` on Windows manipulates only the read-only bit — see the\nattestation-root passage under **Native Messaging host** for what that means\nfor what the host can attest there.\n\n## Dispatch and transport exposure\n\n`MdxpDispatcher` owns request-schema `safeParse` validation and returns\n`InvalidParams`; handlers and `BridgeReceiver` receive typed data and enforce\nbusiness rules only. Keep `@core/bridge/` transport-neutral: the only production\nfile there allowed to import `ws` is `web-socket-bridge-server.ts`. Other files\nuse `WebSocketLike` from `web-socket-message-stream.ts`.\n\nFor every new MDXP method:\n\n1. Define and publish its schema and method constant in the MDXP repository,\n   then bump the published dependency here. Never commit a machine-local\n   `link:` or `file:` dependency.\n2. Register the schema and typed handler in the dispatcher. Use\n   `setHandlers()` only for shell-supplied extension methods; shared\n   control-plane methods belong in `registerReadHandlers` or\n   `registerWriteHandlers`.\n3. Choose and test exposure explicitly: unary HTTP, extension WebSocket, or\n   both. Do not assume dispatcher registration exposes every transport.\n4. For client notifications, register `conn.onNotification`. For outbound\n   notifications, publish through `BridgeEventBus` and let the shell call\n   `conn.sendNotification(Notifications.*, params)`. For outbound requests,\n   use a domain service and pass a `CancellationToken` when work is cancellable.\n\nPaired extensions may use the WebSocket control plane for `task/list`,\n`task/get`, `task/pause`, `task/resume`, `task/remove`, `stats/get`, and\n`engine/status`, plus the user-gesture-only `task/reveal`; keep the\n`dispatcher.has()` and authorization gates. `task/reveal` accepts only a\npublic task id, derives the path inside the Electron shell, is not agent-facing,\nand must never be exposed by unary `/mdxp`. The headless server does not\nregister it, and `motrix/initialize.capabilities.taskReveal` must be derived\nfrom the dispatcher's actual registration so older/headless peers report it as\nunsupported rather than advertising a dead method.\n`download/add` is unary-only, while extensions submit through\n`download/submit`. Renderer-only plural task commands must not enter MDXP.\nUnary `/mdxp` admits only methods marked agent-facing; dispatcher registration\nmust not make extension handshake or submit methods reachable over HTTP.\nAuthenticated SSE streams must close when their pairing token is revoked.\n\nRenderer-initiated URL resolution enters main through `BridgeQueries.*`; main\nthen sends MDXP `url/probe` / `url/resolve` requests to ready extensions. Use\nthe shared channel constants. For cancellation, renderer supplies a request ID,\nmain owns its `CancellationTokenSource`, and the matching cancel query aborts it.\n\n## Native Messaging host\n\n`packages/native-host/` is a standalone Rust executable spawned by Chromium or\nFirefox. It reads `endpoint.json`, probes `GET /discovery` for liveness\n(§4.1), and fetches exactly one `POST /nonce` with `X-Motrix-Bridge: 1`\n(§4.2) once liveness succeeds — the launch-poll loop probes liveness every\n~200ms but never fetches more than one nonce per resolution attempt. A\nsuccessful liveness probe settles the resolution either way: if the nonce\nfetch then fails, `resolve_endpoint` reports `NotRunning` rather than\nfalling through to a launch, because `fetch_nonce` answers `None` for any\nnon-2xx — including the 429/503 the §4.2 outstanding-nonce cap and issuance\nrate limit return — and retrying there would amplify exactly the load that\ncaused the refusal.\n\nThe **broker** path cannot yet reach one nonce per resolution. `NotRunning`\nis all the versioned stdio contract can say, so a live bridge that refuses a\nnonce is indistinguishable to the companion from one that is down: it\nlaunches and calls `WaitForEndpoint`, fetching a second nonce. Bounded at two\nper resolution and never a loop, but real — separating the two states needs a\nbroker-protocol response the current contract cannot express without\nbreaking older companions. It\nmust not expose the local CLI token itself to the extension or depend on\nsystem Node.js or Electron. Production extension IDs come from\n`src/shared/config/native-messaging-extensions.json`; development IDs may\nbe added only through `MOTRIX_DEV_TRUSTED_EXTENSIONS` and must never ship as\nproduction defaults.\n\nFor a `bootstrap` request, the host also mints a §9.2 NM attestation ticket\n(`mint_ticket_for_bootstrap` in `main.rs`), returned as `nmTicket` in\n`{ action: 'requestPair', protocolVersion: 1, port, nonce, nmTicket? }` —\n`protocolVersion` is always emitted, by both `request_pair` and\n`request_pair_with_ticket`, and §9.1 makes it normative — whenever every trusted\ninput is available: an argv-extracted caller identity, an owner-checked\n`localToken` and ASCII `generation` from `endpoint.json`, and the\nextension's `bindingPub`. A missing input degrades to a ticketless reply\nrather than fabricating one — ticketless resolves to `unverified` on the\nserver, the same outcome as presenting no ticket at all, which is safer\nthan a structurally broken ticket (§9.2 aborts on that). `localToken`\nitself never reaches the wire, only the MAC key it derives.\n\n`endpoint.json`'s `localToken`/`generation` are trusted for minting only\nwhen the file passes `is_owner_only` on the already-open handle (§9.1); a\nfile that fails it still yields a port, but those two fields are dropped. On\nUnix that is a 0600 owner-and-mode check. On Windows it is the deliberately\nweaker analogue documented on `is_owner_only` itself: owner = current process\nuser, and every DACL entry either a deny-family ACE or a plain allow ACE for\nthat user, `LocalSystem`, or `BUILTIN\\Administrators` — any other ACE type\n(the conditional/object allow variants included) fails the check closed and\ndrops the fields. SYSTEM and Administrators are admitted because they can\nalready rewrite anything the user owns, which means an administrator can\n*read* `localToken` on Windows — do not read the ticket-minting path above\nas attesting anything stronger than that there.\n\nOn Flatpak, only the **broker** (`motrix-native-host-broker`) speaks HTTP:\n`probe_bridge` reaches `probe.rs` through `resolve_endpoint`, so the broker\ninherits the `POST /nonce` / `X-Motrix-Bridge: 1` migration. The companion\n(`motrix-flatpak-native-host`) makes **no** HTTP calls of its own — it has no\nreference to `probe_liveness` or `fetch_nonce` — and reaches the bridge only by\nspawning the broker over stdio. Both are ticketless by design; neither calls\n`mint_ticket`. That scope decision stands.\n\n**Do not \"simplify\" this by giving the companion a direct probe call.** The\nindirection is the sandbox boundary: the companion runs outside the Flatpak\nsandbox and the broker inside it, and routing every bridge request through the\nstdio contract is what keeps that boundary a single, auditable seam. A direct\ncall would read as removing a pointless hop.\n\n## Verification\n\nRun the global gate in `commit-and-quality.md`, then the scoped bridge checks;\ndo not encode a fixed test count:\n\n```bash\npnpm exec biome check src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\npnpm exec vitest run src/core/bridge/ src/core/bridge-receiver/ src/main/bridge/ src/server/bridge/\n```\n\n<!-- File: .claude/rules/code-style.md (Tokens: ~593 | Category: anthropic-skill) -->\n---\ndescription: TypeScript naming, import, alias, and schema conventions\npaths: [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.css\", \"*.config.ts\"]\n---\n\n# Code Style\n\nBiome owns formatting and general lint rules; do not duplicate its settings\nhere. The conventions below cover repository-specific boundaries.\n\n## Naming\n\n- JavaScript, TypeScript, TSX, and stylesheet files use `kebab-case`.\n- Conventional qualifiers remain lowercase: `.test`, `.spec`, `.e2e`,\n  `.integration`, `.darwin`, and `.win32`.\n- Exported classes and React components remain PascalCase even though their\n  filenames are kebab-case.\n- Rust and Python use `snake_case`; Cargo binary entrypoints may use\n  kebab-case to match the executable name.\n- Run `pnpm run check:file-names` after adding or renaming files.\n\n## Imports and Aliases\n\n- Use `import type` for type-only imports and `node:` for Node builtins.\n- Prefer the configured alias over a deep relative import; local sibling or\n  parent imports may remain relative.\n- Alias availability is target-specific. Application builds expose\n  `@shared`, `@core`, `@renderer`, and `@main` as appropriate; server targets\n  may expose `@server`, and tests may expose `@test-utils`.\n- Do not assume aliases cross targets: Vitest intentionally has no `@main`,\n  and the QuickJS worker exposes only `@shared` and `@core`. Check the target's\n  TypeScript and Vite configuration before using an alias.\n\n## Shared Runtime Contracts\n\nUse Zod from `zod` for untrusted external data and cross-layer payloads. A\nshared schema is the source of truth for its runtime constraints, inferred\ntype, and defaults; do not create parallel interfaces or hand-written\nvalidators for the same contract. Pure cross-layer schemas belong in\n`src/shared/schemas/`; host-specific validation stays in its owning layer.\n\n## Comments Describe What Exists\n\nA comment in the present tense is a claim about the current code, and readers\nact on it. Describe an intended-but-unwritten consumer in the conditional, or\nas a `TODO`, and say plainly that nothing does it yet.\n\nThis matters most for comments that assert a safety property. Two distinct\nbugs shipped behind such comments: a writer documented a \"backstop\" that\nreported failures which nothing reported, and a socket wedged silently\nforever; and a getter pair was documented as being read by a wiring layer that\ndecided when to reconnect, when the only readers were tests. In both cases the\ncomment was the reason no one looked.\n\nWhen a guard's justification depends on a case, check that it covers every\ncase the code now reaches — a rationale that was true when written and is\nfalse for a later-added branch argues for the bug.\n\n<!-- File: .claude/rules/commit-and-quality.md (Tokens: ~479 | Category: anthropic-skill) -->\n---\ndescription: Required commit gate and change-specific validation\n---\n\n# Commit & Quality Gate\n\n## Required for Every Commit\n\nRun all three commands and fix failures before committing:\n\n```bash\npnpm run check:boundaries\npnpm run lint\npnpm exec tsc --noEmit\n```\n\n`pnpm run lint` (`biome check .`, scoped by biome.json) is the same command\nCI runs — do not substitute a narrower path list, and never pipe it in a way\nthat discards its exit code.\n\n`check:boundaries` is an automated baseline, not a complete architecture\nproof. Review changed imports against `architecture.md` as well.\n\nStage only the intended files or hunks, then inspect `git diff --staged` and\nrun `git diff --cached --check`. Do not hide failures because a CI job is\nnon-blocking.\n\n## Change-Specific Checks\n\nRun every check that matches the change:\n\n- Behavior or logic: focused tests with `pnpm exec vitest run <test-path>`;\n  use `pnpm test` for broad cross-cutting changes.\n- Browser/Electron user flows: `pnpm test:e2e` when the affected flow has E2E\n  coverage.\n- Locale resources or i18n behavior: `pnpm run check:i18n`.\n- Added or renamed files: `pnpm run check:file-names`.\n- Plugin manifest contracts or `@motrix/plugin-manifest-schema`:\n  `pnpm run check:schema-parity`.\n- Dependencies, bundled assets, or license metadata:\n  `pnpm run check:third-party-notices`.\n- Native host Rust:\n  ```bash\n  cargo fmt --manifest-path packages/native-host/Cargo.toml --all -- --check\n  cargo clippy --manifest-path packages/native-host/Cargo.toml --all-targets --locked -- -D warnings\n  cargo test --manifest-path packages/native-host/Cargo.toml --locked --all-targets\n  ```\n- Packaging or release code: run the focused tests under `tests/scripts/` and\n  the relevant verifier. Follow workflow-supplied arguments for checks such as\n  `check:update-artifacts`.\n\nUse `pnpm exec biome check --write .` only for reviewed, auto-fixable\nissues; re-run the required gate afterward.\n\n<!-- File: .claude/rules/domain-model.md (Tokens: ~371 | Category: anthropic-skill) -->\n---\ndescription: Engine-neutral domain aggregates, validation, defaults, and errors\npaths: [\"src/shared/**\", \"src/core/**\"]\n---\n\n# Domain Model\n\n## Engine-Neutral Aggregates\n\nProduct-level code never exposes aria2 raw types or naming. Aria2 values are\ntranslated at the engine adapter boundary into the aggregates in\n`src/shared/types/`:\n\n- `DownloadTask`, `TaskStatus`, and `TaskType` describe task identity and\n  lifecycle; `BtExtension` carries BitTorrent-only details.\n- `EngineState` and `EngineFeatureReport` describe engine lifecycle and\n  detected capabilities without binding consumers to an RPC implementation.\n- `AppSettings` is the persisted aggregate; `EngineSettings` and\n  `MotrixAppSettings` separate engine-facing and application-facing concerns.\n- `GlobalStats` is the cross-engine aggregate used by UI and protocol layers.\n\nKeep managers and stores engine-neutral (`<Domain>Manager`, `<Domain>Store`).\nEngine-specific conversion and RPC fields stay under the concrete adapter.\n\n## Schemas and Defaults\n\n- Cross-layer runtime schemas live in `src/shared/schemas/`; core may compose\n  them but must not duplicate their constraints.\n- Settings schemas use `.catch(safeDefault)` for recoverable persisted values.\n- Derive aggregate defaults from the schema, for example\n  `const DEFAULTS = schema.parse({})`, so validation and defaults cannot drift.\n- Error codes used by settings validation follow\n  `settings.{namespace}.{field}` for localization.\n\n## Errors\n\nUse `AppError` and `ErrorCode` from `@shared/errors` for domain failures that\ncross a service, transport, or UI boundary. Raw `Error` remains appropriate\nfor low-level implementation failures that are caught and translated before\nleaving their owning layer.\n\n<!-- File: .claude/rules/electron-vite.md (Tokens: ~818 | Category: anthropic-skill) -->\n---\ndescription: Build outputs, Electron loading policy, pnpm scripts, and native ABI boundaries\npaths: [\"electron-builder.json\", \"pnpm-workspace.yaml\", \"package.json\", \"Dockerfile\", \"vite.*.config.ts\", \"scripts/dev.mjs\", \"scripts/postinstall.mjs\", \"scripts/ensure-electron-runtime.mjs\", \"scripts/ensure-native-abi.mjs\", \"scripts/native-binary-target.mjs\", \"scripts/stage-*-app.mjs\", \"scripts/verify-*-package.mjs\", \"scripts/before-*-*.mjs\", \"src/main/index.ts\", \"src/main/window/renderer-url-policy*\", \"src/main/window/window-manager*\", \"src/preload/**\"]\n---\n\n# Electron + Vite\n\n## Required Artifacts\n\n| Target | Output |\n|--------|--------|\n| Electron main | `dist/main/index.cjs` |\n| preload | `dist/preload/preload.cjs` |\n| QuickJS worker | `dist/core/plugin/host/quick-js-worker.cjs` |\n| Electron renderer | `dist/renderer/` |\n| Node server and CLI | `dist/server/index.mjs`, `dist/server/motrix-admin.mjs` |\n| browser renderer | `dist/renderer-web/` |\n\nBecause `package.json` declares `\"type\": \"module\"`, main, preload, and worker\noutputs must remain `.cjs`; server outputs remain `.mjs`. The package `main`\nfield must match the main artifact. Build entries sharing an output directory\nmust have unique basenames, and every consuming `build:*` flow must produce\nthe entries it packages.\n\n## Preload and Renderer URL Policy\n\nAt runtime `__dirname` is `dist/main/`, so the preload path is:\n\n```ts\npath.join(__dirname, '../preload/preload.cjs')\n```\n\nRenaming or moving either artifact requires updating this path. Pass\n`VITE_DEV_SERVER_URL` once to `initializeRendererUrlPolicy()` and load every\nrenderer window through `rendererUrlPolicy.loadWindow(win, route)`. Do not add\nfeature-local `loadURL()` or `loadFile()` calls that bypass its loopback-origin\nand packaged-file checks.\n\n## pnpm and Native ABI\n\n- Project pnpm settings belong in `pnpm-workspace.yaml`. Keep `nodeLinker:\n  hoisted` unless the Electron packaging smoke job proves isolated linking\n  works.\n- Install-script permission is controlled by pnpm 12 `allowBuilds`. Keep\n  `electron` allowed for compatibility, but do not rely on `pnpm install` to\n  hydrate Electron: it exposes `install.js` as a package bin without a\n  `postinstall` script. Local workflows that consume Electron or its licenses\n  must run `pnpm run ensure:electron-runtime` first; that command validates the\n  entire payload and repairs partial installs safely. CI/container workflows\n  may invoke `install.js` directly when they immediately validate the result.\n- Native modules such as `better-sqlite3` must match the active ABI. Tests use\n  the Node ABI; Electron and E2E use the Electron ABI. Preserve the\n  `ensure-native-abi.mjs` hooks when changing test or start scripts.\n- `postinstall.mjs` independently controls Electron rebuild and aria2 fetch\n  through `MOTRIX_SKIP_ELECTRON_REBUILD` and `MOTRIX_SKIP_ENGINE_FETCH`.\n  Skipping one must never imply skipping the other.\n- If Electron or a native dependency changes, rebuild for the intended target\n  and exercise the corresponding Node and Electron paths.\n\n## Server Docker Boundary\n\nThe server image uses system aria2 and skips both Electron rebuild and engine\nfetch. `stage-server-app.mjs` selects one target-platform `better-sqlite3`\nprebuild, and `verify-server-package.mjs` verifies the staged payload. The\nruntime image intentionally has no pnpm or build toolchain; never rely on a\nruntime native rebuild.\n\n<!-- File: .claude/rules/git-workflow.md (Tokens: ~677 | Category: anthropic-skill) -->\n---\ndescription: Git commit, branch, PR, merge, and release policy\n---\n\n# Git Workflow\n\n## Commits\n\nUse Conventional Commits in English:\n\n```text\n<type>(<optional-scope>): <imperative summary>\n```\n\nAllowed types are `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `chore`,\n`ci`, and `style`. Keep the summary lowercase, without a period, and under 72\ncharacters. Add a body for non-obvious rationale and a\n`BREAKING CHANGE: ...` footer when applicable.\n\nDo not add AI attribution or co-author trailers automatically. Preserve\ntrailers explicitly supplied by a contributor or required by legal policy.\nFollow `commit-and-quality.md` before creating a commit.\n\n## Branches and Pull Requests\n\n- `main` is the protected active branch; `master` is frozen legacy code. Never\n  target or merge Turbo work into `master`.\n- Branch from current `main`. Use\n  `<type>/<snake_case_topic>_<YYYYMMDD>`, optionally placing an issue number\n  before the topic.\n- Never push directly or force-push to `main`. All changes use PRs.\n- Rebase a private feature branch onto `main` before review. Never rebase\n  `main` or a branch shared with other contributors.\n- After a rebase, update only your own feature branch with\n  `git push --force-with-lease`; never use an unconditional force push.\n- Keep a PR focused. Its title follows Conventional Commits; its description\n  explains what, why, and how the change was verified.\n- Squash merge feature PRs by default. Use a regular merge only when a release,\n  hotfix, or intentionally structured refactor needs commit-level history.\n- Delete merged branches.\n\n## Release Safety\n\nThe checked-in workflows and scripts are the release authority; do not replace\nthem with local publishing steps.\n\n- Set `package.json` to strict SemVer, then create a protected\n  `v<package-version>` tag whose commit is on `main`. The tag and package\n  versions must match; only stable and `beta` channels are supported.\n- A tag push triggers `.github/workflows/release.yml`. Publishing is allowed\n  only after its platform builds, isolated signing/finalization jobs,\n  required signature checks, package verification, artifact assembly, and\n  update-artifact validation succeed. macOS releases require signing and\n  notarization; Windows releases may be explicitly finalized unsigned when\n  both Authenticode secrets are absent. Disclose unsigned Windows artifacts in\n  the public release notes. Manual dispatch validates the build path but does\n  not sign or publish.\n- Do not manually upload release files, reuse unverified artifacts, bypass\n  protected environments, weaken signing-input isolation, or overwrite an\n  existing immutable container tag.\n- Protected stable and beta tags publish one verified amd64/arm64 Snap build\n  set to `latest/edge`. Promote those exact revisions with `snap-promote.yml`\n  from protected `main`; never rebuild during promotion. Stable rejects\n  prereleases.\n- If a release gate fails, fix the source or workflow and run the complete\n  gated path again. Do not publish a partial platform set.\n\n<!-- File: .claude/rules/i18n.md (Tokens: ~917 | Category: anthropic-skill) -->\n---\ndescription: Internationalization contracts for renderer/main strings and locale resources\npaths: [\"src/renderer/**/*.ts\", \"src/renderer/**/*.tsx\", \"src/main/**/*.ts\", \"src/core/i18n/**\", \"src/core/plugin/plugin-registry*\", \"src/core/plugin/capabilities/i18n*\", \"src/core/plugin/manifest/i18n-resolve*\", \"src/server/index.ts\", \"src/server/operator-admin*.ts\", \"src/shared/constants/locales*.ts\", \"src/shared/schemas/locale*.ts\", \"src/shared/i18n-resources.ts\", \"src/shared/locales/*.json\", \"scripts/check-i18n.mjs\", \"tests/scripts/check-i18n.test.ts\"]\n---\n\n# Internationalization\n\nEvery user-visible application or operator string must use i18next; do not\nhardcode UI labels, messages, notifications, dialogs, or CLI output. Renderer\ncomponents use `react-i18next`; Electron main uses the initialized instance\nfrom `src/main/lib/i18n.ts`; the server operator CLI creates an isolated\ninstance from shared resources. Keep `t` in React hook dependency arrays.\nRoute host-wide locale changes through `LocaleCoordinator`: serialize changes,\nskip stale revisions, reconcile late targets, and publish `LocaleChanged` only\nafter the current apply succeeds. Keep plugin-registry and capability-host\nlocale changes transactional, including rollback on failure.\n\n## Resource contract\n\n- `src/shared/locales/en-US.json` is the key-shape source of truth. Add every\n  logical key to every locale registered in `SUPPORTED_LOCALES`; never update\n  only a subset.\n- `src/shared/i18n-resources.ts` statically and exhaustively registers loaders for those\n  locale files. Only English and Simplified Chinese are eager; await resource loading before\n  committing a host or renderer language change. New languages also require a catalog entry in\n  `src/shared/constants/locales.ts` and a matching JSON resource.\n- The server operator CLI uses `src/server/operator-admin-locales.ts` to\n  import only the `operatorAdmin` namespace from every catalog. Keep its locale\n  map exhaustive; importing full application catalogs into the CLI duplicates\n  Web UI resources in the Server package and can exceed its size budget.\n- Every translation leaf is a string. Keep readable locale files as UTF-8;\n  avoid unnecessary `\\uXXXX` escapes.\n- English user-visible prose, including matching test fixtures and assertions,\n  uses the typographic apostrophe `’` for contractions and possessives; do not\n  substitute the ASCII apostrophe `'`. Keep `'` only where technical syntax or\n  an external data contract requires that exact character.\n- Preserve the exact `{{placeholder}}` set across every locale and every plural\n  variant.\n- Plural keys use i18next category suffixes. Supply every cardinal category\n  returned by `Intl.PluralRules(locale)`; `_zero` is the only permitted extra\n  category because i18next treats it as an exact-zero override.\n\n## Namespaces\n\nKeys follow the feature that owns them and use camelCase leaves; descriptions\nnormally pair `name` with `nameDesc`.\n\n- Settings grid cards: `settings.cards.<card>.*`\n- Settings dialogs/pages: `settings.<dialog>.*`\n- BitTorrent settings and tracker defaults: `settings.bittorrent.*`\n- Tracker management: `trackers.effective.*`, `trackers.blacklist.*`,\n  `trackers.combobox.*`, and `trackers.sync.*`\n\nExtend the relevant namespace rather than creating a parallel spelling or\nplacing tracker UI under settings merely because trackers are configurable.\n\n## Marketplace locale independence\n\nApplication locales and registry listing locales are separate contracts.\n`SupportedLocale` must not constrain `listing.defaultLocale` or keys in the\nregistry's open BCP 47 `listing.localizations` map. Resolve marketplace display\nand search text with the vendored registry resolver; do not use\n`resolveSupportedLocale` or infer sibling regions such as `zh-TW -> zh-CN`.\n\n## Verification\n\nRun `pnpm run check:i18n` after any locale, catalog, placeholder, plural, or\nresource-registration change. It verifies file/catalog parity, logical key\ncoverage, plural categories, string leaves, and placeholder parity. Its tests\nmust use temporary fixtures and never rewrite real locale files. Tests that\nrender translated components load the real renderer initialization with:\n\n```ts\nimport '@renderer/lib/i18n'\n```\n\n<!-- File: .claude/rules/language-and-docs.md (Tokens: ~615 | Category: anthropic-skill) -->\n---\ndescription: Language, bilingual public docs, and private-context boundaries\n---\n\n# Language & Documentation\n\n## Language\n\n- Code, comments, commits, PR titles, branches, file names, and identifiers are\n  English.\n- Respond to users in the language they use.\n\n## Public Documentation\n\n- New end-user guides and documents with an existing translated counterpart\n  ship as English `<name>.md` and Chinese `<name>.zh-CN.md` pairs.\n- Governance files, generated artifacts, and subtree/developer READMEs may stay\n  single-language unless maintainers establish a pair.\n- When editing one file in a pair, update the other in the same change. Keep\n  headings, commands, paths, and code samples aligned while writing idiomatic\n  prose in each language.\n- `docs/` contains reviewed public artifacts, not private design material.\n\n## Public/Private Boundary\n\n- Machine-specific agent context belongs in ignored `CLAUDE.local.md`.\n- Never commit private plans, prompts, handoffs, credentials, private\n  application or vault names, or absolute local paths.\n- Public contribution workflows must work from a normal checkout without a\n  private account, application, or adjacent repository.\n- Rewrite private source material as a standalone public artifact and review\n  it before committing.\n\n## Maintainer Documentation\n\n- Maintainer operation manuals, deployment runbooks, internal specifications,\n  and implementation plans belong in Obsidian by default. A request to prepare\n  one does not authorize adding it to the repository, committing it, or\n  pushing it. Put it in the repository only when the user explicitly requests\n  a public repository document.\n- Use the project's `pnpm docs` gateway for these documents. Invoke it as\n  `pnpm run docs -- <command>` (or the `docs:*` scripts), because bare\n  `pnpm docs` can resolve to pnpm's built-in package-documentation command.\n  Start with `pnpm run docs -- help` and `pnpm run docs -- doctor`.\n- Resolve the destination from the ignored `obsidian-docs.config.json`. In a\n  new worktree, use `--config <existing-local-config>` when a configured\n  checkout is available. Keep vault names and machine paths out of tracked\n  rules and public documentation.\n- Create documents through `pnpm run docs -- create <directory> <name.md>\n  --from <temporary-file>` using a source file outside the repository. Check\n  existing notes before replacing content; use `--overwrite` only for the\n  intended update. Do not substitute raw vault filesystem writes or UI\n  automation for this gateway.\n- Read back saved content and check code blocks and literal escape sequences;\n  a successful create command alone does not prove the manual is intact.\n- If the gateway or configuration is unavailable, report the specific blocker\n  and retain a temporary copy outside the repository. Do not fall back to\n  placing the manual in `docs/` or publishing it in a PR.\n\n<!-- File: .claude/rules/panel-layout.md (Tokens: ~491 | Category: anthropic-skill) -->\n---\ndescription: Height-chain and sticky-scroll rules for panels inside SidebarInset\npaths: [\"src/renderer/routes/**/*.tsx\", \"src/renderer/layouts/app-layout*.tsx\", \"src/renderer/components/ui/sidebar*.tsx\", \"src/renderer/components/desktop-kit/panel/**\", \"src/renderer/components/settings-kit/**/*.tsx\"]\n---\n\n# Panel Layout\n\nPages inside `SidebarInset` stay within the viewport. The document and panel\nshell do not scroll; the innermost data region owns scrolling.\n\n## Height chain\n\nEvery flex ancestor between the viewport clamp and the data region must allow\nits child to shrink:\n\n```text\nSidebarProvider  h-svh overflow-hidden\n  SidebarInset   flex-1 min-h-0 overflow-hidden\n    PanelShell   h-full flex flex-col\n      content    flex-1 min-h-0 flex flex-col\n        Tabs     flex-1 min-h-0 flex flex-col\n          list   shrink-0\n          content flex min-h-0 flex-1\n            panel flex min-h-0 flex-1 flex-col\n              fixed regions shrink-0\n              data region flex min-h-0 flex-1 overflow-auto\n```\n\n`flex-1` without `min-h-0` is insufficient because the default intrinsic\nminimum height lets content grow the page.\n\n## Scrolling and sticky content\n\n- The chain has exactly one `overflow-auto`: the leaf data region. Ancestors\n  use `overflow-hidden` where a clamp is needed; do not make `PanelShell`,\n  `Tabs`, or `TabsContent` a competing scroller.\n- Non-scrolling headers, filters, summaries, tab lists, and footers use\n  `shrink-0`.\n- A sticky column header is the first child inside the scroll region so\n  `sticky top-0` resolves against the intended scroller.\n- Sticky content needs an opaque background, `z-10`, and `border-b`; a separate\n  separator sibling scrolls away and does not preserve the boundary.\n- Under tabs, keep `TabsContent` as `flex min-h-0 flex-1` and its panel as\n  `flex min-h-0 flex-1 flex-col` so both axes remain bounded.\n\nWhen changing a panel, verify that the page has no document scrollbar, only the\ndata region scrolls, sticky headers remain readable, and the inset/footer stay\nvisible at short viewport heights.\n\n<!-- File: .claude/rules/plugin-registry.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/rules/plugin-registry.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugin-registry.md)\n\n<!-- File: .claude/rules/plugins.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/rules/plugins.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/plugins.md)\n\n<!-- File: .claude/rules/renderer.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/rules/renderer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/.claude/rules/renderer.md)\n\n<!-- File: packages/native-host/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Native-host Skill\n\nPath: `packages/native-host/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/package.json)\n\n<!-- File: packages/native-host/README.zh-CN.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Native-host Skill\n\nPath: `packages/native-host/README.zh-CN.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.zh-CN.md)\n\n<!-- File: tests/fixtures/plugins/test.allcaps/locales/en-US.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Locales Skill\n\nPath: `tests/fixtures/plugins/test.allcaps/locales/en-US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/locales/en-US.json)\n\n<!-- File: CLAUDE.md (Tokens: ~638 | Category: claude-rule) -->\n# CLAUDE.md\n\n## Project\n\nMotrix Turbo is an Electron and Node/Web download manager. Keep `src/core/`\nhost-neutral so it can be reused by both shells and replaced independently.\n\nDevelopment happens on **`main`**. The frozen **`master`** branch is the legacy\nElectron 22/Vue 2 application. Branch from and target PRs at `main`, never\n`master`.\n\n## Core Commands\n\n```bash\npnpm start                         # Electron development runner\npnpm start:server                  # Built Node/Web server\npnpm test                          # Vitest suite\npnpm exec vitest run <test-path>   # Focused test\npnpm run lint                      # Biome over the repository\npnpm exec tsc --noEmit             # Type-check\npnpm build                         # Desktop production build\npnpm build:server                  # Node/Web production build\npnpm test:e2e                      # Playwright suite\n```\n\nUse `pnpm exec`, not `npx`. The authoritative commit gate is\n`.claude/rules/commit-and-quality.md`.\n\n## Architecture Boundaries\n\n1. `src/core/` never imports `electron` or `src/main/`.\n2. `src/renderer/` never imports `src/core/`, `src/main/`, or `src/server/`;\n   use `@renderer/lib/transport` with shared protocol constants.\n3. `src/shared/` contains pure cross-layer contracts and descriptive runtime\n   data only: no IO, timers, network, Electron, or Node-specific APIs.\n4. Use `Commands`, `Queries`, `Events`, and their `Bridge*` counterparts from\n   `src/shared/protocol/`; never use raw transport channel strings.\n\n## Rule Routing\n\nRules without `paths` frontmatter are global. Load path-scoped rules only when\ntheir patterns match the files under inspection or modification.\n\n| Rule | Scope |\n|---|---|\n| `commit-and-quality.md` | Required checks and conditional validation |\n| `git-workflow.md` | Commits, branches, PRs, and releases |\n| `language-and-docs.md` | Language and public/private documentation |\n| `browser-artifacts.md` | Browser test output paths and temporary UI verification files |\n| `architecture.md` | Layer boundaries and transport flow |\n| `electron-vite.md` | Build, packaging, native ABI, and pnpm |\n| `code-style.md` | TypeScript, React, CSS, and file naming |\n| `renderer.md` | Renderer state, components, forms, and transport |\n| `icons.md` | Semantic icon imports, library isolation, and tree shaking |\n| `panel-layout.md` | Viewport height and scrolling layout |\n| `i18n.md` | Locale catalogs and user-visible strings |\n| `domain-model.md` | Shared domain types, validation, and errors |\n| `plugins.md` | Plugin sandbox, capabilities, and builtins |\n| `plugin-registry.md` | Registry compatibility and install integrity |\n| `bridge.md` | MDXP pairing, dispatch, and transports |\n\n<!-- File: packages/native-host/README.md (Tokens: ~300 | Category: plugin-manifest) -->\n# native-host Documentation\n\nPath: `packages/native-host/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/packages/native-host/README.md)\n\n<!-- File: tests/fixtures/plugins/test.allcaps/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.allcaps Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.allcaps/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.allcaps/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.audit-band/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.audit-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.audit-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.audit-band/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.echo/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.echo Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.echo/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.echo/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.enrich-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band-throws/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.enrich-band/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.enrich-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.enrich-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.enrich-band/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.resolve-band-throws Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band-throws/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.resolve-band/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.resolve-band Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.resolve-band/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.resolve-band/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.toplevel-http Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-http/motrix-plugin.json)\n\n<!-- File: tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# test.toplevel-register-only Plugin Manifest\n\nPath: `tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/agalwood/Motrix/HEAD/tests/fixtures/plugins/test.toplevel-register-only/motrix-plugin.json)\n</yakaai_skills>"}