New obsidian-dev-skills setup.

This commit is contained in:
David V. Kimball 2026-01-23 16:02:55 -08:00
parent 23fe4814c3
commit 2ebc6e054a
35 changed files with 594 additions and 2995 deletions

View file

@ -1,35 +1,35 @@
---
name: obsidian-dev
description: Logic patterns, lifecycle management, and core development rules for Obsidian plugins. Load when editing src/main.ts, implementing new features, or handling plugin lifecycle events.
description: Development patterns for Obsidian. Load when implementing features or following coding conventions.
---
# Obsidian Development Skill
This skill provides patterns and rules for developing Obsidian plugins, focusing on core logic and lifecycle management.
This skill provides patterns and rules for developing Obsidian plugins and themes.
## Purpose
To ensure consistent implementation of plugin features, proper resource management, and adherence to Obsidian's development patterns.
To ensure consistent development across plugins and themes, proper code organization, and adherence to Obsidian's development patterns.
## When to Use
Load this skill when:
- Implementing or modifying the `main.ts` entry point.
- Creating new plugin features or logic patterns.
- Managing the plugin lifecycle (`onload`, `onunload`).
- Implementing commands or settings.
- Implementing new features
- Following coding conventions
- Debugging issues
- Managing project structure
## Core Rules
- **Use "properties" instead of "frontmatter"**: Always refer to YAML metadata as "properties" in UI, comments, and documentation.
- **Capitalize "Markdown"**: Always treat "Markdown" as a proper noun.
- **Resource Cleanup**: Ensure all event listeners and resources are properly cleaned up in `onunload`.
- **Async Safety**: Most Obsidian API operations are asynchronous; use `await` appropriately.
- Follow established patterns for your project type (plugin/theme)
- Use appropriate tools and conventions
- Test thoroughly across different environments
- Document important decisions and patterns
## Bundled Resources
- `references/agent-dos-donts.md`: Critical do's and don'ts for development.
- `references/code-patterns.md`: Reusable code snippets and architectural patterns.
- `references/coding-conventions.md`: Style guides and naming conventions.
- `references/commands-settings.md`: Guidance for implementing commands and settings tabs.
- `references/common-tasks.md`: Quick reference for frequent implementation tasks.
- `references/agent-dos-donts.md`: Critical development guidelines
- `references/code-patterns.md`: Implementation patterns and examples
- `references/coding-conventions.md`: Code style and organization
- `references/commands-settings.md`: Command and settings patterns
- `references/common-tasks.md`: Frequently needed operations

View file

@ -22,7 +22,7 @@ Load this skill when:
## Core Rules
- **NEVER perform automatic git operations**: AI agents must never execute `git commit`, `git push`, or any command that automatically stages or commits changes without explicit user approval for each step.
- **Verify Build**: Always run a build after significant changes to ensure TypeScript and esbuild compatibility.
- **Verify Build**: Always run a build/lint after significant changes to ensure compatibility.
- **Sync Status**: Keep `sync-status.json` updated when updating reference materials.
## Bundled Resources
@ -32,4 +32,4 @@ Load this skill when:
- `references/sync-procedure.md`: How to pull updates from reference repositories.
- `references/versioning-releases.md`: Workflow for versioning and GitHub releases.
- `references/troubleshooting.md`: Common issues and their resolutions.
- `references/quick-reference.md`: One-page cheat sheet for common operations.
- `references/quick-reference.md`: One-page cheat sheet for common operations.

View file

@ -8,26 +8,53 @@ Update frequency: Update as build process evolves
**CRITICAL**: Always run the build command after making changes to catch errors early.
After making any changes to plugin code:
After making any changes to theme code:
1. **Run the build** (assume pnpm is already installed):
### Simple CSS Themes
If your theme is simple with just `theme.css` in the root and no build tools:
- **No build step required** - just edit `theme.css` directly
- Changes take effect immediately when Obsidian reloads the theme (reload Obsidian with Ctrl+R / Cmd+R)
- **Linting**: Run `npm run lint` to check CSS quality (optional but recommended)
**How to detect**: If you have `theme.css` in root and no `src/scss/` directory, you have a simple CSS theme.
### Complex Themes (SCSS + Build Tools)
If your theme uses build tools (Grunt, npm scripts, SCSS compiler, etc.) and has `src/scss/` directory:
1. **Run the build** (assume npm is already installed):
```powershell
pnpm build # Production build (outputs to main.js in root)
pnpm dev # Development build with watch mode (outputs to main.js in root)
# For themes using Grunt (like obsidian-oxygen)
npx grunt build
# For themes using npm scripts
npm run build
# For themes using Grunt watch mode (auto-rebuild on changes)
npx grunt
# Or whatever build command your theme uses
```
**Note**: Both `pnpm build` and `pnpm dev` output to `main.js` in the root directory. The difference is that `pnpm build` is a one-time build, while `pnpm dev` watches for changes and rebuilds automatically.
**Backwards compatibility**: `npm run build` and `npm run dev` will also work since npm scripts execute the same commands.
2. **If the build fails with pnpm/node errors**, then check if pnpm is installed:
2. **If the build fails with npm/node errors**, then check if npm is installed:
```powershell
pnpm --version
npm --version
```
- If pnpm is not found, inform the user that pnpm needs to be installed (via `npm install -g pnpm` or `corepack enable`)
- Do not automatically install pnpm - let the user handle installation
- If npm is not found, inform the user that Node.js (which includes npm) needs to be installed
- Do not automatically install npm - let the user handle installation
3. **Check for errors** and fix any build issues before proceeding. See [troubleshooting.md](troubleshooting.md) and [common-pitfalls.md](common-pitfalls.md) for common build issues.
3. **Check for errors** and fix any build issues before proceeding. See [troubleshooting.md](troubleshooting.md) for common build issues.
4. **Linting**: Run `npm run lint` to check SCSS/CSS quality. The lint wrapper automatically detects SCSS files in `src/scss/` and lints them appropriately.
**How to detect**: If you have a `src/scss/` directory, you have a complex theme with build tools. Check for `Gruntfile.js`, `package.json` scripts, or other build configuration files.
**Common build tools**:
- **Grunt**: Look for `Gruntfile.js` → Run `npx grunt build` or `npx grunt` (watch mode)
- **npm scripts**: Check `package.json` for `build` script → Run `npm run build`
- **Sass CLI**: Some themes use `sass` directly → Check `package.json` scripts
## Why This Matters

View file

@ -1,942 +0,0 @@
<!--
Source: Based on Obsidian developer docs warnings, community patterns, and API best practices
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Update as common issues are identified
-->
# Common Pitfalls
**Purpose**: This document covers common mistakes and gotchas when developing Obsidian plugins. It focuses on general development pitfalls with examples and solutions.
**For bot-specific requirements**: See [obsidian-bot-requirements.md](obsidian-bot-requirements.md) (authoritative source for bot review requirements).
**For fix procedures**: See [linting-fixes-guide.md](linting-fixes-guide.md) (step-by-step fixes for linting errors).
Always verify API details in `.ref/obsidian-api/obsidian.d.ts` as it's the authoritative source.
## Async/Await Issues
**Problem**: Forgetting to await `loadData()` or `saveData()` causes settings not to persist.
**Wrong**:
```ts
onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, this.loadData()); // Missing await!
}
```
**Correct**:
```ts
async onload() {
await this.loadSettings(); // Properly awaited
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
```
**Also**: Always await `saveData()`:
```ts
async saveSettings() {
await this.saveData(this.settings); // Don't forget await!
}
```
## GitHub Release Tag Format
**Problem**: Using "v" prefix in GitHub release tags (e.g., "v0.1.0") instead of the version number without prefix (e.g., "0.1.0"). The tag must match `manifest.json`'s `version` field exactly.
**Wrong**:
- Tag: `v0.1.0`
- Release name: `v0.1.0`
- Manifest version: `0.1.0` (mismatch!)
**Correct**:
- Tag: `0.1.0` (matches manifest.json version exactly)
- Release name: `0.1.0` (or any descriptive name, but tag must be version number)
- Manifest version: `0.1.0` (matches tag exactly)
**Why it matters**: The Obsidian community plugin system expects the GitHub release tag to exactly match the version in `manifest.json`. Using a "v" prefix breaks this matching and can cause issues with plugin updates and version detection.
**Rule**: GitHub release tags must be the version number WITHOUT the "v" prefix. The tag must match `manifest.json`'s `version` field exactly.
**See also**: [versioning-releases.md](versioning-releases.md), [release-readiness.md](release-readiness.md)
## Settings Object.assign Gotcha
**Source**: Warning from `.ref/obsidian-developer-docs/en/Plugins/User interface/Settings.md`
**Problem**: `Object.assign()` performs a shallow copy. Nested properties share references, causing changes to affect all copies.
**Wrong** (with nested properties):
```ts
interface MySettings {
nested: { value: string };
}
const DEFAULT_SETTINGS: MySettings = {
nested: { value: "default" }
};
// This creates a shallow copy - nested properties share references!
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
```
**Correct**: Use deep copy for nested properties:
```ts
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
// Deep copy nested properties:
if (!this.settings.nested) {
this.settings.nested = { ...DEFAULT_SETTINGS.nested };
}
```
Or use a deep merge utility for complex nested structures.
## Listener Cleanup
**Problem**: Not using `registerEvent()`, `registerDomEvent()`, or `registerInterval()` causes memory leaks when the plugin unloads.
**Wrong**:
```ts
onload() {
this.app.workspace.on("file-open", this.handleFileOpen); // Not registered!
window.addEventListener("resize", this.handleResize); // Not registered!
setInterval(this.updateStatus, 1000); // Not registered!
}
```
**Correct**:
```ts
onload() {
this.registerEvent(this.app.workspace.on("file-open", this.handleFileOpen));
this.registerDomEvent(window, "resize", this.handleResize);
this.registerInterval(setInterval(this.updateStatus, 1000));
}
```
All registered events/intervals are automatically cleaned up when the plugin unloads.
## Settings Not Updating UI
**Problem**: Changing settings but the settings tab UI doesn't reflect the change.
**Solution**: Call `display()` after updating settings that affect the UI:
```ts
new Setting(containerEl)
.setName("Toggle setting")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
this.display(); // Re-render settings tab
})
);
```
## Settings Tab Scroll Jump
**Problem**: When using conditional settings (showing/hiding settings based on other settings), calling `this.display()` causes the settings tab to jump to the top, losing the user's scroll position.
**Why it happens**: `display()` calls `containerEl.empty()`, which removes all content and rebuilds the settings tab from scratch, resetting the scroll position to the top.
**Solution**: Preserve scroll position when re-rendering:
```ts
new Setting(containerEl)
.setName("Toggle setting")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
// Save scroll position before re-rendering
const scrollContainer = containerEl.closest('.vertical-tab-content') ||
containerEl.closest('.settings-content') ||
containerEl.parentElement;
const scrollTop = scrollContainer?.scrollTop || 0;
this.display(); // Re-render settings tab
// Restore scroll position after rendering
requestAnimationFrame(() => {
if (scrollContainer) {
scrollContainer.scrollTop = scrollTop;
}
});
})
);
```
**Alternative approach**: Instead of calling `this.display()`, conditionally show/hide specific settings elements without rebuilding the entire tab. This avoids scroll issues but requires more code to manage visibility:
```ts
// Show/hide settings without re-rendering entire tab
const conditionalSetting = containerEl.querySelector('.conditional-setting');
if (conditionalSetting) {
// Use CSS classes or setCssProps instead of direct style manipulation
conditionalSetting.toggleClass('hidden', !this.plugin.settings.enabled);
// Or: setCssProps(conditionalSetting, { display: this.plugin.settings.enabled ? 'block' : 'none' });
}
```
## View Management
**Source**: Warning from `.ref/obsidian-plugin-docs/docs/guides/custom-views.md`
**Problem**: Storing references to views causes issues because Obsidian may call the view factory function multiple times.
**Wrong**:
```ts
let myView: MyView; // Don't store view references!
onload() {
this.registerView(VIEW_TYPE_MY_VIEW, (leaf) => {
myView = new MyView(leaf); // BAD: Storing reference
return myView;
});
}
```
**Correct**: Always use `getLeavesOfType()` to access views:
```ts
onload() {
this.registerView(VIEW_TYPE_MY_VIEW, (leaf) => new MyView(leaf));
}
// Access views when needed:
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_MY_VIEW);
leaves.forEach((leaf) => {
if (leaf.view instanceof MyView) {
// Access view instance
}
});
```
## Mobile vs Desktop APIs
**Problem**: Using desktop-only APIs without setting `isDesktopOnly: true` in `manifest.json`.
**Solution**:
- Check if an API is desktop-only in `.ref/obsidian-api/obsidian.d.ts`
- Set `isDesktopOnly: true` in `manifest.json` if using Node.js/Electron APIs
- Or use feature detection and provide mobile alternatives
**Example**: Status bar items don't work on mobile. Check before using:
```ts
// Status bar is desktop-only
if (this.app.isMobile) {
// Use alternative UI for mobile
} else {
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText("Status");
}
```
## TypeScript Strict Mode
**Problem**: Common type errors when using strict TypeScript.
**Common issues**:
- `Object.assign()` may not satisfy strict null checks - use proper typing
- Event handlers may have `undefined` types - add null checks
- Settings may be `undefined` on first load - provide defaults
- Using `any` type defeats TypeScript's type safety - avoid explicit `any`
**Solution**: Always provide proper defaults and type guards:
```ts
interface MySettings {
value: string;
}
const DEFAULT_SETTINGS: MySettings = {
value: "",
};
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
// Now this.settings.value is always defined
}
```
**Avoid `any` type**: Prefer proper types, `unknown`, or type assertions:
**Wrong**:
```ts
function processData(data: any) { // Avoid any!
return data.value;
}
```
**Correct** - Use proper types:
```ts
interface Data {
value: string;
}
function processData(data: Data) {
return data.value;
}
```
**Correct** - Use `unknown` when type is truly unknown:
```ts
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data');
}
```
**Correct** - Use type assertions when you know the type:
```ts
const result = someApiCall() as MyType;
```
## Command ID Stability
**Problem**: Changing command IDs after release breaks user keybindings.
**Solution**: Use stable command IDs. Once released, never change them. If you need to rename, keep the old ID and add a new one, or use aliases.
## Forgetting to Clean Up Views
**Problem**: Views remain in workspace after plugin unloads.
**Solution**: Always detach views in `onunload()`:
```ts
async onunload() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE_MY_VIEW);
}
```
## main.ts File Location
**Problem**: Having `main.ts` in both the project root AND `src/` directory, which causes build confusion and errors.
**Acceptable** (simple plugins):
```
project-root/
main.ts # ✅ OK for very simple plugins (like sample plugin template)
manifest.json
```
**Also Acceptable** (recommended for most plugins):
```
project-root/
src/
main.ts # ✅ Recommended for plugins with multiple files
settings.ts
commands/
```
**Wrong** (duplicate - causes build errors):
```
project-root/
main.ts # ❌ Don't have it in both places
src/
main.ts # ❌ This causes build confusion and errors
```
**Why this matters**:
- Having `main.ts` in both locations causes ambiguity - build tools don't know which one to use
- This leads to build errors, confusion about which file is being compiled
- The compiled `main.js` always outputs to `main.js` in the root directory
- You should have only ONE source `main.ts`
**Solution**:
- For simple plugins: Keep `main.ts` in root (like the sample plugin template)
- For plugins with multiple files: Move `main.ts` to `src/` and organize all source files there
- **Never have `main.ts` in both locations** - choose one location and stick with it
- If you see `main.ts` in both places, remove one (preferably keep it in `src/` for better organization)
## Settings Not Persisting
**Problem**: Settings appear to save but don't persist after restart.
**Common causes**:
1. Not awaiting `saveData()`
2. Settings object structure changed (old data doesn't match new interface)
3. Settings file is corrupted or locked
**Solution**:
- Always await `saveData()`
- Use migration logic if settings structure changes
- Handle errors when loading settings:
```ts
async loadSettings() {
try {
const data = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
} catch (error) {
console.error("Failed to load settings:", error);
this.settings = { ...DEFAULT_SETTINGS };
}
}
```
## Common Linting Issues
**Source**: Based on `eslint-plugin-obsidianmd` (npm package) rules and Obsidian plugin review requirements. The repository is at `.ref/eslint-plugin/` - see its README and rule documentation for complete details.
**Note**: Install and configure `eslint-plugin-obsidianmd` in your project to catch these issues automatically. See [environment.md](environment.md) for setup instructions.
### ESLint Disable Comment Placement (AI Agents Often Get This Wrong!)
**Problem**: AI coding assistants (Cursor, GitHub Copilot, ChatGPT, etc.) frequently place `eslint-disable` comments in the wrong location, causing the error: "Fixing eslint-disable comment placement. They must be directly before the line with the error:"
**Why it happens**: AI agents often place disable comments several lines above the error, or add blank lines between the comment and the error line. ESLint requires the disable comment to be **directly on the line immediately before** the error line with **no blank lines or other code** in between.
**Wrong** (common AI agent mistakes):
```ts
// ❌ Wrong - Comment too far above error
// eslint-disable-next-line obsidianmd/ui/sentence-case
.setName('Show button')
.setDesc('Display the button.') // Error is here, but disable is too far up
// ❌ Wrong - Blank line between comment and error
// eslint-disable-next-line obsidianmd/ui/sentence-case
.setDesc('Display the button.') // Blank line breaks it
// ❌ Wrong - Comment on same line as error
.setDesc('Display the button.') // eslint-disable-next-line obsidianmd/ui/sentence-case
```
**Correct**:
```ts
// ✅ Correct - Comment directly before error line (no blank lines!)
// False positive: Already in sentence case
// eslint-disable-next-line obsidianmd/ui/sentence-case
.setDesc('Display the button.') // Error is here, disable is right above
```
**Rule**: The `eslint-disable-next-line` comment **MUST** be on the line immediately before the error line. There can be NO blank lines or other code between them.
**Solution**: Always verify placement after an AI agent adds a disable comment. If you see the placement error, move the comment to the line directly before the error.
**See also**: [linting-fixes-guide.md](linting-fixes-guide.md) for detailed examples and formatting rules.
### Setting Styles Directly on DOM Elements
**Problem**: Setting styles via `element.style.*` with static literals prevents proper theming and maintainability.
**Wrong**:
```ts
element.style.display = 'block';
element.style.opacity = '0.5';
element.style.marginTop = '10px';
element.style.setProperty('color', 'red');
element.setAttribute('style', 'color: red;');
```
**Correct**: Use CSS classes or `setCssProps()`:
```ts
// Option 1: CSS classes (preferred)
element.addClass('my-custom-class');
// Option 2: setCssProps for dynamic styles
import { setCssProps } from 'obsidian';
setCssProps(element, {
display: 'block',
opacity: '0.5',
marginTop: '10px'
});
```
**Note**: The ESLint rule only flags static literal assignments. Dynamic values (from variables or template literals) are allowed, but CSS classes are still preferred.
**ESLint rule**: `no-static-styles-assignment` (from `eslint-plugin-obsidianmd`)
### Manual HTML Headings in Settings
**Problem**: Creating HTML heading elements directly in settings tabs instead of using the Settings API.
**Wrong**:
```ts
containerEl.createEl('h2', { text: 'My Settings' });
containerEl.createEl('h3', { text: 'General' });
```
**Correct**: Use `Setting.setHeading()`:
```ts
new Setting(containerEl)
.setHeading('My Settings');
new Setting(containerEl)
.setHeading('General');
```
**ESLint rule**: `settings-tab/no-manual-html-headings` (from `eslint-plugin-obsidianmd`)
### UI Text Not in Sentence Case
**Problem**: UI text should use sentence case for consistency with Obsidian's design system. See [ux-copy.md](ux-copy.md) for complete UX guidelines.
**Wrong**:
```ts
.setName("Enable Feature")
.setDesc("This Feature Does Something")
```
**Correct**: Use sentence case:
```ts
.setName("Enable feature")
.setDesc("This feature does something")
```
**ESLint rule**: `ui/sentence-case` (from `eslint-plugin-obsidianmd`)
### Using Vault.delete() Instead of FileManager.trashFile()
**Problem**: `Vault.delete()` doesn't respect user's file deletion preferences (trash vs. permanent delete).
**Wrong**:
```ts
await this.app.vault.delete(file);
```
**Correct**: Use `FileManager.trashFile()`:
```ts
await this.app.fileManager.trashFile(file);
```
**ESLint rule**: `prefer-file-manager-trash-file` (from `eslint-plugin-obsidianmd`)
### addSetting Callback Return Type
**Problem**: Using expression body arrow functions with `addSetting` from `createSettingsGroup()` or `SettingGroup` causes "Promise returned in function argument" errors.
**When This Applies**: This only affects plugins using `SettingGroup` (API 1.11.0+) or the `createSettingsGroup()` compatibility utility. If you use `new Setting(containerEl)` directly (the most common pattern), you don't have this issue.
**Wrong**:
```typescript
group.addSetting(setting =>
setting.setName("Feature").addToggle(toggle => {
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
});
})
);
```
**Why It Fails**: The expression body returns the result of the chain (a `Setting` object), but `addSetting` expects a callback that returns `void`. The type signature is `addSetting(cb: (setting: Setting) => void)`, so expression body syntax violates this contract.
**Correct**:
```typescript
group.addSetting(setting => {
setting.setName("Feature").addToggle(toggle => {
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
});
});
});
```
**Rule**: Always use block body `{ }` with `addSetting` when using `createSettingsGroup()`. This ensures the callback returns `void` as expected.
**See also**: [linting-fixes-guide.md](linting-fixes-guide.md#critical-addsetting-callbacks-must-return-void) for detailed explanation and troubleshooting.
**ESLint rule**: `no-floating-promises` / `promise-return-in-void-context` (from `eslint-plugin-obsidianmd`)
### Disabling TypeScript Rules for `any`
**Problem**: Disabling `@typescript-eslint/no-explicit-any` defeats TypeScript's type safety.
**Wrong**:
```ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function process(data: any) { }
```
**Correct**: Use proper types or `unknown`:
```ts
function process(data: unknown) {
if (typeof data === 'object' && data !== null) {
// Type guard and process
}
}
```
### Async Functions Without Await
**Problem**: Marking functions as `async` but never using `await` is unnecessary and can cause confusion. The Obsidian review bot flags this as an error.
**Why it matters**: The Obsidian review bot enforces this rule. Your local ESLint should be configured to catch this before submission.
**When to use `async`**:
- ✅ When you use `await` inside the function
- ✅ When you return a Promise that callers need to await
- ❌ **NOT** when you only call synchronous functions
- ❌ **NOT** when you use synchronous Node.js APIs (fs.existsSync, dialog.showOpenDialogSync)
- ❌ **NOT** for callbacks that don't need to be async
**Wrong**:
```ts
async handleClick() {
this.doSomething(); // No await needed
}
// Using synchronous APIs
async selectFolder() {
const result = dialog.showOpenDialogSync({ ... }); // Synchronous API!
return result;
}
```
**Correct**: Remove `async` if not needed, or use `await`:
```ts
handleClick() {
this.doSomething();
}
// Synchronous APIs don't need async
selectFolder() {
const result = dialog.showOpenDialogSync({ ... });
return result;
}
// Or if you need async:
async handleClick() {
await this.doSomethingAsync();
}
```
**Common patterns that don't need async**:
- Callbacks that just call synchronous functions
- Functions using `fs.existsSync()`, `fs.statSync()`, `dialog.showOpenDialogSync()`
- Render methods that don't use await
- Wrapper functions that call synchronous methods
**ESLint Configuration**: Ensure your `eslint.config.mjs` includes:
```javascript
"@typescript-eslint/require-await": "error"
```
### Awaiting Non-Promise Values
**Problem**: Using `await` on values that aren't Promises is unnecessary and can cause confusion.
**Wrong**:
```ts
const value = await this.getSetting(); // getSetting() returns string, not Promise
```
**Correct**: Only await Promises:
```ts
const value = this.getSetting(); // No await needed
```
### Console Statements
**Problem**: Using `console.log()` in production code. Only `console.warn()`, `console.error()`, and `console.debug()` are allowed.
**Why it matters**: The Obsidian review bot enforces this restriction. Your local ESLint should be configured to catch this before submission.
**Wrong**:
```ts
console.log("Debug info");
```
**Correct**: Use appropriate console method:
```ts
console.debug("Debug info"); // For development debugging
console.warn("Warning message");
console.error("Error message");
```
**ESLint Configuration**: Ensure your `eslint.config.mjs` includes:
```javascript
"no-console": ["error", { "allow": ["warn", "error", "debug"] }]
```
### Using Deprecated APIs
**Problem**: Using deprecated Obsidian APIs that may be removed in future versions.
**Example**: `activeLeaf` is deprecated. Use `getActiveViewOfType()` or `getLeaf()` instead.
**Wrong**:
```ts
const leaf = this.app.workspace.activeLeaf;
```
**Correct**:
```ts
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
// Or for navigation:
const leaf = this.app.workspace.getLeaf();
```
**Solution**: Always check `.ref/obsidian-api/obsidian.d.ts` for deprecated warnings and use recommended alternatives.
### Object Stringification Issues
**Problem**: Stringifying objects directly results in `[object Object]` instead of useful output.
**Wrong**:
```ts
const tags = { tag1: 'value1', tag2: 'value2' };
console.log(`Tags: ${tags}`); // Outputs: "Tags: [object Object]"
```
**Correct**: Use `JSON.stringify()` or access specific properties:
```ts
console.log(`Tags: ${JSON.stringify(tags)}`);
// Or:
console.log(`Tags: ${Object.keys(tags).join(', ')}`);
```
### Unnecessary Type Assertions
**Problem**: Type assertions that don't change the type are unnecessary and should be removed.
**Wrong**:
```ts
const value: string = "test";
const result = value as string; // Unnecessary - value is already string
```
**Correct**: Remove unnecessary assertions:
```ts
const value: string = "test";
const result = value; // No assertion needed
```
### Promise Rejection Should Be Error
**Problem**: Rejecting Promises with non-Error values makes error handling difficult.
**Wrong**:
```ts
Promise.reject("Something went wrong");
```
**Correct**: Always reject with Error objects:
```ts
Promise.reject(new Error("Something went wrong"));
```
### Using require() Instead of import
**Problem**: CommonJS `require()` style imports are not allowed in modern TypeScript/ES modules.
**Wrong**:
```ts
const fs = require('fs');
```
**Correct**: Use ES module imports:
```ts
import * as fs from 'fs';
```
### Using hasOwnProperty Directly
**Problem**: Accessing `hasOwnProperty` directly from objects can fail if the object has a null prototype.
**Wrong**:
```ts
if (obj.hasOwnProperty('key')) { }
```
**Correct**: Use `Object.prototype.hasOwnProperty.call()` or `Object.hasOwn()`:
```ts
if (Object.prototype.hasOwnProperty.call(obj, 'key')) { }
// Or (modern):
if (Object.hasOwn(obj, 'key')) { }
```
### Type Casting to TFile or TFolder
**Problem**: Type casting to `TFile` or `TFolder` is unsafe and can cause runtime errors.
**Wrong**:
```ts
const file = abstractFile as TFile;
file.basename; // May fail if abstractFile is actually a TFolder
```
**Correct**: Use `instanceof` checks to safely narrow the type:
```ts
if (abstractFile instanceof TFile) {
abstractFile.basename; // Safe - TypeScript knows it's a TFile
} else if (abstractFile instanceof TFolder) {
// Handle folder case
}
```
**ESLint rule**: `no-tfile-tfolder-cast` (from `eslint-plugin-obsidianmd`)
### Iterating All Files to Find by Path
**Problem**: Iterating through all files to find one by path is inefficient and slow.
**Wrong**:
```ts
const file = this.app.vault.getFiles().find(f => f.path === 'path/to/file.md');
```
**Correct**: Use `getAbstractFileByPath()` for direct lookup:
```ts
const file = this.app.vault.getAbstractFileByPath('path/to/file.md');
if (file instanceof TFile) {
// Use file
}
```
**ESLint rule**: `vault/iterate` (from `eslint-plugin-obsidianmd`) - automatically fixable
### Using Navigator API for OS Detection
**Problem**: Using `navigator` API for OS detection is unreliable and not recommended.
**Wrong**:
```ts
const isMac = navigator.platform.includes('Mac');
```
**Correct**: Use Obsidian's built-in platform detection:
```ts
// Check if mobile
if (this.app.isMobile) { }
// Check platform via app (if available in API)
// Or use feature detection instead of OS detection
```
**ESLint rule**: `platform` (from `eslint-plugin-obsidianmd`)
### Using Custom TextInputSuggest Instead of AbstractInputSuggest
**Problem**: Using a custom `TextInputSuggest` implementation when Obsidian provides `AbstractInputSuggest`.
**Wrong**: Copying a custom `TextInputSuggest` implementation.
**Correct**: Use Obsidian's built-in `AbstractInputSuggest`:
```ts
import { AbstractInputSuggest } from 'obsidian';
class MySuggest extends AbstractInputSuggest<string> {
// Implement required methods
getSuggestions(inputStr: string): string[] {
// Return suggestions
}
renderSuggestion(item: string, el: HTMLElement): void {
// Render suggestion
}
selectSuggestion(item: string): void {
// Handle selection
}
}
```
**ESLint rule**: `prefer-abstract-input-suggest` (from `eslint-plugin-obsidianmd`)
### Using Regex Lookbehinds
**Problem**: Regex lookbehinds are not supported in some iOS versions, causing plugins to fail on mobile.
**Wrong**:
```ts
const regex = /(?<=prefix)match/; // Lookbehind not supported on iOS
```
**Correct**: Rewrite regex without lookbehinds:
```ts
const regex = /prefix(match)/; // Use capturing group instead
const match = text.match(regex);
if (match) {
const result = match[1]; // Get captured group
}
```
**ESLint rule**: `regex-lookbehind` (from `eslint-plugin-obsidianmd`)
### Pop-Out Windows Compatibility Issues
**Source**: Based on [Supporting Pop-Out Windows guide](https://docs.obsidian.md/plugins/guides/supporting-pop-out-windows) (available since Obsidian v0.15.0)
**Problem**: Pop-out windows have separate `Window` and `Document` objects, causing `instanceof` checks and global references to fail.
**Wrong**:
```ts
// This will fail in pop-out windows
if (myElement instanceof HTMLElement) {
// ...
}
// This always appends to main window, not the element's window
document.body.appendChild(myElement);
// This fails in pop-out windows
if (event instanceof MouseEvent) {
// ...
}
```
**Correct**: Use Obsidian's cross-window compatibility APIs:
```ts
// Use instanceOf() instead of instanceof
if (myElement.instanceOf(HTMLElement)) {
// Works correctly in pop-out windows
}
// Use element.doc instead of document
myElement.doc.body.appendChild(myElement);
// Use event.instanceOf() instead of instanceof
element.on('click', '.my-class', (event) => {
if (event.instanceOf(MouseEvent)) {
// Works correctly in pop-out windows
}
});
// Use activeWindow and activeDocument for current focused window
activeDocument.body.appendChild(newElement);
// Use element.win and element.doc for element's window/document
someElement.doc.body.appendChild(myElement);
```
**Key APIs for Pop-Out Windows**:
- `element.instanceOf(Type)` - Cross-window compatible `instanceof` check
- `element.win` - The `Window` object the element belongs to
- `element.doc` - The `Document` object the element belongs to
- `activeWindow` - Global variable pointing to currently focused window
- `activeDocument` - Global variable pointing to currently focused document
- `HTMLElement.onWindowMigrated(callback)` - Hook for when element moves to different window
**When to use**: If your plugin creates DOM elements, handles events, or uses `instanceof` checks, you should use these APIs to ensure compatibility with pop-out windows.
**See also**: [Supporting Pop-Out Windows guide](https://docs.obsidian.md/plugins/guides/supporting-pop-out-windows)
## Obsidian Bot Review Requirements
The Obsidian review bot has stricter requirements than local linting. This section covers general pitfalls related to bot review. For comprehensive bot requirements and detailed guidance, see the authoritative sources below.
**Key Bot Requirements** (see [obsidian-bot-requirements.md](obsidian-bot-requirements.md) for complete details):
- Console statements: Only `console.debug()`, `console.warn()`, `console.error()` allowed
- Async functions: Must use `await` or remove `async` keyword
- ESLint disable comments: Must include descriptions, cannot disable certain rules
- Sentence-case: Cannot be disabled in code (use `/skip` in bot review for false positives)
**For detailed information, see**:
- **[obsidian-bot-requirements.md](obsidian-bot-requirements.md)** - Complete bot requirements (authoritative source)
- **[linting-fixes-guide.md](linting-fixes-guide.md)** - Step-by-step fix procedures for linting issues
- **[release-readiness.md](release-readiness.md)** - Pre-submission checklist
**Note**: The sections above in this document cover general development pitfalls. For bot-specific requirements and detailed fix procedures, refer to the authoritative sources listed above.

View file

@ -1,872 +0,0 @@
<!--
Source: Created to document fixes for Obsidian plugin linting issues
Last synced: N/A - Project-specific documentation
Update frequency: Update as new linting issues are identified
Applicability: Plugin
-->
# Linting Fixes Guide
**Purpose**: This guide provides **step-by-step fix procedures** for common linting issues detected by `eslint-plugin-obsidianmd`. Use this when you need to fix specific linting errors in your plugin code.
**Related documentation**:
- [obsidian-bot-requirements.md](obsidian-bot-requirements.md) - Bot requirements (authoritative source)
- [common-pitfalls.md](common-pitfalls.md) - General development pitfalls
- [release-readiness.md](release-readiness.md) - Pre-submission checklist
## Table of Contents
1. [Promise Handling](#promise-handling)
2. [Command ID and Name](#command-id-and-name)
3. [Style Element Creation](#style-element-creation)
4. [Direct Style Manipulation](#direct-style-manipulation)
5. [Unnecessary Type Assertions](#unnecessary-type-assertions)
6. [Promise Return in Void Context](#promise-return-in-void-context)
7. [Object Stringification](#object-stringification)
8. [Navigator API Usage](#navigator-api-usage)
9. [Unused Imports](#unused-imports)
---
## Promise Handling
**Issue**: Promises must be awaited, have `.catch()`, or be marked with `void` operator.
**Error Message**: "Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator."
### Fix Options
#### Option 1: Await the Promise (Recommended)
```typescript
// ❌ Wrong
this.loadData();
this.saveData(this.settings);
// ✅ Correct
await this.loadData();
await this.saveSettings();
```
#### Option 2: Add Error Handling with .catch()
```typescript
// ❌ Wrong
this.loadData();
// ✅ Correct
this.loadData().catch((error) => {
console.error("Failed to load data:", error);
});
```
#### Option 3: Mark as Intentionally Ignored with void
```typescript
// ❌ Wrong
this.loadData();
// ✅ Correct (when you intentionally want to fire-and-forget)
void this.loadData();
```
### Common Patterns
**Settings Loading:**
```typescript
// ❌ Wrong
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, this.loadData());
}
// ✅ Correct
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
```
**Settings Saving:**
```typescript
// ❌ Wrong
onChange(async (value) => {
this.plugin.settings.enabled = value;
this.plugin.saveData(this.plugin.settings);
});
// ✅ Correct
onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveData(this.plugin.settings);
});
```
**File Operations:**
```typescript
// ❌ Wrong
this.app.vault.create("path/to/file.md", "content");
// ✅ Correct
await this.app.vault.create("path/to/file.md", "content");
```
---
## Command ID and Name
**Issue**: Command ID should not include plugin ID, command name should not include plugin name, and UI text should use sentence case.
**Error Messages**:
- "The command ID should not include the plugin ID. Obsidian will make sure that there are no conflicts with other plugins."
- "The command name should not include the plugin name, the plugin name is already shown next to the command name in the UI."
- "Use sentence case for UI text."
### Fix
```typescript
// ❌ Wrong
this.addCommand({
id: "obsidian-ui-tweaker-toggle-sidebar",
name: "Obsidian UI Tweaker: Toggle Sidebar",
callback: () => { /* ... */ }
});
// ✅ Correct
this.addCommand({
id: "toggle-sidebar",
name: "Toggle sidebar",
callback: () => { /* ... */ }
});
```
### Sentence Case Rules
- **First word capitalized**: "Toggle sidebar" ✅
- **Proper nouns capitalized**: "Open GitHub repository" ✅
- **No title case**: "Toggle Sidebar" ❌
- **No all caps**: "TOGGLE SIDEBAR" ❌
### Sentence Case - Cannot Be Disabled in Code
**CRITICAL**: The `obsidianmd/ui/sentence-case` rule **cannot be disabled with eslint-disable comments**. The Obsidian bot will reject any attempt to disable this rule in code.
**However**, the Obsidian bot does allow you to use `/skip` in bot review comments to handle legitimate false positives. This is the only way to handle cases where the bot incorrectly flags text that is already correct.
**When to fix text vs. use `/skip`**:
1. **Fix the text** if:
- The text is actually not in proper sentence case
- You can rephrase to avoid the false positive
- The issue is a simple capitalization error
2. **Use `/skip` in bot review** if:
- The text is already correct but the bot flags it as incorrect
- The text contains proper nouns (framework names, product names) that must be capitalized
- The text contains technical notation (date format codes, file paths) that cannot be changed
- Rephrasing would make the text less clear or accurate
- The bot is incorrectly interpreting context
**If the linter flags text that appears to be correct**, you have two options:
1. Try to rephrase the text to avoid the false positive (preferred)
2. Use `/skip` in the bot review comment with an explanation (acceptable for legitimate false positives)
#### Common Scenarios Requiring Text Fixes
### 1. Proper Nouns (Framework/Product Names)
When proper nouns like framework or product names appear in the middle of sentences, ensure they are capitalized correctly:
```typescript
// ❌ Wrong - Cannot disable, must fix text
// eslint-disable-next-line obsidianmd/ui/sentence-case // ❌ Bot will reject this!
.setDesc("Choose the default format for copied heading links. Obsidian format respects your Obsidian settings for wikilink vs markdown preference. Astro link uses your link base path from above and converts the heading into kebab-case format as an anchor link")
// ✅ Correct - Fix text to proper sentence case with capitalized proper nouns
.setDesc("Choose the default format for copied heading links. Obsidian format respects your Obsidian settings for wikilink vs markdown preference. Astro link uses your link base path from above and converts the heading into kebab-case format as an anchor link")
// Note: "Astro" is a proper noun and should remain capitalized
```
### 2. Date/Time Format Codes
Date format placeholders and format codes (like "YYYY-MM-DD", "MMMM", "yyyy") are technical notation. The linter may flag these, but you cannot disable the rule. Ensure the surrounding text is in proper sentence case:
```typescript
// ❌ Wrong - Cannot disable, must ensure text is correct
// eslint-disable-next-line obsidianmd/ui/sentence-case // ❌ Bot will reject this!
.setPlaceholder("YYYY-MM-DD")
.setDesc("Format for the date in properties (e.g., yyyy-mm-dd, MMMM D, yyyy, yyyy-mm-dd HH:mm)")
// ✅ Correct - Text is already in proper sentence case
// Format codes like "YYYY-MM-DD" and "MMMM" are technical notation and acceptable
.setPlaceholder("YYYY-MM-DD")
.setDesc("Format for the date in properties (e.g., yyyy-mm-dd, MMMM D, yyyy, yyyy-mm-dd HH:mm)")
// Note: If the linter still flags this, you may need to rephrase the description
```
### 3. Technical Notation and Code Examples
When descriptions contain code examples, file paths, or technical notation:
```typescript
// ❌ Linter error (false positive)
.setDesc("Path relative to the Obsidian vault root folder. Use ../.. for two levels up. Leave blank to use the vault folder")
// ✅ Correct - Suppress with explanation
// False positive: Text is already in sentence case; "Obsidian" is a proper noun and "../.." is a path example
// eslint-disable-next-line obsidianmd/ui/sentence-case
.setDesc("Path relative to the Obsidian vault root folder. Use ../.. for two levels up. Leave blank to use the vault folder")
```
### 4. Dropdown Options with Proper Nouns
Dropdown option labels that include proper nouns:
```typescript
// ❌ Linter error (false positive)
.addOption("astro", "Astro link")
// ✅ Correct - Suppress with explanation
// False positive: "Astro" is a proper noun (framework name) and should be capitalized
// eslint-disable-next-line obsidianmd/ui/sentence-case
.addOption("astro", "Astro link")
```
#### How to Handle Sentence Case Issues
**⚠️ CRITICAL: Sentence Case Rule Cannot Be Disabled in Code**
**The Obsidian bot will reject any attempt to disable `obsidianmd/ui/sentence-case` with eslint-disable comments**. However, you can use `/skip` in bot review comments for legitimate false positives.
**Step 1: Verify the text is correct**
- First word capitalized
- Rest lowercase except proper nouns
- Proper nouns (framework names, product names) should be capitalized
- Technical notation (date codes, file paths) may need special handling
**Step 2: Try to fix the text** (preferred):
- Rephrase to avoid the false positive
- Use code formatting for technical notation
- Restructure sentences if needed
**Step 3: If text is already correct but bot flags it** (legitimate false positive):
- You cannot disable the rule in code
- Use `/skip` in the bot review comment with an explanation
- Example: `/skip False positive: "Astro" is a proper noun (framework name) and must be capitalized`
3. **Common scenarios**:
- Proper nouns (framework names, product names, company names) - Keep capitalized
- Technical notation (date format codes, file paths, code examples) - May need rephrasing or code formatting
- Placeholders that are format strings - Consider using code formatting or rephrasing
#### Fixing Sentence Case Issues
**Remember: You cannot disable this rule. You must fix the text.**
**Example: Fixing text to proper sentence case**
```typescript
// ❌ Wrong - Cannot disable, must fix text
// eslint-disable-next-line obsidianmd/ui/sentence-case // ❌ Bot will reject!
.setDesc('Display the button in the CMS toolbar.')
// ✅ Correct - Text is already in proper sentence case
.setDesc('Display the button in the CMS toolbar.')
```
**Example: Handling proper nouns**
```typescript
// ❌ Wrong - Cannot disable
// eslint-disable-next-line obsidianmd/ui/sentence-case // ❌ Bot will reject!
.setDesc('Choose the default format. Astro link uses your link base path.')
// ✅ Correct - Proper nouns (Astro) remain capitalized, rest is sentence case
.setDesc('Choose the default format. Astro link uses your link base path.')
```
**Example: Handling technical notation**
```typescript
// ❌ Wrong - Cannot disable
// eslint-disable-next-line obsidianmd/ui/sentence-case // ❌ Bot will reject!
.setDesc('Format for the date in properties (e.g., yyyy-mm-dd, MMMM D, yyyy)')
// ✅ Correct - Rephrase to avoid false positives, or ensure text is correct
.setDesc('Format for the date in properties. Examples: yyyy-mm-dd, MMMM D, yyyy')
// Or use code formatting for technical notation:
.setDesc('Format for the date in properties. Use format codes like `yyyy-mm-dd` or `MMMM D, yyyy`')
```
#### When NOT to Use Disable Comments
**Do NOT use disable comments to:**
- Skip fixing legitimate errors
- Avoid refactoring problematic code
- Work around type safety issues
- Suppress warnings you don't understand
**Only use disable comments when:**
- The text is already correct and the linter is wrong
- You've verified the text follows all formatting rules
- You can clearly explain why it's a false positive
- The error cannot be fixed by changing the code
---
## Style Element Creation
**Issue**: Creating and attaching `<style>` elements is not allowed. Use `styles.css` instead.
**Error Message**: "Creating and attaching 'style' elements is not allowed. For loading CSS, use a 'styles.css' file instead, which Obsidian loads for you."
### Fix
```typescript
// ❌ Wrong
onload() {
const style = document.createElement("style");
style.textContent = `
.my-class {
color: red;
}
`;
document.head.appendChild(style);
}
// ✅ Correct
// Move CSS to styles.css file:
// .my-class {
// color: red;
// }
// Then in your code, just use the class:
onload() {
// No style element creation needed
// Obsidian automatically loads styles.css
}
```
**Note**: If you need dynamic styles, use CSS custom properties (CSS variables) and update them with `setCssProps()`:
```typescript
// ✅ Correct - Using CSS variables
// In styles.css:
// .my-element {
// color: var(--dynamic-color);
// }
// In TypeScript:
import { setCssProps } from "obsidian";
setCssProps(element, {
"--dynamic-color": "red"
});
```
---
## Direct Style Manipulation
**Issue**: Avoid setting styles directly via `element.style.*`. Use CSS classes or `setCssProps()`.
**Error Messages**:
- "Avoid setting styles directly via `element.style.display`. Use CSS classes for better theming and maintainability. Use the `setCssProps` function to change CSS properties."
- "Avoid setting styles directly via `element.style.setProperty`. Use CSS classes for better theming and maintainability. Use the `setCssProps` function to change CSS properties."
### Fix Options
#### Option 1: Use CSS Classes (Recommended)
```typescript
// ❌ Wrong
element.style.display = "block";
element.style.opacity = "0.5";
// ✅ Correct
// In styles.css:
// .visible {
// display: block;
// opacity: 0.5;
// }
// In TypeScript:
element.addClass("visible");
element.removeClass("hidden");
```
#### Option 2: Use setCssProps() for Dynamic Styles
```typescript
// ❌ Wrong
element.style.display = "block";
element.style.setProperty("margin-top", "10px");
// ✅ Correct
import { setCssProps } from "obsidian";
setCssProps(element, {
display: "block",
marginTop: "10px"
});
```
### Common Patterns
**Show/Hide Elements:**
```typescript
// ❌ Wrong
element.style.display = "none";
element.style.display = "block";
// ✅ Correct - Using CSS classes
element.addClass("hidden");
element.removeClass("hidden");
// In styles.css:
// .hidden {
// display: none !important;
// }
```
**Dynamic Values:**
```typescript
// ❌ Wrong
element.style.setProperty("--custom-property", value);
// ✅ Correct
import { setCssProps } from "obsidian";
setCssProps(element, {
"--custom-property": value
});
```
---
## Unnecessary Type Assertions
**Issue**: Type assertions that don't change the type are unnecessary.
**Error Message**: "This assertion is unnecessary since it does not change the type of the expression."
### Fix
```typescript
// ❌ Wrong
const value: string = "test";
const result = value as string; // Unnecessary
// ✅ Correct
const value: string = "test";
const result = value; // No assertion needed
```
### When Type Assertions Are Needed
Type assertions are only needed when you're narrowing or widening the type:
```typescript
// ✅ Correct - Narrowing from unknown
const data: unknown = getData();
const result = data as MyType;
// ✅ Correct - Widening for API compatibility
const element = document.createElement("div") as HTMLElement;
```
---
## Promise Return in Void Context
**Issue**: Promise returned in function argument where a void return was expected.
**Error Message**: "Promise returned in function argument where a void return was expected."
### Fix Options
#### Option 1: Add void Operator
```typescript
// ❌ Wrong
new Setting(containerEl)
.addToggle((toggle) =>
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
})
);
// ✅ Correct
new Setting(containerEl)
.addToggle((toggle) =>
toggle.onChange(async (value) => {
void this.plugin.saveData(this.plugin.settings);
})
);
```
#### Option 2: Make Callback Async and Await
```typescript
// ❌ Wrong
new Setting(containerEl)
.addToggle((toggle) =>
toggle.onChange((value) => {
this.plugin.saveData(this.plugin.settings);
})
);
// ✅ Correct - Works with direct Setting usage
new Setting(containerEl)
.addToggle((toggle) =>
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
})
);
```
**Important**: Option 2 works with direct `Setting` usage, but **does NOT work** with `addSetting` from `createSettingsGroup()`:
```typescript
// ❌ FAILS with addSetting from createSettingsGroup
group.addSetting(setting =>
setting.addToggle(toggle =>
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
})
)
);
// ✅ CORRECT - Use block body for addSetting
group.addSetting(setting => {
setting.addToggle(toggle => {
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
});
});
});
```
**Why**: `addSetting` expects `(setting: Setting) => void`, but expression body returns the `Setting` object from the chain. You MUST use block body `{ }` with `addSetting`.
### Common Patterns
**Settings Tab Callbacks:**
```typescript
// ❌ Wrong
new Setting(containerEl)
.setName("Enable feature")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange((value) => {
this.plugin.settings.enabled = value;
this.plugin.saveData(this.plugin.settings);
})
);
// ✅ Correct
new Setting(containerEl)
.setName("Enable feature")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveData(this.plugin.settings);
})
);
```
---
## Critical: addSetting Callbacks Must Return Void
**Issue**: When using `createSettingsGroup().addSetting()` or `SettingGroup.addSetting()`, the callback MUST return `void`, not a `Setting` object.
**When This Applies**: This issue **only** affects plugins using `SettingGroup` (API 1.11.0+) or the `createSettingsGroup()` compatibility utility. If you're using `new Setting(containerEl)` directly (the most common pattern), you don't have this issue.
**Error Message**: "Promise returned in function argument where a void return was expected" on the `addSetting` line.
**Root Cause**: Expression body arrow functions return the result of the expression. When you chain methods like `setting.setName(...).addToggle(...)`, the expression returns the `Setting` object (for method chaining), but `addSetting` expects a callback that returns `void`.
**❌ Wrong - Expression Body (Returns Setting)**:
```typescript
group.addSetting(setting =>
setting
.setName("Enable feature")
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.enabled);
toggle.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveData(this.plugin.settings);
});
})
);
```
**✅ Correct - Block Body (Returns Void)**:
```typescript
group.addSetting(setting => {
setting
.setName("Enable feature")
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.enabled);
toggle.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveData(this.plugin.settings);
});
});
});
```
**Key Difference**:
- **Expression body**: `setting => setting.setName(...)` - Returns the result of the chain (a `Setting` object)
- **Block body**: `setting => { setting.setName(...); }` - Explicitly returns `void`
**Rule**: Always use block body `{ }` with semicolons when using `addSetting` from `createSettingsGroup()` or `SettingGroup`.
**Note**: This may only fail with strict ESLint rules, but using block body is safer, clearer, and prevents potential type errors.
**Direct Setting Usage (No Issue)**:
```typescript
// ✅ This works fine - no addSetting callback
new Setting(containerEl)
.setName("Enable feature")
.addToggle((toggle) =>
toggle.onChange(async (value) => {
await this.plugin.saveData(this.plugin.settings);
})
);
```
Most plugins use `new Setting(containerEl)` directly, which doesn't have this restriction. The issue only applies when using `SettingGroup` or the compatibility utility.
---
## Troubleshooting: When Fixes Don't Work
**Problem**: You've tried making `onChange` async, added `void` operators, but still get "Promise returned in function argument" errors.
**Check**: Is the error on the `addSetting` line or the `onChange` line?
- **Error on `addSetting` line**: The callback itself is returning a value (like `Setting`) instead of `void`. Use block body `{ }` instead of expression body.
- **Error on `onChange` line**: The `onChange` callback is returning a Promise. Make it async and await, or use `void` operator.
**Common Mistake**: Adding eslint-disable comments instead of fixing the root cause. The disable comment should be on the EXACT line with the error, but it's better to fix the actual issue.
**Debugging Steps**:
1. Check which line the error is on (column number matters)
2. If it's the `addSetting` callback, ensure it uses block body `{ }`
3. If it's the `onChange` callback, ensure it's properly async or uses `void`
4. Run `pnpm lint` after each change to verify
5. Never suppress errors without understanding the root cause
---
## Object Stringification
**Issue**: Objects may use default stringification format `[object Object]` when stringified.
**Error Message**: "'this.plugin.settings[key]' may use Object's default stringification format ('[object Object]') when stringified."
### Fix
```typescript
// ❌ Wrong
const settings = { key1: "value1", key2: "value2" };
console.log(`Settings: ${settings}`); // Outputs: "Settings: [object Object]"
// ✅ Correct - Use JSON.stringify()
console.log(`Settings: ${JSON.stringify(settings)}`);
// Outputs: "Settings: {\"key1\":\"value1\",\"key2\":\"value2\"}"
// ✅ Correct - Access specific properties
console.log(`Key1: ${settings.key1}, Key2: ${settings.key2}`);
// Outputs: "Key1: value1, Key2: value2"
// ✅ Correct - For arrays of objects
const items = [{ name: "item1" }, { name: "item2" }];
console.log(`Items: ${items.map(item => item.name).join(", ")}`);
// Outputs: "Items: item1, item2"
```
### Common Patterns
**Settings Display:**
```typescript
// ❌ Wrong
new Setting(containerEl)
.setDesc(`Current value: ${this.plugin.settings[key]}`);
// ✅ Correct
new Setting(containerEl)
.setDesc(`Current value: ${JSON.stringify(this.plugin.settings[key])}`);
// ✅ Better - If it's a simple value
new Setting(containerEl)
.setDesc(`Current value: ${String(this.plugin.settings[key] || "")}`);
```
---
## Navigator API Usage
**Issue**: Avoid using `navigator` API to detect the operating system. Use the Platform API instead.
**Error Messages**:
- "Avoid using the navigator API to detect the operating system. Use the Platform API instead."
- "`platform` is deprecated."
### Fix
```typescript
// ❌ Wrong
const isMac = navigator.platform.includes("Mac");
const isWindows = navigator.platform.includes("Win");
// ✅ Correct - Use Obsidian's Platform API
import { Platform } from "obsidian";
if (Platform.isMacOS) {
// Mac-specific code
} else if (Platform.isWin) {
// Windows-specific code
} else if (Platform.isLinux) {
// Linux-specific code
}
// ✅ Correct - Check mobile
if (this.app.isMobile) {
// Mobile-specific code
} else {
// Desktop-specific code
}
```
### Platform Detection Patterns
```typescript
import { Platform } from "obsidian";
// Check specific platform
if (Platform.isMacOS) { /* ... */ }
if (Platform.isWin) { /* ... */ }
if (Platform.isLinux) { /* ... */ }
if (Platform.isAndroidApp) { /* ... */ }
if (Platform.isIOSApp) { /* ... */ }
// Check mobile vs desktop
if (this.app.isMobile) { /* ... */ }
// Check desktop platform
if (Platform.isDesktop) {
if (Platform.isMacOS) { /* Mac */ }
else if (Platform.isWin) { /* Windows */ }
else if (Platform.isLinux) { /* Linux */ }
}
```
---
## Unused Imports
**Issue**: Imported module is defined but never used.
**Error Message**: "'Setting' is defined but never used." (or similar for other imports)
### Fix
```typescript
// ❌ Wrong
import { Setting, Plugin } from "obsidian";
// Setting is never used
// ✅ Correct - Remove unused import
import { Plugin } from "obsidian";
```
### Auto-fix
Most IDEs and ESLint can auto-remove unused imports:
```bash
# Run ESLint with --fix flag
pnpm lint:fix
```
Or configure your IDE to remove unused imports on save.
---
## Quick Reference: Common Fixes Summary
| Issue | Quick Fix |
|-------|-----------|
| Promise not awaited | Add `await` or `void` operator |
| Command ID includes plugin ID | Remove plugin ID prefix |
| Command name includes plugin name | Remove plugin name prefix |
| Not sentence case | Use sentence case (first word capitalized) |
| Creating style elements | Move CSS to `styles.css` |
| Direct style manipulation | Use CSS classes or `setCssProps()` |
| Unnecessary type assertion | Remove `as Type` assertion |
| Promise in void context | Add `void` operator or make async |
| Object stringification | Use `JSON.stringify()` or access properties |
| Navigator API | Use `Platform` from Obsidian |
| Unused import | Remove unused import |
---
## Setting Up ESLint
### Installation
```bash
pnpm add -D eslint eslint-plugin-obsidianmd
```
### Configuration
Update your `.eslintrc` file:
```json
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint",
"obsidianmd"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
}
}
```
### Version Compatibility Note
If you encounter dependency conflicts, you may need to:
- Update TypeScript to 4.8.4 or higher
- Update `@typescript-eslint/eslint-plugin` and `@typescript-eslint/parser` to compatible versions
- Install missing peer dependencies like `@typescript-eslint/utils` or `@eslint/json`
The exact versions depend on your project's setup. If issues persist, check the [eslint-plugin-obsidianmd documentation](https://github.com/obsidianmd/eslint-plugin).
## Running ESLint
After setting up ESLint (see [environment.md](environment.md)), run:
```bash
# Check for issues
pnpm lint
# Auto-fix issues where possible
pnpm lint:fix
# Check specific file
npx eslint src/main.ts
# Check specific directory
npx eslint src/
```
---
## Related Documentation
- [environment.md](environment.md) - ESLint setup instructions
- [common-pitfalls.md](common-pitfalls.md) - More common mistakes and gotchas
- [build-workflow.md](build-workflow.md) - Build commands and workflow
- [release-readiness.md](release-readiness.md) - Release checklist

View file

@ -6,9 +6,9 @@ Update frequency: Check Obsidian community discussions for updates
# 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.
- Keep CSS file size reasonable. Large themes can slow down Obsidian startup.
- Use CSS variables efficiently. Avoid excessive specificity.
- Minimize use of complex selectors that require expensive DOM queries.
- Test theme performance on lower-end devices, especially mobile.

View file

@ -1,49 +0,0 @@
# Pre-Commit Hooks (Optional)
Pre-commit hooks are scripts that run automatically before each commit. They can help catch linting errors before code is committed.
## What Are Pre-Commit Hooks?
Pre-commit hooks are Git hooks that run before a commit is finalized. If the hook fails, the commit is blocked until the issues are fixed.
**Example**: A pre-commit hook runs `pnpm lint`. If linting fails, the commit is rejected with an error message.
## How They Work
1. **Git hooks**: Git has a `.git/hooks/` directory with executable scripts
2. **Hook tools**: Tools like Husky make it easier to manage hooks
3. **Automatic execution**: Git runs the hook script before completing the commit
4. **Blocking commits**: If the hook exits with a non-zero code, the commit is blocked
## Should You Use Them?
**Pros**:
- ✅ Catches linting errors before commit
- ✅ Prevents committing broken code
- ✅ Enforces code quality standards
**Cons**:
- ❌ Adds a step to the commit process (can slow down quick commits)
- ❌ Requires setup in each repository
- ❌ Can be bypassed with `git commit --no-verify` (defeats the purpose)
## Recommendation
**Optional, not required**: Pre-commit hooks are a nice-to-have, but not essential. The current workflow (running `pnpm lint` manually before pushing) works well.
**If you want to add them**:
1. Install Husky: `pnpm add -D husky`
2. Initialize: `pnpm exec husky init`
3. Add pre-commit hook: Create `.husky/pre-commit` with `pnpm lint`
**Note**: This is optional. The ESLint configuration already catches these issues, and running `pnpm lint` before pushing is sufficient.
## Alternative: CI/CD Checks
Instead of pre-commit hooks, you can rely on:
- Running `pnpm lint` manually before pushing
- GitHub Actions or other CI/CD to run linting on pull requests
- The Obsidian bot review (final check before approval)
These approaches are less intrusive and work well for most workflows.

View file

@ -6,7 +6,7 @@ Update frequency: Update as workflows evolve
# Quick Reference
One-page cheat sheet for common Obsidian plugin development tasks.
One-page cheat sheet for common Obsidian theme development tasks.
## Quick Commands
@ -14,11 +14,11 @@ One-page cheat sheet for common Obsidian plugin development tasks.
| Command | Action |
|---------|--------|
| `build` | Run `pnpm build` to compile TypeScript |
| `build` | Run build command (varies by theme: `npx grunt build`, `npm run build`, etc.) |
| `sync` or `quick sync` | Pull latest changes from all 6 core `.ref` repos |
| `what's the latest` or `check updates` | Check what's new in reference repos (read-only, then ask to pull) |
| `release ready?` | Run comprehensive release readiness checklist |
| `summarize` | Generate git commit message from all changes since last tag (or uncommitted if no tag) |
| `release ready?` or `is my theme ready for release?` | Run comprehensive release readiness checklist |
| `summarize` | Generate git commit message from all changed files |
| `summarize for release` | Generate markdown release notes for GitHub |
| `bump the version` or `bump version` | Bump version by 0.0.1 (patch) by default, or specify: `patch`, `minor`, `major`, or exact version |
| `add ref [name]` | Add a reference project (external URL or local path) |
@ -27,95 +27,60 @@ One-page cheat sheet for common Obsidian plugin development tasks.
**Usage examples:**
- `build` → Runs build command automatically
- `sync` → Pulls latest from all core repos automatically
- `bump the version` → Bumps version by 0.0.1 (patch) in package.json and manifest.json
- `bump the version` → Bumps version by 0.0.1 (patch) in manifest.json
- `bump version minor` → Bumps minor version (e.g., 1.0.0 → 1.1.0)
- `bump version major` → Bumps major version (e.g., 1.0.0 → 2.0.0)
- `add ref my-plugin https://github.com/user/my-plugin.git` → Clones external repo
- `add ref ../my-local-plugin` → Creates symlink to local project
- `check API SettingGroup` → Searches obsidian.d.ts for SettingGroup
- `check API [feature]` → Searches obsidian.d.ts for feature (for theme CSS variables, etc.)
**Note**: These commands are interpreted by AI agents and execute the corresponding workflows automatically. See detailed documentation in [AGENTS.md](../../AGENTS.md) for full workflows.
## Build Commands
**Simple CSS themes**: No build step required - just edit `theme.css` directly.
**Complex themes with build tools**:
```powershell
pnpm build # Build plugin (compile TypeScript to JavaScript)
pnpm dev # Development build with watch mode
npx grunt build # For themes using Grunt
npm run build # For themes using npm scripts
npx grunt # Watch mode (auto-rebuild on changes)
```
**Always run build after making changes** to catch errors early. See [build-workflow.md](build-workflow.md).
## File Paths
**Plugin location** (in vault):
**Theme location** (in vault):
```
<Vault>/.obsidian/plugins/<plugin-id>/
├── main.js # Compiled plugin code
├── manifest.json # Plugin manifest
└── styles.css # Plugin styles (if any)
<Vault>/.obsidian/themes/<theme-name>/
├── theme.css # Compiled theme CSS
└── manifest.json # Theme manifest
```
**Build output**: Must be at top level of plugin folder in vault.
**Build output**: Must be at top level of theme folder in vault.
## Common API Patterns
## CSS Patterns
### Command
```ts
this.addCommand({
id: "command-id",
name: "Command name",
callback: () => { /* ... */ }
});
```
### Settings Tab
```ts
class MySettingTab extends PluginSettingTab {
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName("Setting")
.addText((text) => text.onChange(async (value) => {
this.plugin.settings.value = value;
await this.plugin.saveSettings();
}));
}
**CSS Variables** (for theming):
```css
:root {
--color-base-00: #ffffff;
--color-base-10: #f7f6f3;
--color-text-normal: #383a42;
}
this.addSettingTab(new MySettingTab(this.app, this));
```
### Settings Groups (Conditional for 1.11.0+)
```ts
// Use compatibility utility for backward compatibility
import { createSettingsGroup } from "./utils/settings-compat";
const group = createSettingsGroup(containerEl, "Group Name");
group.addSetting((setting) => {
setting.setName("Setting").addToggle(/* ... */);
});
```
See [code-patterns.md](../obsidian-dev/references/code-patterns.md) for full implementation.
### Modal
```ts
class MyModal extends Modal {
onOpen() { this.contentEl.setText("Content"); }
onClose() { this.contentEl.empty(); }
**Dark Mode**:
```css
.theme-dark {
--color-base-00: #1e1e1e;
--color-base-10: #252525;
--color-text-normal: #dcddde;
}
new MyModal(this.app).open();
```
### Status Bar
```ts
const item = this.addStatusBarItem();
item.setText("Status text");
```
### Ribbon Icon
```ts
this.addRibbonIcon("icon-name", "Tooltip", () => { /* ... */ });
```
See Obsidian's CSS variables documentation for complete variable list.
## Git Workflow
@ -139,13 +104,10 @@ this.addRibbonIcon("icon-name", "Tooltip", () => { /* ... */ });
## Release Preparation
**Before releasing** (plugins only):
**Before releasing**:
- Run release readiness check: See [release-readiness.md](release-readiness.md)
- Verify all checklist items (platform testing, files, policies, etc.)
- Ensure LICENSE file exists and third-party code is properly attributed
- **GitHub release tag format**: Tag must match `manifest.json` version exactly **WITHOUT** "v" prefix
- Correct: `0.1.0` (matches `manifest.json` version)
- Wrong: `v0.1.0` (with "v" prefix will NOT match)
See [versioning-releases.md](versioning-releases.md) for release process.
@ -167,74 +129,41 @@ cd eslint-plugin && git pull && cd ..
**Note**: If using symlinks, navigate to the actual target location (usually `..\.ref\obsidian-dev`) before running git commands. See [quick-sync-guide.md](quick-sync-guide.md) for setup detection.
## API Authority
## Reference Materials
**Always check `.ref/obsidian-api/obsidian.d.ts` first** - it's the authoritative source. Plugin docs and developer docs may be outdated.
**Check `.ref/obsidian-api/obsidian.d.ts`** for CSS variable definitions and Obsidian's internal structure (useful for advanced theming).
## Testing
**Manual installation**:
1. Build plugin (`pnpm build`)
2. Copy `main.js`, `manifest.json`, and `styles.css` (if any) to vault `.obsidian/plugins/<plugin-id>/`
3. Enable plugin in Obsidian: **Settings → Community plugins**
4. Reload Obsidian (Ctrl+R / Cmd+R)
1. Build theme (if using build tools) or ensure `theme.css` is ready
2. Copy `manifest.json` and `theme.css` to vault `.obsidian/themes/<theme-name>/`
3. Select theme in Obsidian: **Settings → Appearance → Themes**
4. Reload Obsidian (Ctrl+R / Cmd+R) to see changes
See [testing.md](testing.md) for details.
## Linting: Promise in Void Context
**Quick Fix Guide** - When you see "Promise returned in function argument where a void return was expected":
| Error Location | Cause | Fix |
|----------------|-------|-----|
| `addSetting` line (from `SettingGroup`/`createSettingsGroup`) | Callback returns `Setting` instead of `void` | Use block body `{ }` instead of expression body |
| `onChange` line | Callback returns Promise | Make async + await, or use `void` operator |
| `addToggle` line | Callback returns Promise | Use block body `{ }` |
**Quick Fix**: If error is on `addSetting`/`addToggle`, change `=>` to `=> { ... }`
**Example**:
```typescript
// ❌ Wrong - Expression body (only affects SettingGroup.addSetting)
group.addSetting(setting => setting.setName("Feature"));
// ✅ Correct - Block body
group.addSetting(setting => { setting.setName("Feature"); });
// ✅ This works fine - Direct Setting usage (most common pattern)
new Setting(containerEl)
.setName("Feature")
.addToggle(toggle => toggle.onChange(async (value) => { ... }));
```
**Note**: The `addSetting` issue only applies when using `SettingGroup` or `createSettingsGroup()`. Direct `new Setting(containerEl)` usage (the most common pattern) doesn't have this restriction.
See [linting-fixes-guide.md](linting-fixes-guide.md#critical-addsetting-callbacks-must-return-void) for detailed explanation.
## Common File Structure
**Simple CSS theme**:
```
src/
main.ts
settings.ts
commands/
ui/
theme.css # Source CSS (edit directly)
manifest.json
package.json
```
See [file-conventions.md](../obsidian-ref/references/file-conventions.md) for details.
**Complex theme with build tools**:
```
src/
scss/
index.scss
variables.scss
components/
theme.css # Compiled output
manifest.json
package.json
Gruntfile.js # Build configuration (if using Grunt)
```
## Obsidian File Formats
See [file-conventions.md](file-conventions.md) for details.
**For plugins that manipulate vault files**, see [obsidian-file-formats.md](../obsidian-ref/references/obsidian-file-formats.md) for comprehensive syntax documentation:
- **Markdown** (`.md`) - Obsidian Flavored Markdown with wikilinks, callouts, embeds, properties
- **Bases** (`.base`) - YAML-based database views with filters, formulas, summaries
- **Canvas** (`.canvas`) - JSON Canvas format for visual canvases
**Quick syntax reference:**
- Wikilinks: `[[Note Name]]`, `[[Note#Heading]]`, `[[Note#^block-id]]`
- Embeds: `![[Note]]`, `![[image.png|300]]`
- Callouts: `> [!note]`, `> [!tip] Title`
- Properties: YAML properties (always use "properties" in UI text)
- Tags: `#tag`, `#nested/tag`

View file

@ -2,7 +2,6 @@
Source: Project-specific quick reference
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Update as needed
Applicability: Both
-->
# Quick Sync Guide
@ -127,10 +126,7 @@ After pulling and reviewing changes:
1. **Compare** relevant files from `.ref/` with your `.agents/` files
2. **Update** `.agents/` files with new information
3. **Update sync status** (easy way):
```bash
node scripts/update-sync-status.mjs "Description of sync"
```
3. **Update** the "Last synced" date in file headers
4. **Commit** your changes
See [sync-procedure.md](sync-procedure.md) for the complete workflow.

View file

@ -1,24 +1,19 @@
<!--
Source: Based on Obsidian Developer Policies, Plugin Guidelines, and official release checklist
Source: Based on Obsidian Developer Policies, Theme Guidelines, and official release checklist
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Developer Policies and Plugin Guidelines for updates
Update frequency: Check Obsidian Developer Policies and Theme Guidelines for updates
-->
# Release Readiness Checklist
**Purpose**: This document provides a comprehensive checklist to verify your plugin is ready for release to the Obsidian community. Use this when preparing a release or when asked "is my plugin ready for release?"
This document provides a comprehensive checklist to verify your theme is ready for release to the Obsidian community. Use this when preparing a release or when asked "is my theme ready for release?"
**For AI Agents**: When a user asks about release readiness, run through this checklist systematically. Perform automated checks where possible, and ask the user interactively for items that require their input.
**Related documentation**:
- [obsidian-bot-requirements.md](obsidian-bot-requirements.md) - Bot requirements (authoritative source)
- [versioning-releases.md](versioning-releases.md) - Versioning and release process
- [common-pitfalls.md](common-pitfalls.md) - Common development pitfalls
## Quick Reference
- **Developer Policies**: [https://docs.obsidian.md/Developer+policies](https://docs.obsidian.md/Developer+policies)
- **Plugin Guidelines**: [https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines)
- **Developer Policies**: [Developer Policies](https://docs.obsidian.md/Developer+policies)
- **Theme Guidelines**: [Theme Guidelines](https://docs.obsidian.md/Themes/Releasing/Theme+guidelines)
- **Release Process**: See [versioning-releases.md](versioning-releases.md)
## Automated Checks (AI Can Verify)
@ -27,59 +22,49 @@ These checks can be performed automatically by reading files and scanning code:
### File Requirements
- [ ] **`main.js`** exists (compiled output)
- Check `main.js` in root (created by `pnpm build` or `pnpm dev`)
- **Note**: All builds output to `main.js` in the root directory
- [ ] **`theme.css`** exists in project root (or compiled from SCSS/Sass)
- For themes with build tools: Check that `theme.css` is generated correctly
- For simple themes: Check that `theme.css` exists and is valid CSS
- [ ] **`manifest.json`** exists in project root with valid JSON structure
- [ ] **`styles.css`** exists (if plugin uses custom styles) - optional but should be included if present
- [ ] **`LICENSE`** file exists in project root
- [ ] **`README.md`** exists in project root
### Manifest Validation
- [ ] **Required fields present**: `id`, `name`, `version`, `minAppVersion`, `description`, `isDesktopOnly`
- [ ] **`id` format**: lowercase with hyphens (e.g., `"my-plugin-name"`), matches folder name
- [ ] **Required fields present**: `name`, `version`, `minAppVersion`, `description`, `author`
- [ ] **`name` format**: Should match the theme's display name
- [ ] **`version` format**: Semantic Versioning (x.y.z, e.g., `"1.0.0"`)
- [ ] **`minAppVersion`**: Set appropriately for APIs used
- [ ] **`isDesktopOnly`**: Set correctly based on mobile compatibility
- [ ] **`minAppVersion`**: Set appropriately for CSS features used
- [ ] **Optional fields** (if applicable): `authorUrl`, `fundingUrl`
- [ ] **JSON syntax**: Valid JSON (proper quotes, commas, brackets)
### Version Consistency
- [ ] **GitHub release tag**: Matches `manifest.json` version exactly (no "v" prefix)
- **Correct**: If `manifest.json` has `"version": "0.1.0"`, tag must be `0.1.0` (not `v0.1.0`)
- **Wrong**: `v0.1.0` (with "v" prefix) - this will NOT match and can cause issues
- If checking before release: Verify version format is ready
- If checking after release: Verify tag matches manifest version exactly
- If checking after release: Verify tag matches manifest version
### Code Quality Checks
### CSS Quality Checks
- [ ] **No code obfuscation**: Code is readable and not minified/obfuscated
- [ ] **No `eval()` usage**: Search codebase for `eval(` patterns
- [ ] **No `innerHTML` misuse**: Check for unsafe `innerHTML` assignments (should use `createEl` or safe alternatives)
- [ ] **No remote code execution**: No fetching and executing remote scripts
- [ ] **No self-updating mechanisms**: No automatic code updates outside normal releases
- [ ] **Console logging**: Only `console.warn()`, `console.error()`, or `console.debug()` - no `console.log()` in production code
- [ ] **Listener cleanup**: All event listeners registered using `registerEvent()`, `registerDomEvent()`, `registerInterval()`
- [ ] **ESLint configuration matches Obsidian bot**:
- [ ] `no-console` rule configured to only allow warn/error/debug
- [ ] `@typescript-eslint/require-await` enabled
- [ ] No disallowed rule disables present (no-static-styles-assignment, no-explicit-any, ui/sentence-case)
- [ ] All disable comments have descriptions
- [ ] No unused eslint-disable directives
- [ ] **Valid CSS syntax**: No syntax errors in `theme.css`
- [ ] **No tracking or analytics**: No external tracking scripts, analytics, or telemetry in CSS
- [ ] **No remote resources**: No `@import` statements loading external stylesheets (unless explicitly disclosed)
- [ ] **Browser compatibility**: CSS features used are compatible with Obsidian's browser targets (Chrome and iOS Safari)
- [ ] **No obfuscated code**: CSS is readable and not minified/obfuscated (unless using build tools that minify for production)
### README.md Content
- [ ] **File exists**: `README.md` present in root
- [ ] **Describes purpose**: Clear description of what the plugin does
- [ ] **Usage instructions**: How to install and use the plugin
- [ ] **Attribution**: If using third-party code, proper attribution included
- [ ] **Describes purpose**: Clear description of what the theme does and its design philosophy
- [ ] **Usage instructions**: How to install and use the theme
- [ ] **Screenshots**: Visual examples of the theme (recommended)
- [ ] **Attribution**: If using third-party code or design elements, proper attribution included
### LICENSE File
- [ ] **File exists**: `LICENSE` file present in root
- [ ] **License specified**: Clear license type (MIT, GPL, etc.)
- [ ] **Third-party compliance**: If using code from other plugins, verify license compatibility and attribution
- [ ] **Third-party compliance**: If using code or design elements from other themes, verify license compatibility and attribution
## Interactive Checks (AI Asks User)
@ -87,61 +72,69 @@ These checks require user input or confirmation:
### Platform Testing
- [ ] **Windows**: Plugin tested and working on Windows
- [ ] **macOS**: Plugin tested and working on macOS
- [ ] **Linux**: Plugin tested and working on Linux
- [ ] **Android**: Plugin tested and working on Android (if `isDesktopOnly: false`)
- [ ] **iOS**: Plugin tested and working on iOS (if `isDesktopOnly: false`)
- [ ] **Windows**: Theme tested and working on Windows
- [ ] **macOS**: Theme tested and working on macOS
- [ ] **Linux**: Theme tested and working on Linux
- [ ] **Android**: Theme tested and working on Android (if applicable)
- [ ] **iOS**: Theme tested and working on iOS (if applicable)
**Note**: If user doesn't have access to all platforms, they should test on available platforms and note limitations.
### Theme-Specific Testing
- [ ] **Dark mode**: Theme includes dark mode styles and they work correctly
- [ ] **Light mode**: Theme includes light mode styles and they work correctly (or theme is dark-only and this is documented)
- [ ] **Mode switching**: Theme correctly switches between dark and light modes
- [ ] **All Obsidian views**: Theme tested in:
- [ ] Editor (Live Preview, Source Mode, Reading Mode)
- [ ] File explorer
- [ ] Settings pages
- [ ] Command palette
- [ ] Graph view
- [ ] Canvas view (if applicable)
- [ ] Other views used by the theme
### GitHub Release
- [ ] **Release created**: GitHub release exists for the version
- [ ] **Required files attached**: `main.js`, `manifest.json`, `styles.css` (if present) attached as **individual binary assets** (not just in source.zip)
- [ ] **Release tag format**: The release tag must exactly match `manifest.json`'s `version` field **WITHOUT** a leading "v" prefix
- **Correct**: If `manifest.json` has `"version": "0.1.0"`, tag must be `0.1.0` (not `v0.1.0`)
- **Wrong**: `v0.1.0` (with "v" prefix) - this will NOT match and can cause issues
- The release name can be descriptive, but the tag itself must be the version number without "v" prefix
- [ ] **Required files attached**: `theme.css` and `manifest.json` attached as **individual binary assets** (not just in source.zip)
- [ ] **Release name matches version**: Release name/tag exactly matches `manifest.json` version (no "v" prefix)
### Community Plugin Registration
### Community Theme Registration
- [ ] **`manifest.json` id matches `community-plugins.json`**: The `id` in your `manifest.json` matches the `id` in the `community-plugins.json` file (for plugins already in the community catalog)
- [ ] **`manifest.json` name matches `community-css-themes.json`**: The `name` in your `manifest.json` matches the `name` in the `community-css-themes.json` file (for themes already in the community catalog)
### Documentation Quality
- [ ] **README.md describes purpose**: Clear explanation of what the plugin does
- [ ] **README.md provides usage instructions**: Step-by-step guide on how to use the plugin
- [ ] **README.md describes purpose**: Clear explanation of what the theme does and its design philosophy
- [ ] **README.md provides usage instructions**: Step-by-step guide on how to install and use the theme
- [ ] **Screenshots included**: Visual examples showing the theme in use (highly recommended)
### Developer Policies Adherence
- [ ] **Read Developer Policies**: User confirms they have read https://docs.obsidian.md/Developer+policies
- [ ] **Read Developer Policies**: User confirms they have read [Developer Policies](https://docs.obsidian.md/Developer+policies)
- [ ] **No prohibited features**:
- [ ] No code obfuscation
- [ ] No dynamic ads
- [ ] No client-side telemetry (unless explicitly opt-in and disclosed)
- [ ] No tracking or analytics
- [ ] No remote code execution
- [ ] No self-updating mechanisms
- [ ] **Mandatory disclosures** (if applicable):
- [ ] Payments required: Disclosed in README and settings
- [ ] User accounts required: Disclosed in README and settings
- [ ] Network usage: Disclosed in README and settings
- [ ] Files outside vault: Disclosed in README and settings
- [ ] **Licensing**: LICENSE file present and compliant with any third-party code licenses
- [ ] Remote resources: Disclosed in README if using `@import` for external stylesheets
- [ ] Network usage: Disclosed in README and settings (if any)
- [ ] **Licensing**: LICENSE file present and compliant with any third-party code/licenses
### Plugin Guidelines Adherence
### Theme Guidelines Adherence
- [ ] **Read Plugin Guidelines**: User confirms they have read https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
- [ ] **Code organization**: Code is well-organized (not all in one file if complex)
- [ ] **Class naming**: Placeholder class names (like "MyPlugin") renamed to reflect actual functionality
- [ ] **Console logging**: Minimal console logging (only warnings, errors, debug - no `console.log()`)
- [ ] **UI text conventions**: UI text uses sentence case (see [ux-copy.md](ux-copy.md))
- [ ] **Security**: No unsafe patterns (eval, innerHTML misuse, etc.)
- [ ] **Read Theme Guidelines**: User confirms they have read [Theme Guidelines](https://docs.obsidian.md/Themes/Releasing/Theme+guidelines)
- [ ] **CSS organization**: CSS is well-organized (logical structure, comments where helpful)
- [ ] **Browser compatibility**: CSS features are compatible with Obsidian's browser targets
- [ ] **Performance**: Theme doesn't cause significant performance issues
- [ ] **Accessibility**: Theme maintains reasonable contrast ratios and readability
### Third-Party Code
- [ ] **License compliance**: All third-party code licenses are compatible with your plugin's license
- [ ] **Attribution**: Proper attribution given in README.md for any code from other plugins/projects
- [ ] **License compatibility**: Your plugin's license is compatible with any third-party code used
- [ ] **License compliance**: All third-party code/licenses are compatible with your theme's license
- [ ] **Attribution**: Proper attribution given in README.md for any code or design elements from other themes/projects
- [ ] **License compatibility**: Your theme's license is compatible with any third-party code used
## Developer Policies Summary
@ -149,54 +142,51 @@ For reference, key points from [Developer Policies](https://docs.obsidian.md/Dev
### Prohibited
- **Code obfuscation**: Code must be readable
- **Dynamic ads**: No dynamic advertising
- **Client-side telemetry**: No hidden telemetry (opt-in telemetry must be clearly disclosed)
- **Tracking or analytics**: No tracking scripts, analytics, or telemetry
- **Remote code execution**: No fetching and executing remote scripts
- **Self-updating**: No automatic code updates outside normal releases
### Mandatory Disclosures
If your plugin requires any of the following, you **must** disclose it clearly:
- Payments or subscriptions
- User accounts
- Network usage (API calls, external services)
- Accessing files outside the Obsidian vault
If your theme requires any of the following, you **must** disclose it clearly:
- Remote resources (external stylesheets via `@import`)
- Network usage (if any)
### Licensing
- Include a LICENSE file
- Respect licenses of any third-party code used
- Provide proper attribution for third-party code
- Respect licenses of any third-party code or design elements used
- Provide proper attribution for third-party code or design elements
## Plugin Guidelines Summary
## Theme Guidelines Summary
For reference, key points from [Plugin Guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines):
For reference, key points from [Theme Guidelines](https://docs.obsidian.md/Themes/Releasing/Theme+guidelines):
- **Code organization**: Organize code into logical files/folders
- **Console logging**: Minimize console output (only warnings, errors, debug)
- **UI text**: Use sentence case for UI text (see [ux-copy.md](ux-copy.md))
- **Class naming**: Use descriptive class names (not placeholders)
- **Security**: Avoid unsafe patterns (eval, innerHTML misuse, etc.)
- **Testing**: Test on all applicable platforms
- **CSS organization**: Organize CSS into logical sections
- **Browser compatibility**: Ensure CSS features work in Obsidian's browser targets (Chrome and iOS Safari)
- **Performance**: Avoid CSS that causes performance issues
- **Testing**: Test on all applicable platforms and in all Obsidian views
- **Documentation**: Include clear README with screenshots
## AI Agent Workflow
When user asks "is my plugin ready for release?" or similar:
When user asks "is my theme ready for release?" or similar:
1. **Run automated checks**:
- Check file existence (`main.js`, `manifest.json`, `styles.css`, `LICENSE`, `README.md`)
- Check file existence (`theme.css`, `manifest.json`, `LICENSE`, `README.md`)
- Validate `manifest.json` structure and required fields
- Check version format and consistency
- Scan code for prohibited patterns (eval, innerHTML misuse, obfuscation, etc.)
- Scan CSS for prohibited patterns (tracking, remote imports, etc.)
- Verify README.md has basic content
2. **Present interactive checklist**:
- Ask about platform testing (Windows, macOS, Linux, Android, iOS)
- Ask about theme-specific testing (dark/light mode, all Obsidian views)
- Ask about GitHub release status and file attachments
- Ask about community-plugins.json id matching (if applicable)
- Ask about README.md quality (purpose and usage instructions)
- Ask about community-css-themes.json name matching (if applicable)
- Ask about README.md quality (purpose, usage instructions, screenshots)
- Ask about Developer Policies adherence
- Ask about Plugin Guidelines adherence
- Ask about Theme Guidelines adherence
- Ask about third-party code license compliance and attribution
3. **Report results**:
@ -214,7 +204,7 @@ When user asks "is my plugin ready for release?" or similar:
- [security-privacy.md](security-privacy.md) - Security and privacy guidelines
- [manifest.md](manifest.md) - Manifest requirements and validation
- [testing.md](testing.md) - Testing procedures and platform testing
- [ux-copy.md](ux-copy.md) - UI text conventions
- [common-pitfalls.md](common-pitfalls.md) - Common mistakes to avoid
- [build-workflow.md](build-workflow.md) - Build commands (must run before release)
- [ux-copy.md](ux-copy.md) - UI text conventions (for theme names and descriptions)
- [build-workflow.md](build-workflow.md) - Build commands (if using build tools)
- [performance.md](performance.md) - Performance optimization best practices

View file

@ -6,79 +6,56 @@ Update frequency: Check Obsidian Developer Policies for updates
# Security, privacy, and compliance
Follow Obsidian's [**Developer Policies**](https://docs.obsidian.md/Developer+policies) and [**Plugin Guidelines**](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). See [release-readiness.md](release-readiness.md) for a comprehensive release checklist.
Follow Obsidian's **Developer Policies** (https://docs.obsidian.md/Developer+policies) and **Theme Guidelines** (https://docs.obsidian.md/Themes/Releasing/Theme+guidelines). See [release-readiness.md](release-readiness.md) for a comprehensive release checklist.
## Developer Policies Requirements
### Prohibited Practices
- **Code obfuscation**: Code must be readable and not minified/obfuscated
- **Dynamic ads**: No dynamic advertising in plugins
- **Client-side telemetry**: No hidden telemetry. If you collect optional analytics, require explicit opt-in and document clearly in `README.md` and in settings
- **Code obfuscation**: CSS must be readable and not minified/obfuscated
- **Dynamic ads**: No dynamic advertising
- **Client-side telemetry**: No hidden telemetry. If you collect optional analytics, require explicit opt-in and document clearly in `README.md`
- **Self-updating mechanisms**: No automatic code updates outside of normal releases. Never execute remote code, fetch and eval scripts, or auto-update code
### Mandatory Disclosures
If your plugin requires any of the following, you **must** disclose it clearly in `README.md` and in settings:
If your theme requires any of the following, you **must** disclose it clearly in `README.md`:
- **Payments or subscriptions**: Clearly state if the plugin requires payment
- **Payments or subscriptions**: Clearly state if the theme requires payment
- **User accounts**: Disclose if user accounts are required
- **Network usage**: Disclose any API calls, external services, or network requests
- **Files outside vault**: Disclose if the plugin accesses files outside the Obsidian vault
- **Files outside vault**: Disclose if the theme accesses files outside the Obsidian vault (rare for themes, but applicable if using any external resources)
### Privacy and Security
- Default to local/offline operation. Only make network requests when essential to the feature.
- 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, file names, or personal information unless absolutely necessary and explicitly consented.
- 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.
### Secret Storage
**Source**: Based on [SecretStorage and SecretComponent guide](https://docs.obsidian.md/plugins/guides/secret-storage) (available since Obsidian 1.11.4)
When your plugin needs to store sensitive data like API keys, tokens, or passwords, **always use SecretStorage** instead of storing secrets directly in your plugin's `data.json` file.
**Why use SecretStorage?**
- **Security**: Secrets are stored securely in a centralized location, not in plaintext alongside other plugin data
- **User experience**: Users can share secrets across multiple plugins without duplicating them
- **Maintenance**: When a token changes, users only need to update it once in SecretStorage, not in every plugin
**Best Practices**:
- **Never store secrets in `data.json`**: Use `SecretComponent` in your settings UI and store only the secret *name* (ID) in your settings
- **Always check for null**: When retrieving secrets with `app.secretStorage.get()`, the value may be `null` if the secret doesn't exist
- **Use descriptive secret IDs**: Follow the lowercase alphanumeric format with optional dashes (e.g., `my-plugin-api-key`)
- **Handle missing secrets gracefully**: Provide clear error messages if a required secret is not found
See [common-tasks.md](common-tasks.md) for code examples and [code-patterns.md](code-patterns.md) for comprehensive implementation patterns.
### Licensing
- Include a LICENSE file in your project root
- Respect licenses of any third-party code used
- Provide proper attribution for third-party code in `README.md`
- Ensure license compatibility between your plugin's license and any third-party code licenses
- Ensure license compatibility between your theme's license and any third-party code licenses
## Plugin Guidelines
## Theme Guidelines
- **Code organization**: Organize code into logical files/folders
- **Console logging**: Minimize console output (only `console.warn()`, `console.error()`, or `console.debug()` - no `console.log()` in production)
- **UI text**: Use sentence case for UI text (see [ux-copy.md](ux-copy.md))
- **Class naming**: Use descriptive class names (not placeholders like "MyPlugin")
- **Security**: Avoid unsafe patterns (eval, innerHTML misuse, etc.)
- **CSS organization**: Organize CSS/SCSS into logical files/folders
- **CSS variables**: Use consistent naming conventions for CSS variables
- **Security**: Themes are CSS-only and have minimal security surface area
## Implementation
Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
Themes are CSS-only and have minimal security surface area, but still follow privacy guidelines for any optional features.
## Related Documentation
- [release-readiness.md](release-readiness.md) - Comprehensive release checklist including policy adherence
- [manifest.md](manifest.md) - Manifest requirements (includes security-related fields)
- [Developer Policies](https://docs.obsidian.md/Developer+policies) - Official Obsidian Developer Policies
- [Plugin Guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines) - Official Plugin Guidelines
- [Theme Guidelines](https://docs.obsidian.md/Themes/Releasing/Theme+guidelines) - Official Theme Guidelines

View file

@ -2,7 +2,6 @@
Source: Project-specific workflow
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Update as workflow evolves
Applicability: Both
-->
# Summarize Commands
@ -11,59 +10,31 @@ When the user requests "Summarize" or "Summarize for release", use these workflo
## "Summarize" Command
**Purpose**: Generate a succinct git commit message based on all changes since the last tag (or all uncommitted changes if no tags exist).
**Purpose**: Generate a succinct git commit message based on all changed files.
**Workflow**:
1. **Find the last tag** (if any exist):
1. **Get all changed files**:
```bash
git describe --tags --abbrev=0 2>/dev/null || echo "" # Get last tag, or empty if none
```
- If a tag exists, use it as the baseline
- If no tags exist, fall back to analyzing uncommitted changes only
2. **Get all commits since last tag** (or recent commits if no tag):
```bash
# If tag exists:
git log --oneline <last-tag>..HEAD # All commits since last tag
git log --oneline <last-tag>..HEAD --format="%H %s" # With full hashes
# If no tag exists, get recent commits:
git log --oneline -10 # Last 10 commits as fallback
```
3. **Get all file changes since last tag** (or uncommitted if no tag):
```bash
# If tag exists:
git diff <last-tag>..HEAD --name-status # All changed files since tag
git diff <last-tag>..HEAD # Full diff of all changes
# Also check for uncommitted changes:
git status
git diff --cached # For staged changes
git diff # For unstaged changes
# If no tag exists, use uncommitted changes:
git diff --cached # For staged changes
git diff # For unstaged changes
```
4. **Read and analyze all changed files**:
- Look at the actual file contents and diffs, not just the chat history
- Review commit messages to understand the intent of each change
- Understand what changed across all files since the last tag
- Get the overall picture of all changes (committed + uncommitted)
- Prioritize feature changes over version bumps or minor updates
2. **Read and analyze all changed files**:
- Look at the actual file contents, not just the chat history
- Understand what changed across all files
- Get the overall picture of the changes
5. **Generate commit message** in this format:
```text
3. **Generate commit message** in this format:
```
[Summary of changes]
- [more detailed item 1]
- [more detailed item 2]
- [more detailed item 3]
```
6. **Present as a code block** so the user can easily copy it:
4. **Present as a code block** so the user can easily copy it:
````
```
[Summary of changes]
@ -73,14 +44,11 @@ When the user requests "Summarize" or "Summarize for release", use these workflo
````
**Important**:
- **Always check for tags first** - this ensures you capture all important commits, not just what's in the working directory
- Look at actual file changes and commit history, not just chat context
- Include all significant changes since the last tag, even if they're already committed
- Look at actual file changes, not just chat context
- Be succinct but descriptive
- Focus on what changed, not how it was changed
- Use present tense (e.g., "Add feature" not "Added feature")
- Group related changes together
- Prioritize feature additions and important fixes over version bumps
**Example**:
```

View file

@ -1,4 +1,4 @@
<!--
<!--
Source: Project-specific procedure
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Update as sync process evolves
@ -117,17 +117,17 @@ If `.ref` contains actual repos (not symlinks), update from project root:
**Windows (PowerShell)**:
```powershell
# Always start from project root for each command
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/obsidian-api; git pull
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/obsidian-sample-plugin; git pull
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/obsidian-developer-docs; git pull
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/obsidian-plugin-docs; git pull
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/obsidian-sample-theme; git pull
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref/eslint-plugin; git pull
```
@ -178,10 +178,10 @@ cd ..
**Windows (PowerShell)** - If using local clones:
```powershell
# Always start from project root for each command
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref\obsidian-api
git log --oneline -10
cd C:\Users\david\Development\obsidian-sample-plugin
cd C:\path\to\your\obsidian-project
cd .ref\obsidian-sample-plugin
git log --oneline -10
git diff HEAD~1 HEAD -- AGENTS.md
@ -259,13 +259,13 @@ For each file that needs updating:
node scripts/update-sync-status.mjs "Description of what was synced"
```
**Manual way**: Edit `.agents/sync-status.json` directly:
**Manual way**: Edit `.agent/sync-status.json` directly:
```powershell
# Get the current date
$syncDate = Get-Date -Format "yyyy-MM-dd"
# Update the central sync-status.json file
# Edit .agents/sync-status.json and update:
# Edit .agent/sync-status.json and update:
# - "lastFullSync" to the current date
# - "lastSyncSource" to describe what was synced
# - Update relevant source repo dates in "sourceRepos"
@ -273,7 +273,7 @@ For each file that needs updating:
**Important**: Always use the actual current date from `Get-Date -Format "yyyy-MM-dd"`, never use placeholder dates.
4. **Note**: Individual file headers still have "Last synced" dates, but the authoritative source is `.agents/sync-status.json`. When syncing, update the central file rather than individual file headers.
4. **Note**: Individual file headers still have "Last synced" dates, but the authoritative source is `.agent/sync-status.json`. When syncing, update the central file rather than individual file headers.
### Step 6: Verify and Test
@ -297,8 +297,8 @@ For each file that needs updating:
- [ ] Review API documentation for breaking changes
- [ ] Review developer docs for policy/guideline updates
- [ ] Review plugin docs for best practices
- [ ] Update relevant `.agents/*.md` files
- [ ] **Update `.agents/sync-status.json` with actual current date** (use `Get-Date -Format "yyyy-MM-dd"` - never use placeholder dates)
- [ ] Update relevant `.agent/skills/**/*.md` files
- [ ] **Update `.agent/sync-status.json` with actual current date** (use `Get-Date -Format "yyyy-MM-dd"` - never use placeholder dates)
- [ ] Review and commit changes
## Troubleshooting
@ -353,7 +353,7 @@ Consider creating a script to:
## Updating Sync Status
After completing a sync, update `.agents/sync-status.json`:
After completing a sync, update `.agent/sync-status.json`:
**Easy way** (recommended): Use the helper script:
```bash

View file

@ -1,16 +1,16 @@
<!--
Source: Based on Obsidian Sample Plugin
Source: Based on Obsidian Sample Theme
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Sample Plugin repo for updates
Update frequency: Check Obsidian Sample Theme repo for updates
-->
# Testing
- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
```plaintext
<Vault>/.obsidian/plugins/<plugin-id>/
- Manual install for testing: copy `manifest.json` and `theme.css` to:
```
- Reload Obsidian and enable the plugin in **Settings → Community plugins**.
<Vault>/.obsidian/themes/<theme-name>/
```
- Reload Obsidian and select the theme in **Settings → Appearance → Themes**.
**Platform testing**: Before release, test on all applicable platforms (Windows, macOS, Linux, Android, iOS). See [release-readiness.md](release-readiness.md) for the complete testing checklist.

View file

@ -6,38 +6,13 @@ Update frequency: Update as common issues are identified
# Troubleshooting
**Source**: Based on common errors from developer docs, community patterns, and API best practices. Always verify API details in `.ref/obsidian-api/obsidian.d.ts`.
**Source**: Based on common errors from developer docs, community patterns, and best practices.
## Build and Loading Issues
- **Plugin doesn't load after build**: Ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`.
- **Build issues**: If `main.js` is missing, run `pnpm build` or `pnpm dev` to compile your TypeScript source code.
- **TypeScript compilation errors**: Check `tsconfig.json` settings, ensure `"strict": true` is handled properly, verify all imports are correct.
- **Module not found errors**: Ensure all dependencies are in `package.json` and run `pnpm install`. Check that imports use correct paths.
## Command Issues
- **Commands not appearing**: Verify `addCommand` runs after `onload` completes, and command IDs are unique.
- **Command not executing**: Check that callback/editorCallback/checkCallback is properly defined and not throwing errors.
- **Command only works sometimes**: If using `checkCallback`, ensure it returns `true` when the command should be available.
## Settings Issues
- **Settings not persisting**: Ensure `loadData`/`saveData` are awaited. Check that `saveSettings()` is called after changes.
- **Settings not loading**: Verify `loadSettings()` is called in `onload()` and properly awaited.
- **Settings UI not updating**: Call `display()` after changing settings that affect the UI.
- **Settings structure changed**: Old saved data may not match new interface. Add migration logic or reset settings.
## View Issues
- **Views not appearing**: Verify `registerView()` is called in `onload()`, and view type constant matches.
- **Views not cleaning up**: Ensure `detachLeavesOfType()` is called in `onunload()`.
- **View errors**: Never store view references. Use `getLeavesOfType()` to access views.
## Mobile Issues
- **Mobile-only issues**: Confirm you're not using desktop-only APIs; check `isDesktopOnly` in `manifest.json` and adjust.
- **Status bar not working on mobile**: Status bar items are not supported on mobile. Use feature detection.
- **Theme doesn't appear**: Ensure `manifest.json` and `theme.css` are at the top level of the theme folder under `<Vault>/.obsidian/themes/<theme-name>/`.
- **Theme not applying**: Check that `manifest.json` has correct `name` field matching the folder name.
- **CSS not loading**: Verify `theme.css` exists and is properly formatted.
- **SCSS compilation issues**: If using SCSS, ensure build process runs and outputs `theme.css`.
- **Mobile display issues**: Test CSS on mobile devices and check for viewport-specific styles.
## AI Agent Issues
@ -56,173 +31,84 @@ Update frequency: Update as common issues are identified
## Common Error Messages
### TypeScript Errors
### CSS Errors
- **"Property does not exist on type"**: Check API types in `.ref/obsidian-api/obsidian.d.ts`. Plugin docs may be outdated.
- **"Cannot find module 'obsidian'"**: Ensure `obsidian` is in `package.json` dependencies (usually `"obsidian": "latest"`).
- **"Type 'undefined' is not assignable"**: Add proper null checks or provide defaults for optional properties.
- **"Invalid property value"**: Check CSS syntax, ensure all values are properly formatted.
- **"Unknown property"**: Verify CSS property names are correct and supported by Obsidian's rendering engine.
- **"Selector not working"**: Check CSS selector specificity and ensure you're targeting the correct Obsidian elements.
### Runtime Errors
### SCSS Compilation Errors
- **"Cannot read property of undefined"**: Add null checks before accessing properties. Verify objects are initialized.
- **"Plugin failed to load"**: Check browser console (Help → Toggle Developer Tools) for detailed error messages.
- **"Settings failed to load"**: Check that settings file isn't corrupted. Handle errors in `loadSettings()`.
- **"File to import not found"**: Check `@import` paths are correct relative to SCSS files.
- **"Undefined variable"**: Ensure all SCSS variables are defined before use.
- **"Syntax error"**: Verify SCSS syntax is correct (semicolons, brackets, etc.).
### Build Errors
- **"Cannot find name"**: Check imports, ensure all dependencies are installed.
- **"Module not found"**: Verify file paths in imports are correct relative to source files.
- **"Command not found"**: Ensure build tools (Grunt, npm, sass) are installed.
- **"Build failed"**: Check build configuration files (`Gruntfile.js`, `package.json` scripts).
- **"Output file missing"**: Verify build process completed and `theme.css` was generated.
## Debugging Techniques
### Console Logging
### Browser Console
```ts
// Log plugin state
console.log("Plugin loaded:", this);
console.log("Settings:", this.settings);
Open browser console (Help → Toggle Developer Tools) to check for:
- CSS parsing errors
- Missing CSS variables
- Conflicting styles
// Log in event handlers
this.app.workspace.on("file-open", (file) => {
console.log("File opened:", file.path);
});
```
### Inspect Theme CSS
### Inspect Plugin State
Open browser console (Help → Toggle Developer Tools) and inspect:
In browser console, inspect the theme's CSS:
```javascript
// Access your plugin instance
app.plugins.plugins['your-plugin-id']
// Check if theme CSS is loaded
document.querySelector('style[data-theme="your-theme-name"]')
```
### Check Settings File
### Verify CSS Variables
Settings are stored at:
```text
<Vault>/.obsidian/plugins/<plugin-id>/data.json
Check that Obsidian CSS variables are being used correctly:
```css
/* Use Obsidian's built-in variables */
color: var(--text-normal);
background: var(--background-primary);
```
You can manually inspect this file (backup first!) to see what's saved.
### Check Manifest
### Verify API Usage
Verify `manifest.json` has correct `name` field matching the theme folder name.
Always check `.ref/obsidian-api/obsidian.d.ts` for:
- Correct method signatures
- Available properties
- New features (e.g., `SettingGroup` since 1.11.0)
- Deprecated methods
## SCSS Build Issues (Detailed)
Plugin docs may not reflect the latest API changes.
## Settings Issues (Detailed)
### Settings Not Saving
**Symptoms**: Changes don't persist after restart.
### SCSS Not Compiling
**Causes**:
1. Not awaiting `saveData()`
2. Settings object structure changed
3. File permissions issue
**Solution**:
```ts
async saveSettings() {
await this.saveData(this.settings); // Must await!
}
```
### Settings Not Loading
**Symptoms**: Settings always use defaults.
**Causes**:
1. `loadSettings()` not called or not awaited
2. Settings file doesn't exist (first run)
3. Settings file corrupted
**Solution**:
```ts
async onload() {
await this.loadSettings(); // Must be called and awaited
}
async loadSettings() {
try {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
} catch (error) {
console.error("Failed to load settings:", error);
this.settings = { ...DEFAULT_SETTINGS };
}
}
```
### Settings UI Not Updating
**Symptoms**: Settings tab doesn't reflect changes.
**Solution**: Call `display()` after changing settings:
```ts
.onChange(async (value) => {
this.plugin.settings.value = value;
await this.plugin.saveSettings();
this.display(); // Re-render settings tab
})
```
## View Issues (Detailed)
### Views Not Appearing
**Checklist**:
1. `registerView()` called in `onload()`?
2. View type constant matches?
3. `activateView()` method called?
4. View class properly extends `ItemView`?
### Views Not Cleaning Up
**Solution**: Always detach in `onunload()`:
```ts
async onunload() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE_MY_VIEW);
}
```
### View Reference Errors
**Problem**: Storing view references causes issues.
**Solution**: Always use `getLeavesOfType()`:
```ts
// Don't store: let myView: MyView;
// Instead:
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_MY_VIEW);
```
## Build Issues (Detailed)
### Missing main.js
**Cause**: Build didn't run or failed.
1. Build command not run
2. Build tool not installed
3. Incorrect build configuration
**Solution**:
1. Run `pnpm build`
2. Check for TypeScript errors
3. Verify `esbuild.config.mjs` or build config is correct
1. Run build command (`npx grunt build` or `npm run build`)
2. Verify `Gruntfile.js` or `package.json` scripts are correct
3. Check that `theme.css` is generated in root directory
### TypeScript Compilation Errors
### SCSS Import Errors
**Common causes**:
- Missing type definitions
- Incorrect import paths
- Strict mode type errors
**Problem**: `@import` statements fail.
**Solution**:
1. Check `tsconfig.json` settings
2. Verify all imports are correct
3. Add proper type annotations
4. Check `.ref/obsidian-api/obsidian.d.ts` for correct types
1. Check file paths are correct relative to importing file
2. Verify all imported files exist
3. Use relative paths: `@import "../variables.scss";`
### CSS Output Issues
**Problem**: Compiled CSS doesn't match expected output.
**Solution**:
1. Check SCSS source files for syntax errors
2. Verify build process completes without errors
3. Inspect generated `theme.css` for issues

View file

@ -1,20 +1,16 @@
<!--
Source: Based on Obsidian Sample Plugin
Source: Based on Obsidian Sample Theme
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Sample Plugin repo for updates
Update frequency: Check Obsidian Sample Theme repo for updates
-->
# Versioning & releases
**Before releasing**: Use the comprehensive [release-readiness.md](release-readiness.md) checklist to verify your plugin is ready for release.
**Before releasing**: Use the comprehensive [release-readiness.md](release-readiness.md) checklist to verify your theme is ready for release.
- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version.
- **Build for production**: Run `pnpm build` to create `main.js` in root
- **Create a GitHub release**: The release tag must exactly match `manifest.json`'s `version` field **WITHOUT** a leading "v" prefix.
- **Correct**: If `manifest.json` has `"version": "0.1.0"`, the tag must be `0.1.0` (not `v0.1.0`)
- **Wrong**: `v0.1.0` (with "v" prefix) - this will NOT match and can cause issues
- The tag name and release name should both use the version number without "v" prefix
- Attach `main.js`, `manifest.json`, 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.
- Bump `version` in `manifest.json` (SemVer).
- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
- Attach `manifest.json` and `theme.css` to the release as individual assets.
- After the initial release, follow the process to add/update your theme in the community catalog as required.

View file

@ -32,4 +32,4 @@ Load this skill when:
- `references/ux-copy.md`: UI text conventions and UX best practices.
- `references/references.md`: External links and primary source locations.
- `references/mobile.md`: Considerations for mobile compatibility.
- `references/performance.md`: Best practices for plugin performance.
- `references/performance.md`: Best practices for performance.

View file

@ -1,157 +1,52 @@
<!--
Source: Based on Obsidian Sample Plugin
Source: Based on Obsidian Sample Theme
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Sample Plugin repo for updates
Update frequency: Check Obsidian Sample Theme repo for updates
-->
# Environment & tooling
**Purpose**: This document covers development environment setup, tooling configuration, and ESLint setup. Use this for initial setup and configuration.
- **Optional**: Node.js and npm if using SCSS/Sass preprocessors or build tools.
- **Simple themes**: Can be developed with just CSS and `manifest.json` (no build tools required).
- **SCSS themes**: Use Sass/SCSS compiler (e.g., `sass`, `node-sass`, or build tools like Vite).
- No TypeScript or bundler required for basic themes.
**Related documentation**:
- [obsidian-bot-requirements.md](obsidian-bot-requirements.md) - Bot requirements and ESLint configuration
- [build-workflow.md](build-workflow.md) - Build commands and workflow
- [common-pitfalls.md](common-pitfalls.md) - Common development pitfalls
## Simple Theme Setup
- Node.js: use current LTS (Node 18+ recommended).
- **Package manager: pnpm** (required for this sample - `package.json` defines pnpm 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.
No build tools needed - just edit `theme.css` directly.
**Note**: This sample project has specific technical dependencies on pnpm 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
## SCSS Theme Setup
```bash
pnpm install
npm install -D sass
npm run build # Compile SCSS to CSS
```
### Dev (watch)
## Theme Build Tools (Optional)
Simple themes with just CSS don't need build tools. More complex themes might use Grunt, npm scripts, or other build tools for tasks like SCSS compilation, minification, or preprocessing:
```bash
pnpm dev
# For themes using Grunt
npx grunt build
# For themes using npm scripts
npm run build
```
### Production build
Only use build commands if your theme has a `Gruntfile.js`, `package.json` with build scripts, or other build configuration files.
```bash
pnpm build
```
## Linting (Optional)
**Important for AI Agents**: Always run `pnpm build` after making changes to plugins to catch build errors early. If pnpm is not installed, install it via `npm install -g pnpm` or enable corepack with `corepack enable`. Do not run `pnpm install` to install pnpm itself - that command installs project dependencies.
- Use `stylelint` for CSS/SCSS linting: `npm install -D stylelint`
- Configure stylelint for Obsidian theme conventions
- **Quick Setup**: Run `node scripts/setup-stylelint.mjs` to set up Stylelint with helpful success messages
- **Run Stylelint**:
```bash
npm run lint # Uses lint-wrapper.mjs for helpful success messages
npm run lint:fix # Auto-fix issues where possible
```
**Backwards compatibility**: `npm install` will automatically proxy to `pnpm install` via a preinstall hook. `npm run build`, `npm run dev`, and `npm run lint` will also work since npm scripts execute the same commands.
### Linting
**Recommended**: Use `eslint-plugin-obsidianmd` for Obsidian-specific linting rules. The repository is at `.ref/eslint-plugin/` - see its README for setup and complete rule documentation.
**Quick Setup**:
```bash
pnpm add -D eslint@^9.39.1 eslint-plugin-obsidianmd@^0.1.9 @typescript-eslint/eslint-plugin@^8.33.1 @typescript-eslint/parser@^8.33.1 typescript-eslint@^8.35.1 @eslint/js@^9.30.1 @eslint/json@^0.14.0
```
**Important**: ESLint v9 is required (the plugin requires `eslint >=9.0.0 <10.0.0`). ESLint 9 uses the flat config format (`eslint.config.mjs`).
**Basic eslint.config.mjs configuration**:
```javascript
// eslint.config.mjs
import tsparser from "@typescript-eslint/parser";
import { defineConfig } from "eslint/config";
import obsidianmd from "eslint-plugin-obsidianmd";
export default defineConfig([
...obsidianmd.configs.recommended,
{
files: ["**/*.ts"],
languageOptions: {
parser: tsparser,
parserOptions: {
project: "./tsconfig.json",
sourceType: "module"
},
},
// You can add your own configuration to override or add rules
rules: {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"no-prototype-builtins": "off",
},
},
]);
```
**Note**: The `obsidianmd.configs.recommended` config includes all recommended Obsidian plugin linting rules and is the same configuration used by the Obsidian Review Bot. However, some rules need explicit configuration to match the bot's requirements (see "Obsidian Bot vs Local Linting" below).
**Run ESLint**:
```bash
pnpm lint # Uses lint-wrapper.mjs for helpful success messages
pnpm lint:fix # Auto-fix issues where possible
# Or for specific files:
pnpm exec eslint src/**/*.ts
```
**Note**: The setup script (`node scripts/setup-eslint.mjs`) automatically creates `scripts/lint-wrapper.mjs` which adds helpful success messages when linting passes. The wrapper is included in the template and copied during setup.
**Common issues caught by `eslint-plugin-obsidianmd`**: See [common-pitfalls.md](common-pitfalls.md#common-linting-issues) for details on style manipulation, settings headings, UI text case, file deletion, and more.
### Obsidian Bot vs Local Linting
**Important**: The Obsidian review bot uses stricter rules than the default `obsidianmd.configs.recommended` config. To match the bot's requirements, you need to explicitly configure some rules:
1. **Console statements**: Only `console.warn()`, `console.error()`, and `console.debug()` are allowed. Configure:
```javascript
"no-console": ["error", { "allow": ["warn", "error", "debug"] }]
```
2. **Required rules**: Enable `@typescript-eslint/require-await` to catch async methods without await:
```javascript
"@typescript-eslint/require-await": "error"
```
3. **Disallowed rule disabling**: The bot prevents disabling these rules:
- `obsidianmd/no-static-styles-assignment` - Refactor code instead
- `@typescript-eslint/no-explicit-any` - Use `unknown` with type guards
- `obsidianmd/ui/sentence-case` - Fix text to proper sentence case
4. **ESLint disable comments**: All disable comments must include descriptions explaining why they're necessary. The bot will flag undescribed directives.
5. **Unused disables**: Remove any eslint-disable directives that aren't actually needed.
**Production-Ready ESLint Configuration**:
For production code that will be reviewed by the Obsidian bot, use this configuration:
```javascript
// eslint.config.mjs
import tsparser from "@typescript-eslint/parser";
import { defineConfig } from "eslint/config";
import obsidianmd from "eslint-plugin-obsidianmd";
export default defineConfig([
...obsidianmd.configs.recommended,
{
files: ["**/*.ts"],
languageOptions: {
parser: tsparser,
parserOptions: {
project: "./tsconfig.json",
sourceType: "module"
},
},
rules: {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"no-prototype-builtins": "off",
// Console rules: Match Obsidian bot requirements
"no-console": ["error", { "allow": ["warn", "error", "debug"] }],
// Require await in async functions (matches Obsidian bot)
"@typescript-eslint/require-await": "error",
},
},
]);
```
**See also**: [obsidian-bot-requirements.md](obsidian-bot-requirements.md) for complete bot requirements documentation, [common-pitfalls.md](common-pitfalls.md#obsidian-bot-review-requirements) for common bot review issues, and [release-readiness.md](release-readiness.md) for pre-submission checklist.
**Note**: The setup script automatically creates `scripts/lint-wrapper.mjs` which adds helpful success messages when linting passes. The wrapper is included in the template and copied during setup.

View file

@ -1,49 +1,65 @@
<!--
Source: Based on Obsidian Sample Plugin
<!--
Source: Based on Obsidian Sample Theme
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Sample Plugin repo for updates
Update frequency: Check Obsidian Sample Theme repo for updates
-->
# File & folder conventions
- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
- **Source file location**:
- **Recommended**: Place `main.ts` in `src/` directory (standard for most plugins)
- **Acceptable for simple plugins**: `main.ts` in project root is acceptable for minimal plugins (like the sample plugin template)
- **CRITICAL**: Never have `main.ts` in both root AND `src/` - this causes build confusion and errors
- Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
- **Recommended file structure** (for plugins with multiple files):
```plaintext
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
```
**Simple plugin structure** (acceptable for minimal plugins):
```plaintext
main.ts # ✅ OK for simple plugins (like sample plugin template)
manifest.json
package.json
```
**Wrong structure** (common mistake):
```plaintext
main.ts # ❌ DON'T have it in both places
src/
main.ts # ❌ This causes build confusion
```
**Best practice**: As your plugin grows beyond a single file, move `main.ts` to `src/` and organize other files there too.
- **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. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`).
- **Organize CSS/SCSS into logical files**: Split styles into separate files for maintainability.
- **Theme structure patterns**:
- **Simple CSS theme** (recommended for simple themes): `theme.css` in project root, no build step required
- **Complex theme with build tools** (for themes using SCSS, Grunt, etc.): `src/scss/` directory with SCSS source files that compile to `theme.css`
- **CRITICAL**: Never have both `theme.css` as source AND `src/scss/` - choose one pattern
### Simple CSS Theme Structure
**Recommended for simple themes** (like the sample theme template):
```
theme.css # ✅ Source CSS file (edit directly)
manifest.json
package.json
```
- No build step required - just edit `theme.css` directly
- Changes take effect when Obsidian reloads the theme
- Perfect for learning and simple themes
### Complex Theme Structure (SCSS + Build Tools)
**For themes using SCSS, Grunt, npm scripts, or other build tools**:
```
src/
scss/ # ✅ SCSS source files
index.scss
variables.scss
components/
css/ # Compiled CSS (intermediate, not committed)
main.css
theme.css # ✅ Final compiled output (committed)
Gruntfile.js # Build configuration (if using Grunt)
manifest.json
package.json
```
- Source files in `src/scss/` are compiled to `theme.css`
- Build tools (Grunt, npm scripts, etc.) handle compilation
- Run build command after making changes (see [build-workflow.md](build-workflow.md))
- **Example**: The `obsidian-oxygen` theme uses this pattern with Grunt
### Wrong Structure (Common Mistakes)
```
theme.css # ❌ DON'T have both
src/
scss/ # ❌ This causes confusion about which is the source
index.scss
```
**Best practice**: Choose one pattern and stick with it. Simple themes should use `theme.css` directly. Complex themes should use `src/scss/` and compile to `theme.css`.
- **Do not commit build artifacts**: Never commit `node_modules/`, intermediate compiled CSS files (like `src/css/main.css`), or other generated files. Only commit `theme.css` (the final output).
- Keep themes lightweight. Avoid complex build processes unless necessary.
- Release artifacts: `manifest.json` and `theme.css` must be at the top level of the theme folder in the vault.

View file

@ -6,55 +6,34 @@ Update frequency: Check Obsidian releases repo for validation requirements
# Manifest rules (`manifest.json`)
## Required Fields
### Required Fields
All plugins must include these fields in `manifest.json`:
All themes must include these fields in `manifest.json`:
- **`id`** (string, required) - Unique plugin identifier. Should be lowercase with hyphens (e.g., `"my-plugin-name"`). For local development, it should match the folder name. **Never change `id` after release** - treat it as stable API.
- **`name`** (string, required) - Display name of the plugin (e.g., `"My Plugin Name"`)
- **`version`** (string, required) - Semantic Versioning format `x.y.z` (e.g., `"1.0.0"`, `"0.9.3"`)
- **`minAppVersion`** (string, required) - Minimum Obsidian version required (e.g., `"0.15.0"`). Keep this accurate when using newer APIs.
- **`description`** (string, required) - Brief description of what the plugin does
- **`isDesktopOnly`** (boolean, required) - Set to `false` if the plugin works on mobile, `true` if desktop-only
- **`name`** (string, required) - Theme name
- **`version`** (string, required) - Semantic Versioning format `x.y.z` (e.g., `"1.0.0"`)
- **`minAppVersion`** (string, required) - Minimum Obsidian version required
- **`author`** (string, required) - Author name (required for themes)
## Optional Fields
### Optional Fields
- **`author`** (string, optional) - Author name
- **`authorUrl`** (string, optional) - URL to author's website or profile
- **`fundingUrl`** (string, optional) - URL for funding/support (e.g., Patreon, Ko-fi, GitHub Sponsors)
- **`fundingUrl`** (string, optional) - URL for funding/support
## Example Structure
### Important Notes
```json
{
"id": "your-plugin-id",
"name": "Your Plugin Name",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "A brief description of what your plugin does.",
"author": "Your Name",
"authorUrl": "https://yourwebsite.com",
"fundingUrl": "https://your-funding-url.com",
"isDesktopOnly": false
}
```
## Important Notes
- Never change `id` after release. Treat it as stable API.
- Keep `minAppVersion` accurate when using newer APIs.
- Use semantic versioning for `version` field.
- Canonical requirements: [Obsidian Plugin Validation Workflow](https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml)
- Themes do **not** use `id` or `isDesktopOnly` fields.
- Canonical requirements: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-theme-entry.yml
## Validation Checklist
When reviewing or creating a `manifest.json` file, ensure:
- [ ] All required fields are present (`id`, `name`, `version`, `minAppVersion`, `description`, `isDesktopOnly`)
- [ ] `id` uses lowercase with hyphens, matches folder name
- [ ] All required fields are present (`name`, `version`, `minAppVersion`, `author`)
- [ ] `version` follows semantic versioning (x.y.z)
- [ ] `minAppVersion` is set appropriately for the APIs used
- [ ] `isDesktopOnly` is set correctly based on mobile compatibility
- [ ] `minAppVersion` is set appropriately
- [ ] JSON syntax is valid (proper quotes, commas, brackets)
- [ ] All string values are properly quoted
- [ ] Boolean values are `true` or `false` (not strings)
- [ ] No plugin-specific fields (`id`, `isDesktopOnly`) are present

View file

@ -6,8 +6,9 @@ Update frequency: Check Obsidian community discussions for updates
# 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.
- Test themes on iOS and Android devices.
- Ensure CSS works correctly on mobile viewports.
- Consider touch-friendly UI elements and spacing.
- Test theme appearance in both light and dark modes on mobile.

View file

@ -1,220 +0,0 @@
<!--
Source: Based on Obsidian bot review feedback and requirements
Last synced: N/A - Project-specific documentation
Update frequency: Update as new bot requirements are identified
Applicability: Plugin
-->
# Obsidian Bot Review Requirements
**Purpose**: This is the **authoritative source** for Obsidian bot review requirements. It outlines requirements enforced by the Obsidian review bot that may differ from local linting. Use this as a reference when preparing your plugin for submission.
**Related documentation**:
- [linting-fixes-guide.md](linting-fixes-guide.md) - Step-by-step fix procedures
- [common-pitfalls.md](common-pitfalls.md) - General development pitfalls
- [release-readiness.md](release-readiness.md) - Pre-submission checklist
## Required Configuration
Your `eslint.config.mjs` should include these rules to match the bot:
```javascript
rules: {
// Restrict console to only warn, error, debug (Obsidian bot requirement)
"no-console": ["error", { "allow": ["warn", "error", "debug"] }],
// Require await in async functions (Obsidian bot requirement)
"@typescript-eslint/require-await": "error",
}
```
## Rules That Cannot Be Disabled
These rules cannot be disabled with eslint-disable comments. If you encounter these, you must fix the underlying issue rather than disabling the rule:
- **`obsidianmd/no-static-styles-assignment`** - Refactor code to use `setCssProperties()` or CSS classes instead of direct style manipulation
- **`@typescript-eslint/no-explicit-any`** - Use proper types, `unknown` with type guards, or type assertions instead
- **`obsidianmd/ui/sentence-case`** - Fix UI text to use proper sentence case, or use `/skip` in bot review for legitimate false positives
### Examples
**Static Style Assignment** (cannot disable):
```ts
// ❌ Wrong - Cannot disable this rule
// eslint-disable-next-line obsidianmd/no-static-styles-assignment
element.style.cssText = 'color: red;';
// ✅ Correct - Use setCssProperties or CSS classes
import { setCssProps } from 'obsidian';
setCssProps(element, { color: 'red' });
// Or:
element.addClass('my-custom-class');
```
**No Explicit Any** (cannot disable):
```ts
// ❌ Wrong - Cannot disable this rule
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function process(data: any) { }
// ✅ Correct - Use unknown with type guards
function process(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data');
}
```
**UI Sentence Case** (cannot disable in code):
```ts
// ❌ Wrong - Cannot disable this rule in code
// eslint-disable-next-line obsidianmd/ui/sentence-case // Bot will reject this!
.setName("Enable Feature")
// ✅ Correct - Use sentence case
.setName("Enable feature")
// ✅ Also acceptable - Use /skip in bot review for legitimate false positives
// If text is already correct but bot flags it (e.g., proper nouns, technical notation),
// use /skip in bot review comment: /skip False positive: "Astro" is a proper noun
```
## ESLint Disable Comment Requirements
All eslint-disable comments must:
1. **Include descriptions** explaining why the disable is necessary (REQUIRED - bot will reject without description)
2. **Be placed directly before** the line with the error (no blank lines)
3. **Not disable any of the rules listed above** (non-disablable rules)
**CRITICAL**: The bot will flag any eslint-disable comment that lacks a description. This is a required field, not optional.
### Format
**Wrong** (no description):
```ts
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = await fetchExternalData();
```
**Correct** (with description):
```ts
// External API returns unknown type - reason: External API
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = await fetchExternalData();
```
**Wrong** (blank line between comment and error):
```ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = fetchData();
```
**Correct** (comment directly before error):
```ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = fetchData();
```
## Unused ESLint Disable Directives
Remove any eslint-disable directives that aren't actually needed. The bot will flag unused disables.
**Problem**: Disabling a rule that isn't being violated:
```ts
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const value = this.settings.enabled; // This line doesn't actually violate the rule
```
**Solution**: Remove the disable comment if it's not needed.
## Common Issues
### Console.log() Usage
**Problem**: Using `console.log()` will fail bot review.
**Solution**: Use `console.debug()`, `console.warn()`, or `console.error()` instead.
```ts
// ❌ Wrong
console.log("Debug info");
// ✅ Correct
console.debug("Debug info");
```
### Async Functions Without Await
**Problem**: Async functions that don't use await will fail bot review.
**Solution**: Remove `async` keyword if no await is needed, or add await if the function should be async.
```ts
// ❌ Wrong
async handleClick() {
this.doSomething(); // No await needed
}
// ✅ Correct
handleClick() {
this.doSomething();
}
// Or if you need async:
async handleClick() {
await this.doSomethingAsync();
}
```
## Handling False Positives
### Sentence Case False Positives
The `obsidianmd/ui/sentence-case` rule cannot be disabled in code, but the Obsidian bot allows `/skip` in review comments for legitimate false positives.
### When to use `/skip`
- Text is already correct but bot flags it
- Text contains proper nouns (framework names, product names) that must be capitalized
- Text contains technical notation (date format codes, file paths) that cannot be changed
- Rephrasing would make the text less clear or accurate
### How to use `/skip`
In the bot review comment, use: `/skip False positive: [explanation]`
**Example**:
```markdown
/skip False positive: "Astro" is a proper noun (framework name) and must be capitalized
```
**Note**: Only use `/skip` for legitimate false positives. If the text can be fixed, fix it instead.
## Testing Before Submission
Before submitting to Obsidian:
1. **Run `pnpm build`** - Must pass
2. **Run `pnpm lint`** - Must pass with the stricter configuration
3. **Verify no `console.log()` statements** (only debug/warn/error)
4. **Verify all async methods have await** or are not async
5. **Verify no disallowed rule disables present** (except use `/skip` for sentence-case false positives)
6. **Verify all disable comments have descriptions**
7. **Remove any unused eslint-disable directives**
## Configuration Checklist
Ensure your `eslint.config.mjs` includes:
- [ ] `"no-console": ["error", { "allow": ["warn", "error", "debug"] }]`
- [ ] `"@typescript-eslint/require-await": "error"`
- [ ] No attempts to disable non-disablable rules
- [ ] All disable comments include descriptions
## Related Documentation
- [environment.md](environment.md) - ESLint setup and configuration
- [common-pitfalls.md](common-pitfalls.md#obsidian-bot-review-requirements) - Common bot review issues
- [release-readiness.md](release-readiness.md) - Pre-submission checklist

View file

@ -2,7 +2,6 @@
Source: Project-specific instructions
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Update as reference management strategy evolves
Applicability: Both
-->
# .ref Folder Instructions
@ -175,7 +174,7 @@ ln -s ../.ref/obsidian-dev/eslint-plugin .ref/eslint-plugin
**Important**: Adjust the relative path (`../.ref/obsidian-dev`) based on where your project is relative to your central `.ref/obsidian-dev` location. If they're in different directory structures, use absolute paths.
**Easiest method**: Use the setup scripts in the `scripts/` folder:
- **Windows**: `scripts\setup-ref-links.bat`
- **Windows**: `scripts\setup-ref-links.bat` or `.\scripts\setup-ref-links.ps1`
- **macOS/Linux**: `./scripts/setup-ref-links.sh`
These scripts will automatically:
@ -191,7 +190,7 @@ You can run the setup script anytime to keep your reference repos up to date.
**Note**: Updates are **optional**. The reference materials work fine with whatever version was cloned initially. Most users never need to update. Only update if you want the latest documentation.
**Easiest way to update**: Simply re-run the setup script from any project:
- **Windows**: `scripts\setup-ref-links.bat`
- **Windows**: `scripts\setup-ref-links.bat` or `.\scripts\setup-ref-links.ps1`
- **macOS/Linux**: `./scripts/setup-ref-links.sh`
The setup script will automatically pull the latest changes for all 6 core repos if they already exist.

View file

@ -2,30 +2,21 @@
Source: Obsidian official documentation and resources
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check periodically for new resources
Applicability: Both
-->
# References
## Official Repositories
- [Obsidian sample plugin](https://github.com/obsidianmd/obsidian-sample-plugin)
- [Obsidian API](https://github.com/obsidianmd/obsidian-api)
- [Obsidian developer docs](https://github.com/obsidianmd/obsidian-developer-docs)
- [Obsidian plugin docs](https://github.com/obsidianmd/obsidian-plugin-docs)
- [Obsidian sample theme](https://github.com/obsidianmd/obsidian-sample-theme)
- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
- Obsidian API: https://github.com/obsidianmd/obsidian-api
- Obsidian developer docs: https://github.com/obsidianmd/obsidian-developer-docs
- Obsidian plugin docs: https://github.com/obsidianmd/obsidian-plugin-docs
- Obsidian sample theme: https://github.com/obsidianmd/obsidian-sample-theme
## Official Documentation
- [API documentation](https://docs.obsidian.md)
- [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)
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
- [Obsidian Bases Syntax](https://help.obsidian.md/bases/syntax)
- [JSON Canvas Specification](https://jsoncanvas.org/)
- [Secret Storage guide](https://docs.obsidian.md/plugins/guides/secret-storage) - Secure storage for API keys, tokens, and passwords (available since Obsidian 1.11.4)
- [Supporting Pop-Out Windows guide](https://docs.obsidian.md/plugins/guides/supporting-pop-out-windows) - Cross-window compatibility for plugins (available since Obsidian v0.15.0)
## Community Resources
- [obsidian-skills](https://github.com/kepano/obsidian-skills) - Claude Skills for Obsidian (comprehensive syntax documentation source)
- API documentation: https://docs.obsidian.md
- 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

View file

@ -2,7 +2,6 @@
Source: Based on Obsidian style guide and UX guidelines
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian style guide for updates
Applicability: Both
-->
# UX & copy guidelines (for UI text, commands, settings)
@ -10,11 +9,7 @@ Applicability: Both
- 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**.
- Use arrow notation for navigation: **Settings → Appearance → Themes**.
- Keep in-app strings short, consistent, and free of jargon.
- **Terminology**:
- Both "properties" and "frontmatter" are valid Obsidian terms with distinct meanings: use "properties" when referring to the Properties pane UI (editable typed metadata) and "frontmatter" when referring to the underlying YAML block serialized between --- markers. This aligns with Obsidian's official terminology.
- Use "Markdown" (capitalized, as a proper noun) in UI text. This aligns with standard terminology conventions.
- Use "file name" (two words, not "filename") when referring to the name of a file. This aligns with Obsidian's official terminology.

View file

@ -1,34 +1,32 @@
---
name: project
description: Project-specific architecture, maintenance tasks, and unique conventions for this repository. Load when performing project-wide maintenance or working with the core architecture.
description: Project-specific architecture, maintenance tasks, and unique conventions for Astro Composer.
---
# Project Context
# Astro Composer Project Skill
This skill provides the unique context and architectural details for the **Obsidian Sample Plugin Plus** repository.
Turn your notes into posts and pages for your Astro blog with automated content management features. This plugin simplifies the workflow of publishing Obsidian notes to an Astro project by managing slugs, dates, and frontmatter requirements.
## Purpose
## Core Architecture
To provide guidance on project-specific structures and tasks that differ from general Obsidian development patterns.
## When to Use
Load this skill when:
- Understanding the repository's unique architecture.
- Performing recurring maintenance tasks.
- Following project-specific coding conventions.
## Project Overview
- **Architecture**: Organized structure with main code in `src/main.ts` and settings in `src/settings.ts`.
- **Reference Management**: Uses a `.ref` folder with symlinks to centralized Obsidian repositories for API and documentation.
## Maintenance Tasks
- **Sync References**: Run the setup scripts (`scripts/setup-ref-links.*`) to update symlinks to the 6 core Obsidian projects.
- **Update Skills**: Use `node scripts/update-agents.mjs "Description"` after syncing or updating reference materials.
- **Astro Integration**: Designed to bridge the gap between Obsidian's markdown and Astro's content collection requirements.
- **Metadata Automation**: Manages slugs and publishing dates automatically.
- **UI Components**: Provides a set of tools/modals for configuring post metadata.
## Project-Specific Conventions
- **Organized Source**: Prefer keeping logic separated into files within `src/` rather than bloating `main.ts`.
- **Ref Symlinks**: Always use the `.ref/` path when looking up API documentation to ensure parity with the central reference store.
- **Astro Patterns**: Follows standard Astro frontmatter conventions.
- **Slug Management**: Centralizes slug generation logic to avoid collisions.
- **Style Consistency**: Uses `styles.css` (9KB) for specialized modal and view styling.
## Key Files
- `src/main.ts`: Main plugin logic and command registration.
- `manifest.json`: Plugin registration and id (`astro-composer`).
- `styles.css`: Custom UI styling for composer tools.
- `esbuild.config.mjs`: Build script for production bundling.
## Maintenance Tasks
- **Astro Compatibility**: Track changes in Astro's content collection specification.
- **Validation**: Ensure slug generation is safe for all OS file systems.

View file

@ -1,14 +0,0 @@
<!--
Source: Based on Obsidian Sample Plugin and community guidelines
Last synced: See sync-status.json for authoritative sync dates
Update frequency: Check Obsidian Sample Plugin repo for updates
-->
# Project overview
- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
- Entry point: `main.ts` (in root for simple plugins, or `src/main.ts` for organized plugins) compiled to `main.js` and loaded by Obsidian.
- **Important**: `main.ts` can be in root for simple plugins, or in `src/` for better organization. Never have it in both locations. See [file-conventions.md](file-conventions.md) for structure.
- Required release artifacts: `main.js` (compiled from `main.ts`), `manifest.json`, and optional `styles.css`.

View file

@ -1,7 +1,6 @@
{
"_comment": "Sync date tracking: 'lastChecked' = when repo was checked for updates, 'lastSynced' = when content was actually synced from that repo. If a repo shows 'lastChecked' newer than 'lastSynced', it means we checked it but there were no new changes to sync.",
"lastFullSync": "2026-01-20",
"lastSyncSource": "Description",
"lastFullSync": "2026-01-23",
"lastSyncSource": "obsidian-dev-skills initialization",
"filesSynced": [
"project-overview.md",
"environment.md",
@ -23,28 +22,28 @@
],
"sourceRepos": {
"obsidian-sample-plugin": {
"lastChecked": "2026-01-07",
"lastSynced": "2026-01-07"
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
},
"obsidian-api": {
"lastChecked": "2026-01-07",
"lastSynced": "2026-01-07"
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
},
"obsidian-developer-docs": {
"lastChecked": "2026-01-07",
"lastSynced": "2026-01-07"
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
},
"obsidian-plugin-docs": {
"lastChecked": "2026-01-07",
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
},
"obsidian-sample-theme": {
"lastChecked": "2026-01-07",
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
},
"eslint-plugin": {
"lastChecked": "2026-01-07",
"lastSynced": "2026-01-07"
"lastChecked": "2025-12-15",
"lastSynced": "2025-12-15"
}
}
}
}

View file

@ -1,6 +1,6 @@
# AGENTS
This project uses the OpenSkills system for AI agent guidance.
This project uses the OpenSkills system for AI agent guidance. General development skills are provided by the [obsidian-dev-skills](https://github.com/davidvkimball/obsidian-dev-skills) repository.
<skills_system priority="1">
@ -64,3 +64,4 @@ Usage notes:
## Terminology
- Use **"properties"** (never "frontmatter" or "front-matter") when referring to YAML metadata at the top of Markdown files.
- **"Markdown"** is a proper noun and must always be capitalized.

View file

@ -16,18 +16,19 @@
"author": "David V. Kimball",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.30.1",
"@eslint/json": "^0.14.0",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1",
"esbuild": "0.25.5",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "5.8.3",
"@eslint/js": "^9.30.1",
"@eslint/json": "^0.14.0",
"eslint": "^9.39.1",
"eslint-plugin-obsidianmd": "0.1.9",
"globals": "14.0.0",
"obsidian": "latest",
"obsidian-dev-skills": "^1.0.3",
"tslib": "2.4.0",
"typescript": "5.8.3",
"typescript-eslint": "8.35.1"
},
"packageManager": "pnpm@10.20.0"

View file

@ -38,6 +38,9 @@ importers:
obsidian:
specifier: latest
version: 1.11.0(@codemirror/state@6.5.0)(@codemirror/view@6.38.6)
obsidian-dev-skills:
specifier: ^1.0.3
version: 1.0.3
tslib:
specifier: 2.4.0
version: 2.4.0
@ -1167,6 +1170,11 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
obsidian-dev-skills@1.0.3:
resolution: {integrity: sha512-vkld8CA0d+M8dUOGu7vl8KHOS8sVwYLAggVr3YW/ikwSi7QUaJnaSIzyQIR6M+lAa6VSWyoPD/a5DDF/sLDf7g==}
engines: {node: '>=16.0.0'}
hasBin: true
obsidian@1.11.0:
resolution: {integrity: sha512-lVqN9AmDWHzhNATi2tDnjqVgI6WUYKeT+lIsAycAyLt4XCC6zRsWzb+tFCiB7Rn3PpttefjoovilhYwvS4Iqxw==}
peerDependencies:
@ -2859,6 +2867,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
obsidian-dev-skills@1.0.3: {}
obsidian@1.11.0(@codemirror/state@6.5.0)(@codemirror/view@6.38.6):
dependencies:
'@codemirror/state': 6.5.0

View file

@ -5,24 +5,6 @@ REM Run this from anywhere: scripts\setup-ref-links.bat
REM Change to project root (parent of scripts folder)
cd /d "%~dp0\.."
REM Check Node.js version (requires v16+)
where node >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Node.js is not installed or not in PATH
echo Please install Node.js v16+ from https://nodejs.org/
exit /b 1
)
REM Verify Node.js version is v16 or higher
for /f "tokens=1" %%i in ('node --version') do set NODE_VERSION=%%i
set NODE_VERSION=%NODE_VERSION:v=%
for /f "tokens=1 delims=." %%a in ("%NODE_VERSION%") do set NODE_MAJOR=%%a
if %NODE_MAJOR% lss 16 (
echo ERROR: Node.js v16+ is required (found v%NODE_VERSION%)
echo Please upgrade Node.js from https://nodejs.org/
exit /b 1
)
echo Setting up symlinks to core Obsidian projects...
REM Central .ref location (one level up from project)
@ -232,3 +214,5 @@ echo.
echo Verifying symlinks...
dir .ref

View file

@ -10,49 +10,6 @@ if ! cd "$PROJECT_ROOT"; then
exit 1
fi
# Check Node.js version (requires v16+)
if ! command -v node &> /dev/null; then
echo "ERROR: Node.js is not installed or not in PATH"
echo "Please install Node.js v16+ from https://nodejs.org/"
exit 1
fi
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [ "$NODE_VERSION" -lt 16 ]; then
echo "ERROR: Node.js v16+ is required (found v$NODE_VERSION)"
echo "Please upgrade Node.js from https://nodejs.org/"
exit 1
fi
echo "Setting up symlinks to core Obsidian projects..."
# Central .ref location (one level up from project)
CENTRAL_REF_ROOT="../.ref"
CENTRAL_REF="../.ref/obsidian-dev"
# Create central .ref root if it doesn't exist
if [ ! -d "$CENTRAL_REF_ROOT" ]; then
mkdir -p "$CENTRAL_REF_ROOT"
echo "Created central .ref directory"
fi
# Create obsidian-dev subfolder if it doesn't exist
if [ ! -d "$CENTRAL_REF" ]; then
mkdir -p "$CENTRAL_REF"
echo "Created obsidian-dev subfolder"
fi
# Ensure plugins and themes folders exist
mkdir -p "$CENTRAL_REF/plugins"
mkdir -p "$CENTRAL_REF/themes"
# Check if git is available
if ! command -v git &> /dev/null; then
echo "ERROR: git is not installed or not in PATH"
echo "Please install git from https://git-scm.com/"
exit 1
fi
# Clone the 6 core repos if they don't exist, or pull latest if they do
if [ ! -d "$CENTRAL_REF/obsidian-api" ]; then
echo "Cloning obsidian-api..."
@ -182,3 +139,7 @@ for project in "${CORE_PROJECTS[@]}"; do
fi
done

52
scripts/setup-skills.ps1 Normal file
View file

@ -0,0 +1,52 @@
# Setup skills symlinks for Obsidian Sample Plugin Plus
# This script creates symlinks to the obsidian-dev-skills repository
param(
[string]$SkillsRepoPath = "$PSScriptRoot\..\obsidian-dev-skills"
)
$ErrorActionPreference = "Stop"
Write-Host "Setting up skills symlinks..." -ForegroundColor Cyan
# Check if skills repo exists
if (-not (Test-Path $SkillsRepoPath)) {
Write-Host "Skills repository not found at: $SkillsRepoPath" -ForegroundColor Red
Write-Host "Please clone obsidian-dev-skills to a sibling directory." -ForegroundColor Yellow
Write-Host "Example: git clone https://github.com/davidvkimball/obsidian-dev-skills.git obsidian-dev-skills" -ForegroundColor Yellow
exit 1
}
$skillsDir = "$PSScriptRoot\..\.agent\skills"
# Create skills directory if it doesn't exist
if (-not (Test-Path $skillsDir)) {
New-Item -ItemType Directory -Path $skillsDir -Force | Out-Null
}
$skills = @("obsidian-dev", "obsidian-ops", "obsidian-ref")
foreach ($skill in $skills) {
$targetPath = Join-Path $skillsDir $skill
$sourcePath = Join-Path $SkillsRepoPath $skill
# Remove existing symlink/directory if it exists
if (Test-Path $targetPath) {
$item = Get-Item $targetPath
if ($item.LinkType -eq "Junction" -or $item.LinkType -eq "SymbolicLink") {
Remove-Item $targetPath -Force
} else {
Remove-Item $targetPath -Recurse -Force
}
}
# Create symlink
Write-Host "Creating symlink: $skill" -ForegroundColor Green
cmd /c mklink /J "$targetPath" "$sourcePath" | Out-Null
}
Write-Host "Skills setup complete!" -ForegroundColor Cyan
Write-Host "The following skills are now available:" -ForegroundColor Gray
Write-Host " - obsidian-dev (core development)" -ForegroundColor Gray
Write-Host " - obsidian-ops (operations & workflows)" -ForegroundColor Gray
Write-Host " - obsidian-ref (technical references)" -ForegroundColor Gray

45
scripts/setup-skills.sh Normal file
View file

@ -0,0 +1,45 @@
#!/bin/bash
# Setup skills symlinks for Obsidian Sample Plugin Plus
# This script creates symlinks to the obsidian-dev-skills repository
set -e
SKILLS_REPO_PATH="${1:-"../obsidian-dev-skills"}"
echo "Setting up skills symlinks..."
# Check if skills repo exists
if [ ! -d "$SKILLS_REPO_PATH" ]; then
echo "Skills repository not found at: $SKILLS_REPO_PATH"
echo "Please clone obsidian-dev-skills to a sibling directory."
echo "Example: git clone https://github.com/davidvkimball/obsidian-dev-skills.git ../obsidian-dev-skills"
exit 1
fi
SKILLS_DIR=".agent/skills"
# Create skills directory if it doesn't exist
mkdir -p "$SKILLS_DIR"
SKILLS=("obsidian-dev" "obsidian-ops" "obsidian-ref")
for skill in "${SKILLS[@]}"; do
target_path="$SKILLS_DIR/$skill"
source_path="$SKILLS_REPO_PATH/$skill"
# Remove existing symlink/directory if it exists
if [ -L "$target_path" ] || [ -d "$target_path" ]; then
rm -rf "$target_path"
fi
# Create symlink
echo "Creating symlink: $skill"
ln -s "$source_path" "$target_path"
done
echo "Skills setup complete!"
echo "The following skills are now available:"
echo " - obsidian-dev (core development)"
echo " - obsidian-ops (operations & workflows)"
echo " - obsidian-ref (technical references)"