mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
155 lines
10 KiB
Markdown
155 lines
10 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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):
|
|
|
|
```bash
|
|
# 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()` |
|