Skip to content

fix: Make "Insight" button visible on mobile#2691

Open
alessiocappa wants to merge 2 commits into
we-promise:mainfrom
alessiocappa:FIX-InsightButtonMobile
Open

fix: Make "Insight" button visible on mobile#2691
alessiocappa wants to merge 2 commits into
we-promise:mainfrom
alessiocappa:FIX-InsightButtonMobile

Conversation

@alessiocappa

@alessiocappa alessiocappa commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

The new “Insight” button is not visible on mobile devices. This PR adds it to the mobile navigation bar as well.

Before After
Screenshot 2026-07-15 alle 20 38 21 Screenshot 2026-07-16 alle 00 08 14

Summary by CodeRabbit

  • New Features
    • Added an “Insights” button to the mobile top navigation when available.
    • Displays a badge for unread insights, capped at 9+.
    • Highlights the current unread count for quick visibility.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The mobile top navigation centers the logo link and conditionally displays an Insights icon link for families, with Turbo prefetch disabled and an unread-count badge capped at 9+.

Changes

Mobile navigation

Layer / File(s) Summary
Centered mobile logo
app/views/layouts/application.html.erb
The mobile home/logo link uses an absolutely positioned wrapper to center it horizontally.
Mobile Insights link
app/views/layouts/application.html.erb
The navigation renders the Insights link when Current.family is present, disables Turbo prefetch, and displays the active unread count with a 9+ cap.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the Insights button to the mobile navigation.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/views/layouts/application.html.erb (1)

62-63: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid counting active insights twice per request.

The mobile block performs Current.family.insights.active.count, while the desktop block repeats the same query at Lines 179-180 even though both navs are rendered in the same response. Compute the count once before the markup and reuse it in both links.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/layouts/application.html.erb` around lines 62 - 63, Compute
Current.family.insights.active.count once before the navigation markup, store it
in a shared local, and reuse that value in both the mobile and desktop
navigation blocks instead of querying again in the desktop block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/views/layouts/application.html.erb`:
- Around line 62-77: Update the application layout header around the Insights
action group so the mobile logo remains centered independently of the right-side
actions. Replace the width-dependent justify-between positioning with a
three-column layout or absolute centering, while preserving the existing
Insights link and responsive desktop alignment.
- Around line 67-74: Update the insights link generated by the link_to block to
expose unread_insights_count to assistive technology. Use a localized accessible
label that includes the displayed count, or associate the link with the
notification badge through aria-describedby, while preserving the existing
“Insights” label and capped “9+” display behavior.

---

Nitpick comments:
In `@app/views/layouts/application.html.erb`:
- Around line 62-63: Compute Current.family.insights.active.count once before
the navigation markup, store it in a shared local, and reuse that value in both
the mobile and desktop navigation blocks instead of querying again in the
desktop block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 041895e1-1b2b-4106-bd31-6dfb7ff443f9

📥 Commits

Reviewing files that changed from the base of the PR and between 94d5ade and 80835e3.

📒 Files selected for processing (1)
  • app/views/layouts/application.html.erb

Comment thread app/views/layouts/application.html.erb
Comment on lines +67 to +74
<%= link_to insights_path,
class: "relative inline-flex items-center justify-center w-9 h-9 pointer-coarse:w-11 pointer-coarse:h-11 rounded-lg text-secondary hover:text-primary hover:bg-container-inset-hover transition-colors focus-ring",
title: t("layouts.application.insights"),
aria: { label: t("layouts.application.insights") },
data: { turbo_prefetch: false } do %>
<%= icon("lightbulb", size: "sm") %>
<% if unread_insights_count.positive? %>
<span class="absolute top-1 right-1 min-w-4 h-4 px-1 rounded-full bg-inverse text-inverse text-[10px] font-medium flex items-center justify-center pointer-events-none"><%= unread_insights_count > 9 ? "9+" : unread_insights_count %></span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expose the unread count to assistive technology.

The static aria-label replaces the link’s descendant text, so screen readers announce “Insights” but not the unread count. Include the count in a localized accessible label or associate it with the link via aria-describedby.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/layouts/application.html.erb` around lines 67 - 74, Update the
insights link generated by the link_to block to expose unread_insights_count to
assistive technology. Use a localized accessible label that includes the
displayed count, or associate the link with the notification badge through
aria-describedby, while preserving the existing “Insights” label and capped “9+”
display behavior.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80835e3737

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

<% end %>

<div class="flex items-center gap-1">
<% if Current.family %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Hide insights shortcut in intro mode

For guest users in the intro layout, this mobile header still renders because the new guard only checks Current.family; the desktop insights button is inside the lower unless intro_mode header, and intro users intentionally get the reduced assistant-first navigation. On any mobile intro/profile page for a guest with a family, this adds a direct /insights shortcut and unread badge into the restricted intro workflow, so gate this block with !intro_mode as well.

Useful? React with 👍 / 👎.

Comment on lines +67 to +71
<%= link_to insights_path,
class: "relative inline-flex items-center justify-center w-9 h-9 pointer-coarse:w-11 pointer-coarse:h-11 rounded-lg text-secondary hover:text-primary hover:bg-container-inset-hover transition-colors focus-ring",
title: t("layouts.application.insights"),
aria: { label: t("layouts.application.insights") },
data: { turbo_prefetch: false } do %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Extract the duplicated insights badge

AGENTS.md Design System Hygiene says repeated hand-rolled shapes should be lifted to a DS::* primitive before the second copy lands. This pastes the same insights icon link and unread badge markup that already exists in the desktop header, so changes to the count query, prefetch guard, accessibility label, or badge styling now have to be kept in sync in two places; extract a shared/DS primitive instead of adding the duplicate.

Useful? React with 👍 / 👎.

@jjmata jjmata left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for fixing the mobile visibility of the Insights button — the fix itself works and follows most of the repo's conventions well: it reuses the existing layouts.application.insights i18n key (no new hardcoded strings), uses the icon helper (not lucide_icon), and has both title and aria-label on the icon-only link for accessibility.

Two things worth addressing before merge, left as inline comments:

  1. Containing-block bug: the logo link now uses absolute left-1/2 -translate-x-1/2, but the parent <nav> isn't relative (and nothing further up the tree is positioned either), so it centers against the viewport rather than the nav bar itself. Minor visually here, but fragile/incorrect CSS.
  2. Duplication: the new mobile insights button + "9+" badge block is a verbatim copy-paste of the existing desktop insights button block elsewhere in this same file, including the code comment. Given the codebase already has a pattern for sharing nav markup (layouts/shared/_nav_item.html.erb), extracting this into a shared partial would avoid two copies of the same badge-capping logic drifting apart over time.

No i18n or ViewComponent-placement concerns otherwise — this is a reasonable place to add the markup, just should be de-duplicated.


Generated by Claude Code

<% end %>

<%= link_to home_path, class: "block" do %>
<%= link_to home_path, class: "absolute left-1/2 -translate-x-1/2" do %>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The logo link switches from class: "block" to class: "absolute left-1/2 -translate-x-1/2", but its parent <nav class="lg:hidden flex justify-between items-center p-3 ..."> (line 51) isn't given relative (or any position other than static). Since there's no positioned ancestor between this <nav> and <html> either (checked _htmldoc.html.erb and the wrapping <div> on line 24 — neither sets position), the containing block for this absolute element resolves to the initial containing block (viewport) rather than the nav bar. In practice left: 50% will center against the full viewport width instead of the nav's padded content box (the nav has p-3, so there's a ~12px discrepancy), and the behavior is fragile — it only "works" because nothing currently establishes a positioning context up the tree; adding position: relative anywhere in that ancestor chain later would silently break this centering.

Suggest adding relative to the <nav> class here so the logo is explicitly centered within the nav bar itself.


Generated by Claude Code

Comment on lines +62 to +77
<% if Current.family %>
<% unread_insights_count = Current.family.insights.active.count %>
<%# Prefetch disabled: /insights marks insights read on real visits
and deliberately skips prefetch requests, so a prefetched
response served on click would never clear the badge. %>
<%= link_to insights_path,
class: "relative inline-flex items-center justify-center w-9 h-9 pointer-coarse:w-11 pointer-coarse:h-11 rounded-lg text-secondary hover:text-primary hover:bg-container-inset-hover transition-colors focus-ring",
title: t("layouts.application.insights"),
aria: { label: t("layouts.application.insights") },
data: { turbo_prefetch: false } do %>
<%= icon("lightbulb", size: "sm") %>
<% if unread_insights_count.positive? %>
<span class="absolute top-1 right-1 min-w-4 h-4 px-1 rounded-full bg-inverse text-inverse text-[10px] font-medium flex items-center justify-center pointer-events-none"><%= unread_insights_count > 9 ? "9+" : unread_insights_count %></span>
<% end %>
<% end %>
<% end %>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This entire block — the Current.family guard, unread_insights_count calculation, the prefetch-disabled comment, the link_to insights_path with its classes/title/aria, the icon("lightbulb", ...), and the 9+-capped badge <span> — is a byte-for-byte duplicate of the existing desktop insights button further down in this same file (the block under <%# SHARED - Main content %>DESKTOP - Left navbar's sticky header, unread_insights_count = Current.family.insights.active.count / link_to insights_path / same badge markup). Even the inline comment is copy-pasted verbatim.

Given this codebase already extracts shared nav markup into partials (e.g. layouts/shared/_nav_item.html.erb, rendered from both the mobile bottom nav and desktop left navbar), it'd be more consistent to pull this into a layouts/shared/_insights_button.html.erb partial and render it from both the mobile top nav and the desktop sticky header, rather than maintaining two copies of the same badge-capping logic (unread_insights_count > 9 ? "9+" : unread_insights_count) that can now drift out of sync.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

DS Drift Patrol — 1 finding

Rule 1 — Bypassing DS components

app/views/layouts/application.html.erb (insights nav-icon unread-count badge)

<span class="absolute top-1 right-1 min-w-4 h-4 px-1 rounded-full bg-inverse text-inverse text-[10px] font-medium flex items-center justify-center pointer-events-none"><%= unread_insights_count > 9 ? "9+" : unread_insights_count %></span>

Hand-rolled rounded-full count badge instead of DS::Pill.

Everything else in this PR is clean — the new insights link uses icon(), t() for its label/aria-label, and only functional-token classes (text-secondary, bg-container-inset-hover, focus-ring).


Scanned by DS Drift Patrol — diff-scope only (+ lines); app/components/DS/**, generated assets, test/, and db/ excluded.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

DS Drift Patrol — 2 findings

0 of 1 previously-flagged issue(s) resolved since the last run.

Rule 1 — Bypassing DS components

  • app/views/layouts/application.html.erb:69title: t("layouts.application.insights") on the insights icon link is a hand-rolled tooltip pattern. Use DS::Tooltip (with as: :span if placed inline) instead.
  • app/views/layouts/application.html.erb:74<span class="absolute top-1 right-1 min-w-4 h-4 px-1 rounded-full bg-inverse text-inverse text-[10px] font-medium ..."> unread-count badge is a hand-rolled pill (still present from the prior run). Use DS::Pill instead.

Scanned by DS Drift Patrol — diff-scope only (+ lines); app/components/DS/**, generated assets, test/, and db/ excluded.


Generated by Claude Code

@jjmata jjmata left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for always keeping an eye on PWA regressions/UX, @alessiocappa!

Can you address the PR review comments?

Copy link
Copy Markdown
Collaborator

DS Drift Patrol — 2 findings (update)

0 of 2 previously flagged issues resolved.

Bypassing DS components (rule 1)

  • app/views/layouts/application.html.erb:69title: t("layouts.application.insights") on the insights icon-only link is a hand-rolled tooltip; use DS::Tooltip (with as: :span if kept inline) instead.
  • app/views/layouts/application.html.erb:74 — unread-count <span class="... rounded-full ..."> badge is a hand-rolled pill; use DS::Pill instead.

Scanned by DS Drift Patrol — diff-scope only (+ lines); app/components/DS/**, generated assets, test/, and db/ excluded.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants