Contributions are welcome! This guide will help you set up your development environment and understand the project architecture.
- Node.js (v16 or higher)
- npm
- Obsidian (for testing)
# Clone the repo to your vault's plugin folder
cd /path/to/vault/.obsidian/plugins
git clone https://github.com/inattendu/dashreader
cd dashreader
# Install dependencies
npm install
# Build for production
npm run build
# Or build with watch mode for development
npm run dev# Option 1: Use install script (creates symlink)
./install-local.sh /path/to/vault
# Option 2: Manual copy after build
cp main.js manifest.json styles.css /path/to/vault/.obsidian/plugins/dashreader/
# Reload Obsidian
# macOS: Cmd+R
# Windows/Linux: Ctrl+RThese guidelines from Obsidian Plugin Guidelines MUST be followed. Violations will cause the plugin to fail review.
❌ NEVER use innerHTML/outerHTML with user input
- User notes can contain
<script>tags that will execute - Always escape HTML or use DOM API:
createEl(),createDiv(),createSpan() - Bad:
el.innerHTML = userText - Good:
el.textContent = userText
Example for HTML escaping when necessary:
function escapeHtml(text: string): string {
return text.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}❌ DO NOT call detachLeavesOfType() in onunload()
- Prevents Obsidian from restoring leaf positions during plugin updates
- Leaves will be reinitialized automatically at their original position
Prefer CSS classes over inline styles
- Bad:
el.style.color = 'red' - Good: Use CSS classes and CSS variables
el.addCls('warning-text');
// In CSS: .warning-text { color: var(--text-error); }Use toggleClass() for visibility
- Bad:
el.style.display = 'none' - Good:
el.toggleClass('hidden', shouldHide)
Minimize console output
- Remove debug logs before release
- Keep only error logs:
console.error() - The console should be clean by default
Use Obsidian helper functions
containerEl.createEl('div', { cls: 'my-class' })
containerEl.createDiv({ cls: 'my-div' })
containerEl.createSpan({ text: 'My text' })
el.empty() // Clear element contentsUse sentence case, not Title Case
- Good: "Template folder location"
- Bad: "Template Folder Location"
Use setHeading() for settings sections
new Setting(containerEl).setName('Section name').setHeading();Use this.app, never window.app
- The global
appis for debugging only - Always use the reference from your plugin
main- Stable releases onlyrefactor/*- Major refactoring workfeature/*- New featuresfix/*- Bug fixes
Follow conventional commits:
feat: Add new feature
fix: Fix bug
refactor: Code refactoring
docs: Documentation changes
style: Code style changes (formatting)
test: Add or update tests
chore: Maintenance tasks
Examples:
git commit -m "feat: Add minimap navigation"
git commit -m "fix: Correct cursor position tracking"
git commit -m "refactor(phase2): Extract MicropauseService"
git commit -m "docs: Update CLAUDE.md architecture section"dashreader/
├── main.ts # Plugin entry point
├── manifest.json # Plugin manifest
├── versions.json # Version compatibility
├── styles.css # Plugin styles
├── CLAUDE.md # Architecture documentation
├── src/
│ ├── Core Architecture (6 files)
│ ├── rsvp-view.ts # UI layer (ItemView)
│ ├── rsvp-engine.ts # Reading engine logic
│ ├── markdown-parser.ts # Markdown to plain text
│ ├── settings.ts # Settings UI
│ ├── types.ts # TypeScript interfaces
│ │
│ ├── Support Modules (11 files)
│ ├── constants.ts # CSS classes, timing, limits
│ ├── logger.ts # Centralized logging
│ ├── hotkey-handler.ts # Keyboard shortcuts
│ ├── word-display.ts # Word rendering logic
│ ├── dom-registry.ts # DOM element management
│ ├── view-state.ts # Reactive state management
│ ├── breadcrumb-manager.ts # Navigation breadcrumb
│ ├── minimap-manager.ts # Visual minimap
│ ├── menu-builder.ts # Dropdown menus
│ ├── auto-load-manager.ts # Auto-load functionality
│ ├── ui-builders.ts # UI component builders
│ │
│ └── services/ # Business logic services (4 files)
│ ├── timeout-manager.ts # Timer lifecycle management
│ ├── settings-validator.ts # Settings validation & clamping
│ ├── micropause-service.ts # Micropause calculation (Strategy Pattern)
│ └── stats-formatter.ts # Statistics formatting
View (rsvp-view.ts) owns UI and events:
- User interactions
- DOM rendering
- Cursor tracking
- Display updates
Engine (rsvp-engine.ts) owns reading logic:
- Word iteration
- Timing control
- Micropause calculation
- Progress tracking
Communication:
- View → Engine:
setText(),play(),pause(),updateSettings() - Engine → View:
onWordChange(),onComplete()callbacks
Dedicated services for specific concerns:
- TimeoutManager - Timer lifecycle (prevent memory leaks)
- SettingsValidator - Settings validation and clamping
- MicropauseService - Pause calculation (Strategy Pattern)
- StatsFormatter - Statistics formatting
MicropauseService implements Strategy Pattern for micropause types:
interface MicropauseStrategy {
matches(word: string): boolean;
getMultiplier(settings: DashReaderSettings): number;
}
// Strategies: Punctuation, Numbers, LongWords, Paragraphs, etc.- Use
constandlet, nevervar - Prefer
async/awaitover Promises - Enable strict mode
- Minimize
as anycasts (currently only 1 in entire codebase)
- Prefix with
DashReader:for easy filtering - Remove debug logs before release
- Use centralized logger.ts
- Always use word index (count) after parsing
- Never character position from raw text
- Parse Markdown FIRST, then count words
- Cursor tracking: 150ms throttle (balance responsiveness/performance)
- Use Obsidian's debounce utilities when appropriate
- Use CSS variables for theme compatibility
- Examples:
var(--text-muted),var(--background-primary) - Never hardcode colors
# Development with watch mode
npm run dev
# Production build (TypeScript check + esbuild)
npm run build
# Version bump (updates manifest.json and versions.json)
npm run version
# Type checking only
npx tsc --noEmitImportant: The build output main.js MUST be tracked in git for Obsidian plugin releases.
For Obsidian plugin submission:
- Update version in
manifest.json(must match git tag, novprefix) - Run build:
npm run build - Commit changes including
main.js:git add main.js manifest.json versions.json git commit -m "chore: Release v1.x.x" - Create GitHub release with tag matching manifest version (e.g.,
1.4.1) - Attach files as binary assets:
main.js,manifest.json,styles.css
The manifest.json at repo root is used by Obsidian to check version. Actual files are fetched from GitHub release assets.
- Run
npm run buildand verify it succeeds - Test in actual Obsidian vault
- Verify Obsidian plugin guidelines compliance
- Update documentation if needed
- Check console for errors/warnings
## Summary
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactoring
- [ ] Documentation
## Testing
How the changes were tested
## Checklist
- [ ] Code follows project conventions
- [ ] Obsidian guidelines compliant
- [ ] No console.log() spam
- [ ] Documentation updated
- [ ] Build succeeds- Check CLAUDE.md for architecture details
- Open an issue for questions or suggestions
- Review existing code for examples
Thank you for contributing to DashReader! ⚡