Sprint M8 — correct, and correct in time
Nineteen pull requests merged between September 7th and 21st. 533 tests across 52 suites. And one bug that every build of this app has had, found by launching it.
The guard was right
The rule is simple and it has never been in doubt: no session, no household data. A signed-out phone shows the welcome screen, never cached content from an account that is not yours. The check enforcing that has been correct since the day it was written. It was reviewed as correct. It has tests.
Launch the app signed out and you would see a screen of tasks for about a second, and then the welcome screen.
In the wrong place in time
The check ran in an effect — React’s after you have painted hook — and the tab tree is the first thing the router mounts. So the app rendered Today from the encrypted local database, then read the keychain, then redirected.
No amount of care inside that check could have fixed it, because an effect is by definition after the frame. The logic was not wrong. Its position in the sequence was.
Three rounds of review and a full test suite had passed over this, and none of them could have caught it. A test can assert what a component renders. It cannot assert when it paints. Continuous integration does not see frames. A person launching the app does.
Nothing here reached the public — the app is not publicly available, and this was found in internal use — but it was present in every build we have ever made, and it is privacy-shaped rather than cosmetic. Household data on a screen belonging to nobody is exactly the thing the guard exists to prevent, and the guard was running.
The fix moved the session read above the first paint, into the gate that already holds the splash screen for this class of reason. That gate’s own comment says the splash must not lift onto a blank frame while the app is still deciding whether this session should exist. The problem had been described, in a comment, next to the code that solved it, for a different reason.
The fix nearly shipped two worse bugs
The first was mine. Read the session once at startup and remember the answer — the obvious implementation, and wrong in a way that is worse than the bug it fixes. The remembered answer says signed out forever, so the first thing you would see after successfully signing in is the guard throwing you back to the welcome screen. Not a flash of the wrong screen: a loop you cannot get out of. Caught before it shipped, and there is now a test for it that was confirmed to fail without the fix.
The second was not mine. The automated reviewer found a race I had not seen. On a fresh install the app purges leftover credentials from a previous installation, and that purge clears the session. My new read started at the same moment. A read that began first and finished second would republish the session the purge had just deleted — using the very credential it had deleted.
That is the path where it matters most, and it was invisible to me.
What this sprint is actually about
“Correct” and “correct in time” are different properties, and only one of them has a test.
Everything else in M8 — a deterministic way to compose an event, event bars that span days properly, a quick capture straight into chat, the client half of a contract nothing consumes yet — is ordinary sprint work and none of it has reached anyone’s phone. The bug above is the one worth writing down, because the code was right, the review was right, the tests were right, and the app still showed the wrong thing.
Somebody has to open it and look.