diff --git a/.gitignore b/.gitignore index 5c85376..1762a52 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.db-shm *.db-wal backend/server +backend/backend .playwright-mcp/ graphify-out/ plans/ diff --git a/backend/latest.go b/backend/latest.go index 4d08d0d..40a36aa 100644 --- a/backend/latest.go +++ b/backend/latest.go @@ -148,6 +148,13 @@ func (p *latestPoller) checkOne(ctx context.Context, b Bookmark) { // Re-read: the row may have been updated or deleted while the fetch was in // flight, and writing b back wholesale would undo that. + // + // ponytail: non-transactional read-modify-write, wrap Get+Upsert in a tx if + // this ever runs for more than one user. A client PUT that commits between + // these two statements is lost to the stale re-read — reverting read + // progress or a status change, and moving updated_at because the stored + // value now differs. Accepted for a single-user deployment: the window is + // milliseconds and the loser is one poll cycle. cur, found, err := p.store.Get(b.Key) if err != nil { log.Printf("latest poll %q: reread: %v", b.Key, err) diff --git a/backend/web.go b/backend/web.go index 7a5487d..61f5840 100644 --- a/backend/web.go +++ b/backend/web.go @@ -262,6 +262,13 @@ func (h *webHandler) loadForMutation(w http.ResponseWriter, r *http.Request) (Bo // saveAndRenderCard upserts and renders the row as stored. Upsert decides // whether updated_at moves, so the argument's timestamp is only a candidate and // the response must come from the return value. +// +// ponytail: the swapped card stays put even when its new status no longer +// matches the active tab, add an hx-swap-oob list refresh if that reads as a +// bug rather than as feedback. Archiving from the All tab leaves the card on +// screen until the next list load. The alternative costs a full list round +// trip on every toggle, and the card visibly showing its new state is the +// feedback the user needs. func (h *webHandler) saveAndRenderCard(w http.ResponseWriter, b Bookmark) { stored, err := h.store.Upsert(b) if err != nil {