The plugin system
Every tool in PocketArk is a plugin: one directory is one plugin, with frontend and backend side
by side. plugin.json is the single source of truth.
Directory conventions
Section titled “Directory conventions”plugins/<plugin-id>/├── plugin.json # manifest (single source of truth; shared by both scanners)├── README.md # feature notes (for users)├── AGENTS.md # dev conventions (for AI / maintainers, optional)├── frontend/ # frontend│ ├── views/ # tool pages (one PascalCase.vue per tools[] entry)│ ├── settings/ # settings panel (optional, declared in plugin.json)│ ├── components/ # plugin-private components (optional; a feature with ≥2 → components/<feature>/)│ ├── composables/ # composables (optional, useXxx.ts)│ ├── lib/ # pure logic TS (optional, no Vue dependency)│ ├── shared.ts # shared constants / types (optional)│ ├── schema.ts # database tables (optional, imported by code)│ └── setup.ts # plugin lifecycle hooks (optional, build-scanned)└── backend/ # backend (optional) ├── mod.rs # #[tauri::command] commands (**only here**) ├── migrations.rs # migration aggregation: exports `pub fn all()` (optional) ├── <feature>/ # multi-file feature (≥2 files): <feature>/mod.rs + other .rs └── <single>.rs # single-file feature / assetHard rules
- The
frontend/top level only allows the whitelist:views/ settings/ components/ composables/ lib/ shared.ts schema.ts setup.ts. Everything else lives in those directories orlib/. - Pure logic (no Vue dependency) goes in
frontend/lib/; usefrontend/composables/only when reactivity / lifecycle is needed. - Naming: components
PascalCase.vue; composablesuseXxx.ts; other TSkebab-case.ts; Rust filessnake_case.rs; feature directories use a singular lowercase noun. - Commands live only in
backend/mod.rs(build.rsscans only that file); other.rsfiles are its submodules. - A pure-frontend plugin may ship only
frontend/, butplugin.json/README.md/AGENTS.mdare still required. - Directories starting with
_or.are not registered (e.g. the_template).
Machine checks live in scripts/lint-plugins.mjs (R-P1~R-P6), wired into pnpm lint; for a genuine
exception, write lint-plugins-ignore in the file with a reason.
Build-time auto-registration
Section titled “Build-time auto-registration”Adding a plugin requires no registration in any framework file:
| Scan point | Location | Purpose |
|---|---|---|
| Manifest | plugins/*/plugin.json | tools / settings / icons / ordering |
| Tool pages | plugins/*/frontend/views/*.vue | targeted by manifest.tools[].entry |
| Settings panel | plugins/*/frontend/settings/*.vue | targeted by manifest.settings.entry |
| Frontend hooks | plugins/*/frontend/setup.ts | plugin initialization |
| Backend commands | plugins/*/backend/mod.rs | build.rs parses #[tauri::command] and generates the registry |
| Migrations | plugins/*/backend/migrations.rs | aggregated into all(), scoped per version |
Navigation, routing, titles, enable/disable toggles, the settings panel, the error boundary and KeepAlive all work automatically.
Naming and routing
Section titled “Naming and routing”| Item | Rule |
|---|---|
| Plugin id | Globally unique, kebab-case, equal to the directory name; route /tool/:id |
| Tool id | tools[].id, globally unique; prefer <plugin-id> or <plugin-id>-<feature> |
| Rust command | Function name = frontend call name, prefixed with <plugin_id>_ |
| Migration | migration(scope, version, ...) with scope = plugin id, version starting at 1 per scope |
Ordering
Section titled “Ordering”- Plugin group order = the smallest
orderin the group. - Tool order within a group =
order * 1000 + index in the tools array. - Default
orderis 100, then alphabetical by directory name.
Reference implementations
Section titled “Reference implementations”plugins/_template/: the minimal skeleton (used by the scaffolder), demonstrating the directory conventions and page structure.plugins/hello-world/: the full path (commands, logging, database, forms, tables, tasks, notifications, multi-window).plugins/system/: a pure-frontend plugin (no backend, just tables and dialogs).
Next: Create a plugin and the plugin.json manifest.