mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-19 01:17:31 +00:00
* feat(plugins): adopt PERSISTED_DATA_CHANGED in document-mode (#7752) Document-mode's iframe editor went stale when another device edited the same context's doc — the editor kept typing on in-memory `storedState` and the next throttled save merged-on-stale-base, partially clobbering the remote edit. Background's `enabledIds` set had the same gap for remote toggles. Adopts the host-side `PERSISTED_DATA_CHANGED` hook (shipped in #7805, made multi-handler-safe in #7811) in both surfaces: - `background.ts` — on fire: re-read `enabledIds`, diff, and call `showInWorkContext`/`closeWorkContextView` only when the active context's membership flipped. Idempotent on no-op re-fires; the hook also fires for `doc:` writes which background ignores via the set-unchanged short-circuit. - `ui/editor.ts` — on fire: load the current ctx's raw `doc:` bytes via the new `loadContextDoc` `{ raw, parsed }` shape, byte-compare to `lastSeenRemoteData`. Equal → noop (self-echo or another key's fire). Different → show a one-button "reload" banner with a distinct `doc-banner--remote-update` modifier class. Clicking Reload re-runs `setActiveContext` which already re-reads + setContent's. `isDocCorrupt === true` short-circuits to a direct reload (the corruption banner already promises saved data is untouched). The load-bearing piece is `lastSeenRemoteData` — captured as the raw `loadSyncedData()` string on both load (`setActiveContext`) and write (`flushSave` / `flushSaveSync`). The editor's own `JSON.stringify(getJSON())` is NOT byte-stable across the load path because `prepareStoredDoc` reshapes chip content against the local task cache, so comparing against editor output would mis-flag every load as remote. Raw-against-raw keeps the host's deterministic encoding the only thing that matters. Acceptance criteria from #7752: all met except the E2E append, which needs iframe-level state injection that the existing spec helpers don't cover; covered by manual verification + unit specs instead. What this doesn't fix: - No cross-context notification (editor catches up silently on switch). - Same-context concurrent edits still resolve whole-doc LWW. - No silent swap or selection preservation. - No "Keep mine" force-flush — dismiss-and-keep-typing already wins LWW. Tests: 84/84 plugin specs pass (+6 background hook membership branches, +1 corrupt-entry raw-bytes case, +1 saveContextDoc-returns-raw case). Plan v6 changelog documents the v5/v6 multi-review iterations and the cuts (pre-reload backup, pure-fn file, coalesce timer, "Keep mine"). * refactor(plugins): apply doc-mode review findings (#7752) Multi-review of the prior commit surfaced two real and one likely-real bugs in the reconciler and a few cleanup wins. Apply all that survived verification. W1 — wrap getActiveWorkContext in try/catch. The prior handler only caught loadEnabledCtxIds rejections; a getActiveWorkContext throw would escape via the void onPersistedDataChanged() registration as an unhandled rejection and leave enabledIds stale across every subsequent fire. W2 — snapshot enabledIds at entry. The prior handler read the closure variable across multiple awaits before assigning at the end; two interleaved fires could mis-compute wasActiveEnabled and drop a close/show call. Pass-by-parameter to reconcileEnabledIds eliminates the in-function race; concurrent fires now race only on the final caller assignment, which is at least eventual-consistent. W3 — extract reconcileEnabledIds to its own file and import the real function from the spec (no more local copy). reconcile-enabled.ts sidesteps the testability problem at its root: background.ts has top-level PluginAPI side-effects (registerWorkContextHeaderButton, registerHook) that crash in node, so the spec can't import from there. The new file holds only the pure reconciler + try/catch hardening. W4 — extract serializeContextDoc(doc) helper. flushSave and flushSaveSync now produce byte-identical output via the same function, so a future encoding change can't silently desync the sync path from the async one. W5 — fix the inline comment on showRemoteUpdateBanner re-entrancy to match what the code actually does (early-return because text is invariant, not "replace text in place" as the v6 plan implied). S1 — rename lastSeenRemoteData → lastSeenDocBytes. The variable also holds local-write bytes, not only "remote" ones; the new name follows the existing lastSeenTaskIds convention. S2 — drop the try/catch around loadSyncedData in onRemotePersistedDataChanged. The hook dispatcher already catches + logs handler rejections (PluginHooksService._invokeWithTimeout), and loadContextDoc elsewhere in this file follows the no-wrap convention. S3 — add primitive-JSON case to persistence.spec.ts. loadContextDoc's new {raw, parsed} shape silently forwards parsed=123/null/"hi" to callers; the editor's truthy guard is the safety net, but the persistence contract is now locked. S5 — explain why isDocCorrupt short-circuits to a direct reload (auto-recovery without user click; alternative would leave the user stuck on the corruption banner even when the remote already fixed the entry). Tests: 86/86 plugin specs pass (+7 reconciler error/membership cases, +1 persistence primitive-JSON). Bundle redeployed. * docs(plugins): update stale lastSeenRemoteData refs in JSDoc/comments Two comments referenced the pre-rename variable name. |
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| tsconfig.json | ||