Hive Hive
Sign in

feat(specs): surface new activity since the user’s last visit

GitHub issue · Closed

Metadata
Source
tuist/hive #55
Updated
Jun 24, 2026
Domains
Hive
Details

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_views table (user_id, spec_id, last_viewed_at, unique on the pair). When a signed-in user opens a spec, Specs.mark_viewed/2 upserts after the socket connects.
  • Index badges. Specs.list_specs/1 decorates each row with last_activity_at = max(spec.updated_at, latest_comment.inserted_at) and has_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?/1 answers “any of my viewed specs have changed?” in a single EXISTS query and is assigned in DashboardLive.Hooks so the Specs sidebar item shows a purple dot from every dashboard page.
  • Show-page surfacing. Mount captures the prior last_viewed_at before the upsert advances it, then the header gets a “New activity” badge and any comment newer than that timestamp gets an inline “New” tag.

Specs list with “New activity” badges and the sidebar dot

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 BY for the latest comment per spec, one for the user’s views). The sidebar query is one Repo.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_at via the revision flow) plus any new comment. Status changes go through update_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/2 is called once before mark_viewed/2 so 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 fresh mix ecto.reset && mix run priv/repo/seeds.exs.
  • One fresh view so a quiet row stays quiet for comparison.

Testing

  • mix test — 557 passing
  • mix format --check-formatted
  • mix credo
  • Manual: signed in as test@hive.dev against the dev instance, confirmed badges on rows 1 and 3, sidebar dot present from /forage and /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.
Comments

No GitHub comments yet.