Files
electron-opencode/.junie/AGENTS.md

134 lines
5.0 KiB
Markdown

# Zhiju AI Assistant — Developer Notes
## Project Overview
Electron desktop app (Electron Forge + Vite + Vue 3) that wraps a locally-spawned `opencode` binary (located in `resources/`). The renderer is a Vue 3 SPA; the main process manages the `opencode` child process and exposes IPC to the renderer via a preload script.
---
## Build & Configuration
### Prerequisites
- Node.js ≥ 18
- The `opencode` binary must exist at `resources/windows/x64/opencode.exe` (Windows) before packaging. It is **not** committed to the repo — obtain it separately.
### Install dependencies
```bash
npm install
```
### Run in development
```bash
npm start
```
This uses `electron-forge start`, which runs Vite for the renderer and launches Electron. Hot-reload is active for the renderer; changes to `src/main/index.js` or `src/preload/index.js` require a manual restart.
### Package / distribute
```bash
npm run make # builds installers for the current platform
npm run package # produces an unpackaged app directory
```
Packaged output lands in `out/`. The `opencode.exe` binary is bundled via `extraResource` in `forge.config.js` and unpacked from ASAR at runtime.
### Vite configs
| File | Purpose |
|---|---|
| `vite.main.config.mjs` | Main process bundle |
| `vite.preload.config.mjs` | Preload script bundle |
| `vite.renderer.config.mjs` | Renderer (Vue SPA), alias `@``src/renderer` |
---
## Architecture Notes
- **Main process** (`src/main/index.js`): spawns `opencode` binary, resolves a free port starting at `4096`, waits for TCP readiness, then injects `window.__opencodeBaseUrl` into the renderer via `executeJavaScript`. Also runs Bonjour service discovery.
- **Preload** (`src/preload/index.js`): bridges IPC between main and renderer using `contextBridge`.
- **Renderer** (`src/renderer/`): Vue 3 + Pinia + Vue Router. HTTP calls go through `src/renderer/http/` (axios-based). SSE streaming is handled in `src/renderer/http/sse.js`.
- **URL constants** (`src/renderer/http/url.js`): single source of truth for all API endpoint paths. `getBaseUrl()` reads `window.__opencodeBaseUrl` (injected by main) with fallback to `http://127.0.0.1:4096`.
- **Crypto** (`src/renderer/utils/crypto.js`): passwords are Base64-encoded then RSA-encrypted (public key hardcoded) before being sent to the login API.
---
## Testing
### Framework
[Vitest](https://vitest.dev/) — chosen for native Vite/ESM compatibility, zero extra config needed.
### Run all tests
```bash
npm test # vitest run (single pass, CI-friendly)
npm run test:watch # vitest watch mode (development)
```
Or directly:
```bash
npx vitest run
```
### Writing tests
- Place test files alongside the source file as `*.test.js` (Vitest picks them up automatically).
- For pure utility/logic modules (e.g. `url.js`, `crypto.js`) no additional setup is needed.
- Modules that depend on `window`, `electron`, or Pinia require mocking. Use `vi.stubGlobal` for `window` properties and `vi.mock` for module mocks.
### Example: testing `url.js`
```js
// src/renderer/http/url.test.js
import { describe, it, expect } from 'vitest';
import url from './url.js';
describe('url constants', () => {
it('session.detail returns correct path', () => {
expect(url.session.detail('abc123')).toBe('/session/abc123');
});
it('message.send returns correct path', () => {
expect(url.message.send('sess1')).toBe('/session/sess1/message');
});
});
```
Run with:
```bash
npx vitest run src/renderer/http/url.test.js
```
### Testing modules that use `window.__opencodeBaseUrl`
```js
import { vi, describe, it, expect, beforeEach } from 'vitest';
beforeEach(() => {
vi.stubGlobal('__opencodeBaseUrl', 'http://127.0.0.1:5000');
});
// import and test getBaseUrl() etc.
```
---
## Code Style
- **Formatter**: Prettier (config in `package.json` defaults). Run `npm run format` to auto-fix, `npm run format:check` for CI check.
- **Commit messages**: enforced by commitlint (`@commitlint/config-conventional`). Use conventional commits: `feat:`, `fix:`, `chore:`, etc.
- **Pre-commit hook**: husky + lint-staged runs Prettier on staged `*.js` and `*.vue` files automatically.
- **No ESLint** is configured — only Prettier for formatting.
- Vue components use the Composition API (`<script setup>` style in newer components, options-style `setup()` returning refs in stores).
- Pinia stores use the Setup Store pattern (function returning refs/actions), not the Options Store pattern.
- Chinese comments are common throughout the codebase — maintain them in Chinese when editing existing files.
---
## Key Dependencies
| Package | Role |
|---|---|
| `electron` v41 | Desktop shell |
| `electron-forge` v7 | Build/package toolchain |
| `vite` v5 + `@vitejs/plugin-vue` | Renderer bundler |
| `vue` v3 + `pinia` + `vue-router` | UI framework |
| `axios` | HTTP client (renderer) |
| `element-plus` | UI component library |
| `bonjour-service` | LAN service discovery |
| `jsencrypt` + `js-base64` | Password encryption before login |
| `unified` / `remark` / `rehype` | Markdown rendering pipeline |
| `katex` | Math formula rendering in chat |