0.22.1 2026-07-16
Fixed
CRDTStoretodo round-trip —doc()now rebuilds the in-memory Loro map keyed byidem_key(the client todo id) instead of the PocketBase row id, matching howCreate/Update/Delete/ClearCompletedkey items. Before the fix, afterClose+New,List/Getlost todos and mis-keyed rows, soTestCRDTStore_RecordRoundTripfailed; it now passes (3 items,completedround-trips,Getworks post-reload).- Test DB DSN missing
file:prefix —newTestApp(crdtstore_test.go) and the identical siblingdb/seed_test.gonow open SQLite withfile:<path>?_pragma=.... Without thefile:prefix,ncruces/go-sqlite3silently dropped all pragmas (journal_mode=DELETE,foreign_keys=OFF), so tests ran under DELETE journal + FK-off instead of the production WAL + FK-on config.
Added (Phase 2 + 3 closure)
- Cross-instance CRDT transport wired:
cmd/web/main.gocallsserver.WireCRDTStoreTransport(JetStream ops) ANDserver.WireCRDTStorePublisher(SSE Hub fan-out) afterserver.Run. - Local mutation → saveSnapshot → bumpVersion → publishes
doc-version-bumpedto the SSE Hub → client merges$docVersionsignal → re-fetches fragment. - Remote mutation → ApplyRemoteOp → saveSnapshot → bumpVersion → same path.
crdtstore.DocPublisherinterface: plugin event sink;SetPublisher(p)wires one after boot. The SSE Hub adapter (internal/server/crdtstore_wire_publisher.go) implements it; tests use afakePublisherthat records every event.SSE handler.dispatch("doc-version-bumped"): new branch infeatures/todo/handlers/todo_sse.gomerges{docVersion, docVersionSeen}signals via Datastar.- Client-side
$docVersionwatcher:features/todo/components/realtime.templpollsdata-signals-docVersionevery 250ms; on change, clicks the existingpb-realtime-resyncbutton so the same fragment fetch handles both PB record and CRDT doc events. - *
server.Runreturnsqueue.Queue** so main.go can accessq.Hub()without leaking queue refs through the router. Also affectscmd/desktop. router.ConcreteTodoStore()race-safe: guarded byconcreteTodoStoreMu.
Tests (Phase 3 E2E pipeline)
features/store/crdtstore/pipeline_test.go—TestCRDTStore_FullPipeline_BumpPublisherFires:- Two CRDTStores share one JetStream; mutual Subscribe; store A has a fake publisher wired.
- Store B creates → op flows through JetStream → store A's ApplyRemoteOp fires → bumpVersion → publisher count goes up.
- Mirror: store A creates → publisher fires again.
- Closes Phase 3 missing test gap. Existing cross_process + transport tests only verified doc propagation; this one covers the full SSE-bound path end-to-end without race flakes (fake publisher eliminates goroutine timing race).
Fixed
cmd/desktop/main.goupdated for newserver.Runsignature.pipeline_test.goIDs renamed frompipe-1,pipe-2(renamed to avoid Tailwind class-name extraction) to avoid Tailwind's content scanner treating test data as utility classes (which generated spurious.p-1CSS).
AGENTS.md (developer-facing)
- Feedback loop section rebuilt as 4 tiers (format → compile → lint scoped → tests scoped → full gate → remote CI).
- Corrected the persistent misconception that
make buildruns the full gate —make buildonly runsgo build. The actual fast feedback loop usesgofumpt+go build+ scopedgolangci-lint run. - Documented
make checkas redundant withmake ci-localand removed it from the recommended paths. - Added "When to use what" reference table.
Removability
All Phase 2 + Phase 3 code is SCOPE:plugin and gated on ENTITY_STORE=crdt. Setting ENTITY_STORE=pb (default) skips the entire cross-instance pipeline at startup. To remove entirely, delete features/store/crdtstore/{transport,pipeline_test,}.go, internal/server/crdtstore_wire.go, internal/server/uuid.go, the WireCRDTStoreTransport/WireCRDTStorePublisher calls in cmd/web/main.go, and the streamDocVersionBumped branch + watchDocVersion block in the SSE handler / template.