fix(surface): an agent's answer marks a channel unread live, like any message#563
Merged
Conversation
… message The rule for which events make a channel unread was written down twice: once in the server SQL behind latest_event_id, once in the client reducer as isNewMessage. They disagreed. The client's copy was missing session.replied, so an agent's broadcast answer did not bump the badge live — then the next /sync recomputed the count from the server, which does count it, and the badge materialized out of nowhere. There is now one list, in shared, that the SQL builder and the reducer both read. Neither side can drift from the other, because there is no other side to drift from. The generated SQL is unchanged. The server reaches it through the @atrium/surface-client/timeline subpath, the convention every other shared import here follows; the barrel is deliberately avoided because it carries a React peer dependency. Verified through the real esbuild bundle from deploy/Dockerfile.server, which CI never builds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #562, which unified the two SQL copies of the unread rule. There was a third copy — on the client — and it had drifted.
The bug
isNewMessagein the reducer was:session.repliedis missing, but the server'slatest_event_idcounts it. So when an agent posts a broadcast answer, the client doesn't bumplatestEventIdlive and no badge appears. On the next/syncor channel-list load,channels-loadedrecomputescoldUnreadLevelfrom the server's counter — which does count it — and the badge materializes out of nowhere.An agent's answer is an ordinary channel message and should mark a channel unread like one. That's the entire point of the product.
The fix
One list,
CHANNEL_UNREAD_EVENT_TYPES, living inshared/. The server's SQL builder and the client reducer both read it. Neither can drift, because there's no second copy to drift from — structurally stronger than a contract test.The generated SQL is byte-unchanged;
readCursorstests confirm it against real Postgres.Import boundary
The server reaches the list via
@atrium/surface-client/timeline— the subpath convention every other shared import here follows (/handle,/prefs,/agentProfiles). The barrel is deliberately avoided: it carries a React peer dependency. That required adding the./timelinesubpath to shared's exports map.Since
deploy/Dockerfile.serverbundles the server with esbuild and CI never builds that image, a bad import here would break deploy and nothing else. So the real bundle was run locally against this change — it resolves and links clean.Verification
The new reducer test fails without the fix —
expected 10 to be 21, the live counter not moving on a broadcastsession.replied.Local:
pnpm lint·typecheck(7/7) · shared 453 · web 775 · serverreadCursors6/6 · esbuild bundle.🤖 Generated with Claude Code