feat(local-first): A0.1 perf harness → B0.1 PoC → ADR-0008 → B1.1 shadow sync#140
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@CodeAnt-AI review |
|
CodeAnt AI is running the review. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis PR adds an experimental Local-First sync feature that, when enabled, mirrors the Redux project state into a per-project Yjs document with IndexedDB persistence, while Redux remains the source of truth and a shadow binding verifies and self-heals any drift. sequenceDiagram
participant User
participant SettingsUI
participant ReduxStore
participant ListenerMiddleware
participant LocalFirstBinding
participant IndexedDB
User->>SettingsUI: Enable Local-First sync
SettingsUI->>ReduxStore: Set enableLocalFirstSync = true
ReduxStore->>ListenerMiddleware: Project state changed with flag enabled
ListenerMiddleware->>LocalFirstBinding: Create or reuse project doc with project id
LocalFirstBinding->>IndexedDB: Attach persistence and load existing doc state
ListenerMiddleware->>LocalFirstBinding: Sync from current ProjectData
LocalFirstBinding->>LocalFirstBinding: Incrementally update Y.Doc and verify against ProjectData
LocalFirstBinding->>IndexedDB: Persist updated doc in background
User->>SettingsUI: Disable Local-First sync
SettingsUI->>ListenerMiddleware: Flag turned off, teardown binding and persistence
Generated by CodeAnt AI |
…oundary (CodeAnt #140) - docPersistence.test.ts: drop the fixed setTimeout(100) flush; poll a fresh provider via vi.waitFor (condition-driven, no wall-clock guess). Fake timers can't advance fake-indexeddb's async IDBRequest resolution, so waitFor is the correct deterministic mechanism. Tests now run in ~half the time. - docPersistence.ts: document why this is the centralized local-first persistence chokepoint (mirroring logger/proForgeMemoryBank/aiInferenceCache IDB wrappers) and why a Yjs update-log provider cannot route through the high-level StorageBackend API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
CodeAnt AI finished running the review. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
#140) Move every persistProjectDoc teardown (destroy/clearData) into finally so a failing assertion or a vi.waitFor timeout can never leak an open IndexedDB provider into later tests. Addresses the missing-cleanup + fixed-delay-race findings on docPersistence.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@CodeAnt-AI review |
|
CodeAnt AI is running the review. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis PR adds an experimental Local-First sync flag that, when enabled, mirrors the Redux project state into a per-project Yjs document with IndexedDB persistence, verifies for drift, and self-heals while remaining shadow-only. sequenceDiagram
participant User
participant SettingsUI
participant ReduxStore
participant Listener
participant LocalFirstSync
participant Storage
User->>SettingsUI: Change Local-First sync setting
SettingsUI->>ReduxStore: Update enableLocalFirstSync flag
ReduxStore-->>Listener: Notify flag and project state change
alt Flag enabled
Listener->>LocalFirstSync: Create project doc and attach persistence
LocalFirstSync->>Storage: Load persisted state into project doc
Listener->>LocalFirstSync: On debounced project change mirror Redux project
LocalFirstSync->>LocalFirstSync: Update doc incrementally and verify vs project
LocalFirstSync-->>Listener: Log drift count and reproject on mismatch
else Flag disabled
Listener->>LocalFirstSync: Tear down doc binding and persistence
end
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Six valid findings from the second review pass: docBinding.ts: - reconcileSection: a field cleared to `undefined` is now deleted from the Yjs map (was left stale — `key in section` stays true for explicit undefined). - reconcileMeta: nextKeys built from DEFINED values so cleared meta fields are actually removed, not left stale. - observe(): binding writes now run in `doc.transact(fn, this.origin)` (new public readonly origin symbol) so consumers can echo-filter self-updates. - verify(): compares EVERY meta field reconcileMeta writes (logline, author, goals…), not just title — non-title drift now triggers self-heal. listenerMiddleware.ts: - Handle create/teardown serialized behind a promise lock — overlapping sync and enable/disable runs can no longer race on the module-global handle. - OFF→ON now performs an immediate initial projection via a dedicated enable listener (shared runLocalFirstShadowSync helper) instead of waiting for the next edit. Tests: +4 docBinding (undefined-clear section + meta, non-title verify drift, observe origin). typecheck + lint + 22 localFirst tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis diagram shows how enabling the Local-First sync feature flag causes the app to mirror the Redux project state into a Yjs document with IndexedDB persistence, and how subsequent edits trigger debounced shadow sync updates. sequenceDiagram
participant User
participant SettingsUI
participant ReduxStore
participant Listener
participant LocalFirstSync
participant IndexedDB
User->>SettingsUI: Toggle Local-First sync on
SettingsUI->>ReduxStore: Dispatch enableLocalFirstSync true
ReduxStore->>Listener: Feature flags state updated
Listener->>LocalFirstSync: Start shadow sync with current project
LocalFirstSync->>IndexedDB: Attach y-indexeddb and load project doc
IndexedDB-->>LocalFirstSync: Return persisted doc state
LocalFirstSync->>LocalFirstSync: Project to Y.Doc projection and verify
ReduxStore->>Listener: Project manuscript edited
Listener->>LocalFirstSync: Debounced shadow sync from Redux
Generated by CodeAnt AI |
…ent (#140 wave-4) - Privacy: the shadow doc was persisted as PLAINTEXT to y-indexeddb even when at-rest encryption (enableIdbAtRestEncryption) was active. getLocalFirstHandle now uses NOOP_PERSISTENCE (in-memory only) when isIdbEncryptionReady(), so no unencrypted manuscript copy hits disk; shadow-sync still validates vs Redux. - docPersistence: `active` is now a getter reflecting live state (false once teardown begins, incl. the async whenSynced-rejection path) instead of a constant true; destroy() memoizes the real teardown promise so concurrent/repeat calls share one in-flight destroy (no double-destroy, no flag flipped before destroy completes). - docBinding.reconcileSection: if the content slot holds a non-Y.Text (corrupted/legacy) value, recreate a Y.Text seeded with the content instead of silently dropping the edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
@CodeAnt-AI review |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis PR adds an experimental Local-First sync feature that, when enabled, mirrors the Redux project state into a Yjs document with optional IndexedDB persistence using a debounced listener and self-healing verification. sequenceDiagram
participant User
participant SettingsUI
participant ReduxStore
participant LocalFirstSync
participant YDocBinding
participant IndexedDb
User->>SettingsUI: Toggle Local-First sync on
SettingsUI->>ReduxStore: Dispatch enableLocalFirstSync true
ReduxStore->>LocalFirstSync: Feature flag is enabled
LocalFirstSync->>YDocBinding: Initialize project doc with current project data
YDocBinding->>YDocBinding: Project data into Y.Doc and verify, reproject on drift
YDocBinding->>IndexedDb: Persist doc state when persistence is active
User->>ReduxStore: Edit manuscript section
ReduxStore->>LocalFirstSync: Debounced project change event
LocalFirstSync->>YDocBinding: Incremental sync from updated project and verify
Generated by CodeAnt AI |
…ence in tests (#140 wave-5) - Privacy: the LocalFirst handle chose NOOP vs y-indexeddb only at creation, then was reused by projectId — so if at-rest encryption activated AFTER a plaintext- persisting handle was made, it kept writing plaintext. getLocalFirstHandle now re-checks isIdbEncryptionReady() on cache hit and, if encryption is now active on a still-persisting handle, wipes the plaintext (clearData) and tears it down so it's recreated in-memory. - docPersistence tests: the persistence/clear assertions read while the writer provider was still alive, so the value could arrive via live cross-provider sync rather than persisted IndexedDB (false positive). Destroy the writer, then re-read — the value can now only come from (or be absent in) the IDB store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
@CodeAnt-AI review |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Sequence DiagramThis PR adds an experimental Local-First sync flag that, when enabled, mirrors the Redux project into a Yjs document with optional IndexedDB persistence, verifies for drift, and runs as a background shadow sync without affecting the user. sequenceDiagram
participant User
participant SettingsUI
participant FeatureFlagsStore
participant ListenerMiddleware
participant LocalFirstSync
participant IndexedDB
User->>SettingsUI: Toggle Local-First sync on
SettingsUI->>FeatureFlagsStore: Dispatch enableLocalFirstSync = true
FeatureFlagsStore-->>ListenerMiddleware: Feature flags state updated
ListenerMiddleware->>LocalFirstSync: Run initial shadow sync for current project
LocalFirstSync->>IndexedDB: Attach or skip project doc persistence
LocalFirstSync->>LocalFirstSync: Mirror Redux project into Y doc and verify
alt Drift detected between Redux and Y doc
LocalFirstSync->>LocalFirstSync: Reproject from Redux and log drift
end
Generated by CodeAnt AI |
… CF Pages rename (#166) * chore(release): finalize v1.23.0 changelog + rename CF Pages project Promote the [Unreleased] changelog to [1.23.0] — 2026-06-16 and add this release's remaining entries (Local-First foundation #140, OpenRouter reactive policy #163, Local AI i18n #164, WorldScript icons + URL cleanup #165, and Dependabot maintenance). Rename the Cloudflare Pages project in wrangler.toml storycraft-studio → worldscript-studio (CF is not actively deployed; the project rename on the CF side is a later step). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tauri): resolve Vite base from TAURI_ENV_PLATFORM (fixes blank desktop window) The Tauri build detected desktop mode via `TAURI_PLATFORM`, the Tauri 1.x env var that Tauri 2.x never sets. Every desktop build therefore fell through to the GitHub Pages base (`/WorldScript-Studio/`) and 404'd its hashed assets under `tauri://localhost/`, leaving only the native file/help menu over an empty webview on Windows/macOS. Base resolution now checks `TAURI_ENV_PLATFORM` (legacy `TAURI_PLATFORM` kept as a fallback) and is extracted into a pure, unit-tested `config/resolveViteBase.ts` so the regression cannot silently return. No runtime landmines: locale/community fetches use `import.meta.env.BASE_URL`, which resolves correctly under `./`. Linux `.deb`/AppImage still require webkit2gtk-4.1 (Ubuntu 22.04+) — a Tauri 2 platform minimum documented in the changelog, not a bug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
User description
Context
First concrete delivery of the Local-First track from the strategic audit (Y.Doc-as-source-of-truth, ADR-0008). The app was offline-first but not local-first in the CRDT sense — Yjs was an inert bolt-on. This branch lays the foundation and proves the direction is sound, all behind a flag that is off by default (Redux stays the source of truth).
Six commits, each independently green:
86438dbpnpm bench, deterministic ~120k-word fixture)8d77f67services/localFirst/projectDoc.ts)56ce87fcebededdocBinding.ts)4230bf8enableLocalFirstSyncflag + listenerMiddleware shadow sync + y-indexeddbWhy it matters
Y.UndoManagerundo all proven.Scope / deliberate deferrals
enableLocalFirstSyncis off by default; Redux remains the source of truth. This branch does not flip anything.pnpm run parity:check) was left to CI — the repo-wide grep swaps on the low-end dev host. Flag consistency verified by construction (defaults + locale + section + handler + runtime all present across 11 locales).Verification (local, all green)
typecheck✅ ·lint✅ (1346 files) · targeted unit tests ✅ (76 — incl. newlocalFirst/suites + all 8FeatureFlagsStatemocks) ·i18n:check✅ (2697 × 11) ·pnpm audit✅ (clean; new depy-indexeddb@^9.0.12).Next (later sessions)
Y.Textdata migration + the gated Redux→Y.Doc flip (one-way door — keeps POJO backup).🤖 Generated with Claude Code
CodeAnt-AI Description
Add a gated local-first sync shadow, with performance baselines and docs
What Changed
Local-First syncflag to Settings so the new shadow sync can be turned on and off from the UI.Impact
✅ Experimental local-first sync can be enabled from Settings✅ Shadow project data now persists across reloads when enabled✅ Clear before/after baseline for large-manuscript editor performance💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.