super-productivity/packages
Johannes Millan f75346613d
fix(sync): write split-compaction snapshots to immutable files (#9040) (#9047)
* test(sync): reproduce file-based compaction stranded ops pointer (#9040)

Concurrent split-file compactions can leave the committed sync-ops.json
referencing a snapshot present in neither sync-state.json nor its .bak: the
snapshot is force-written unconditionally while only the ops file is gated, so
the loser of the ops race can clobber the winner's snapshot after backing up an
older generation.

Adds a failing integration test that drives two interleaved compactions against
the stateful MockFileProvider and asserts a fresh client can still hydrate the
committed generation. Fails today (unrecoverable gap); passes once the snapshot
is written under a generation-unique, immutable name.

* fix(sync): write split-compaction snapshots to immutable files (#9040)

Concurrent split-file compactions could strand the winning ops pointer: the
snapshot was force-written to the fixed sync-state.json, so the loser of the
conditional ops-commit race could clobber the winner's snapshot, leaving the
committed sync-ops.json referencing a snapshot absent from both sync-state.json
and its .bak — an unrecoverable gap for a fresh client.

Write the compaction snapshot to a generation+client-unique immutable file
(sync-state__<syncVersion>__<clientId>.json) recorded in snapshotRef.file. A
concurrent compactor writes a DIFFERENT file, so the winning pointer can never
be clobbered. Readers prefer snapshotRef.file and fall back to sync-state.json /
.bak (kept dual-written for pre-#9040 clients). The superseded snapshot is
GC'd O(1) after each commit; a losing compactor's same-generation orphan is a
rare, bounded leak (documented, with a listFiles-prune upgrade path).

Also fixes MockFileProvider to model create-if-absent (If-None-Match: *) so the
fresh-folder concurrent-compaction path is faithfully gated in tests.

Tests: 3 concurrent-compaction integration scenarios (harmful/benign interleave,
fresh-folder race) + 2 unit tests pinning the snapshot resolution order. Full
op-log suite (3427) and sync-providers package (404) green.

* fix(sync): reclaim orphaned snapshot when compaction loses the commit race (#9040)

A concurrent compactor that writes its immutable snapshot but then loses the
conditional ops-commit left that snapshot orphaned — no committed ops file ever
referenced it, and the O(1) predecessor-GC never reclaimed it (same generation).

The losing compactor now deletes the snapshot it just wrote when its commit
throws, eliminating the leak at its only realistic source. Only a crash between
the snapshot write and the commit/cleanup can still orphan a file (rare crash
window; listFiles-prune remains the documented backstop).

Refactors the GC helper into a single-file _removeGenStateFile primitive used by
both the post-commit predecessor delete and the new failure-path cleanup.

Tests assert the loser's orphan is gone after both the near-cap and fresh-folder
concurrent-loss scenarios. Full op-log suite (3427) green.

* fix(sync): only reclaim orphaned snapshot on confirmed rev-mismatch (#9040)

The loser-orphan cleanup deleted the just-written immutable snapshot on ANY
commit failure. A rev-mismatch is a confirmed server rejection (ops did not
commit), but a network/5xx error is ambiguous — the ops PUT may have landed and
committed, in which case the snapshot is still referenced and deleting it would
strand a reader. Restrict the cleanup to UploadRevToMatchMismatchAPIError.

Adds a test asserting the immutable snapshot survives a non-mismatch commit
failure. Full op-log suite (3428) green.

* fix(sync): use a random suffix, not clientId, in immutable snapshot names (#9040)

Filenames are not encrypted, so embedding the clientId in
sync-state__<syncVersion>__<clientId>.json leaked device count/platform and a
per-device counter to the remote — a metadata downgrade for E2EE file-based
users. Replace the clientId with an opaque random suffix, which still gives two
concurrent compactors distinct files. A collision is astronomically unlikely and
self-heals (the reader validates loaded content against snapshotRef, so a wrong
file fails validation and falls back to sync-state.json/.bak). syncVersion stays
in the name for legible ordering and a future listFiles-prune.

Tests now assert on the count of immutable snapshot files (and a captured name)
rather than hardcoding the now-random filenames. Full op-log suite (3428) green.
2026-07-15 16:57:11 +02:00
..
plugin-api feat(plugin): add PluginAPI.request with manifest allowedHosts allowlist (#8721) 2026-07-07 11:50:17 +02:00
plugin-dev fix(plugins): prevent automation rule data loss (#8972) 2026-07-13 19:12:13 +02:00
shared-schema fix(sync): make marked project deletions win LWW conflicts (#9009) 2026-07-14 19:58:33 +02:00
super-sync-server fix(sync): retention pruning, misc→tasks alias boundary & WS local-win re-upload (#9028) 2026-07-15 13:12:04 +02:00
sync-core fix(sync-core): break whole-entity LWW timestamp ties by clientId (#9035) 2026-07-15 13:03:09 +02:00
sync-providers fix(sync): write split-compaction snapshots to immutable files (#9040) (#9047) 2026-07-15 16:57:11 +02:00
vite-plugin chore(deps): bump esbuild in the npm_and_yarn group across 0 directory (#8451) 2026-06-17 14:41:32 +02:00
build-packages.js fix(plugin): refresh procrastination buster i18n #5102 (#8145) 2026-06-08 20:44:43 +02:00
README.md feat: cleanup and update plugins 2 2025-06-29 06:36:02 +02:00

Super Productivity Packages

This directory contains plugin packages and the plugin API for Super Productivity.

Structure

  • plugin-api/ - TypeScript definitions for the plugin API
  • plugin-dev/ - Plugin development examples and tools
    • api-test-plugin/ - Basic API test plugin
    • procrastination-buster/ - Example SolidJS-based plugin
    • yesterday-tasks-plugin/ - Simple plugin showing yesterday's tasks
    • boilerplate-solid-js/ - Template for creating new SolidJS plugins (not built)
    • sync-md/ - Markdown sync plugin (not built)

Building Packages

All packages are built automatically when running the main build process:

npm run build:packages

This command:

  1. Builds the plugin-api TypeScript definitions
  2. Builds plugins that require compilation (e.g., procrastination-buster)
  3. Copies plugin files to src/assets/ for inclusion in the app

Development

To work on a specific plugin:

cd plugin-dev/[plugin-name]
npm install
npm run dev

Adding a New Plugin

  1. Create a new directory in plugin-dev/
  2. Add the plugin configuration to /packages/build-packages.js
  3. Run npm run build:packages to test the build

Notes

  • The boilerplate-solid-js and sync-md plugins are development templates and are not included in production builds
  • Plugin files are automatically copied to src/assets/ during the build process
  • The build script handles dependency installation automatically