super-productivity/CLAUDE.md
2026-01-21 14:34:45 +01:00

10 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project-Specific Guidelines

  1. ALWAYS use npm run checkFile <filepath> on each ts or scss file you modify to ensure proper formatting and linting. Unless you want to lint and format multiple files, then use npm run prettier and npm run lint instead.
  2. 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
    • 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
    • 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=0 to 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
    • IMPORTANT for Claude: When running the full supersync suite, use playwright directly with a line reporter for real-time output (the npm run e2e:supersync script 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

  1. Persistence Layer (/src/app/op-log/persistence/): Handles data storage and operation logging (IndexedDB)
  2. Services (*.service.ts): Business logic and state mutations via NgRx
  3. Components: (*.component.ts) Subscribe to state via selectors, dispatch actions for changes
  4. 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

  1. Type Safety: The codebase uses strict TypeScript. Always maintain proper typing.
  2. State Updates: Never mutate state directly. Use NgRx actions and reducers.
  3. Testing: Add tests for new features, especially in services and state management.
  4. Translations: UI strings must use the translation service (T or TranslateService). When adding translation keys, only edit en.json - never edit other locale files directly.
  5. Electron Context: Check IS_ELECTRON before using Electron-specific features.
  6. Privacy: No analytics or tracking. User data stays local unless explicitly synced.
  7. Effects & Remote Sync: ALL NgRx effects MUST use inject(LOCAL_ACTIONS) instead of inject(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), use ArchiveOperationHandler which is called explicitly by OperationApplierService. See src/app/util/local-actions.token.ts and architecture docs Section 8 in docs/sync-and-op-log/operation-log-architecture-diagrams.md.
  8. 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, bypassing LOCAL_ACTIONS filtering. If you must use a selector-based effect that dispatches actions, guard it with HydrationStateService.isApplyingRemoteOps(). See src/app/features/tag/store/tag.effects.ts for an example.
  9. 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.
  10. TODAY_TAG is a Virtual Tag: TODAY_TAG (ID: 'TODAY') must NEVER be added to task.tagIds. It's a "virtual tag" where membership is determined by task.dueDay, and TODAY_TAG.taskIds only stores ordering. This keeps move operations uniform across all tags. See docs/ai/today-tag-architecture.md.
  11. 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. See OperationApplierService.applyOperations() for the reference implementation.
  12. SYNC_IMPORT Semantics: SYNC_IMPORT (and BACKUP_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. See SyncImportFilterService.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 support
    • fix(sync): handle network timeout gracefully
    • refactor(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()