Imported from dannysmith/astro-editor (
AGENTS.md). Install upstream withnpx skills add dannysmith/astro-editor. Copyright stays with the author.
Claude / AI Agent Instructions for Astro Editor
Current Status
@CLAUDE.local.md
Project Overview
Goal: Native macOS markdown editor for Astro content collections. Distraction-free writing environment with seamless frontmatter editing, inspired by iA Writer.
Purpose: Replace code editors for content writing. Focused environment understanding Astro's content structure.
Key Features:
- Auto-discovers Astro content collections from
src/content/config.ts - Dynamic frontmatter forms from Zod schemas
- File management with context menus
- Real-time auto-save every 2 seconds
- CodeMirror 6 with custom syntax highlighting
- Resizable panels, draft detection, file sorting
- Advanced editor features: URL clicking, drag & drop, markdown commands
- Comprehensive keyboard shortcuts and menu integration
- Toast notifications throughout the app
Note: The website/ directory is an independent project that uses bun (not pnpm). It is not part of the pnpm workspace. See website/AGENTS.md for its own instructions.
Core Rules
New Sessions
- Read @docs/TASKS.md for task management
- Review
docs/developer/architecture-guide.mdfor essential patterns - Consult specialized guides when working on specific features (see Documentation Structure)
- Check git status and project structure
Development Practices
CRITICAL: Follow these strictly:
- Read Before Editing: Always read files first to understand context
- Follow Established Patterns: Use patterns from this file and
docs/developer - Senior Architect Mindset: Consider performance, maintainability, testability
- Batch Operations: Use multiple tool calls in single responses
- Match Code Style: Follow existing formatting and patterns
- Test Coverage: Write comprehensive tests for business logic
- Quality Gates: Run
pnpm run check:allafter significant changes - No Dev Server: Ask user to run and report back
- No Unsolicited Commits: Only when explicitly requested
- Documentation: Update
docs/developer/guides for new patterns - Removing files: Always use
rm -f
Directory Boundaries
- Hooks belong in
/hooks/: If it exports ause*function, it goes in/hooks/ - Pure functions in
/lib/: Business logic, utilities, classes - getState() is allowed: One-way calls from lib to store using
getState()are acceptable - See
docs/developer/architecture-guide.mdfor complete rules
Documentation Structure
Core Guides (read for daily development):
docs/developer/architecture-guide.md- Essential patterns and overview (START HERE)docs/developer/state-management.md- Deep dive into the "Onion" pattern and store decompositiondocs/developer/command-system.md- Command pattern implementation and integrationdocs/developer/ui-patterns.md- Common UI patterns and shadcn/ui best practicesdocs/developer/performance-patterns.md- Performance optimization (getState, memoization)docs/developer/testing.md- Testing strategies and patterns
System Documentation (reference for system features):
docs/developer/cross-platform.md- Platform detection, conditional compilation, title bar architecturedocs/developer/form-patterns.md- Frontmatter fields and settings formsdocs/developer/schema-system.md- Schema parsing and merging (Rust)docs/developer/keyboard-shortcuts.md- Implementing shortcutsdocs/developer/preferences-system.md- Three-tier settings hierarchydocs/developer/color-system.md- Color tokens and dark modedocs/developer/notifications.md- Toast notification systemdocs/developer/editor-styles.md- CodeMirror syntax highlightingdocs/developer/recovery-system.md- Crash recoverydocs/developer/logging.md- Logging system
Implementation (optimization and build):
docs/developer/optimization.md- Bundle optimization and performance budgetsdocs/developer/releases.md- Release workflow and process
Feature Examples (specific implementations):
docs/developer/feature-image-preview.md- Image field preview implementationdocs/developer/astro-generated-contentcollection-schemas.md- Astro JSON Schema reference
Reference (decisions and setup):
docs/developer/decisions.md- Architectural decisions and trade-offsdocs/developer/apple-signing-setup.md- Code signing and deployment
See docs/README.md for the complete categorized list.
Documentation & Versions
- Context7 First: Always use Context7 for framework docs before WebSearch
- Version Requirements: Tauri v2.x, shadcn/ui v4.x, Tailwind v4.x, React 19.x, Zustand v5.x, CodeMirror v6.x, Vitest v4.x
- Progress Tracking: Update current task in
docs/tasks-todoafter major work
Specialized Agents
The project has six specialized agents to help with complex implementation challenges:
Project-Integrated Agents
1. macos-ui-engineer - Use when:
- Implementing native-feeling macOS UI patterns
- Working on typography, spacing, or visual hierarchy
- Creating or refining components that need to feel authentically Mac-like
- Applying Apple HIG principles to interface design
2. react-performance-architect - Use when:
- Reviewing React components for performance issues
- Implementing complex state management patterns
- Addressing render cascades or unnecessary re-renders
- Optimizing React hook usage and component architecture
3. rust-test-engineer - Use when:
- Writing comprehensive tests for Tauri backend code
- Testing file operations, schema parsing, or Rust commands
- Ensuring proper test coverage for business logic
- Creating integration tests for Tauri-frontend communication
4. typescript-test-engineer - Use when:
- Writing tests for React components, hooks, or TypeScript modules
- Testing Zustand stores, TanStack Query hooks, or complex business logic
- Ensuring comprehensive test coverage for frontend code
- Creating test utilities and fixtures
External Expert Consultants
5. tauri-v2-expert - Call in when:
- Facing complex Tauri v2 architectural decisions
- Debugging difficult IPC or native integration issues
- Implementing advanced Tauri features (plugins, multi-window, system integration)
- Optimizing performance or solving platform-specific problems
6. codemirror-6-specialist - Call in when:
- Implementing complex CodeMirror extensions or customizations
- Debugging editor state management or React integration issues
- Building advanced editor features (collaborative editing, custom parsers, etc.)
- Optimizing editor performance or solving rendering problems
Orchestration Guidelines
- Use project-integrated agents for implementation work within established patterns
- Call external consultants when facing novel problems or needing deep expertise
- Combine agents when problems span multiple domains (e.g., performance + UI)
- Agents can collaborate - one may recommend consulting another for specialized aspects
Custom Commands
/check - Quality Control
Use /check to verify work quality before completing tasks:
- Checks adherence to architecture-guide.md patterns
- Removes unnecessary comments and console.logs
- Runs
pnpm run check:all(includes ast-grep architectural linting) and fixes errors - Cleans up leftover code from failed approaches
When to use: Before completing significant features or refactoring work.
/knip-cleanup - Intelligent Unused Code Cleanup
Use /knip-cleanup to clean up unused dependencies, files, and exports:
- Runs knip to detect unused items
- Preserves shadcn/ui components (future use)
- Keeps Radix dependencies used by shadcn components
- Protects Tauri/Rust-called exports
- Auto-removes safe items
- Asks user about ambiguous items
When to use: Periodically during refactoring sessions to keep codebase clean.
/review-duplicates - Intelligent Duplicate Code Review
Use /review-duplicates to find and review duplicated code:
- Runs jscpd to detect code duplication
- Categorizes duplicates by type (business logic, patterns, utilities)
- Assesses risk level (high/medium/low)
- Distinguishes intentional from problematic duplication
- Provides refactoring recommendations
- All refactoring is manual and user-approved
When to use: Periodically during refactoring sessions to identify extraction opportunities.
Task Management
Use the task completion script to mark tasks as done:
# Complete a task (move from tasks-todo to tasks-done with today's date)
pnpm task:complete <task-name>
# Examples:
pnpm task:complete frontend-performance
pnpm task:complete 2
The script automatically:
- Finds matching tasks by partial name or number
- Strips the
task-[number]-prefix - Adds completion date:
task-YYYY-MM-DD- - Moves from
tasks-todo/totasks-done/
See docs/TASKS.md for full task management workflow.
Technology Stack
- Framework: Tauri v2 (Rust + React)
- Frontend: React 19 + TypeScript (strict) with React Compiler
- State: Hybrid approach:
- Server State: TanStack Query v5 for data fetching/caching
- Client State: Zustand for UI state and editing state
- Styling: Tailwind v4 + shadcn/ui
- Editor: CodeMirror 6 (vanilla) with custom extensions
- IMPORTANT: All
@lezer/*packages must use consistent versions across the dependency tree. We usepnpm.overridesin package.json to force@lezer/commonto match CodeMirror's version. Mismatched versions break syntax highlighting and cause runtime errors because Tag/Tree objects from different versions are incompatible. Check withpnpm why @lezer/commonandpnpm why @lezer/highlight.
- IMPORTANT: All
- Testing: Vitest + React Testing Library, Cargo
- Quality: ESLint (with React Compiler rules), Prettier, Clippy
CRITICAL: Use Tauri v2 docs only. Always use modern Rust formatting: format!("{variable}")
Tauri Commands (tauri-specta)
Commands use tauri-specta for end-to-end type safety. TypeScript bindings are auto-generated.
Adding a new command:
- Define in Rust with
#[tauri::command]and#[specta::specta] - Add to
src-tauri/src/bindings.rscollect_commands![] - Run
pnpm tauri devbriefly - bindings regenerate automatically in debug mode - Commit the updated
src/lib/bindings.ts - Add JSDoc to
src/types/domain.tsif exposing a new type
Usage: Import from @/lib/bindings, handle Result types:
import { commands } from '@/lib/bindings'
const result = await commands.scanProject(path)
if (result.status === 'error') throw new Error(result.error)
React Compiler
Automatic Optimization: React Compiler (v1.0) automatically memoizes components and hooks, eliminating the need for manual useMemo, useCallback, and React.memo in most cases. See docs/developer/performance-patterns.md for comprehensive guidance.
Key Points:
- Compiler handles memoization automatically - don't add manual memoization unless profiling shows a need
- Enforces Rules of React through stricter ESLint rules
- "use no memo" directive: Opt-out specific components if needed (document why)
- Zustand patterns unchanged: Selector syntax and
getState()remain critical for performance
Architecture Overview
See docs/developer/architecture-guide.md for comprehensive architectural patterns and detailed implementation guidance.
State Management (Essential Summary)
- Server State: TanStack Query for filesystem data (
useCollectionsQuery,useCollectionFilesQuery,useFileContentQuery) - Client State: Decomposed Zustand stores -
editorStore(file editing),projectStore(project-level),uiStore(layout) - Local State: UI presentation, derived state, component lifecycle only
Key Patterns
Direct Store Pattern (CRITICAL)
Problem: React Hook Form + Zustand causes infinite loops Solution: Components access store directly using selector syntax
// ✅ CORRECT: Direct store pattern with selector syntax
const StringField = ({ name, label, required }) => {
const value = useEditorStore(state => state.frontmatter[name])
const updateFrontmatterField = useEditorStore(state => state.updateFrontmatterField)
return (
<Input
value={value || ''}
onChange={e => updateFrontmatterField(name, e.target.value)}
/>
)
}
// ❌ WRONG: Callback dependencies cause infinite loops
const BadField = ({ name, onChange }) => {
/* Don't do this */
}
Note: Always use selector syntax (useStore(state => state.value)) instead of destructuring (const { value } = useStore()) to create granular subscriptions. For objects/arrays, use useShallow to prevent re-renders from reference changes. See performance-patterns.md for details.
Command Pattern
// Global registry for all editor operations
globalCommandRegistry.execute('toggleBold')
globalCommandRegistry.execute('formatHeading', 1)
Benefits: Decouples UI from logic, enables shortcuts/menus/palette to share commands
Event-Driven Communication
- Tauri Events: Native menu/OS integration
- Custom DOM Events: Component communication
- CodeMirror Transactions: Editor state
- Zustand Subscriptions: Store changes
- Toast Events: Rust-to-frontend notifications
Hybrid Action Hooks Pattern
Problem: Zustand stores can't use React hooks, but user-triggered actions need query data.
Solution: User-triggered actions live in hooks with direct query access; state-triggered actions (auto-save) live in stores and call registered callbacks.
// 1. Hook with direct query access (user-triggered actions)
export function useEditorActions() {
const queryClient = useQueryClient()
const saveFile = useCallback(async (showToast = true) => {
// Direct access to stores via getState() - no subscription
const { currentFile, editorContent, frontmatter } = useEditorStore.getState()
const { projectPath } = useProjectStore.getState()
// Direct access to query data - NO EVENTS!
const collections = queryClient.getQueryData(queryKeys.collections(projectPath))
const schema = collections?.find(c => c.name === currentFile.collection)?.complete_schema
await commands.saveMarkdownContent(/* ... */)
useEditorStore.getState().markAsSaved()
}, [queryClient])
return { saveFile }
}
// 2. Store with state-triggered logic
const useEditorStore = create((set, get) => ({
autoSaveCallback: null,
setAutoSaveCallback: callback => set({ autoSaveCallback: callback }),
// State mutation triggers auto-save
setEditorContent: content => {
set({ editorContent: content, isDirty: true })
get().scheduleAutoSave() // State-triggered
},
scheduleAutoSave: () => {
const { autoSaveCallback } = get()
if (autoSaveCallback) {
setTimeout(() => void autoSaveCallback(), 2000)
}
},
}))
// 3. Layout wires them together
export function Layout() {
const { saveFile } = useEditorActions()
useEffect(() => {
useEditorStore.getState().setAutoSaveCallback(() => saveFile(false))
}, [saveFile])
}
Benefits: No polling, type-safe, synchronous data access, easy to test
TanStack Query Patterns
Query Keys Factory
export const queryKeys = {
all: ['project'] as const,
collections: (projectPath: string) =>
[...queryKeys.all, projectPath, 'collections'] as const,
// ... etc
}
Automatic Cache Invalidation
// In mutation's onSuccess
queryClient.invalidateQueries({
queryKey: queryKeys.collectionFiles(projectPath, collectionName),
})
Keyboard Shortcuts
Uses react-hotkeys-hook for cross-platform shortcuts:
useHotkeys(
'mod+s',
() => {
/* save */
},
{ preventDefault: true }
)
Available: mod+s (save), mod+1 (sidebar), mod+2 (frontmatter), mod+n (new), mod+w (close), mod+comma (preferences)
Code Extraction Patterns
See docs/developer/architecture-guide.md for detailed extraction guidelines.
Quick Reference:
- Extract to
lib/: 50+ lines, 2+ components, business logic, testable modules - Extract to
hooks/: React hooks, component lifecycle, side effects - Process: Single responsibility → minimal API → tests → index.ts exports
Development Commands & Organization
pnpm run dev # Start dev server
pnpm run tauri:build # Build app
pnpm run check:all # All checks (TS + Rust + ast-grep + tests) - RUN BEFORE COMMITS
pnpm run fix:all # Auto-fix all issues
pnpm run ast:lint # Run ast-grep architectural linting
pnpm run ast:fix # Auto-fix ast-grep violations (where possible)
pnpm run test # Watch mode
pnpm run test:run # Run once
Component Organization: kebab-case directories, PascalCase components, index.ts barrel exports, domain-based grouping
Editor System Architecture
Custom Syntax Highlighting
Uses comprehensive custom highlighting replacing CodeMirror defaults:
- Custom Markdown Tags (
markdownTags.ts) - Standard Language Tags from
@lezer/highlight - Single highlight style with 50+ rules
See docs/developer/editor-styles.md for CSS theming details.
Command Palette Architecture
To add new command groups:
- Update Types (
src/lib/commands/types.ts):
export type CommandGroup = 'file' | 'navigation' | 'your-new-group'
- Add Context Function (
src/lib/commands/command-context.ts) - Define Commands (
src/lib/commands/app-commands.ts) - Update Group Order (
src/hooks/useCommandPalette.ts) - Add Event Listener if needed (in Layout.tsx)
Testing & Performance
Testing Strategy: Unit tests for lib/ modules, integration tests for hooks/workflows, component tests for user interactions. See docs/developer/architecture-guide.md for detailed testing patterns.
Performance: React Compiler handles most memoization automatically. Focus on:
- Zustand patterns: Selector syntax and
getState()(compiler doesn't optimize these) - Debouncing: 2s auto-save prevents excessive operations
- Effect cleanup: Use
cancelledflags in async effects - Measurement first: Profile before adding manual memoization
See docs/developer/performance-patterns.md for comprehensive React Compiler guidance and when manual optimization is still needed.
Best Practices
Component Development
- NEVER use React Hook Form (infinite loops)
- NEVER destructure from Zustand stores (
const { ... } = useStore()) - ALWAYS use Direct Store Pattern with selector syntax
- ALWAYS use
useShallowfor object/array subscriptions - EXTRACT helper components for repeated JSX (3+ times)
- MEMOIZATION: React Compiler handles automatically - avoid manual
useMemo/useCallbackunless profiling shows a need - EFFECT CLEANUP: Use
cancelledflags in async effects to prevent stale updates
Module Development
- EXTRACT complex logic into
lib/ - CREATE hooks for stateful logic
- DEFINE clear interfaces
- WRITE comprehensive tests
- DOCUMENT public APIs
Troubleshooting
Performance Issues
- Excessive re-renders: Check for destructuring from Zustand stores - use selector syntax instead
- Components re-render on every keystroke: Add
useShallowto object/array subscriptions - Infinite loops: Check Direct Store Pattern - ensure using selector syntax, not destructuring
Common Issues
- Auto-save: Verify 2s interval and
scheduleAutoSave() - Schema parsing: Check
src/content/config.tssyntax - Version conflicts: Use v2/v4 docs only
- Toast/Theme issues: See
docs/developer/notifications.md
Quick Fix for Performance: Search codebase for const { .* } = use.*Store\(\) and replace with selector syntax. See state-management.md for patterns.
Key Files Reference
Essential Files
src/store/*.ts- State managementsrc/lib/query-keys.ts- TanStack Query keyssrc/components/layout/Layout.tsx- Main orchestratorsrc/lib/editor/- Editor modulessrc/lib/schema.ts- Zod schema parsing
Reusable Utilities
Before creating new utilities, check these existing modules:
src/lib/dates.ts- Date formatting (formatIsoDate(),todayIsoDate())src/lib/ide.ts- IDE integration (openInIde())src/lib/projects/actions.ts- Project operations (openProjectViaDialog())src/lib/files/- File processing and asset managementsrc/components/frontmatter/fields/constants.ts- Field constants (NONE_SENTINEL)
Documentation
docs/developer/architecture-guide.md- Comprehensive patternsdocs/developer/notifications.md- Toast notification systemdocs/developer/editor-styles.md- CSS themingdocs/developer/preferences-system.md- Settingsdocs/developer/recovery-system.md- Error handling
WebKit/Tauri Considerations
field-sizing: contentnot supported → useAutoExpandingTextarea- WebKit differs from Chrome DevTools
- JavaScript-based textarea auto-expansion
- Tauri v2 has different API than v1
This document reflects current architecture. For detailed patterns and decision logs, see docs/developer/architecture-guide.md
