# 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 (`