diff --git a/AGENTS.md b/AGENTS.md index 3f4274a..7133b0e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,250 +1,49 @@ -# Obsidian community plugin - -## Project overview - -- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript). -- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian. -- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`. - -## Environment & tooling - -- Node.js: use current LTS (Node 18+ recommended). -- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies). -- **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`. -- Types: `obsidian` type definitions. - -**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly. - -### Install - -```bash -npm install -``` - -### Dev (watch) - -```bash -npm run dev -``` - -### Production build - -```bash -npm run build -``` - -## Linting - -- To use eslint install eslint from terminal: `npm install -g eslint` -- To use eslint to analyze this project use this command: `eslint main.ts` -- eslint will then create a report with suggestions for code improvement by file and line number. -- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/` - -## File & folder conventions - -- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`. -- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands). -- **Example file structure**: - ``` - src/ - main.ts # Plugin entry point, lifecycle management - settings.ts # Settings interface and defaults - commands/ # Command implementations - command1.ts - command2.ts - ui/ # UI components, modals, views - modal.ts - view.ts - utils/ # Utility functions, helpers - helpers.ts - constants.ts - types.ts # TypeScript interfaces and types - ``` -- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control. -- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages. -- Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`). +# Obsidian plugin development guidelines ## Manifest rules (`manifest.json`) -- Must include (non-exhaustive): - - `id` (plugin ID; for local dev it should match the folder name) - - `name` - - `version` (Semantic Versioning `x.y.z`) - - `minAppVersion` - - `description` - - `isDesktopOnly` (boolean) - - Optional: `author`, `authorUrl`, `fundingUrl` (string or map) - Never change `id` after release. Treat it as stable API. - Keep `minAppVersion` accurate when using newer APIs. -- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml - -## Testing - -- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to: - ``` - /.obsidian/plugins// - ``` -- Reload Obsidian and enable the plugin in **Settings → Community plugins**. - -## Commands & settings - -- Any user-facing commands should be added via `this.addCommand(...)`. -- If the plugin has configuration, provide a settings tab and sensible defaults. -- Persist settings using `this.loadData()` / `this.saveData()`. -- Use stable command IDs; avoid renaming once released. +- `version` uses Semantic Versioning (`x.y.z`). +- Canonical validation: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml ## Versioning & releases -- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version. +- Bump `version` in `manifest.json` and update `versions.json` to map plugin version to minimum app version. - Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`. -- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets. -- After the initial release, follow the process to add/update your plugin in the community catalog as required. +- Attach `manifest.json`, `main.js`, and `styles.css` to the release as individual assets. -## Security, privacy, and compliance +## Security & privacy -Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular: +Follow Obsidian's **Developer Policies** and **Plugin Guidelines**: - Default to local/offline operation. Only make network requests when essential to the feature. -- No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings. +- No hidden telemetry. Require explicit opt-in and document clearly in `README.md` and in settings. - Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases. - Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault. -- Clearly disclose any external services used, data sent, and risks. - Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented. -- Avoid deceptive patterns, ads, or spammy notifications. -- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely. +- Register and clean up all DOM, app, and interval listeners using `this.register*` helpers so the plugin unloads safely. -## UX & copy guidelines (for UI text, commands, settings) +## UX & copy -- Prefer sentence case for headings, buttons, and titles. -- Use clear, action-oriented imperatives in step-by-step copy. -- Use **bold** to indicate literal UI labels. Prefer "select" for interactions. -- Use arrow notation for navigation: **Settings → Community plugins**. -- Keep in-app strings short, consistent, and free of jargon. - -## Performance - -- Keep startup light. Defer heavy work until needed. -- Avoid long-running tasks during `onload`; use lazy initialization. -- Batch disk access and avoid excessive vault scans. -- Debounce/throttle expensive operations in response to file system events. +- Follow the [Obsidian style guide](https://help.obsidian.md/style-guide). +- Sentence case for headings, buttons, and titles. +- Use "select" for interactions, not "click" or "tap". +- Use → for navigation paths: **Settings → Community plugins**. +- Use Global English — avoid idioms and culturally-specific expressions. +- Command IDs are stable once released — never rename them. ## Coding conventions -- TypeScript with `"strict": true` preferred. -- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules. -- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules. -- **Use clear module boundaries**: Each file should have a single, well-defined responsibility. -- Bundle everything into `main.js` (no unbundled runtime deps). -- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly. +- Keep `main.ts` minimal: lifecycle only (onload/onunload, addCommand, addSettingTab). Delegate logic to separate modules. +- `obsidian`, `electron`, `@codemirror/*`, `@lezer/*` are runtime externals — never bundle them. +- Output format must be CJS (`format: "cjs"`). +- Avoid Node/Electron APIs unless `isDesktopOnly` is `true`. - Prefer `async/await` over promise chains; handle errors gracefully. - -## Mobile - -- Where feasible, test on iOS and Android. -- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`. -- Avoid large in-memory structures; be mindful of memory and storage constraints. - -## Agent do/don't - -**Do** -- Add commands with stable IDs (don't rename once released). -- Provide defaults and validation in settings. -- Write idempotent code paths so reload/unload doesn't leak listeners or intervals. -- Use `this.register*` helpers for everything that needs cleanup. - -**Don't** -- Introduce network calls without an obvious user-facing reason and documentation. -- Ship features that require cloud services without clear disclosure and explicit opt-in. -- Store or transmit vault contents unless essential and consented. - -## Common tasks - -### Organize code across multiple files - -**main.ts** (minimal, lifecycle only): -```ts -import { Plugin } from "obsidian"; -import { MySettings, DEFAULT_SETTINGS } from "./settings"; -import { registerCommands } from "./commands"; - -export default class MyPlugin extends Plugin { - settings: MySettings; - - async onload() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - registerCommands(this); - } -} -``` - -**settings.ts**: -```ts -export interface MySettings { - enabled: boolean; - apiKey: string; -} - -export const DEFAULT_SETTINGS: MySettings = { - enabled: true, - apiKey: "", -}; -``` - -**commands/index.ts**: -```ts -import { Plugin } from "obsidian"; -import { doSomething } from "./my-command"; - -export function registerCommands(plugin: Plugin) { - plugin.addCommand({ - id: "do-something", - name: "Do something", - callback: () => doSomething(plugin), - }); -} -``` - -### Add a command - -```ts -this.addCommand({ - id: "your-command-id", - name: "Do the thing", - callback: () => this.doTheThing(), -}); -``` - -### Persist settings - -```ts -interface MySettings { enabled: boolean } -const DEFAULT_SETTINGS: MySettings = { enabled: true }; - -async onload() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - await this.saveData(this.settings); -} -``` - -### Register listeners safely - -```ts -this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ })); -this.registerDomEvent(window, "resize", () => { /* ... */ }); -this.registerInterval(window.setInterval(() => { /* ... */ }, 1000)); -``` - -## Troubleshooting - -- Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `/.obsidian/plugins//`. -- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code. -- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique. -- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes. -- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust. +- Persist settings via `this.loadData()` / `this.saveData()` with `Object.assign({}, DEFAULT_SETTINGS, data)` pattern. ## References -- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin - API documentation: https://docs.obsidian.md - Developer policies: https://docs.obsidian.md/Developer+policies - Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines