From 09abf1798fb389a5bb321f783aacc6738010fac7 Mon Sep 17 00:00:00 2001 From: Dhamari Trice-Hanson <39872667+dhamariT@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:38:59 -0400 Subject: [PATCH 1/4] feat(lookout): nudge never-tracked users to Lookout on the /rng page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/controllers/daily_rolls_controller.rb | 16 +++++++++++ app/views/daily_rolls/_lookout_nudge.html.erb | 28 +++++++++++++++++++ app/views/daily_rolls/leaderboard.html.erb | 4 +++ 3 files changed, 48 insertions(+) create mode 100644 app/views/daily_rolls/_lookout_nudge.html.erb diff --git a/app/controllers/daily_rolls_controller.rb b/app/controllers/daily_rolls_controller.rb index fe5f8c758..683d8cc2a 100644 --- a/app/controllers/daily_rolls_controller.rb +++ b/app/controllers/daily_rolls_controller.rb @@ -49,6 +49,7 @@ def leaderboard @viewer_rank = @viewer_date_roll.rank @viewer_page = ((@viewer_rank - 1) / PAGE_SIZE) + 1 end + @lookout_nudge_origin = lookout_nudge_origin else @anonymous_roll = AnonymousRoll.new(cookies).today end @@ -101,6 +102,21 @@ def roll_for_anonymous locals: { roll: roll, just_rolled: just_rolled, anonymous: true }) ] end + # Re-engagement: the daily roll pulls even people who never track time back + # here, so signed-in viewers who haven't linked Hackatime get a nudge toward + # Lookout. Returns where /auth/hackatime should send them afterward (their most + # recent project, so the on-project record flow is one click away), or nil when + # the nudge shouldn't show. Kept cheap on purpose — keyed off the identity + # association, never a live Hackatime sync, which would add an API call to this + # hot path. Broadening to "linked but 0 hours" needs a cached hours signal first. + def lookout_nudge_origin + return unless Flipper.enabled?(:lookout, current_user) + 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 + end + # rng ships with the week 2 release; until then it 404s for everyone. def require_week_2_release head :not_found unless Flipper.enabled?(:week_2_release, current_user) diff --git a/app/views/daily_rolls/_lookout_nudge.html.erb b/app/views/daily_rolls/_lookout_nudge.html.erb new file mode 100644 index 000000000..d348a8ab2 --- /dev/null +++ b/app/views/daily_rolls/_lookout_nudge.html.erb @@ -0,0 +1,28 @@ +<%# Re-engagement nudge on /rng. The daily roll is a retention hook that even + people who never track time come back to, so it's a good place to catch the + "signed in but never logged any hours" crowd and steer them to Lookout. + Reuses the .idv-setup-card styling from the project page — no new CSS. + Rendered behind :lookout, only when the viewer hasn't linked Hackatime yet. + Locals: origin (required — where to return after the Hackatime link). %> +
+

Make your time count

+

Your rolls are logged — is your build time?

+ +
+

You show up here every day, but you haven't logged any project time yet. Record in your browser with Lookout — no install, just one quick Hackatime link the first time so your hours count.

+
+ + <%= button_to "/auth/hackatime", + method: :post, + params: { origin: origin }, + class: "action-btn action-btn--large action-btn--primary idv-setup-card__cta", + form: { class: "idv-setup-card__cta-form" } do %> + Link Hackatime & start with Lookout<%= inline_svg_tag "icons/right-arrow.svg", class: "action-btn__icon action-btn__icon--trailing", aria: { hidden: true } %> + <% end %> + + +
diff --git a/app/views/daily_rolls/leaderboard.html.erb b/app/views/daily_rolls/leaderboard.html.erb index e146fa0c5..9286f7adb 100644 --- a/app/views/daily_rolls/leaderboard.html.erb +++ b/app/views/daily_rolls/leaderboard.html.erb @@ -22,6 +22,10 @@ <%= render "daily_rolls/hero", roll: @anonymous_roll, just_rolled: false, anonymous: true %> <% end %> + <% if @lookout_nudge_origin %> + <%= render "daily_rolls/lookout_nudge", origin: @lookout_nudge_origin %> + <% end %> + <% show_podium = @page == 1 podium = show_podium ? @rolls.first(3) : [] From 8df065b24c5edd5d22a8d265865ef0bf0742ed62 Mon Sep 17 00:00:00 2001 From: Dhamari Trice-Hanson <39872667+dhamariT@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:06:01 -0400 Subject: [PATCH 2/4] refactor(lookout): use the guest-banner style for the /rng nudge 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. --- app/controllers/daily_rolls_controller.rb | 17 +++++------ .../daily_rolls/_lookout_banner.html.erb | 19 +++++++++++++ app/views/daily_rolls/_lookout_nudge.html.erb | 28 ------------------- app/views/daily_rolls/leaderboard.html.erb | 4 +-- 4 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 app/views/daily_rolls/_lookout_banner.html.erb delete mode 100644 app/views/daily_rolls/_lookout_nudge.html.erb diff --git a/app/controllers/daily_rolls_controller.rb b/app/controllers/daily_rolls_controller.rb index 683d8cc2a..ef1982412 100644 --- a/app/controllers/daily_rolls_controller.rb +++ b/app/controllers/daily_rolls_controller.rb @@ -49,7 +49,7 @@ def leaderboard @viewer_rank = @viewer_date_roll.rank @viewer_page = ((@viewer_rank - 1) / PAGE_SIZE) + 1 end - @lookout_nudge_origin = lookout_nudge_origin + @lookout_banner_origin = lookout_banner_origin else @anonymous_roll = AnonymousRoll.new(cookies).today end @@ -103,13 +103,14 @@ def roll_for_anonymous end # Re-engagement: the daily roll pulls even people who never track time back - # here, so signed-in viewers who haven't linked Hackatime get a nudge toward - # Lookout. Returns where /auth/hackatime should send them afterward (their most - # recent project, so the on-project record flow is one click away), or nil when - # the nudge shouldn't show. Kept cheap on purpose — keyed off the identity - # association, never a live Hackatime sync, which would add an API call to this - # hot path. Broadening to "linked but 0 hours" needs a cached hours signal first. - def lookout_nudge_origin + # here, so signed-in viewers who haven't linked Hackatime get a banner nudging + # them toward Lookout. Returns where /auth/hackatime should send them afterward + # (their most recent project, so the on-project record flow is one click away), + # or nil when the banner shouldn't show. Kept cheap on purpose — keyed off the + # identity association, never a live Hackatime sync, which would add an API call + # to this hot path. Broadening to "linked but 0 hours" needs a cached hours + # signal first. + def lookout_banner_origin return unless Flipper.enabled?(:lookout, current_user) return if current_user.hackatime_identity.present? diff --git a/app/views/daily_rolls/_lookout_banner.html.erb b/app/views/daily_rolls/_lookout_banner.html.erb new file mode 100644 index 000000000..28f39ca25 --- /dev/null +++ b/app/views/daily_rolls/_lookout_banner.html.erb @@ -0,0 +1,19 @@ +<%# Slim re-engagement banner on /rng. Reuses the .guest-banner styling — the + yellow bar shown when you don't have a Hack Club account (see + components/_onboarding.scss) — so it matches the existing banner exactly. + The daily roll pulls back even people who never track time, so it's a good + place to catch the "signed in but never logged any hours" crowd and steer + them to Lookout. Rendered behind :lookout, only when the viewer hasn't + linked Hackatime yet. + Locals: origin (required — where /auth/hackatime returns them). %> +
+

+ your rolls are logged — is your build time? record in your browser with Lookout, no install needed. +

+ <%= button_to "Link Hackatime & record", "/auth/hackatime", + method: :post, + params: { origin: origin }, + class: "guest-banner__cta", + form: { class: "guest-banner__form" }, + data: { turbo: false } %> +
diff --git a/app/views/daily_rolls/_lookout_nudge.html.erb b/app/views/daily_rolls/_lookout_nudge.html.erb deleted file mode 100644 index d348a8ab2..000000000 --- a/app/views/daily_rolls/_lookout_nudge.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -<%# Re-engagement nudge on /rng. The daily roll is a retention hook that even - people who never track time come back to, so it's a good place to catch the - "signed in but never logged any hours" crowd and steer them to Lookout. - Reuses the .idv-setup-card styling from the project page — no new CSS. - Rendered behind :lookout, only when the viewer hasn't linked Hackatime yet. - Locals: origin (required — where to return after the Hackatime link). %> -
-

Make your time count

-

Your rolls are logged — is your build time?

- -
-

You show up here every day, but you haven't logged any project time yet. Record in your browser with Lookout — no install, just one quick Hackatime link the first time so your hours count.

-
- - <%= button_to "/auth/hackatime", - method: :post, - params: { origin: origin }, - class: "action-btn action-btn--large action-btn--primary idv-setup-card__cta", - form: { class: "idv-setup-card__cta-form" } do %> - Link Hackatime & start with Lookout<%= inline_svg_tag "icons/right-arrow.svg", class: "action-btn__icon action-btn__icon--trailing", aria: { hidden: true } %> - <% end %> - - -
diff --git a/app/views/daily_rolls/leaderboard.html.erb b/app/views/daily_rolls/leaderboard.html.erb index 9286f7adb..f8ff8ef6e 100644 --- a/app/views/daily_rolls/leaderboard.html.erb +++ b/app/views/daily_rolls/leaderboard.html.erb @@ -22,8 +22,8 @@ <%= render "daily_rolls/hero", roll: @anonymous_roll, just_rolled: false, anonymous: true %> <% end %> - <% if @lookout_nudge_origin %> - <%= render "daily_rolls/lookout_nudge", origin: @lookout_nudge_origin %> + <% if @lookout_banner_origin %> + <%= render "daily_rolls/lookout_banner", origin: @lookout_banner_origin %> <% end %> <% From 6096a28ef4fbb2984ebe01045c4a74cc4e47db95 Mon Sep 17 00:00:00 2001 From: Dhamari Trice-Hanson <39872667+dhamariT@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:08:24 -0400 Subject: [PATCH 3/4] feat(lookout): render the /rng banner as a top sticky bar 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. --- app/views/daily_rolls/leaderboard.html.erb | 4 ---- app/views/layouts/application.html.erb | 1 + .../{daily_rolls => shared}/_lookout_banner.html.erb | 11 +++++------ 3 files changed, 6 insertions(+), 10 deletions(-) rename app/views/{daily_rolls => shared}/_lookout_banner.html.erb (60%) diff --git a/app/views/daily_rolls/leaderboard.html.erb b/app/views/daily_rolls/leaderboard.html.erb index f8ff8ef6e..e146fa0c5 100644 --- a/app/views/daily_rolls/leaderboard.html.erb +++ b/app/views/daily_rolls/leaderboard.html.erb @@ -22,10 +22,6 @@ <%= render "daily_rolls/hero", roll: @anonymous_roll, just_rolled: false, anonymous: true %> <% end %> - <% if @lookout_banner_origin %> - <%= render "daily_rolls/lookout_banner", origin: @lookout_banner_origin %> - <% end %> - <% show_podium = @page == 1 podium = show_podium ? @rolls.first(3) : [] diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index a930eb696..49594183c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -106,6 +106,7 @@ <% end %>
<%= render "shared/flash" %>
<%= render Onboarding::GuestBannerComponent.new unless @hide_sidebar %> + <%= render "shared/lookout_banner", origin: @lookout_banner_origin if @lookout_banner_origin %> <% if @_admin_layout %>
<%= yield %>
<% else %> diff --git a/app/views/daily_rolls/_lookout_banner.html.erb b/app/views/shared/_lookout_banner.html.erb similarity index 60% rename from app/views/daily_rolls/_lookout_banner.html.erb rename to app/views/shared/_lookout_banner.html.erb index 28f39ca25..9bb9f4028 100644 --- a/app/views/daily_rolls/_lookout_banner.html.erb +++ b/app/views/shared/_lookout_banner.html.erb @@ -1,10 +1,9 @@ -<%# Slim re-engagement banner on /rng. Reuses the .guest-banner styling — the +<%# Top sticky re-engagement banner. Reuses the .guest-banner styling — the yellow bar shown when you don't have a Hack Club account (see - components/_onboarding.scss) — so it matches the existing banner exactly. - The daily roll pulls back even people who never track time, so it's a good - place to catch the "signed in but never logged any hours" crowd and steer - them to Lookout. Rendered behind :lookout, only when the viewer hasn't - linked Hackatime yet. + components/_onboarding.scss) — so it sits at the top of the screen exactly + like that banner. Rendered from the application layout when the controller + set @lookout_banner_origin (currently only on /rng, for signed-in viewers + who are behind :lookout and haven't linked Hackatime yet). Locals: origin (required — where /auth/hackatime returns them). %>

From 00157c7f4ae33b02832496c66a4536408a23a55a Mon Sep 17 00:00:00 2001 From: Dhamari Trice-Hanson <39872667+dhamariT@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:16:09 -0400 Subject: [PATCH 4/4] copy(lookout): frame the /rng banner as the easy way to log hours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/views/shared/_lookout_banner.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_lookout_banner.html.erb b/app/views/shared/_lookout_banner.html.erb index 9bb9f4028..d09064d27 100644 --- a/app/views/shared/_lookout_banner.html.erb +++ b/app/views/shared/_lookout_banner.html.erb @@ -7,9 +7,9 @@ Locals: origin (required — where /auth/hackatime returns them). %>

- your rolls are logged — is your build time? record in your browser with Lookout, no install needed. + the easy way to log your build time — just hit record in your browser with Lookout. no install, no editor extension.

- <%= button_to "Link Hackatime & record", "/auth/hackatime", + <%= button_to "Log hours with Lookout", "/auth/hackatime", method: :post, params: { origin: origin }, class: "guest-banner__cta",