* style(tasks): elevate add-subtask input * fix(tasks): parse eml files without external dependency Replace postal-mime with a bounded parser for sender, subject, and unencoded plain-text bodies. Ignore unsupported MIME body formats and document the root dependency policy. * fix(tasks): isolate and harden eml import Lazy-load the local parser, reject lossy or unsupported bodies, and store accepted text as literal notes so imported content cannot trigger remote resource loads. Document the title-only fallback and expand regression coverage. * fix(tasks): decode eml headers and harden import parsing - Decode RFC 2047 encoded-words in the subject and sender name so international titles show "Grüße" instead of raw "=?UTF-8?...?=". - Replace the charset regex with a quote-aware Content-Type parser so a charset inside another quoted parameter can't be mistaken for the real one, and ignore RFC 822 header comments (e.g. "7bit (comment)"). - Cap the synced title (300) and body (100k) so a crafted .eml can't amplify into an oversized op (the literal fence can double the body). - Document parseEml's intentional MIME omissions to prevent a later "fix" that reopens the untrusted-body attack surface.
9.2 KiB
AGENTS.md
Guidance for AI agents working in this repository. Super Productivity is a todo and time-tracking app on Angular + Electron + Capacitor.
Product principles
From the project manifesto (Deep Work, Your Way), kept to what changes a build decision — weigh them on every feature, and surface the leaner path when a request fights them:
- Avoid feature creep: prefer the smallest change that solves the real problem. New UI, settings, and sync surface are permanent costs, so extend existing building blocks before adding new ones, and let a feature ship only if it makes users faster, not busier. When scope outgrows the problem, propose the leaner option rather than silently building the larger one — it's still the user's call. Scope guard: this is a personal deep-work tool, not a team-management or reporting product.
- Less noise, more depth: reject constant alerts, vanity dashboards, streaks, and dopamine loops. Opt-in reminders and notifications are core to the app, but anything attention-grabbing ships off by default and stays quiet (flow, not friction).
- Adapt, don't impose: people plan, track, and reflect differently, so ship new behavior as building blocks. Prefer one calm default over a new toggle; add a setting only when real workflows genuinely diverge, never to dodge a default decision (don't build it → calm default → opt-in setting).
- Privacy & offline first: no analytics, tracking, or telemetry (see Project rules → Privacy). Core task and time tracking must work fully offline; sync and online integrations are optional layers that degrade gracefully, never prerequisites.
Required reading per task
- Styling changes →
docs/styling-guide.md - User-facing functionality changes →
docs/documentation-guide.md - Sync, op-log, vector clocks →
docs/sync-and-op-log/ - Effects/reducers/bulk-dispatch touching synced state →
docs/sync-and-op-log/contributor-sync-model.md - E2E tests →
e2e/CLAUDE.md - Load-bearing decisions →
ARCHITECTURE-DECISIONS.md
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)
Run the full SuperSync and WebDAV E2E suites via GitHub Actions: manually dispatch E2E Tests (Scheduled) for your branch. This should be preferred over running the full suites locally; the workflow provides dedicated WebDAV and sharded SuperSync jobs. The optional grep input filters the SuperSync job only.
For local SuperSync E2E (docker-compose) and the full E2E reference, see e2e/CLAUDE.md.
Project rules
- Translations: UI strings go through
T/TranslateService. Edit onlyen.json; never other locales. - Privacy: no analytics or tracking — user data stays local unless explicitly synced.
- Dependencies: PRs must not add new packages to the root project's
dependenciesordevDependencies; use platform APIs, existing packages, or a small in-repo implementation instead. Dependencies scoped to an individual plugin are allowed when they are necessary and remain isolated to that plugin. - Electron: check
IS_ELECTRONbefore using Electron-specific APIs. - Templates: plain HTML, minimal CSS/classes, Angular Material sparingly. See
docs/styling-guide.md. - Styling review: do not locally restyle Angular Material or shared
src/app/ui/components for one-off context needs. This includes overriding button styles via.mat-*,.mdc-*,button[mat-*], or component internals in local SCSS. Prefer existing inputs/classes/tokens; if a variant must exist, make it reusable or add it to the shared style layer. - Strict TypeScript: no
any(useunknownif 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.
- Code review: when reviewing new features, always double-check the potential long-term costs and risks a change introduces — maintenance burden, hard-to-reverse choices (data shapes, public/plugin APIs, sync formats), locked-in dependencies/abstractions, and footguns that only surface at scale or across synced clients — not just whether the immediate diff is correct.
- Task component is a hot path: every change to
src/app/features/tasks/task/task.component.*(rendered once per task in long, scrollable lists) must be double-checked for negative performance impact — avoid function/getter calls in the template, extra change-detection work, and uncleaned subscriptions; verify against a large task list.
Sync-correctness rules
Touched on most state-related PRs. Read the linked source/doc for full reasoning before editing. Rules 1–3 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.
Every change to the sync system is high-risk: a subtle bug can silently corrupt or lose user data across devices and is hard to recover from. Carefully check each change for correctness and possible failure modes (replay determinism, concurrent/remote edits, vector-clock conflicts) and call out the risks before reporting work as done.
- Effects inject
LOCAL_ACTIONS, neverActions(ALL_ACTIONSonly for the op-log capture effect; remote archive side effects →ArchiveOperationHandler, notALL_ACTIONS). Lint-enforced (no-actions-in-effects). → contributor-sync-model.md,src/app/util/local-actions.token.ts. - Prefer action-based effects; a selector-based effect needs
skipDuringSyncWindow(). Lint-enforced (require-hydration-guard). → contributor-sync-model.md. - 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/. - Logical clock: route "what day is this?" through
DateService(getLogicalTodayDate,isToday,todayStr). Pure reducers/selectors takestartOfNextDayDiffMsas an arg and callisTodayWithOffsetfor replay determinism. The rawDateService.startOfNextDayDiffisprivate; usegetStartOfNextDayDiffMs()at service boundaries. TODAY_TAG('TODAY') is virtual — never add totask.tagIds; membership comes fromtask.dueWithTimeortask.dueDay.TODAY_TAG.taskIdsonly stores ordering. →ARCHITECTURE-DECISIONS.mdDecision #2.- 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(). SYNC_IMPORT/BACKUP_IMPORTreplace state and intentionally drop concurrent ops (CONCURRENT or LESS_THAN by vector clock) — by design, not a bug. →SyncImportFilterService.- Vector clocks:
MAX_VECTOR_CLOCK_SIZE = 20. Server prunes after conflict detection, before storage. →docs/sync-and-op-log/vector-clocks.md. - Logging:
Log.log({ id: task.id }), neverLog.log(task)orLog.log(title)— log history is exportable, never log user content.
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 |
One-off .mat-*, .mdc-*, button[mat-*], or shared component overrides |
reusable inputs, tokens, or shared styles |