docs(all): updated agent rules

This commit is contained in:
Sébastien Dubois 2026-05-15 13:28:55 +02:00
parent 3c0f6d32cb
commit 4e9bf8f75d

View file

@ -1,5 +1,13 @@
## Project Documentation
## Documentation surfaces
Three locations, do not mix them:
- `README.md` — GitHub landing page; pitch, features, install, quick start.
- `docs/` — end-user guide, published via GitHub Pages (Jekyll).
- `documentation/` — technical documentation for you and coding agents (architecture, domain model, business rules, history, plans).
## Project overview
- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
@ -220,15 +228,32 @@ ## Manifest rules (`manifest.json`)
- `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.
- Keep `minAppVersion` accurate when using newer APIs. Common bumps: `1.1.0` (`ButtonComponent.setIcon`/`setTooltip`), `1.4.10` (`AbstractInputSuggest`), `1.5.7` (`Vault.getFileByPath`), `1.7.2` (`Workspace.revealLeaf`).
- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
### Community catalog listing rules
These rules apply to **`id`**, **`name`**, and **`description`** in `manifest.json` AND are mirrored into `package.json` (`name`, `description`):
- **`id`**: must not contain the word "obsidian" (catalog trademark rule). The GitHub repo name can keep it; only the manifest `id` is gated. Drop the prefix (`obsidian-time-machine` → `time-machine`).
- **`name`**: must not contain "Obsidian". Must not be all-uppercase — acronym chains like `CLI REST MCP` trip the check; include at least one lowercase word.
- **`description`**: must not contain "Obsidian", must not start with the plugin name (the catalog UI already shows it), must end with `.`, `!`, or `?`. These three rules typically fire together — fix in one pass.
**Draft vs accepted timing for `id`:**
- While the catalog entry is still in **draft**: free to rename `id`. Do it before acceptance.
- Once **accepted**: `id` is locked forever (changing it breaks installed users, settings paths, and keyboard shortcuts).
- **Sticky-draft-slug gotcha**: even in draft, the catalog stores the slug from the _first_ submission and compares every later manifest against it. A rename then triggers `ERROR: The plugin ID in (<new>) does not match the existing plugin ID (<old>)` — contradicting the "must not contain obsidian" rule. Resolution: delete the draft listing in the catalog admin and resubmit fresh under the new id, or open a thread with the catalog maintainers to release the slug.
## 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.
- The command `name` must not include the plugin name — Obsidian already prefixes commands with the plugin name in the palette. If you need to rebrand, **rename `name`, not `id`** (renaming an id breaks any user-bound keyboard shortcut). Grep `docs/` and `README.md` for old command names when renaming.
- File pickers in settings tabs: never hand-roll. Use `AbstractInputSuggest` for inline autocomplete and `FuzzySuggestModal` for a browse-button modal — both cover keyboard nav, theming, and popout-window correctness for free. Hand-rolled menus accumulate inline-style + `document.createElement` lint warnings fast.
- Replace `window.confirm(...)` with a `Modal` subclass: `confirm()` blocks the UI thread, can't be themed, doesn't play with popout windows, and is forbidden by the scorecard.
## Versioning & releases
@ -402,6 +427,52 @@ ### Tailwind CSS Guidelines
}
```
## Community catalog review — preventative rules
The community-plugin reviewer runs a fixed set of lint rules against every submitted release. Most warnings repeat across plugins and have known idiomatic fixes. **Apply these patterns from day one** — fixing them retroactively is much more expensive than getting them right the first time.
### API conventions (DOM, timers, popouts)
- `document``activeDocument` (so popout windows hit their own DOM).
- Timers — `setTimeout`/`clearTimeout`/`setInterval`/`clearInterval`/`requestAnimationFrame`/`cancelAnimationFrame` → `window.X`. **Not `activeWindow.X`** — the rule complains either way for timers.
- Timer handle types: declare as plain `number`, not `ReturnType<typeof setTimeout>`. With `@types/bun` in scope, the overload resolves to Bun's `Timer` and breaks the assignment from `window.setTimeout` (which returns `number`).
- `document.createElement(tag)``createEl(tag, …)` / `createSpan(…)` / `createDiv(…)`. Prefer parent-bound `el.createEl(…)` when a parent exists; the child is appended automatically.
- `globalThis.X` for plugin-injected globals (Excalidraw etc.) → `window.X`.
- `processFrontMatter` callback param type: `(frontmatter: Record<string, unknown>) => …` — Obsidian's default is `any` and trips unsafe-access rules.
- External SDKs that need a custom `fetch`: inject a `requestUrl`-based adapter via the SDK's `fetch` option (most SDKs that need a custom fetch — Replicate, OpenAI, etc. — expose one). Never `node-fetch`, never `globalThis.fetch = require('node-fetch')`. The adapter wraps `requestUrl` in a `fetch`-shaped function and refuses non-string/ArrayBuffer bodies; no streaming, no AbortSignal, no FormData. Adequate for plain GET/POST polling.
### Lint / TypeScript rules
- `eslint-disable @typescript-eslint/no-explicit-any` is **forbidden** — the reviewer treats both the violation and the disable as an **error** (blocks the scorecard). Type properly instead: `typeof Chart` for dynamically-imported classes, widen your custom interface rather than `as any`, narrow `unknown` at the call site.
- Every other `eslint-disable-next-line <rule>` requires a `-- reason` description.
- `new Array(n)` leaks `any[]` — write `new Array<T>(n)` or `Array.from({ length: n }, () => …)`. `Array.from` is cleaner when each slot needs a fresh sub-array.
- `Object.values(union)` returns `any[]` for union types — annotate the local as `unknown[]` and narrow at use.
- Drop redundant `as T` casts after `instanceof T` narrowing.
- Switch over an enum: case labels reference enum members (`TimeGranularity.Daily`), not raw string literals.
- `.catch((error: unknown) => …)`: coerce to `Error` before rethrowing — `error instanceof Error ? error : new Error(String(error))`.
- Async function passed to a void-returning callback: wrap in `void (async () => { … })()`, or widen the callback type to `() => void | Promise<void>` for helpers you own.
- "Legacy" ≠ "@deprecated": types that migration code intentionally keeps reading (V1 on-disk shapes etc.) are **legacy**, not deprecated. Drop the `@deprecated` tag and document them as legacy formats — the tag will otherwise fire the no-deprecated rule on every legitimate consumer.
### CSS rules
- No hand-written `!important`. Bump specificity with a doubled-class selector (`.foo.foo`) — that goes from 0,1,0 to 0,2,0 and beats most Obsidian defaults (`.setting-item-control button` 0,1,1; `.modal-container .modal` 0,2,0).
- Before removing `!important`, identify what the rule is fighting and verify in a live vault with `getComputedStyle()`. Inline styles always win — keep `!important` only when the source it beats is itself inline.
- When `!important` is genuinely load-bearing (visibility toggles like `.lt-hidden` are the canonical example), restore it and add a `/* stylelint-disable-next-line declaration-no-important -- reason: … */` comment. The reviewer accepts descriptive disables.
- Collapse mirrored 4-value shorthands: `8px 0 12px 0``8px 0 12px`.
- `obsidianmd/no-static-styles-assignment` only flags **literal** RHS — dynamic style assignments (template literals with expressions, ternaries, variable RHS) can stay inline. Move only the static ones to a CSS class.
### Logging
- The reviewer flags every `console.*` call in shipped code. The template's `src/utils/log.ts` ships with its `console.*` lines commented out — re-enable only behind a `debugModeEnabled` settings toggle when you actually need verbose logs.
- Route stray `console.error(...)` from catch blocks through `log(msg, 'error', err)` so the suppression stays centralized.
### Release workflow
- Attach only `main.js`, `manifest.json`, and `styles.css` (if present) — never a zip. The CI release workflow in this template already does this; don't add zip-upload steps back.
- Build in CI; don't post-edit `main.js`.
- `bun-version-file: package.json` (already wired) keeps Bun pinned across CI and release. Update `packageManager` in `package.json` to bump.
- `actions/attest-build-provenance@v3` (already wired) attaches provenance to release artifacts.
## Mobile
- Where feasible, test on iOS and Android.
@ -529,3 +600,4 @@ ## References
- Developer policies: https://docs.obsidian.md/Developer+policies
- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
- Style guide: https://help.obsidian.md/style-guide
- When fixing community-catalog review warnings, mine sibling plugin commits for prior fixes before re-inventing solutions — the same warnings recur across plugins and most have already been solved once.