fix(timeline): a successful like reported a count of zero - #809
Merged
Conversation
The four reaction RPCs are `RETURNS TABLE(<name>_count integer)`, and
PostgREST renders a set-returning function as an ARRAY of rows —
`[{ like_count: 1 }]`. The client indexed that array as though it were
the row, so the lookup was always undefined and the `|| 0` fallback
turned every successful reaction into zero.
Confirmed in production after the RPCs themselves were repaired: the
like row is written, timeline_event_stats.like_count reads 1, and the
button still renders a blank count.
It hid well. The filled heart comes from a boolean the client sets
itself, so the action looked right while the number beside it stayed
empty — and for the eight months the RPC was also raising 42703 there
was never a successful response to notice it in. The previous row in
timeline_likes before today is dated December 2025.
readCount takes either shape, so a future rewrite to a scalar cannot
silently zero it again. Proven by mutation: restoring the old read fails
the array-shape test and only that one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
…811) * fix(timeline): a like was stored, then read back as zero everywhere The write path was repaired in #806 and #809 and the like still did not appear. It was in timeline_likes, timeline_event_stats.like_count read 1, and the post page rendered an empty, unpressed heart. Nothing on the read side had ever populated these fields — in three separate places, all wrong in the same direction: eventQueries hardcoded `likesCount: 0, userLiked: false` under a comment saying the UI enriched them later. Nothing did. userFeeds called enrichEventsForDisplay and then OVERWROTE its output with `event.like_count` / `event.user_liked` — columns timeline_events has never had — so each value was replaced by `undefined || 0` on its way to the screen. transformEnrichedEventToDisplay, used by the followed-users feed and by search, set no reaction fields at all. It is synchronous and the enriched view carries no reaction columns, so it could not have. Three paths, one direction, which is why the counter never moved no matter which surface you looked at. Reaction state is now resolved by one function. enrichEventsForDisplay calls it for every path that goes through enrichment; the three that cannot — they build rows from an RPC or from the view — call attachReactionState after mapping. Making this each caller's responsibility is precisely what produced three callers that all got it wrong. Counts come from timeline_event_stats, which the reaction RPCs maintain and which is the only place they live. "Did I react" cannot come from there — it is per-reader — so it reads the membership tables. Batched: three queries per page regardless of post count, alongside the existing profile/project batching. Per-post lookups would be 60 round-trips for a 20-post feed. A failure degrades to zeros rather than failing the feed. A post without its counters is a small loss; a blank timeline is a total one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5 * fix(timeline): deleting the post you are looking at left it on screen The post page never passed onDelete, so PostCard called a callback nobody had supplied. The delete itself succeeded — verified in production: is_deleted true, deleted_at set — and the page went on rendering the post until a reload replaced it with "This post doesn't exist". Deleting a reply had the same gap. The main post now navigates to the timeline, with replace rather than push so Back cannot return to a page with nothing to show. A deleted reply is removed from the thread at whatever depth it sat, and its parent's reply count follows it down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5 --------- Co-authored-by: Georgy Butaev <41178744+g-but@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
The four reaction RPCs are
RETURNS TABLE(<name>_count integer), and PostgREST renders a set-returning function as an array of rows —[{ like_count: 1 }]. The client indexed that array as though it were the row, so the lookup was alwaysundefinedand the|| 0fallback turned every successful reaction into zero.Confirmed in production immediately after #806 repaired the RPCs themselves: the like row is written,
timeline_event_stats.like_countreads 1, and the button still renders a blank count.It hid well. The filled heart comes from a boolean the client sets itself, so the action looks right while the number beside it stays empty — and for the eight months the RPC was also raising 42703 there was never a successful response to notice it in. The previous row in
timeline_likesbefore today is dated December 2025.readCountaccepts either shape, so a future rewrite to a scalar cannot silently zero it again.Proven by mutation: restoring the old read fails the array-shape test, and only that one.
🤖 Generated with Claude Code
https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5