Super Productivity
super-productivity.comPrivacy-respecting, open-source task manager and time tracker. All data is stored locally on your device - no account required, no cloud dependency, and zero telemetry. Supports Jira, GitLab, GitHub, Gitea, OpenProject and CalDAV integration. Features include time tracking, Pomodoro timer, break reminders, and idle detection.
- Homepage: super-productivity.com
- GitHub: github.com/johannesjo/super-productivity
- iOS App: apps.apple.com/us/app/super-productivity/id6587185806
- Android App: play.google.com/.../com.superproductivity.superproductivity
- Web info: web-check.xyz/check/super-productivity.com
Super Productivity Source Code
Author
Description
Super Productivity is an advanced todo list app with integrated Timeboxing and time tracking capabilities. It also comes with integrations for Jira, GitLab, GitHub and Open Project.
Homepage
http://super-productivity.com?ref=githubLicense
MIT
Created
06 Jan 17
Last Updated
12 Jul 26
Latest version
Primary Language
TypeScript
Size
160,919 KB
Stars
20,569
Forks
1,844
Watchers
20,569
Language Usage
Star History
Top Contributors
-
@johannesjo (18645)
-
@dependabot[bot] (558)
-
@steindvart (139)
-
@bytrangle (111)
-
@cocojojo5213 (80)
-
@jiongxuan (70)
-
@novikov1337danil (67)
-
@MostafaAmin07 (62)
-
@zenoprax (49)
-
@kiausch (37)
-
@Cyber-Syntax (35)
-
@stkofler (35)
-
@overcuriousity (35)
-
@hugaleno (31)
-
@nonameformr (27)
-
@hajiboy95 (25)
-
@claude (23)
-
@costajohnt (23)
-
@lrq3000 (23)
-
@milotype (23)
-
@thetric (22)
-
@loviuz (22)
-
@Ronaldo93 (21)
-
@aakhter (20)
-
@theBenForce (19)
-
@zoli (18)
-
@miqh (18)
-
@Gitoffthelawn (17)
-
@panoramix360 (16)
-
@kkoyung (15)
-
@Abrar74774 (14)
-
@dXrayb (14)
-
@jpeterburs (14)
-
@lucadallavalle (14)
-
@symonbaikov (14)
-
@wilbowma (14)
-
@consultorseovalencia (13)
-
@symdec (13)
-
@kwongtn (13)
-
@janLo (13)
-
@berhram (13)
-
@Hetsavani (13)
-
@notsecret808 (12)
-
@BadBossy (12)
-
@gotjoshua (11)
-
@Mustache-Games (11)
-
@qievenz (11)
-
@ebbbang (11)
-
@blyedev (11)
-
@alosarjos (11)
-
@Garrett04 (10)
-
@kerkenit (10)
-
@forge34 (10)
-
@Vedant817 (9)
-
@lanesawyer (9)
-
@djanilson-barista (9)
-
@Jackymancs4 (8)
-
@OptionalLion411 (8)
-
@cbergmann (8)
-
@aripollak (8)
-
@penn5 (8)
-
@bugixtix (8)
-
@Morganamilo (7)
-
@siemieniuk (7)
-
@RayBB (7)
-
@NoobFullStack (7)
-
@mhmmdhandika (7)
-
@MParvin (7)
-
@SilverGreen93 (7)
-
@makomi (7)
-
@baflo (7)
-
@LordJABA (6)
-
@PannenetsF (6)
-
@Heapnotizer (6)
-
@Fleker (6)
-
@nskins (6)
-
@Droyder7 (6)
-
@davidvornholt (6)
-
@Copilot (5)
-
@mycochang (5)
-
@TypicalUsername-ai (5)
-
@iwilltry42 (5)
-
@davidrr21 (5)
-
@ExoGitH (5)
-
@maxliesegang (5)
-
@stavby (5)
-
@ReduxST (5)
-
@sespiros (5)
-
@uinstinct (4)
-
@sriram15 (4)
-
@ryziopl (4)
-
@muratsaribas (4)
-
@kanunnikau (4)
-
@d-r-e (4)
-
@Cyborger (4)
-
@AriaMoradi (4)
-
@meyca (4)
-
@Pentracchiano (4)
-
@epavanello (4)
-
@starter727 (4)
Recent Commits
-
Johannes Millan (11 Jul 26)
Merge pull request #8900 from super-productivity/feat/sync-55222e fix(sync): harden replay, conflict recovery and archive durability
-
Johannes Millan (11 Jul 26)
docs(sync): correct two overclaiming comments from the fix review - The archive-mutex comment claimed every archive mutation is locked; compressArchive, the remote loadAllData import and the time-tracking cleanups still write outside it (tracked in #8941). Name them. - The merge-journal comment described an impossible skipped-duplicate path (merged ids are fresh per run); describe the real accepted window instead: durable merge, crash before journaling, entry stays absent (observe-only log).
-
Johannes Millan (11 Jul 26)
refactor(sync): apply multi-review cleanups - Delete the unreachable stage-2 intra-batch duplicate pass: stage 1 (validateAndClampBatch) reserves every op id including invalid first siblings, so validated candidates are unique by construction. - Delete clearRawRebuildIncomplete: superseded by completeRawRebuild, which retires the marker atomically with the recovery token; only specs still called it. - Clear the conflict journal when a USE_REMOTE rebuild completes โ the documented "cleared whenever the full dataset is replaced" contract previously had a single caller (backup import), leaving stale badge counts and review entries describing replaced history. - _notifyResolutionOutcome: drop the win-count parameters left over from the removed count snack and gate on resolutions.length. - Snapshot handler: keep the clean-slate opId invariant local with a defense-in-depth 400 instead of relying on the contract superRefine in another package. - Document that the legacy misc->tasks conflict alias only covers the per-entity path (GLOBAL_CONFIG writes are single-entity today).
-
Johannes Millan (11 Jul 26)
fix(sync): serialize remote archive side effects behind the archive mutex isIgnoreDBLock historically meant "sync already holds the op-log DB lock", but _runTaskArchiveMutation also treated it as permission to skip the new TASK_ARCHIVE mutex โ so every remote archive side effect except moveToArchive ran unserialized against locked local mutations, and a concurrent read-modify-write could silently drop one side's archive write (the exact race the mutex was added to close). TASK_ARCHIVE is deliberately separate from OPERATION_LOG, so acquiring it while sync holds the op-log lock is safe โ the remote moveToArchive path has always done exactly that. The mutex is now unconditional, and the remote flushYoungToOld handler wraps its two-archive read-modify- write in the same lock instead of writing through the adapter bare. The isIgnoreDBLock option is retained as inert API surface; removing the threading is tracked as a follow-up.
-
Johannes Millan (11 Jul 26)
fix(sync): persist disjoint merges atomically and exempt them from checkpoint Review of the #8874 x #8900 merge seam found two defects in STEP 3b: - The synthesized merged op rode in the apply batch, so the reducer checkpoint's pending-only assertion threw on it: every real disjoint auto-merge aborted the checkpoint transaction and wedged sync behind IncompleteRemoteOperationsError until app restart. Checkpoint now covers only pending-appended remote rows; synthetic local ops are exempt (their durability contract is the append + upload path). - appendWithVectorClockUpdate replaced the durable vector clock with a clock computed only from the conflict's own ops, regressing the client counter and enabling silent cross-device op drops. Merge writes now go through appendMixedSourceBatchSkipDuplicates, which rebases the merged op on the durable clock in the same transaction โ also closing the crash window between the remote originals and their superseding merged op, and turning duplicate re-appends into skips instead of ConstraintErrors. Two regression tests enforce the coordinator's whole-batch reducer- commit contract and the pending-only checkpoint against the resolution flow; the journal keeps recording merges only after a durable append.
-
Johannes Millan (11 Jul 26)
test(sync): mock the mixed-source batch port in the journal-hook spec Same #8874-vintage store mock as the disjoint-merge spec: without the atomic mixed-source batch method every local-wins flow through autoResolveConflictsLWW threw before journaling.
-
Johannes Millan (11 Jul 26)
test(sync): mock the mixed-source batch port in the disjoint-merge spec The #8874 spec predates the atomic mixed-source batch and reducer checkpoint on the store port; its two createSpyObj sites lacked the methods, so every flow through the local-wins write path threw.
-
Johannes Millan (11 Jul 26)
Merge remote-tracking branch 'origin/master' into feat/sync-55222e * origin/master: (25 commits) refactor(tasks): extract shared task ordering helpers (#8926) feat(sync): conflict journal + disjoint-field auto-merge + review UI (#8874) refactor(config): reduce duplicated form and selector boilerplate (#8928) feat(work-view): show break time today (#8909) feat(tasks): navigate from empty add-subtask input (#8916) docs(development): add instructions for setting up local tests with Chromium (#8887) chore(lint): remove unused eslint-disable directives (#8913) fix(accessibility): add ARIA roles, live regions, and alt attributes to banner component (#8888) chore(ui): removes dead utilities and op-log leftovers [#8260 - Tier A] (#8892) fix(app): hide donation page on macOS (#8915) chore(scripts): remove one-off codemod scripts [#8260 - Tier A] (#8893) fix(sync): harden SuperSync E2EE against metadata tampering (GHSA-8pxh-mgc7-gp3g) (#8904) feat: add Home Assistant Bridge to community plugins (#8891) docs(sync): note local-file rev-check/write is non-atomic (#8898) (#8902) 18.14.0 fix(tasks): remove postal-mime dep and harden eml import (#8901) style(tasks): elevate add-subtask input fix(config): restore day-start offset after operation replay (#8899) fix(task-repeat): allow selecting day-of-month recurrence (#8896) feat(plugins): add Todoist import plugin with Import/Export launcher (#8882) ... # Conflicts: # docs/wiki/3.06-User-Data.md # src/app/op-log/backup/backup.service.spec.ts # src/app/op-log/backup/backup.service.ts # src/app/op-log/sync/conflict-resolution.service.ts
-
Johannes Millan (11 Jul 26)
test(sync): spy the locked-internal archive paths, not the public wrappers The lock-serialization refactor routes internal archive calls through _updateTasks/_deleteTasks so a held sp_task_archive lock is never re-acquired; five assertions still spied the public wrappers and never fired.
-
Johannes Millan (11 Jul 26)
docs(sync): document atomic checkpoint, db v8 barrier and rebuild undo Update the archive-operation lifecycle docs to the atomic reducer-checkpoint + clock-merge design, record the IndexedDB v8 downgrade barrier, drop the removed PENDING_OPERATION_EXPIRY_MS constant, and describe the durable Use-Server-Data Undo across reloads.
-
Johannes Millan (11 Jul 26)
fix(shared-schema): let existing tasks settings win in v1-to-v2 merge The misc-to-tasks migration spread the transformed legacy misc values over an already-populated tasks section, so a stale v1 misc copy could overwrite settings a v2 client had since changed. Flip the merge order (existing tasks section wins) and drop the already-migrated early return, which made the outcome depend on which duplicate arrived first.
-
Johannes Millan (11 Jul 26)
fix(supersync): isolate duplicate upload ids and make clean-slate idempotent - A request repeating one operation id no longer double-charges quota or wedges the batch: the first occurrence reserves the id and later siblings are terminally rejected (DUPLICATE_OPERATION for an exact retry, INVALID_OP_ID otherwise), in both batch and serial paths. - Clean-slate snapshot uploads become durably idempotent: the request cache is process-local and expiring, so the client-supplied opId is now required (contract superRefine) and checked against the stored operation inside the per-user lock before any data is deleted. An exact retry returns the original serverSeq; a colliding opId is rejected without touching existing data. - Audit logging validates id/entityType/opType/clientId against the known-safe charsets before embedding them in log lines, and the six duplicated reject-audit blocks collapse into one rejectedUploadResult helper; the ops handler logs rejected error codes instead of whole operation objects.
-
Johannes Millan (11 Jul 26)
fix(supersync): widen conflict lookups to aliased and unioned entity ids Two conflict-detection blind spots let concurrent edits slip through without a conflict: - An op carrying both a scalar entityId and an entityIds array only checked the array; the scalar and array sets are now unioned for detection while getStoredEntityIds keeps the historical storage normalization (scalar in entity_id, arrays in entity_ids). - Histories written before schema v2 keep migrated task settings under GLOBAL_CONFIG:misc. Incoming GLOBAL_CONFIG:tasks writes now consult the legacy misc row as an alias (newest of the two wins, compared by serverSeq), and legacy misc ops check the tasks key per-entity. Works for encrypted payloads since only row metadata is consulted.
-
Johannes Millan (11 Jul 26)
fix(sync): serialize archive mutations and persist before dispatch Remote archive side effects must be idempotent and immune to concurrent read-modify-write races, or a retried operation can duplicate or drop archive rows. - TaskArchiveService serializes every archive mutation behind a dedicated sp_task_archive web lock (separate from OPERATION_LOG, which remote archive handlers already hold non-reentrantly). - ArchiveService.moveTasksToArchiveAndFlushArchiveIfDue writes tasks with setMany over a deduplicated sorted id set, so a retry over a partially written archive converges instead of double-adding. - TaskService._moveToArchive coalesces concurrent archive calls for the same ids and persists archive data before dispatching moveToArchive, so a full-state snapshot cannot acknowledge the op while its archive write is still in flight.
-
Johannes Millan (11 Jul 26)
fix(sync): guard rebuild backup identity and keep undo across reload The pre-replace import backup and the USE_REMOTE rebuild recovery marker were matched by timestamp only, so a concurrent capture or a reload between rebuild completion and snack dismissal could clear or restore the wrong backup. - Import backups carry an opaque backupId (uuidv7); clearImportBackup, replaceAllForRawRebuild and applyRemoteReplaceWithSnapshot verify the expected identity inside their transactions before acting. - Completing a raw rebuild atomically replaces the incomplete marker with a durable raw_rebuild_recovery entry, so the "restore previous data" Undo survives a reload; StartupService re-offers it at boot and dismissal retires exactly the offered backup. - SnackService treats a sticky recovery/update action as a single persistent slot: unrelated transient snacks no longer destroy a visible Undo, and dismissal awaits the snack's dismissFn.
-
Rushi (11 Jul 26)
refactor(tasks): extract shared task ordering helpers (#8926) * refactor(tasks): extract shared task ordering helpers Extract getReorderedSubTaskIds (membership check + move shared by moveSubTaskUp/Down/ToTop/ToBottom) and moveValidIdsToFront (shared by removeTasksFromTodayTag and localRemoveOverdueFromToday) into pure, tested utils. No behavior change. Closes #7912 Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(tasks): drop getReorderedSubTaskIds, inline check in reorderSubTask Review feedback (#8926): the moveSubTask* actions already funnel through the single reorderSubTask helper, so the extraction added no dedup value. Keep moveValidIdsToFront, which removes real duplication. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]>
-
Johannes Millan (11 Jul 26)
fix(sync): make remote reducer checkpoint atomic with its clock merge A committed reducer must never be durable without its vector clock, and a merged clock must never be durable without its reducer checkpoint โ either mismatch lets the next local operation be causally older than state already visible in NgRx after a crash. - markArchivePending + separate mergeRemoteOpClocks are replaced by one markReducersCommittedAndMergeClocks transaction (ops + vector_clock); the clock math is extracted into a pure calculateRemoteClockMerge so the standalone merge path keeps identical full-state-reset semantics. - The sync-core RemoteOperationApplyStorePort gains an onRemoteClocksDurable hook so deferred local actions drain exactly when clocks are durable, not merely when ops were applied; a checkpoint rejection can no longer mask the primary apply error. - Conflict resolution's local-wins path writes remote losers and rebased local compensations in one appendMixedSourceBatchSkipDuplicates transaction, so synthetic ops cannot reuse or regress the client counter. - DB_VERSION 7 -> 8 as a deliberate downgrade barrier: released v7 readers only understand 'failed' and would silently overlook outstanding 'archive_pending' work. op-log suite and sync-core suite cover the checkpoint rollback, atomic clock merge, mixed-source ordering and drain failure matrix.
-
aakhter (11 Jul 26)
feat(sync): conflict journal + disjoint-field auto-merge + review UI (#8874) * feat(sync): conflict-journal foundation (observe-only) Add a device-local IndexedDB conflict journal that records every sync- conflict auto-resolution so the discarded ("losing") side is preserved and reviewable later. Foundation subtask for the conflict-review epic; no UI here, verifiable purely by unit tests. - New SUP_CONFLICT_JOURNAL IndexedDB store (own DB; never touches the op-log SUP_OPS schema) + ConflictJournalService with record/query API (unreviewedCount$, list, markKept, markFlipped, getEntry) and 14-day / 200-entry retention pruning, wired to run on app start via APP_INITIALIZER. - Pure classifier buildConflictJournalEntry maps each resolution to the agreed taxonomy (newer/tie/delete-wins/noise/clock-corruption- suspected; disjoint-merge reserved for the next subtask), capturing the loser's field values verbatim. NOISE_FIELDS is limited to metadata timestamps (modified/lastModified/created): the list-ordering arrays carry membership as well as order, so an overlap on them is surfaced as a reviewable conflict rather than silently classified as noise. - Emission is strictly observe-only: journaling runs after the LWW plan is built, wrapped in try/catch (record() swallows its own errors); clock- corruption attribution uses a WeakSet side-channel tagged at detection. The existing conflict-resolution suite stays 138/138 green, proving LWW picks are unchanged. One-sided / sequential / EQUAL updates never become conflicts and produce zero journal entries. SPAP-13 * feat(sync): disjoint-field auto-merge for concurrent edits When two clients concurrently edit the same entity but different fields (A changes title, B changes notes), whole-entity LWW previously discarded one side. Keep both when the non-noise changed-field sets are disjoint. In _resolveConflictsWithLWW, before LWW picks a winner, each CONCURRENT conflict is tested for merge eligibility (neither side deleting/archiving; both changed >=1 real field; non-noise field sets disjoint). If eligible, synthesize a single merged UPDATE op โ the current entity overlaid with the other side's non-noise fields, noise fields resolved by the greater (timestamp, clientId) โ carrying a vector clock that dominates both sides, so it propagates through normal sync. The resolution is winner 'merged' and is journaled reason 'disjoint-merge' / status 'info' (not counted as unreviewed). Any overlap on a non-noise field, or a delete/archive on either side, falls through to the existing LWW path unchanged. Convergence: both clients compute the byte-identical merged entity (disjoint real fields each owned by one side; noise resolved by the same global tiebreak) with clocks dominating both originals, so the two independently-synthesized merged ops carry identical full-entity payloads and re-resolve to the same state via ordinary LWW without re-merging. No sync-core/protocol change. Existing conflict-resolution suite stays 138/138; SPAP-13 journal specs stay green. SPAP-14 * feat(sync): sync conflicts review UI (banner, badge, page, flip) Builds the conflict-review UI on top of the device-local conflict journal. - Post-sync summary banner "N conflicts auto-resolved (X remote, Y local won)" with REVIEW / DISMISS, replacing the bare LWW_CONFLICTS_AUTO_ RESOLVED snack at its emission sites; a persistent badge on the sync icon bound to unreviewedCount$ (survives banner dismiss). - New /sync-conflicts page: Unreviewed | History tabs, rows grouped by entity type with winner + reason chips, expandable per-field diff (LOCAL vs REMOTE, device name + wall-clock time, winner marked), per-row KEEP / FLIP and bulk KEEP ALL / FLIP ALL โ LOCAL / โ REMOTE. History renders merged auto-merges as per-field chips. - Flip dispatches a normal entity update with the loser's journaled field values (syncs like a user edit, no history rewind) and marks the entry flipped; a stale-flip confirm appears (with the current value shown) when the entity changed since resolution. - i18n under F.SYNC.CONFLICT_REVIEW. Delete-restore and archived-entity flip are surfaced as unsupported for now (an update op can't recreate an absent entity) โ follow-up. SPAP-15 * fix(sync): count disjoint-merge ops in localWinOpsCreated autoResolveConflictsLWW returned only newLocalWinOps.length, excluding the synthesized merged ops appended in STEP 3b. A sync whose only conflicts were disjoint-field merges returned 0, so the caller (immediate-upload.service.ts) skipped the immediate re-upload and reported IN_SYNC while the merged op sat unsynced until a later cycle. Count mergedResolutions.length too โ each merge appends exactly one pending local op. Mirrors the rejection-handler path (operation-log-sync.service.ts). Add a regression spec asserting a merge-only conflict returns localWinOpsCreated: 1 (fails on the old return, passes now). Also harden the _corruptionSuspectedConflicts WeakSet doc against a future refactor that clones EntityConflict between detect and resolve. Addresses review feedback on PR #8874. Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(sync): address second-pass review (delete-lost, archive guard test, polish) Second-pass review follow-ups (johannesjo). The MEDIUM localWinOpsCreated item was already fixed in 23e9a56. Important: - delete-lost classification: when a delete LOSES to a concurrent newer edit, the loser side is a pure Delete op (no field changes), so it fell through to `noise`/`info` and never surfaced. Add a `delete-lost` reason classified as `unreviewed` (inverse of `delete-wins`), checked before the noise fallthrough. + regression spec. - archive-vs-disjoint-edit test (d2): an archive is an UPDATE, so eligibility does NOT reject it โ only the `_isArchivePlan` guard does. New test forces eligibility TRUE and asserts no merged op is synthesized, so a guard regression now fails a test (previously nothing covered it). Minor: - pruneOnStart: wrap in try/catch (log + return 0, matching record()'s observe-only contract); reset the poisoned `_initPromise` in `_ensureDb` on open failure so a transient IndexedDB error can't wedge the service. - sync-conflicts-page.scss: use `--color-success` token + color-mix tint instead of hardcoded #4caf50 / rgba(76,175,80,โฆ); drop the dead `--color-warning` fallback (#e6a817 didn't match the real #ff9800 token). - main-header badge: matBadgeColor warn -> accent (a resolved count is not an error); move the count announcement to `matBadgeDescription` and give the button a stable sync-action aria-label (it was null at count 0, leaving the icon-only button unnamed). - remove dead i18n key LWW_CONFLICTS_AUTO_RESOLVED (t.const.ts + en.json). - add direct unit spec for noiseTiebreakSide (incl. equal-timestamp clientId determinism) and buildMergedFieldDiffs. Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(sync): address third-pass review (flip capability, field presence, opaque ops, profile isolation) Blocking #3 โ delete-lost/delete-wins offered a false-success Flip: canFlip() is now reason/field-aware (refuses delete-lost/delete-wins, empty loser changes, and relationship-bearing fields whose bare adapter update would bypass meta-reducer invariants); flip() guards on it and only marks an entry flipped when an op was actually dispatched. Blocking #2 โ field absent vs side-set-undefined: fieldDiffs now record per-side presence (localChanged/remoteChanged); loserChangesFor/ winnerChangesFor only emit fields the side actually changed, so Flip no longer clears winner-only fields and the stale guard no longer compares undefined. Legacy entries without flags fall back to value-presence (exact, since op payloads are pure JSON). Blocking #1 โ non-adapter action payloads: per-op extraction now falls back to capture-time entityChanges (TIME_TRACKING/syncTimeSpent), and ops with no readable delta (convertToSubTask & co) are "opaque": never classified noise/info, preserved verbatim as kind:'action' diffs for review, excluded from flip/stale computations, and never disjoint-merge eligible (merging would drop the mutation and diverge the two clients). Profile isolation โ switchProfile clears the conflict journal (ConflictJournalService.clearAll) so a new profile cannot see the previous profile's entity data or Flip against the wrong dataset. Also: replace the as-any selector cast with a typed union-member narrowing, and add the required docs (sync-and-op-log/ conflict-journal-and-review.md incl. the atomicity/no-re-merge contract, wiki 3.06 + 4.23). Co-Authored-By: Claude Fable 5 <[email protected]> * fix(sync): address fourth-pass review (flip short-syntax, selector throws, restore isolation) HIGH โ a title-only flip matched shortSyntax$'s exact trigger shape, so #tag/+project/@schedule tokens in the discarded title re-parsed into cross-entity mutations: the TASK flip now dispatches with isIgnoreShortSyntax: true (journaled literal value, not user input). MEDIUM โ selectTagById/selectNoteById THROW on a missing entity, so expanding (or flipping) a TAG/NOTE row whose entity is gone rejected unhandled before the !current guard: _readCurrentEntity now catches and returns undefined, and getStaleState/flip degrade gracefully. MEDIUM โ the journal survived backup restores: clearAll() moved to the BackupService.importCompleteBackup chokepoint, covering every full dataset replacement (profile switch, JSON import, local-backup restore, SuperSync restore) instead of only the profile switch. LOW โ ISSUE_PROVIDER's factory selectById is now special-cased (was rendering the inner selector function as the "current" entity); schedule/reminder fields (dueDay/dueWithTime/deadline*/reminderId) join the flip blocklist (renamed FLIP_UNSAFE_FIELDS) since their invariants live in dedicated flows; loser/winnerChangesFor early-return for merged entries (their tiebreak diffs carry pickedSide, so the per-diff check alone did not exclude them). Docs updated accordingly (dev doc + wiki 3.06/4.23). Co-Authored-By: Claude Fable 5 <[email protected]> * fix(sync): refuse flip for remindAt/deadlineRemindAt (reminder-lifecycle) FLIP_UNSAFE_FIELDS is a deny-list: any Task field not listed is flippable via a bare updateTask. reminderId was covered, but the sibling reminder- lifecycle timestamps remindAt and deadlineRemindAt were not โ a conflict on either alone (e.g. concurrent deadline-reminder-lead edits) would pass canFlip and, when flipped, write the field without scheduling/cancelling the actual reminder in ReminderService, leaving a dangling/missing notification. Add both to FLIP_UNSAFE_FIELDS, document the deny-list drift risk, and pin the reminder/schedule members with per-field canFlip=false spec cases. * fix(sync): make disjoint-merge convergent + close flip stale-guard blind spot Two confirmed cross-device data defects found in the SPAP-14 whole-PR review: 1. Full-entity merged op diverges (CRITICAL). The merged op was a full-entity snapshot of each client's CURRENT state, so any field NEITHER conflicting side touched rode along. Under ordinary staggered sync (one client already applied a third device's edit to that field, the other not yet), the two clients synthesize different snapshots that tie under LWW at the identical max(timestamp) and diverge PERMANENTLY. Fix: synthesize a PARTIAL delta (union of the two sides' changed fields only), derived purely from the ops so both clients compute the byte-identical map; lwwUpdateMetaReducer applies it via updateOne (shallow merge), leaving untouched fields alone. synthesizeMergedEntity -> synthesizeMergedChanges. Also: detectConflicts emits one conflict per remote op with no per-entity aggregation, so an entity with >=2 concurrent remote ops synthesized multiple merged ops whose clocks dominate one another -> a dominated sibling's field is silently dropped, falsely journaled as 'kept both'. Fix: refuse merge for any entity with >1 conflict this batch and fall back to whole-entity LWW. 2. Flip stale-guard blind to loser-only fields (HIGH). getStaleState only compared WINNER-changed fields, but flip writes LOSER-changed fields. A post-resolution edit to a loser-only field was undetected and silently overwritten. Fix: also flag stale when a loser-only field flip will write diverged from the value flip would write; bulk flipAllToSide now SKIPS stale entries instead of silently applying them. Adds regression specs for the un-conflicted-field ride-along, multi-remote-op refusal, and loser-only stale detection (per-entry + bulk). Full conflict suite green (disjoint-merge 12, ui 24, conflict-resolution 138, journal 20, hook 6, util 14, review-util 13, superseded 42, banner 3, page 6). * fix(sync): scope flip stale-guard loser-only check to remote-won entries Re-review of efe66d7 found the new loser-only stale check false-positives on LOCAL-won conflicts: there the loser is the REMOTE side, whose value was never applied (current holds the un-recorded base), so current != flipVal is the NORMAL post-resolution state, not an edit. That made flipAllToSide silently skip legitimate local-won entries and single flip nag with a spurious confirm. Scope loserOnlyStale to winner==='remote' (the only case where the loser's optimistically-applied local value persists, giving a valid unedited baseline). Remote-won silent-loss detection (the originally-reported defect) is unchanged. Loser-only fields on LOCAL-won entries remain undetectable without a journaled post-resolution baseline โ documented as a follow-up. +2 regression specs (local-won no-false-positive: getStaleState + bulk flip). * fix(sync): restrict disjoint-merge to types with a RECREATE_FALLBACK The merged op is a partial delta. If it wins over a concurrent DELETE on a passive-observer client (one that already applied that delete), it reaches lwwUpdateMetaReducer's addOne recreate branch WITHOUT passing through the full-entity reconstruction in _convertToLWWUpdatesIfNeeded (that runs only for conflict winners, not non-conflicting remote ops). For a type without a RECREATE_FALLBACK (NOTE/METRIC/TASK_REPEAT_CFG/ISSUE_PROVIDER) the bare partial addOne yields a Typia-invalid entity -> 'Repair failed' dead-end; the parent's full-snapshot merged op recreated validly, so the partial delta is a regression there. Refuse disjoint-merge for fallback-less types (fall back to whole-entity LWW, whose local-win op carries a full snapshot). Residual: fallback types can still recreate with DEFAULT_* backfill diverging in that rare 3-device race โ the same bounded limitation already documented in recreate-fallback.const.ts. +regression spec (NOTE disjoint conflict -> LWW, not merged). * fix(sync): harden conflict-journal failure and lifecycle paths Three hardening improvements from the final review pass: 1. Journal disjoint merges only AFTER the merged op is durably appended (STEP 3b), not at plan time. A 'merged' entry claims both sides were kept, which is only true once the op is persisted โ an append failure could previously leave a phantom 'kept both' journal entry. +regression spec (a6): append failure -> no merged journal entry. 2. Extend the never-throw contract to ALL ConflictJournalService methods. list() is awaited (via maybeShowSummaryBanner) inside autoResolveConflictsLWW's notification step โ i.e. after ops were already applied โ so a transient IndexedDB failure there failed an otherwise-completed sync. list -> [], getEntry -> undefined, markKept/markFlipped swallowed (entry stays unreviewed, user retries). +spec. 3. Recover from abnormal IndexedDB closure: idb's terminated hook now drops the memoized _db/_initPromise handles so the next call reopens instead of failing on a dead connection for the rest of the session (deferred fourth-pass item). +spec. Plus: reciprocal note in recreate-fallback.const.ts that membership also opts a type into disjoint-merge, and doc updates for the new contracts. * test(sync): pin the terminated-hook wiring in the journal recovery spec The recovery spec called _resetDbHandles() directly, so removing the terminated: callback from openDB (functionally reverting the force-close recovery) kept all tests green. Fire the real 'close' event idb listens for instead (duck-typed FakeEvent for fake-indexeddb) and assert the handles were cleared. Mutation-verified: deleting the terminated line now fails this spec. * fix(sync): clear conflict journal inside the op-log lock on import Author-review finding: importCompleteBackup released the OPERATION_LOG lock before clearAll(), leaving a narrow cross-tab window where a concurrent conflict resolution's fresh post-import journal entry gets wiped. Clearing inside the lock serializes the clear strictly before any post-import journaling. (_resetAllLastServerSeqs is journal-independent local bookkeeping and keeps its persist-first ordering.) Also documents the merged-op composition residual from the same review: a merged partial op is not closed under later whole-op LWW composition in the no-pending-local concurrent-apply path โ verified pre-existing (branch and behavior identical at the pre-SPAP-14 merge-base with plain user ops), so recorded as a class-level op-log residual rather than patched here. --------- Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Johannes Millan <[email protected]>
-
Rushi (11 Jul 26)
refactor(config): reduce duplicated form and selector boilerplate (#8928) - sync-form: shared rootSyncProvider/isSyncProvider/isNotSyncProvider helpers replace ~25 repeated parent-depth syncProvider predicates - keyboard-form: drop two dead commented-out field blocks - global-config.reducer.spec: itBehavesLikeConfigSectionSelector helper replaces 10 duplicated describe blocks (same assertions) - config-page: shared tab-label ng-template + isSectionExpanded() replace 6 duplicated template blocks No behavior change. Closes #7922 Co-authored-by: Claude Fable 5 <[email protected]>
-
J. Loops (11 Jul 26)
feat(work-view): show break time today (#8909)
-
Johannes Millan (11 Jul 26)
fix(sync): converge rebuild capture races, unwedge archive recovery Remediate the findings of the 5-agent necessity review: - USE_REMOTE: a local capture racing the locked rebuild now throws a typed CaptureRacedRebuildError, and forceDownloadRemoteState retries phase 2 in-call (bounded, 3 attempts) through the existing crash-resume branch โ raced ops fold into preservedLocalOps and the already-downloaded history is reused (WS downloads and immediate uploads stay gated by the marker, so no re-download is needed). Previously every attempt aborted while e.g. time tracking dispatched continuously, re-downloading the full history per sync trigger and churning the Undo snack (now shown on final failure only). - Hydrator archive retry: pass skipDeferredLocalActions and drain explicitly with a caught error. A drain throw from the coordinator's finally used to mask the archive result and escalate out of hydrateStore() into attemptRecovery(), which can import stale legacy data over a correctly hydrated store. - Incomplete-remote gate: run one in-session archive-only retry when the only blockers are quarantined failed/archive_pending ops, so a transient archive failure self-heals on the next sync instead of wedging until app restart. Never attempted while the rebuild marker is set or reducer-uncommitted pending rows exist. - Boot recovery: StartupService surfaces the pre-replace backup's persistent restore snack when a stranded raw_rebuild_incomplete marker is found, covering users who boot offline or disable sync after finding the app "emptied" by a mid-rebuild crash. - Snack correctness: latch the USE_REMOTE newer-schema warning once per session; guard _notifyBlockedOp and the LWW apply-failure snack with hasPendingPersistentAction() so they cannot destroy a visible Undo; drop the useless "Update app" action for below-minimum data. - Strings: MIGRATION_FAILED / VERSION_UNSUPPORTED now describe the blocking semantics instead of the removed skip behavior. - Store: getPendingRemoteOps excludes rejected rows (parity with getFailedRemoteOps) so a rejected-but-pending row cannot trip the sync gate for a session. - Server: compute the upload request fingerprint eagerly after the rate-limit gate โ identical cost to the lazy closure in every path, minus the memoization machinery; laziness remains in the snapshot handler where it skips hashing multi-MB states. op-log suite 3004/3004, super-sync-server 831/831, checkFile clean on all touched files. Nine regression tests pin the new behavior.
-
Johannes Millan (11 Jul 26)
feat(tasks): navigate from empty add-subtask input (#8916)
-
Lane Sawyer (11 Jul 26)
docs(development): add instructions for setting up local tests with Chromium (#8887) * docs(wiki): document CHROME_BIN setup for unit tests The pre-push hook runs the Karma unit tests, which need a resolvable Chrome binary. Without a system Chrome or CHROME_BIN set, git push fails with "No binary for Chrome browser". Add a section covering both a system Chrome install and installing Chrome for Testing via @puppeteer/browsers with CHROME_BIN wired into the shell profile. Co-Authored-By: Claude Opus 4.8 <[email protected]> * docs(wiki): note snap/flatpak caveats for the test browser Snap Chromium works but isn't auto-detected (set CHROME_BIN=/snap/bin/chromium); Flatpak is not recommended because karma-chrome-launcher execs the binary directly and the sandbox blocks Karma's temp profile dir. Co-Authored-By: Claude Opus 4.8 <[email protected]> * Improve documentation around Chromium * add bash to codeblock * docs(wiki): fix Chrome-for-Testing install path and Chromium claims Address review feedback: - Option A: only real Google Chrome is auto-detected on Linux (karma-chrome-launcher probes google-chrome/-stable, not chromium); drop the overstated "finds it automatically" for Chromium. - Option B: pin `--path "$HOME/.cache/puppeteer"` โ the standalone @puppeteer/browsers CLI defaults to the cwd, so the bare command downloaded chrome into ./chrome and left CHROME_BIN empty. - Note macOS differs (binary is "Google Chrome for Testing" in a .app, so `find -name chrome` is Linux-only). Co-Authored-By: Claude Opus 4.8 <[email protected]> * Human polish of instructions * final PR feedback --------- Co-authored-by: Claude Opus 4.8 <[email protected]>
-
Johannes Millan (11 Jul 26)
test(sync): target transient rejection assertion
-
Lane Sawyer (11 Jul 26)
chore(lint): remove unused eslint-disable directives (#8913) These eslint-disable directives no longer suppress any reported problems, so ESLint flags them as unused directive warnings. Remove them to clear the 11 lint warnings. Co-authored-by: Claude Opus 4.8 <[email protected]>
-
Salma Abdelrhman Mostafa Mahmoud (11 Jul 26)
fix(accessibility): add ARIA roles, live regions, and alt attributes to banner component (#8888) Co-authored-by: SalmaAbdelrhmanMostafaMahmoud <[email protected]>
-
Lane Sawyer (11 Jul 26)
chore(ui): removes dead utilities and op-log leftovers [#8260 - Tier A] (#8892) * chore(ui): removes dead utilities and op-log leftovers [#8260 - Tier A] * update readme and remove-unused-log-imports.ts * restore benchmark test and make runnable
-
Johannes Millan (11 Jul 26)
fix(app): hide donation page on macOS (#8915) * fix(app): hide donation page on macOS Use the stable macOS bridge instead of the MAS runtime flag and redirect direct donation-page navigation on restricted Apple builds. * fix(app): harden donation platform gating Give the restriction a behavior-specific contract, cover platform classification and real router redirects, and correct the platform documentation.
-
Johannes Millan (11 Jul 26)
fix(sync): handle initial provider setup safely
-
Lane Sawyer (11 Jul 26)
chore(scripts): remove one-off codemod scripts [#8260 - Tier A] (#8893) * chore(scripts): remove one-off codemod scripts [#8260 - Tier A] * Address PR feedback
Super Productivity Security
Security Advisories (1)
- high Patched CVSS 7.8
GHSA-256q-p9ff-jv8q Arbitrary OS Command Execution via IPC EXEC Handler with Persistent Whitelist
Super Productivity Website
Website
Super Productivity โ Open-Source Deep Work Task Manager
The open-source deep work task manager for developers. Plan tasks, track time & notes in a privacy-first workspace for Windows, macOS, Linux, Android & iOS.
Redirects
Does not redirect
Security Checks
All 65 security checks passed
Server Details
- IP Address 89.22.100.24
- Hostname vps54630.alfahosting-vps.de
- Location Dortmund, Nordrhein-Westfalen, Germany, EU
- ISP dogado GmbH
- ASN AS8648
Associated Countries
-
DE
Safety Score
Website marked as safe
100%
Blacklist Check
super-productivity.com was found on 0 blacklists
- AntiSocial Blacklist
- Artists Against 419
- Badbitcoin
- Bambenek Consulting
- CERT Polska
- CoinBlockerLists
- CRDF
- CryptoScamDB
- EtherAddressLookup
- EtherScamDB
- Fake Website Buster
- MetaMask EthPhishing
- NABP Not Recommended Sites
- OpenPhish
- PetScams
- PhishFeed
- PhishFort
- Phishing.Database
- PhishStats
- PhishTank
- Phishunt
- RPiList Not Serious
- Scam.Directory
- SecureReload Phishing List
- Spam404
- StopGunScams
- Suspicious Hosting IP
- ThreatFox
- ThreatLog
- TweetFeed
- URLhaus
- ViriBack C2 Tracker
Website Preview
Super Productivity Android App
APK Info
- App Super Productivity
- Creation Date 11 Oct 20
- Last Updated 18 Jul 24
- Current Version 6.0
- Privacy Report View on Exodus โ
De-Googled Compatibility
Trackers
No trackers found
Permissions
- Access Network State
- Internet
- Write External Storage
Super Productivity Reviews
More Task Management
-
A Getting Things Done + Pomodoro productivity system for desktop and mobile. Local-first, no account required, can sync via WebDAV/Dropbox/local file or self-hosted deployment. With a CLI, REST API and MCP for scripting, has optional BYOK AI.
About the Data: Super Productivity
Change History
- Added #438
API
You can access Super Productivity's data programmatically via our API. Simply make a GET request to:
https://api.awesome-privacy.xyz/v1/services/super-productivity The REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.
Share Super Productivity
Help your friends compare Task Management, and pick
privacy-respecting software and services.
Share Super Productivity and Awesome Privacy with your network!