Skip to content

feat(lookout): nudge never-tracked users to Lookout on the /rng page#749

Open
dhamariT wants to merge 4 commits into
feat/lookout-default-flowfrom
feat/lookout-rng-nudge
Open

feat(lookout): nudge never-tracked users to Lookout on the /rng page#749
dhamariT wants to merge 4 commits into
feat/lookout-default-flowfrom
feat/lookout-rng-nudge

Conversation

@dhamariT

@dhamariT dhamariT commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a small "Make your time count" nudge on the daily random number page (/rng) that steers people toward Lookout (record in your browser). It shows only for signed-in users who haven't linked Hackatime yet, and only when the :lookout flag is on — so it's off in production and /rng is unchanged there.

Stacked on top of #728 (base branch feat/lookout-default-flow), since it reuses that PR's :lookout flag and the same "Lookout is the default" premise. Merge #728 first.

Why

The daily roll is a retention hook — people come back for their number every day even when they aren't tracking any coding time. That makes it a great place to catch the crowd this whole effort is about: signed in, engaged, but never logged an hour (the "44% Hackatime Wall"). We catch them at an already-engaged moment (right by their roll) and give them one clear next step: link Hackatime and record with Lookout.

How (reuse-only, tiny diff)

No new CSS, no new component, no migration.

  • New _lookout_nudge partial — reuses the existing .idv-setup-card styling and the /auth/hackatime button pattern from feat(lookout): make Lookout the default time-tracking path (behind :lookout flag) #728's _track_time_card.
  • Rendered under the /rng hero (leaderboard.html.erb) when the nudge origin is set.
  • DailyRollsController#leaderboard sets @lookout_nudge_origin via a private lookout_nudge_origin helper: shows the nudge when :lookout is on and the viewer has no Hackatime identity; the CTA links Hackatime and sends them back to their most recent project (so the on-project record flow is one click away), falling back to /rng if they have no project yet.

The one thing I was careful about — no Hackatime sync on the hot path

The "truest" signal for never logged any hours is User#all_time_coding_seconds, but that calls HackatimeService.fetch_stats, which is a live, uncached HTTP request (cache-buster + no-cache headers). Putting that on /rng — a high-traffic daily page — would add an API round-trip per eligible visit.

So eligibility here is the cheap hackatime_identity association check, which is also the exact funnel leak we care about (never linked Hackatime → definitely never logged hours). Broadening the audience to linked-but-0-hours should wait for a cached hours signal (a synced column / background job) rather than a per-request sync.

Flag-off safety

With :lookout disabled, @lookout_nudge_origin is always nil, the partial never renders, and /rng is byte-for-byte unchanged. Flag is off by default.

Test

bin/rails runner 'Flipper.enable_actor(:lookout, User.find_by(email: "you@example.com"))'

Sign in as a user with no Hackatime linked and open /rng. The nudge appears under your roll; the CTA links Hackatime and returns you to your most recent project. Link Hackatime (or Flipper.disable(:lookout)) and it disappears.

⚠️ erb_lint / rubocop / tests were not run — the local Docker daemon was down. Ruby syntax + ERB compile check pass. Please run bin/lint + bin/rails test in CI before un-drafting.

Follow-ups (not in this PR)

  • Cached "has logged hours" signal so the nudge can also catch linked-but-0-hours users without a live sync.
  • Dismiss / frequency cap so daily rollers don't see it every single day (a seen record or cookie).
  • Optionally gate to the "today" view only, and/or move it into the post-roll turbo-stream reveal for the sharper "you just rolled → now log real time" moment.

Note

Low Risk
Changes are gated by :lookout, use a cheap identity check on an already-flagged page, and only add UI plus a post-auth redirect origin—no auth or data-model changes.

Overview
Signed-in viewers on /rng can see a top sticky Lookout re-engagement banner when the :lookout Flipper flag is on and they have no linked Hackatime identity.

DailyRollsController#leaderboard sets @lookout_banner_origin through a new lookout_banner_origin helper that returns nil when the flag is off or Hackatime is already linked, otherwise the most recent project path (or rng_path if they have no projects) as the origin for POST /auth/hackatime. Eligibility uses only the hackatime_identity association—no live Hackatime API on this page.

The application layout renders a new shared/_lookout_banner partial when that origin is set. The partial reuses .guest-banner styling and a “Log hours with Lookout” CTA with turbo: false, matching the existing guest banner pattern.

Reviewed by Cursor Bugbot for commit 00157c7. Bugbot is set up for automated code reviews on this repo. Configure here.

The daily random number is a retention hook people come back to even when
they aren't tracking any time — so it's a natural place to catch the folks
who signed in but never logged hours and steer them to Lookout.

- New _lookout_nudge partial, reusing the .idv-setup-card styling (no new CSS)
- Rendered under the /rng hero when :lookout is on and the viewer hasn't
  linked Hackatime yet
- Eligibility is a cheap identity-association check, never a live Hackatime
  sync (that would add an API call to a hot path); the CTA links Hackatime
  and returns them to their most recent project so recording is one click away

Behind the same :lookout flag (off by default), so /rng is unchanged in prod.
@dhamariT
dhamariT marked this pull request as ready for review July 1, 2026 15:39

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 09abf17. Configure here.

return if current_user.hackatime_identity.present?

recent_project = current_user.projects.order(created_at: :desc).first
recent_project ? project_path(recent_project) : rng_path

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong project recency ordering

Medium Severity

lookout_nudge_origin picks the Hackatime return destination with projects.order(created_at: :desc), but elsewhere the app treats the user’s active project as projects.order(updated_at: :desc) (home composer and project show). After linking, users with multiple projects can land on a stale or empty project instead of the one they actually use.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 09abf17. Configure here.

dhamariT added 3 commits July 1, 2026 12:06
Swaps the .idv-setup-card treatment for the existing .guest-banner styling
(the yellow bar shown when you don't have a Hack Club account), so the nudge
matches a banner already in the app instead of introducing a new look. Renames
the partial and its origin ivar from nudge -> banner. No new CSS.
Move the banner out of the leaderboard body and into the application layout
(same spot as the guest banner), so it pins to the top of the screen like a
real sticky banner instead of sitting inline. Still scoped to /rng via the
@lookout_banner_origin ivar the controller only sets there. Partial moves to
shared/ since the layout now owns the render.
Lead with the benefit (record in your browser, no install, no editor
extension) instead of 'Link Hackatime & record'. CTA becomes 'Log hours with
Lookout'. Still routes through /auth/hackatime — the account link happens on
click, just no longer front-and-center — so nothing overpromises.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant