Use when working on Escrcpy/escrcpy-x Electron Vue scrcpy code: architecture, renderer/main IPC, wscrcpy, Element Plus, VueUse, Floating UI, Tango ADB, i18n, UnoCSS, validation.
Use when working on Escrcpy/escrcpy-x Electron Vue scrcpy code: architecture, renderer/main IPC, wscrcpy, Element Plus, VueUse, Floating UI, Tango ADB, i18n, UnoCSS, validation.
argument-hint
Describe the Escrcpy code change, review, or debugging task
Escrcpy - AI Code Generation Guidelines
Skill Objective
Automatically constrain AI-assisted code generation for Escrcpy to match the project's existing style, architecture, and conventions. Generated code should prioritize reusing existing modules, aliases, IPC, stores, i18n, UnoCSS, and workspace package boundaries, such that a developer familiar with the project cannot clearly distinguish human-written from AI-generated code.
0. Analysis Basis
These rules are grounded in real file reads, not assumptions. Core reference files:
AGENTS.md: monorepo, windows, IPC, i18n, naming, and validation command constraints.
develop.md, README.md: project positioning, Electron + Vue + scrcpy stack, and contribution workflow.
desktop/vite.config.js: multi-renderer entry points and alias system.
desktop/electron/main.js: createElectronApp() and plugin-based main process via mainApp.use().
desktop/unocss.config.js: UnoCSS preset, icons, shades, typography, and shortcuts.
packages/electron-setup/README.md, packages/electron-setup/main/*.ts: plugins, windows, dependency injection, and dispose mechanism.
Use ipcxMain / ipcxRenderer when renderer callbacks are needed
desktop/electron/modules/terminal/service.js: payload includes onData, onExit, onError; desktop/electron/middleware/terminal/index.js: ipcxRenderer.invokeRetained(...)
Preload boundary
All renderer exposure centralized in createMiddleware() via $preload
desktop/electron/middleware/index.js:
On-Demand Context References
Use context/** as local, version-aware reference material before guessing third-party APIs. Load only the document that matches the current task, then reconcile it with existing project code and desktop/package.json versions.
Task Area
Read First
Use When
Element Plus UI
context/element-plus.md
Choosing Element Plus components, props, slots, icons, messages, dialogs, form/table behavior, or confirming component API details.
VueUse composables
context/vue-use.md
Using or reviewing VueUse helpers, lifecycle utilities, watchers, DOM/event composables, and auto-imported composables.
If these docs conflict with nearby repository code, treat repository code as the source of truth and mention the mismatch during review.
1. Architectural Constraints
The following rules are hard constraints. Violations are considered code quality failures.
A. Renderer Process Boundaries
Each renderer window (main / control / explorer / copilot / terminal / automation / mirror) is an independent Vite entry. Do not directly import another window's entry or private page resources across window boundaries.
desktop/src is the main window and shared renderer resources; desktop/pages/<window> contains window-private resources.
Renderer code must not directly import Electron main process modules, Node main process services, or desktop/electron business implementations.
Renderer-to-main communication must go through interfaces exposed via window.$preload, window.$preload.ipcRenderer, or wrapped IPC channels.
Business capabilities shared across multiple windows should be extracted to desktop/src/hooks, desktop/src/utils, desktop/src/components, or a workspace package. Do not reverse-import from one window directory into another.
B. IPC Selection Rules
Simple request-response: use window.$preload.ipcRenderer.invoke(...) / ipcMain.handle(...).
Event notifications: use window.$preload.ipcRenderer.send/on(...), keeping channel names clear and cleanable.
Passing callbacks/functions across the process boundary: use ipcxRenderer.invoke / ipcxRenderer.invokeRetained and ipcxMain.handle from @escrcpy/electron-ipcx.
All main-process handlers must be registered inside the module/service/plugin apply() and must removeHandler or release resources in the returned dispose function.
Do not add ad-hoc global exposures in preload that bypass createMiddleware(). Do not leak raw main-process objects to the renderer.
C. Main Process Extension Rules
New features must be registered as @escrcpy/electron-setup plugins, modules, or services.
desktop/electron/main.js is responsible only for importing and assembling via mainApp.use(...). Business logic does not belong here.
Window capabilities use createWindowManager(...); window lifecycle logic goes in hooks.
Do not put business logic in preload. Preload is for bridging and middleware exposure only.
Do not hardcode platform paths. scrcpy, adb, and gnirehtet paths must be resolved through desktop/electron/configs/which/ and whichResolve().
D. Package Boundary Rules
packages/wscrcpy maintains core contracts including WscrcpySession, ScrcpyConnectionManager, DeviceTarget, and SCRCPY_CHANNELS. The desktop layer must not override or bypass these.
Shared types from packages/wscrcpy must come from shared/types.ts; channel names must come from shared/channels.ts. Avoid scattering literal strings and interfaces.
Workspace packages use TypeScript with explicit types; the desktop app body uses JavaScript + JSDoc. Do not mass-migrate desktop to TS, and do not downgrade TS packages to loose JS style.
Native dependencies and core build dependencies (Electron, sharp, tsdown, TypeScript, Vite) are pinned/overridden centrally in pnpm-workspace.yaml. Do not override them in individual packages.
E. Build and Style Rules
Turbo disables cache for Electron-related builds. Do not assume build artifacts are incremental or cache-hit.
Styling uses existing UnoCSS utilities, @escrcpy/unocss-preset-shades, UnoCSS icons, Element Plus, and project-local PostCSS. Do not introduce new CSS-in-JS, Tailwind configs, or UI component libraries.
Element Plus is the existing UI infrastructure and may be reused. Do not introduce Ant Design, Naive UI, shadcn, or other new UI systems.
New desktop SFCs default to Vue 3 Composition API + <script setup>.
Desktop .vue files primarily use JavaScript SFC style. defineProps({ ... }) and defineEmits([...]) use runtime object/array syntax. Do not write defineProps<{ ... }>() in JS SFCs.
ref, computed, watch, nextTick, Pinia stores, VueUse, and router are typically auto-imported and do not require manual imports, sourced from desktop/src/plugins/internal.js.
Use $t('key') in templates and window.t('key') in scripts. Do not assume bare t() is auto-imported.
Split components by responsibility. Reusable components go in components/<feature>/index.vue; window-private components go in desktop/pages/<window>/components.
Minor edits to existing Options API components may retain their style. New components should use <script setup>.
Use setup stores: defineStore('app-domain', () => { ... }).
State via ref; derived values via computed; actions are plain or async functions.
Persistent business state may use pinia-plugin-persistedstate's persist config. Electron config, device config, and window-shared persistent data go through window.$preload.store.
When syncing with electron-store, follow the get/set/onDidChange pattern and update local refs in watchers.
When updating arrays or objects, prefer creating new arrays/objects to maintain the immutable update style found in existing stores.
Channel naming should follow existing domain prefix conventions: mirror-*, terminal:*, scrcpy:*, show-*, etc. wscrcpy must reuse SCRCPY_CHANNELS.
Return values should follow existing project formats: simple handlers may return data directly; complex services may return { success, error } or { ok, error }, but be consistent within a domain.
When using invokeRetained for renderer callback lifecycles, the caller must save and call dispose().
Plugin names follow the pattern module:<name>, module:<name>:window, module:<name>:service, or service:<name>.
When apply() returns a function, that function is the dispose handler. It must clean up IPC handlers, sessions, watchers, processes, and event listeners.
Path Alias Conventions
// renderer sharedimportAppHeaderfrom'$/components/app-header/index.vue'import { sleep } from'$/utils/index.js'// root metadataimport { name as packageName } from'$root/package.json'// main process onlyimport { getAdbPath } from'$electron/configs/index.js'// window-private resourceimport { ApiModelEnum } from'$copilot/dicts/api.js'
$ / $renderer points to desktop/src for shared renderer code.
$root points to the desktop package root, not the monorepo root. Confirm the target before reaching for cross-layer package metadata.
$electron is for the Electron main/preload/middleware side only.
$control, $explorer, $copilot, $terminal, $automation, $mirror are for the corresponding window's private resources or configurations that window explicitly owns.
Avoid deep ../../../ relative paths. Prefer Vite aliases.
UI interactions should use async/await + try/catch/finally to manage loading state.
Main process services should catch errors with domain-prefixed logging and return a domain-consistent error structure or re-throw.
Silent catches are not permitted. User-visible failures must be surfaced via ElMessage, ElMessageBox, or the existing message/loading mechanism.
wscrcpy connection lifecycle must be serialized, following the enqueueDeviceOp / enqueue pattern to prevent concurrent start/stop/restart collisions.
Special rule for scrcpy middleware thenables: do not resolve with the scrcpy process object. That object is wrapped with Object.assign(..., { then }), so resolving with it will trigger Promise adoption and may cause hangs. Resolve with plain data or undefined.
TypeScript / JSDoc Conventions
desktop/ uses JS + JSDoc as the primary style. Complex functions, stores, and payloads may use JSDoc to document intent and parameters.
packages/wscrcpy, packages/electron-setup, and packages/electron-ipcx use TypeScript types, interfaces, type aliases, and exported contracts.
When adding new wscrcpy capabilities, update shared/types.ts / shared/channels.ts first, then update the service, hook, and component surface.
Do not add widespread strict TS assertions or .ts migrations to desktop unless the task explicitly requires it and the impact is controlled.
3. Best Practices
Reuse-First Principle
Before generating any new code, search desktop/src, desktop/pages/<window>, desktop/electron, and packages for similar existing implementations.
For hooks/composables, check desktop/src/hooks, window-private hooks, and packages/wscrcpy/hooks first.
For utilities, check desktop/src/utils, desktop/electron/helpers, and packages/wscrcpy/shared first.
For IPC channels and wscrcpy types, reuse constants and shared types. Do not copy strings and interfaces.
For UI interactions, reuse Element Plus, project plugins, and existing loading/message/dialog patterns.
When a task depends on third-party API details, read the matching context/** document before implementing; do not invent API shape from memory.
Modular Design Principles
New main-process features are implemented as plugin/module/service with an apply() lifecycle.
Renderer logic is split by window responsibility. Do not implement control/explorer/terminal/mirror private logic inside the main window.
Extract shared business logic to desktop/src or a workspace package. Do not copy-paste repeated logic across windows.
Long-lived resources (processes, sessions, watchers, IPC listeners) must have a clear owner and cleanup path.
Windows/Linux/macOS branching should be centralized in helpers/configs/window hooks, not scattered across UI templates.
Performance Guidelines
Large lists, file trees, terminals, and mirror streams must avoid unnecessary deep-reactive objects and template side effects.
Cache expensive computations with computed. Do not call methods with side effects in templates.
Multi-device operations should use concurrency limits or queues, following existing patterns with p-limit, deviceQueues, and Promise.allSettled.
wscrcpy video/audio/control streams must respect session ownership. Stop sessions owned by a window when that window is destroyed.
Audio stream handling should be conservative: on Windows, clipboardAutosync may affect stability in audio + control scenarios. Do not change the default strategy casually.
Documentation and Comment Conventions
Comments explain business intent, lifecycle constraints, or protocol semantics — not line-by-line code narration.
Public IPC channels, preload surface, and wscrcpy public controllers should have JSDoc or centralized constant comments.
Use JSDoc for complex desktop JS; use the type system for package-level contracts in TS packages.
When modifying user-facing documentation or locale keys, check whether README*.md, docs/, or locale files also need updating.
4. Anti-Patterns
The following patterns should be actively avoided in Escrcpy:
❌ Importing Electron main process modules directly in renderer
❌ Directly importing another window's private resources from one independent window directory
❌ Adding ad-hoc global bridges outside preload, or exposing raw APIs not organized through middleware
❌ Registering ipcMain.handle at the top level or outside a module, without removeHandler
❌ Hardcoding platform paths like /usr/local/bin/adb or scrcpy.exe
❌ Adding new Vue Options API components as the default approach
❌ Writing TypeScript generic props/emits in desktop JS SFCs
❌ Introducing new CSS frameworks, CSS-in-JS, or UI component libraries
❌ Adding user-visible hardcoded Chinese or English strings
❌ Bypassing Pinia actions or store methods to directly mutate shared persistent state
❌ Bypassing packages/wscrcpy contracts: WscrcpySession, DeviceTarget, SCRCPY_CHANNELS
❌ Passing the scrcpy process object to a Promise resolve or deferred resolve
❌ Mass-migrating desktop to strict TS
❌ Overriding workspace-pinned dependencies (Electron, sharp, Vite, TypeScript, etc.) in individual packages
❌ Naming new directories or plain files in camelCase/PascalCase, unless extending an existing public API file with an established naming pattern
5. Code Review Checklist
After AI generates code, verify each item before committing:
Architecture
Renderer/main process boundaries are clean with no cross-boundary imports?
New windows or main process capabilities properly wired via Vite input, module, service, or window manager?
IPC uses the correct mechanism: plain invoke, event send/on, or ipcx callback?
IPC handlers registered inside apply() and cleaned up in dispose?
Binary paths resolved through desktop/electron/configs/which or resources helper?
wscrcpy changes respect session ownership, DeviceTarget, and SCRCPY_CHANNELS?
Code Style
New desktop Vue components use <script setup> + Composition API?
Desktop JS SFCs use runtime defineProps({}) / defineEmits([])?
No unnecessary import { ref, computed } from 'vue' (desktop auto-import typically handles this)?
Paths use correct aliases, avoiding deep relative paths?
File/directory names follow kebab-case, or have a clear existing API naming exception?
No silent catches, bare debug console logs, or failures without user feedback?
Engineering Standards
New i18n keys synced to at least zh-CN.json and en-US.json?
No reimplementation of existing hooks, stores, helpers, IPC channels, or wscrcpy types?
Element Plus, UnoCSS preset, and project message/loading/dialog patterns reused?
Multi-device, file transfer, terminal, and scrcpy stream logic reusing existing queue, callback, and cleanup patterns?
Relevant context/** docs consulted for Element Plus, VueUse, Floating UI, or Tango ADB API details when those areas changed?
6. Agent Workflow
When an agent receives a development task for Escrcpy, follow this standard workflow:
STEP 1 — Understand the Task
├── Read AGENTS.md and this file; confirm scope: desktop/src, desktop/pages, desktop/electron, packages
├── If third-party APIs are involved, load the matching local context doc from context/**
├── Identify target window: main/control/explorer/copilot/terminal/automation/mirror
├── Determine whether this is renderer, preload, main-process module/service, or workspace package
└── Determine if i18n, IPC, binary paths, wscrcpy session, or dependency versions are involved
STEP 2 — Explore Existing Code
├── Search relevant directories for reusable components, hooks, stores, helpers, channels, and types
├── Read the current implementation of affected modules — do not generate from memory
├── Cross-check local context docs against nearby project usage and package versions
├── For cross-process communication, confirm domain channel naming and cleanup patterns
└── For UI, confirm existing Element Plus + UnoCSS visual patterns
STEP 3 — Implement
├── Strictly follow the Code Generation Guidelines
├── Reuse existing infrastructure; do not reinvent the wheel
├── For i18n changes, sync zh-CN + en-US and run lang-sync
├── Wire new main-process capabilities as module/service/plugin
└── For wscrcpy additions, update shared contract first, then service/hook/component
STEP 4 — Self-Review
├── Go through the Review Checklist item by item
├── Proactively flag any compromise that deviates from existing patterns
└── Do not touch unrelated dirty worktree; do not revert user changes
STEP 5 — Validate
├── Regular renderer/desktop changes → pnpm lint
├── wscrcpy changes → pnpm exec tsc -p packages/wscrcpy/tsconfig.json --pretty false
├── i18n changes → pnpm lang-sync
├── Electron main/preload/Vite/dependency changes → pnpm build
└── Packaging/release → pnpm build:win | pnpm build:mac | pnpm build:linux
7. Skill Evolution Notes
Conditions That Trigger an Update
A new renderer window entry is added or desktop/vite.config.js inputs/aliases change.
A new workspace package is introduced or package boundaries shift.
UnoCSS preset, Element Plus plugins, or global style strategy changes.
Major version upgrades to Electron, Vue, Vite, Pinia, UnoCSS, or scrcpy/yume-chan.
Auto-import symbols, global plugins, or preload surface is added or removed.
New IPC channel naming conventions or wscrcpy channel constants are established.
New cross-team code style consensus is reached or old patterns are deprecated.
Update Process
Re-run the analysis process for this file: read architecture files, sample store/views/modules/wscrcpy, compare architectural changes in git diff, update the relevant sections, and update the last_analyzed commit hash.
Conflicting Style Notes
2026-05-28: Desktop contains a mix of legacy Options API components and <script setup>. New components use <script setup>; minor edits to old components may maintain local consistency.
2026-05-28: Desktop plain files/directories follow kebab-case. packages/wscrcpy existing public hook files like useWscrcpyConnection.ts use camel/PascalCase API naming. New desktop files continue kebab-case; extensions to wscrcpy public hooks may follow that package's existing export naming.
2026-05-28: Template suggestions showing defineProps<{ ... }>() do not match current desktop JS SFC conventions. Desktop uses runtime props/emits; TypeScript generics belong in .ts packages or explicitly typed TS SFCs.