High-signal notes for OpenCode sessions working in this repo. Rockoon is a Ballance game launcher: Vue 3 + TypeScript frontend, Tauri 2.0 (Rust) backend, and a C++ BMLPlus native module. Windows-only. Deeper architecture notes live in CLAUDE.md.
pnpm install # must be pnpm (not npm/yarn)
pnpm tauri dev # full dev: runs `pnpm dev` (vite :1420) + Rust backend. RUST_LOG=info preset.
pnpm dev # frontend-only (vite, no backend)
pnpm build # UI + C++ module (runs build:ui then build:bmodp)
pnpm build:ui # vue-tsc --noEmit + vite build (type-check gates the build)
pnpm build:bmodp # C++ module via src-bmodp/build.ps1
pnpm tauri build # release bundle (nsis installer + updater artifacts)
pnpm lint # eslint + prettier + stylelint (all with --fix)
pnpm icon # regenerate app icons from public/logo.pngNo test suite exists. Do not invent test commands; verify changes by running the app or pnpm build:ui (type-check).
- Git submodule:
src-bmodp/3rd-party/Virtools-SDK-2.1is a submodule. Clone withgit submodule update --init --recursivebefore any C++ build. - BMLPlus SDK:
build.ps1auto-downloads it (version pinned insrc-bmodp/config.ps1, currently v0.3.10) intosrc-bmodp/3rd-party/BMLPlus/on first build. SetBMLP_PROXYinconfig.ps1if behind a proxy. - C++ is 32-bit: CMake invokes
-A Win32because Ballance is a 32-bit game. The builtRockoonIO.bmodpis copied tosrc-tauri/resources/builtin-mods/and bundled as a Tauri resource. Requires CMake 4.0+ and C++20. pnpm tauri buildtriggerspnpm buildviabeforeBuildCommand, so the C++ module rebuilds automatically — but only if the submodule is present.
Communication flow: Vue Component → Store/Service → backend wrapper → tauri invoke → Rust #[command].
- Backend abstraction (
src/backend/index.ts): Allinvoke()calls live here, grouped intocommon/ballance/fs/process. Importbackendfrom@/backendand call methods. Never callinvoke()directly from components or stores. - Pinia stores (
src/stores/):app(selected instance + running process),instances,pref(theme/lang/route),hub(auth token).initStores()instores/index.tswires debounced$subscribepersistence to localStorage and watchesselectedInstanceData.optionsdeep, auto-writing back to the instance'sDatabase.tdbvia the Rust backend — editing options has a side effect. - Services (
src/services/):launcher.ts(composableuseLauncherService()),hub.ts(REST client over@tauri-apps/plugin-http; defaulthttp://127.0.0.1:8000, configurable viahubApiUrlin pref store). All hub calls go through here. - Router (
src/routers/): Routes are auto-generated frommenu.ts, which also defines the sidebar.createMemoryHistory()(desktop, no URL bar). - i18n (
src/i18n/): Auto-loads every JSON in./languages/(en.json,zh.json). Ctrl+T toggles language at runtime. - Logger (
src/utils/logger.ts): Hooks allconsole.*and forwards to Rust viabackend.log(). Frontend logs appear in the Rust log stream; setRUST_LOGfor Rust-side level. - Global types (
src/types/*.d.ts):Instance,BallanceOptions,ModConfig, etc. are ambient — no imports needed. - Path alias:
@/→src/.
- Commands live in
src-tauri/src/commands/{app,fs,process,ballance}.rsand are registered inlib.rsviatauri::generate_handler![]. Adding a command requires registering it there. - Plugins enabled in
lib.rs: http, updater, upload, single-instance, positioner, dialog, shell, opener. - Error types (
src-tauri/src/common/exception.rs):RcErroris athiserrorenum withFromimpls forio::Error,zip::ZipError,tauri::Error. It serializes to a string for the frontend.RcResult = Result<(), RcError>— use for void commands (no generic param).RcResultWith<T> = Result<T, RcError>— use when returning data.
- Ballance logic (
src-tauri/src/ballance/):options.rsreads/writesDatabase.tdb(game options + scores),tdb/is a custom Virtools DB parser,mod_config.rshandles BML/BMLPlus INI configs. - Serde structs must use
#[serde(rename_all = "camelCase")]so Rust snake_case maps to the TS frontend. tauri.conf.json(notpackage.json) holds the app version for releases/updater — currently2.0.0-alpha8;package.jsonversion (0.1.0) is not the shipped version.
BMLPlus plugin (RockoonIO.bmodp) built via CMake + build.ps1. Sources in src/, third-party in 3rd-party/ (Virtools-SDK submodule + downloaded BMLPlus SDK). .clangd present; CMake exports compile_commands.json.
- Prettier: double quotes,
arrowParens: "avoid",trailingComma: "none",bracketSpacing: true. - ESLint: flat config is
eslint.config.js(the active one)..eslintrc.jsis legacy — do not edit.@typescript-eslint/no-explicit-anyis OFF. Unused vars/args prefixed with_are ignored.consistent-type-importsenforced (inlineimport type). Vue HTML elements always self-close (void/normal/component). - TypeScript:
strict,noUnusedLocals,noUnusedParameters,noFallthroughCasesInSwitch. Target ES2022. - Vue:
<script setup lang="ts">. Reusable UI components use theBasicprefix. UI is shadcn-vue (Reka UI primitives + Tailwind v4) on top of@/components/ui/*(added via shadcn-vue CLI). Icons are@lucide/vue(kebab-case names, e.g.<Rocket />). Toasts viavue-sonner; modal dialogs via@/utils/ui/dialog-store+GlobalDialogHost.vue. Avoid adding any new UI library — extend the existing shadcn-vue set instead. - Rust:
?propagation,thiserrorfor errors,logcrate macros (info!, etc.).
- Windows-only: macOS/Linux are commented out in
.github/workflows/release.yml. - Hot reload: Vite ignores
src-tauri/**(vite.config.ts). Rust changes require restartingpnpm tauri dev. - Vite port 1420 is strict (
strictPort: true) — if taken, dev fails rather than incrementing. - Release flow: pushing to the
releasebranch (or manual dispatch) triggersrelease.yml, which builds viatauri-apps/tauri-actionwithsubmodules: recursive, signs withTAURI_SIGNING_PRIVATE_KEYsecret, and publishes a GitHub release taggedrockoon-v<version>. The updater pullslatest.jsonfrom that release. - Debugging Rust in VSCode:
.vscode/launch.jsonhas CodeLLDB configs (Tauri Development Debug/Tauri Production Debug) with preLaunch tasksui:dev/ui:build. - Vue LSP: use
@vue/language-serverfor.vuesupport.