super-productivity/CLAUDE.md
Johannes Millan 42e6626b76 docs(sync): consolidate sync docs + enforce the contributor model
Collapse the sprawling, partly-stale docs/sync-and-op-log/ tree into a
small authoritative set and make the sync-correctness invariant
partly lint-enforced instead of convention-only.

Docs:
- Delete superseded/duplicate/provably-stale design, plan, and
  background-research docs (quick-reference, the architecture-diagrams
  monolith, the "Hybrid Manifest" docs describing code that does not
  exist, completed long-term plans, LLM-synthesis analyses).
- Salvage load-bearing decision history into the surviving docs before
  deletion: rejected-alternatives rationale -> operation-log-architecture
  ("Why this architecture"); vector-clock pruning incident history ->
  vector-clocks.md; archive-payload optimization -> architecture E.7.
- Add contributor-sync-model.md as the single-invariant entry point
  (one user intent = one op; replayed/remote ops must not re-trigger
  effects), with a decision table mapping to the enforcing linters.
- Repoint external/internal cross-refs; add CONTRIBUTING.md + CLAUDE.md
  pointers; record the migration in a dated docs/plans/ design doc.

Enforcement (new eslint-local-rules):
- no-actions-in-effects (error): effects must inject LOCAL_ACTIONS /
  ALL_ACTIONS, never the raw @ngrx/effects Actions stream.
- no-multi-entity-effect (warn, heuristic): flags a literal returned
  array of >=2 action-creator calls; docstring + valid-case specs pin
  exactly which shapes are and are not detected.
- run-specs.js runner wired into `npm run lint` via test:lint-rules;
  refuses to run under test-framework globals and counts RuleTester.run
  invocations so a spec that asserts nothing fails instead of passing.
- Correct the ALL_ACTIONS JSDoc in local-actions.token.ts to match
  reality (archive-operation-handler uses LOCAL_ACTIONS).

Reviewed via parallel multi-agent review; findings W1/W2/W4 and a
dangling doc anchor addressed.
2026-05-15 16:51:50 +02:00

5.6 KiB
Raw Blame History

CLAUDE.md

Guidance for Claude Code working in this repository. Super Productivity is a todo and time-tracking app on Angular + Electron + Capacitor.

Required reading per task

Core commands

ALWAYS run npm run checkFile <filepath> on every .ts or .scss file you modify before reporting work as done.

npm run checkFile <filepath>   # prettier + lint a single file
npm run prettier               # multi-file format
npm run lint                   # multi-file lint
npm test                       # all unit tests (Jasmine/Karma, .spec.ts co-located)
npm run test:file <filepath>   # single spec
npm run e2e                    # all E2E (Playwright, slow)
npm run e2e:file <path> -- --retries=0   # single E2E (~20s/test); add --grep "name" for one test
npm start                      # Electron dev
ng serve                       # web dev (or npm run startFrontend)
npm run dist                   # production build (all platforms available locally)

For SuperSync E2E (docker-compose) and the full E2E reference, see e2e/CLAUDE.md.

Project rules

  • Translations: UI strings go through T / TranslateService. Edit only en.json; never other locales.
  • Privacy: no analytics or tracking — user data stays local unless explicitly synced.
  • Electron: check IS_ELECTRON before using Electron-specific APIs.
  • Templates: plain HTML, minimal CSS/classes, Angular Material sparingly. See docs/styling-guide.md.
  • Strict TypeScript: no any (use unknown if truly unknown).
  • State: never mutate NgRx state — return new objects in reducers. Prefer Signals to Observables.
  • Tests: add unit tests for new services and state logic.

Sync-correctness rules

Touched on most state-related PRs. Read the linked source/doc for full reasoning before editing. Rules 13 and 6 are one invariant — one user intent = one op; replayed/remote ops must not re-trigger effects — fully explained in docs/sync-and-op-log/contributor-sync-model.md.

  1. Effects inject LOCAL_ACTIONS, never Actions (ALL_ACTIONS only for the op-log capture effect; remote archive side effects → ArchiveOperationHandler, not ALL_ACTIONS). Lint-enforced (no-actions-in-effects). → contributor-sync-model.md, src/app/util/local-actions.token.ts.
  2. Prefer action-based effects; a selector-based effect needs skipDuringSyncWindow(). Lint-enforced (require-hydration-guard). → contributor-sync-model.md.
  3. Multi-entity change = meta-reducer, not an effect fan-out (one reducer pass = one op). → contributor-sync-model.md, src/app/root-store/meta/task-shared-meta-reducers/.
  4. Logical clock: route "what day is this?" through DateService (getLogicalTodayDate, isToday, todayStr). Pure reducers/selectors take startOfNextDayDiffMs as an arg and call isTodayWithOffset for replay determinism. The raw DateService.startOfNextDayDiff is private; use getStartOfNextDayDiffMs() at service boundaries.
  5. TODAY_TAG ('TODAY') is virtual — never add to task.tagIds; membership comes from task.dueWithTime or task.dueDay. TODAY_TAG.taskIds only stores ordering. → ARCHITECTURE-DECISIONS.md Decision #2.
  6. Bulk dispatch loop: await new Promise(r => setTimeout(r, 0)) after the loop (else 50+ rapid dispatches lose state). → contributor-sync-model.md, OperationApplierService.applyOperations().
  7. SYNC_IMPORT / BACKUP_IMPORT replace state and intentionally drop concurrent ops (CONCURRENT or LESS_THAN by vector clock) — by design, not a bug. → SyncImportFilterService.
  8. Vector clocks: MAX_VECTOR_CLOCK_SIZE = 20. Server prunes after conflict detection, before storage. → docs/sync-and-op-log/vector-clocks.md.
  9. Logging: Log.log({ id: task.id }), never Log.log(task) or Log.log(title) — log history is exportable, never log user content.

Commit messages

Angular format type(scope): description. Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore. Examples: feat(tasks): add recurring task support, fix(sync): handle network timeout. Never fix(test): or fix(e2e): — test changes use test:.

Anti-patterns

Avoid Do instead
any type proper types, unknown if truly unknown
Direct DOM access Angular bindings, viewChild()
Side effects in constructors async pipe or toSignal
Subscribing without cleanup takeUntilDestroyed() or async pipe
NgModules for new code standalone components
Re-declaring Material theme styles existing theme variables