Hive
feat(specs): surface new activity since the user’s last visit
GitHub issue · Closed
Source
tuist/hive #55
Updated
Jun 24, 2026
Domains
Hive
Why
Reviewers told me the Specs page felt like a queue I had no way to triage from a distance. The sidebar link was hard-coded to ?filter_status_val=draft, hiding everything else, and there was nothing — on the list page or the show page — telling me which specs had changed since I last looked. That meant either re-reading every row or relying on dashboard chatter.
This change tracks per-user spec views and surfaces that signal in three places so members can spot updated work without opening every spec.
What changed
- No more default filter. The Specs sidebar link goes to
/specs(unfiltered). The filter dropdown still defaults to “Draft” when you open it manually. - Per-user view tracking. New
spec_viewstable (user_id,spec_id,last_viewed_at, unique on the pair). When a signed-in user opens a spec,Specs.mark_viewed/2upserts after the socket connects. - Index badges.
Specs.list_specs/1decorates each row withlast_activity_at = max(spec.updated_at, latest_comment.inserted_at)andhas_new_activity. A row gets a small “New activity” Noora badge whenever the user has viewed before and activity has landed since. - Sidebar dot.
Specs.has_new_activity_for_user?/1answers “any of my viewed specs have changed?” in a singleEXISTSquery and is assigned inDashboardLive.Hooksso the Specs sidebar item shows a purple dot from every dashboard page. - Show-page surfacing. Mount captures the prior
last_viewed_atbefore the upsert advances it, then the header gets a “New activity” badge and any comment newer than that timestamp gets an inline “New” tag.

Approach and tradeoffs
A few choices worth flagging:
- N+1 avoidance. The list query stays a single SELECT; decoration adds two batched queries (one
GROUP BYfor the latest comment per spec, one for the user’s views). The sidebar query is oneRepo.exists?regardless of how many specs the user has viewed. There is no per-row query path. - “Viewed before” rule. Specs the user has never opened don’t get a badge. The alternative (treat unviewed specs as new) would flood new members on day one and give the badge less meaning over time. The current rule means the badge only ever says “you saw this, but it changed.”
- Activity definition. Counts spec edits (which already bump
updated_atvia the revision flow) plus any new comment. Status changes go throughupdate_spec/3, so they’re picked up automatically. - Decoration over schema split. Two virtual fields on
Hive.Specs.Spec(last_activity_at,has_new_activity) instead of a separate decorated struct. Keeps templates dumb and avoids leaking implementation details into LiveView assigns. - Show-page snapshot.
Specs.last_viewed_at/2is called once beforemark_viewed/2so the show page knows the prior state. Without that snapshot, opening the spec would clear the indicator before it could render.
Demo data
Seeds now insert three spec_views for test@hive.dev:
- Two backdated to
2020-01-01(rows 1 and 3 in the screenshot) so the index badges and sidebar dot appear immediately on a freshmix ecto.reset && mix run priv/repo/seeds.exs. - One fresh view so a quiet row stays quiet for comparison.
Testing
mix test— 557 passingmix format --check-formattedmix credo- Manual: signed in as
test@hive.devagainst the dev instance, confirmed badges on rows 1 and 3, sidebar dot present from/forageand/meadows, opened spec 1 and saw the header “New activity” badge plus per-comment “New” tags on the comments added after the backdated view, then reloaded and confirmed the indicators clear once viewed.
No GitHub comments yet.