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",