mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* 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. |
||
|---|---|---|
| .. | ||
| plugin-api | ||
| plugin-dev | ||
| shared-schema | ||
| super-sync-server | ||
| sync-core | ||
| sync-providers | ||
| vite-plugin | ||
| build-packages.js | ||
| README.md | ||
Super Productivity Packages
This directory contains plugin packages and the plugin API for Super Productivity.
Structure
plugin-api/- TypeScript definitions for the plugin APIplugin-dev/- Plugin development examples and toolsapi-test-plugin/- Basic API test pluginprocrastination-buster/- Example SolidJS-based pluginyesterday-tasks-plugin/- Simple plugin showing yesterday's tasksboilerplate-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:
- Builds the plugin-api TypeScript definitions
- Builds plugins that require compilation (e.g., procrastination-buster)
- 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
- Create a new directory in
plugin-dev/ - Add the plugin configuration to
/packages/build-packages.js - Run
npm run build:packagesto test the build
Notes
- The
boilerplate-solid-jsandsync-mdplugins 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