# Repository Guidelines ## Project Structure & Module Organization PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` for main-process and IPC logic, `src/renderer` for Vue views, and `src/universal` for shared helpers (`types/`, `events/constants.ts`). `background.ts` wires Electron Builder. Static assets and locale YAML files stay in `public/` (add languages under `public/i18n/`), while `docs/` hosts user-facing guides. Automation scripts live in `scripts/`, and legacy tests sit under `test/unit` (Karma) and `test/e2e` (Spectron). ## Build, Test, and Development Commands - `pnpm install` — install dependencies; `npm install` is unsupported. Only run this when the user explicitly asks/coordinates it. - Always add/remove dependencies with `pnpm` (never edit package.json versions by hand then install). - `pnpm dev` — electron-vite dev server for main/preload/renderer. - `pnpm build` — electron-vite build outputs to `dist/main`, `dist/preload`, `dist/renderer`; `pnpm preview` for preview mode. - Packaging config lives in `electron-builder.yml` (read by electron-builder via package.json `build` field/extraResources); set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` if downloads are slow. - `pnpm lint` / `pnpm lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules). - `pnpm lint:dpdm` — fail fast on circular dependencies in `src/`. - `pnpm check` — run `tsc` + `lint` (run once before finishing a task). - Before completing a task, always run `pnpm check` and resolve any issues it reports. - i18n type files are auto-generated by the Vite `i18nTypesPlugin` when `public/i18n/*.yml` changes. Do not add or rely on a manual `gen-i18n` step. ## Coding Style & Naming Conventions Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable. Static assets are served from `public/` and resolved via `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`); avoid using `__static` directly. Static assets are served from `public/`. In the main process use `getStaticPath`/`getStaticFileUrl` (`src/universal/utils/staticPath.ts`). In the renderer, place assets under `public/` and resolve them via `import.meta.env.BASE_URL + filename` (helper: `src/renderer/utils/static.ts`); do not rely on `__static` in renderer code. - Do not use `as any` under any circumstances; keep typings explicit and safe. - Avoid `as any` in tests as well; build concrete typed stubs (e.g., `IpcMainInvokeEvent`) instead. - Do not prefix method calls with `void` (e.g. use `store?.refreshPicBeds()` rather than `void store?.refreshPicBeds()`). - Do not write `void someMethod()` or `void object.method()` anywhere in the codebase. If you need fire-and-forget behavior, use an `async` callback with `await`, or handle errors explicitly with `try/catch` and logging instead of swallowing them. - Because the renderer uses React Compiler, do not add `useMemo` or `useCallback` by default. Prefer plain values and inline functions unless a specific API requires stable identity or there is a proven performance issue. - For Zustand store state/actions, prefer reading them directly in the consuming component with `useAppStore` / `useStore` instead of passing them down through unnecessary prop layers. If a child can access the needed store value or action itself, do not thread it through parent props. - If a child component only needs a store action (for example `providerStoreActions.toggleExpanded`, `settingsStoreActions.setSearchValue`, or `galleryStoreActions.setViewMode`), do not pass that store action through props. Import and use the action directly inside the child component. - Renderer Zustand stores must follow the project store architecture: - `src/renderer/store/app-store.ts` is for true global renderer state only. - Feature/page-local UI state must live under `src/renderer/store//` (for example `src/renderer/store/gallery/store.ts`, `src/renderer/store/gallery/actions.ts`), not inside `app-store.ts`. - Do not add an extra nested `store/` directory like `src/renderer/store/gallery/store/store.ts`. - Zustand state and actions must be separated: - Do not define state-mutating actions inside `create()`. - Keep store files focused on state shape and initial state. - Put global actions in `src/renderer/store/app-actions.ts`. - Put feature actions in `src/renderer/store//actions.ts`. - Actions must update state via `useXxxStore.setState(...)`. - Follow strict IPC boundaries for Zustand actions: - Pure IPC/service calls without Zustand state changes should call the adapter/service directly from the component or helper, not through a Zustand action. - Flows that combine IPC/service work with Zustand state updates must live in actions files. - Keep server state and client state separated: - TanStack Query should own server state: remote API data, loading/error/stale status, refetching, cache, and request dedupe. - Zustand should own client/UI state: selected source, selected ids, filters, view mode, panel open state, and other local user intent. - Connect the two with ids, query params, and local UI state (for example `albumSource`, `typeFilter`, or `searchValue`), but do not mirror query response data back into Zustand. - If server state invalidates a local UI choice (for example a user becomes non-paid while `albumSource` is cloud), use a small effect/action to correct the local UI state instead of storing the whole server response in Zustand. - When updating nested Zustand state (especially config-like objects), use `zustand/middleware/immer`; do not reintroduce deep `...state` spread chains for nested updates. - Components must consume Zustand state through auto-generated selectors (for example `useAppStore.use.appConfig()`), not by destructuring the whole store or writing ad-hoc hook selectors in components. - Renderer-side shared constants (for example responsive breakpoints, UI timing values, fixed dimensions, thresholds, and repeated literal values used across components) should be centralized in `src/renderer/utils/consts.ts` instead of being hardcoded inline in components. When a new renderer constant may be reused or affects shared behavior, add it there first. - Renderer-side date/time formatting should use `dayjs` and shared format constants from `src/renderer/utils/consts.ts` (for example `DEFAULT_DATE_TIME_FORMAT`) instead of `Intl.DateTimeFormat` or ad-hoc inline format strings. - Enum-like object constants declared with `as const` (for example status maps, option maps, and value registries) must use PascalCase names, not camelCase. Prefer names like `PicGoCloudRequestStatusValues`, `SettingsAppearanceValues`, or `AppPlatformValues`. - If a renderer → main request mutates persisted config/state without using `saveConfig`, call `notifyAppConfigUpdated()` in main to inform renderers. - Prefer enums over union types for discrete value sets (e.g., encryption methods). Avoid introducing new string literal union types. - Renderer page/component styles should prefer Tailwind utility classes; avoid adding new Vue `