The per-project issue list rides the filtered myAll cache. Changing an
issue's project is a membership change, but the surgical patch
(patchIssueInBuckets) is filter-blind and never removes a card that no
longer matches the list's project filter — so a moved issue stayed visible
in the old project's list until a manual refetch (#4548 / MUL-3669).
Root cause: project_id was the only membership-affecting field with no
server *_changed flag. The WS handler fell back to diffing project_id
against its own cache, which breaks once onMutate has optimistically
overwritten the cached value on a local move.
- server: stamp project_changed on issue:updated (UpdateIssue + Batch),
alongside status_changed / assignee_changed.
- events.ts: surface project_changed (optional, additive — old clients ignore).
- ws-updaters: prefer the server flag, fall back to the cache diff only when
absent (older backend) so a new frontend on an old backend does not regress.
- mutations: onSettled invalidates myAll when project_id changed — a local
safety net that never depends on the WS echo (update + batch).
Tests: WS flag wins over a matching cache (local-move repro), explicit false
suppresses the legacy diff, the cache-diff fallback still fires, and both
mutations invalidate myAll on a project change.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>