10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project-Specific Guidelines
- ALWAYS use
npm run checkFile <filepath>on eachtsorscssfile you modify to ensure proper formatting and linting. Unless you want to lint and format multiple files, then usenpm run prettierandnpm run lintinstead. - When creating HTML templates, prefer plain HTML (
<table>,<div>). Keep CSS, nesting, and classes to a minimum. Use Angular Material components where appropriate but sparingly.
Project Overview
Super Productivity is an advanced todo list and time tracking application built with Angular, Electron, and Capacitor for web, desktop, and mobile platforms.
Essential Commands
Development
# Install dependencies
npm i -g @angular/cli
npm i
# Run development server (web)
ng serve # or npm run startFrontend
# Run with Electron (desktop)
npm start
# Run tests
npm test # Unit tests
npm run e2e # E2E tests
npm run prettier # Prettier formatting
npm run lint # Linting
# Build for production
npm run dist # All platforms Builds (all available in current environment)
# IMPORTANT: Check individual files before committing
# Example: npm run checkFile src/app/features/tasks/task.service.ts
# Use this command OFTEN when modifying files to ensure code quality
npm run checkFile <filepath> # Runs prettier and lint on a single file
# executes unit tests of a single spec file
npm run test:file <filepath>
Testing
-
Unit tests:
npm test- Uses Jasmine/Karma, tests are co-located with source files (.spec.ts) -
E2E tests:
npm run e2e- Uses Playwright, located in/e2e/tests/-
npm run e2e- Run all tests with minimal output (shows failures clearly) -
npm run e2e:file <path>- Run a single test file with detailed output- Example:
npm run e2e:file tests/work-view/work-view.spec.ts
- Example:
-
npm run e2e:supersync:file <path>- Run SuperSync E2E tests (auto-starts the server)- Example:
npm run e2e:supersync:file e2e/tests/sync/supersync.spec.ts
- Example:
-
Running tests is slow. When fixing tests always prefer running only the affected test files first. Only when everything seems to work run the full suite to confirm.
-
IMPORTANT for Claude: When running E2E tests:
- Use
--retries=0to avoid long waits:npm run e2e:file <path> -- --retries=0 - Use
--grep "test name"to run a single test:npm run e2e:file <path> -- --grep "test name" --retries=0 - Tests take ~20s each, don't use excessive timeouts
- Use
-
IMPORTANT for Claude: When running the full supersync suite, use playwright directly with a line reporter for real-time output (the
npm run e2e:supersyncscript buffers output):# Start the server first docker compose -f docker-compose.yaml -f docker-compose.supersync.yaml up -d supersync && \ until curl -s http://localhost:1901/health > /dev/null 2>&1; do sleep 1; done && \ echo 'Server ready!' # Run with line reporter for real-time output npx playwright test --config e2e/playwright.config.ts --grep @supersync --reporter=line # Stop server when done docker compose -f docker-compose.yaml -f docker-compose.supersync.yaml down supersync
-
-
Linting:
npm run lint- ESLint for TypeScript, Stylelint for SCSS
Architecture Overview
State Management
The app uses NgRx (Redux pattern) for state management. Key state slices:
- Tasks, Projects, Tags - Core entities
- WorkContext - Current working context (project/tag)
- Global config - User preferences
- Feature-specific states in
/src/app/features/ - Prefer Signals to Observables if possible
Data Flow
- Persistence Layer (
/src/app/op-log/persistence/): Handles data storage and operation logging (IndexedDB) - Services (
*.service.ts): Business logic and state mutations via NgRx - Components: (
*.component.ts) Subscribe to state via selectors, dispatch actions for changes - Effects: Handle side effects (persistence, sync, notifications)
Key Architectural Patterns
- Feature Modules: Each major feature in
/src/app/features/is self-contained with its own model, service, and components - Lazy Loading: Routes use dynamic imports for code splitting
- Model Validation: Uses Typia for runtime type validation of data models
- IPC Communication: Electron main/renderer communication via defined IPC events in
/electron/shared-with-frontend/ipc-events.const.ts
Cross-Platform Architecture
- Web/PWA: Standard Angular app with service worker
- Desktop: Electron wraps the Angular app, adds native features (tray, shortcuts, idle detection)
- Mobile: Capacitor bridges Angular to native Android/iOS
Data Sync
- Multiple sync providers: Dropbox, WebDAV, local file
- Sync is conflict-aware with vector-clock resolution
- All sync operations go through
/src/app/imex/sync/
Important Development Notes
- Type Safety: The codebase uses strict TypeScript. Always maintain proper typing.
- State Updates: Never mutate state directly. Use NgRx actions and reducers.
- Testing: Add tests for new features, especially in services and state management.
- Translations: UI strings must use the translation service (
TorTranslateService). When adding translation keys, only editen.json- never edit other locale files directly. - Electron Context: Check
IS_ELECTRONbefore using Electron-specific features. - Privacy: No analytics or tracking. User data stays local unless explicitly synced.
- Effects & Remote Sync: ALL NgRx effects MUST use
inject(LOCAL_ACTIONS)instead ofinject(Actions). Effects should NEVER run for remote sync operations - side effects happen exactly once on the originating client. For archive-specific side effects needed on remote clients (writing/deleting from IndexedDB), useArchiveOperationHandlerwhich is called explicitly byOperationApplierService. Seesrc/app/util/local-actions.token.tsand architecture docs Section 8 indocs/sync-and-op-log/operation-log-architecture-diagrams.md. - Avoid Selector-Based Effects: Prefer action-based effects (
this._actions$.pipe(ofType(...))) over selector-based effects (this._store$.select(...)). Selector-based effects fire whenever the store changes, including during hydration/sync replay, bypassingLOCAL_ACTIONSfiltering. If you must use a selector-based effect that dispatches actions, guard it withHydrationStateService.isApplyingRemoteOps(). Seesrc/app/features/tag/store/tag.effects.tsfor an example. - Atomic Multi-Entity Changes: When one action affects multiple entities (e.g., deleting a tag removes it from tasks), use meta-reducers instead of effects to ensure all changes happen in a single reducer pass. This creates one operation in the sync log, preventing partial sync and state inconsistency. See
src/app/root-store/meta/task-shared-meta-reducers/and Part F in the architecture docs. - TODAY_TAG is a Virtual Tag: TODAY_TAG (ID:
'TODAY') must NEVER be added totask.tagIds. It's a "virtual tag" where membership is determined bytask.dueDay, andTODAY_TAG.taskIdsonly stores ordering. This keeps move operations uniform across all tags. Seedocs/ai/today-tag-architecture.md. - Event Loop Yield After Bulk Dispatches: When applying many operations to NgRx in rapid succession (e.g., during sync replay), add
await new Promise(resolve => setTimeout(resolve, 0))after the dispatch loop.store.dispatch()is non-blocking and returns immediately. Without yielding, 50+ rapid dispatches can overwhelm the store and cause state updates to be lost. SeeOperationApplierService.applyOperations()for the reference implementation. - SYNC_IMPORT Semantics:
SYNC_IMPORT(andBACKUP_IMPORT) operations represent a complete fresh start - they replace the entire application state. All operations without knowledge of the import (CONCURRENT or LESS_THAN by vector clock) are dropped for all clients. SeeSyncImportFilterService.filterOpsInvalidatedBySyncImport(). This is correct behavior: the import is an explicit user action to restore to a specific state, and concurrent work is intentionally discarded.
Git Commit Messages
Use Angular commit message format: type(scope): description
- Types:
feat,fix,docs,style,refactor,perf,test,build,ci,chore - Scope: Optional, e.g.,
tasks,projects,sync - Examples:
feat(tasks): add recurring task supportfix(sync): handle network timeout gracefullyrefactor(projects): simplify project selector logic
Note: Use test: for test changes, not fix(test):.
🚫 Anti-Patterns → Do This Instead
| Avoid | Do Instead |
|---|---|
any type |
Use proper types, unknown if truly unknown |
| Direct DOM access | Use Angular bindings, viewChild() if needed |
| Side effects in constructors | Prefer async pipe or toSignal |
| Mutating NgRx state directly | Return new objects in reducers |
| Subscribing without cleanup | Use takeUntilDestroyed() or async pipe |
NgModules for new code |
Use standalone components |
| Re-declaring Material theme styles | Use existing theme variables |
inject(Actions) in effects |
Use inject(LOCAL_ACTIONS) - effects must not run for remote sync ops |
| Selector-based effects that dispatch | Convert to action-based or guard with HydrationStateService.isApplyingRemoteOps() |