Make the mock report what changed #105

Closed
opened 2026-08-27 19:40:20 +00:00 by jcoffey-dev · 0 comments
Owner

Groundwork for the rest of #100, not a fix for it.

Two silences in the mock, and between them the whole change-reconciliation path was untestable:

  • Email/set never announced anything. A real server pushes a state change after a set and the client acts on it — Email/changes, then the store deciding what to do with the answer. The mock said nothing, so that path never ran.
  • Email/changes returned three empty arrays whatever had happened. So even when asked, the answer was that nothing had changed.

Together they mean every version of the mark-read code has been checked against a server that never reported the change being made. That's how #100 reached production, and why the fix for it could be verified in the message view — where the flicker partly was — while whatever remains stayed invisible, because the code that runs when the server answers back has never run here at all.

The mock now records what each set created, updated and destroyed against the state it happened in, answers Email/changes from that log, and broadcasts afterwards the way Stalwart does.

What this is not

It fixes nothing and isn't meant to. It makes a path testable that wasn't — the prerequisite for finding what's left of #100 rather than guessing at it.

I had a theory about the fullIds eviction in applyChanges (evicting the full copy of an updated email takes it out of the open thread until a refetch puts it back) and reverted it. Three attempts to reproduce the symptom against this mock failed, and shipping an unverified fix for a bug already half-fixed once seemed the wrong trade. That the mock couldn't reproduce it was itself the finding, and this PR is the consequence.

333 web and 77 server tests pass. npm run typecheck clean.

🤖 Generated with Claude Code

Merged 2026-08-27 as coffey-labs/ihasmail@6a98dd22fd

Rebuilt from: git history, session transcript.

Groundwork for the rest of #100, not a fix for it. Two silences in the mock, and between them the whole change-reconciliation path was untestable: - **`Email/set` never announced anything.** A real server pushes a state change after a set and the client acts on it — `Email/changes`, then the store deciding what to do with the answer. The mock said nothing, so that path never ran. - **`Email/changes` returned three empty arrays** whatever had happened. So even when asked, the answer was that nothing had changed. Together they mean every version of the mark-read code has been checked against a server that never reported the change being made. That's how #100 reached production, and why the fix for it could be verified in the message view — where the flicker partly was — while whatever remains stayed invisible, because the code that runs when the server answers back has never run here at all. The mock now records what each set created, updated and destroyed against the state it happened in, answers `Email/changes` from that log, and broadcasts afterwards the way Stalwart does. ### What this is not It fixes nothing and isn't meant to. It makes a path testable that wasn't — the prerequisite for finding what's left of #100 rather than guessing at it. I had a theory about the `fullIds` eviction in `applyChanges` (evicting the full copy of an updated email takes it out of the open thread until a refetch puts it back) and **reverted it**. Three attempts to reproduce the symptom against this mock failed, and shipping an unverified fix for a bug already half-fixed once seemed the wrong trade. That the mock couldn't reproduce it was itself the finding, and this PR is the consequence. 333 web and 77 server tests pass. `npm run typecheck` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Merged** 2026-08-27 as coffey-labs/ihasmail@6a98dd22fd90 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.