Mobile Responsiveness Enhancement Plan — Lifehub

Master, this plan outlines the path to make Lifehub fully responsive and mobile-first. The Force of DRY patterns guides every decision here.


1. Current State Audit

What Already Works Well

  • main uses p-4 lg:p-6 — appropriate gutter switch between mobile and desktop
  • Most KPI grids already use grid-cols-2 lg:grid-cols-4 — stacks gracefully
  • The layout already separates _footer_nav.html.erb (mobile) from the desktop navbar in _navbar.html.erb
  • The mobile sidebar drawer (_sidebar.html.erb) exists with a hamburger trigger
  • Background, card, and border colours are already CSS variables — easy consistent theming
  • Modal has mx-4 on mobile and max-h-[90vh] overflow-hidden — a solid start

What Needs Fixing

Area Issue
Bottom nav Icons only, no labels, tiny size-6 touch targets, no "More" overflow menu, no bottom safe-area padding
Main content No bottom padding on mobile — content hidden under bottom nav
Modal Stays centre-anchored on mobile — should slide up as a bottom sheet on phones
Action headers Multiple buttons in page headers overflow on narrow viewports
Data tables Tables (accounts, balance_registries, investments) have no overflow-x-auto wrapper
Charts No explicit min-height on mobile — can collapse
Forms in modals Inputs, selects and date fields too small for comfortable touch
Tabs & filter pills Overflow is clipped rather than horizontally scrollable
Dashboard patrimony card min-w-[280px] forces horizontal scroll on small phones
Analytics & planning Dense charts and tables untouched for mobile
FAB pattern No floating action button — primary "new" actions hard to reach

2. Design Principles

  1. Mobile-first Tailwind classes — write sm: / md: / lg: as enhancement, not the baseline.
  2. DRY via shared partials — every repeated pattern (page header, KPI card, table wrapper, bottom sheet) becomes a reusable partial.
  3. Bottom-sheet modals on mobile — modals on < sm viewports slide up from the bottom (iOS/Android native feel).
  4. 44 px minimum touch targets — all interactive elements meet WCAG 2.5.5.
  5. Safe-area insets — use pb-safe (env(safe-area-inset-bottom)) on sticky bars so notch devices aren't clipped.
  6. Consistent CSS variable tokens — all colours stay on var(--bg-*), var(--text-*), var(--sidebar-*).

3. Shared Component Upgrades (DRY Foundation)

Goal: Promote to a proper bottom navigation bar with labelled icons, active state, "More" overflow drawer, and safe-area padding.

New structure:

┌─────────────────────────────────────┐
│  🏠       💸       ✅       📈     ⋯  │
│ Home    Expenses  Habits  Invest  More│
└─────────────────────────────────────┘
           safe-area-inset-bottom

Changes:

  • Items: Dashboard · Expenses · Habits · Investments · More (drawer)
  • "More" opens a mini drawer (Turbo Frame or Stimulus) showing: Goals, Analytics, Accounts, Simulator, Planning, Sports, Notes, Market Lists, Birthdays, Countdowns, Construction, Converter, Gamification
  • Each item = flex column (icon + label), min-w-[44px] min-h-[44px]
  • Active item: coloured icon + white label, inactive: text-gray-500
  • pb-[env(safe-area-inset-bottom)] + pb-3 fallback on the nav itself
  • Pin to fixed bottom-0 left-0 right-0 (not sticky — safer on mobile browsers)
  • New Stimulus controller: bottom-nav-controller.js — handles "More" toggle

DRY helper to reuse: nav_link helper already exists — extend it to support label: argument and render the vertical layout.

Relevant file: app/views/shared/_footer_nav.html.erb


3.2 _turbo_modal.html.erb — Bottom Sheet on Mobile

Goal: On mobile (< sm), the modal slides up from the bottom like a native sheet; on sm+ it remains centre-anchored.

Changes:

  • Add sm:items-center items-end to the backdrop flex container — modal anchors to bottom on mobile, centre on desktop.
  • Modal wrapper: remove mx-4 fixed margin; use w-full sm:w-auto sm:mx-6 md:mx-8
  • Add rounded-b-none rounded-t-2xl sm:rounded-2xl to the card
  • Add drag-handle indicator (w-10 h-1 bg-white/20 rounded-full mx-auto mb-2) visible on mobile only
  • Reduce max-h on mobile: max-h-[85vh] sm:max-h-[90vh]
  • Animation: translate-y-full → translate-y-0 on mobile (slide up), scale-95 opacity-0 → scale-100 opacity-100 on desktop
  • Add pb-[env(safe-area-inset-bottom)] to modal body on mobile

Relevant file: app/views/shared/_turbo_modal.html.erb


3.3 _page_header.html.erb — Responsive Header Pattern

Goal: Standardise how every feature page renders its title + action buttons, making them stack vertically on mobile with full-width CTA buttons.

Changes:

  • Two-line layout on mobile: title row, then actions row (flex-col sm:flex-row)
  • Action buttons go w-full sm:w-auto on mobile
  • Title font: text-xl sm:text-2xl
  • Expose mobile_actions: local to allow a compact "+" FAB on mobile when multiple actions exist

Relevant file: app/views/shared/_page_header.html.erb


3.4 New Shared Partial — _table_scroll_wrapper.html.erb

Goal: Wrap every data table in a scrolable container to prevent horizontal overflow breaking layouts.

<%# app/views/shared/_table_scroll_wrapper.html.erb %>
<div class="overflow-x-auto -mx-4 sm:mx-0 rounded-xl">
  <div class="min-w-[540px] sm:min-w-0">
    <%= yield %>
  </div>
</div>

Apply to: accounts table, balance registries table, investments table, analytics tables.


3.5 New Shared Partial — _fab.html.erb (Floating Action Button)

Goal: On mobile, replace multiple header buttons with a single FAB that reveals a speed-dial if multiple actions exist.

<%# app/views/shared/_fab.html.erb %>
<%# Usage: render "shared/fab", actions: [{label:, path:, color:, icon:}] %>
<div class="fixed bottom-20 right-4 z-30 lg:hidden" data-controller="fab">
  ...speed-dial buttons...
</div>

Apply to: accounts (2 actions), dashboard (2 quick actions), balance registries.


3.6 Tailwind main Padding — Bottom Clearance

Goal: Ensure content isn't hidden under the bottom nav.

Change in layouts/application.html.erb:

<main class="flex-grow p-4 pb-24 lg:pb-6 lg:p-6 ...">

pb-24 gives 96px clearance which safely clears the bottom nav + FAB on mobile.


3.7 Filter Pills / Tab Rows — Horizontal Scroll

Goal: All flex gap-2 flex-wrap pill rows become horizontally scrollable on mobile (no wrap = no reflow jank).

Change pattern (applied consistently across expenses, investments, balance registries, habits, analytics):

<%# Before: %>
<div class="flex flex-wrap gap-2 mb-5">

<%# After: %>
<div class="flex gap-2 mb-5 overflow-x-auto pb-1 scrollbar-none">
  <%# Each pill gets flex-shrink-0 %>

Add CSS utility once in app/assets/stylesheets/application.css:

.scrollbar-none { scrollbar-width: none; }
.scrollbar-none::-webkit-scrollbar { display: none; }

4. Feature-by-Feature View Changes

4.1 Dashboard (organizations/dashboard/index.html.erb)

Issue Fix
min-w-[280px] patrimony card forces scroll on 320px phones Remove min-w, use w-full lg:w-auto
KPI table (table element) not scrollable Wrap in _table_scroll_wrapper
Quick actions buttons in header overflow Move to FAB (_fab.html.erb) on mobile
lg:grid-cols-[auto_1fr] — already good ✅ No change needed
Bottom charts row uses grid-cols-1 lg:grid-cols-2 ✅ Already stacks

4.2 Accounts (organizations/accounts/index.html.erb)

Issue Fix
Header has 2 action buttons that may wrap Consolidate into _page_header + FAB on mobile
Accounts data table has many columns Wrap with _table_scroll_wrapper
Balance registries table Same wrapper
Drag-to-sort on account cards Add note: "drag-sort disabled on touch — reorder via edit on mobile" (future: touch-drag handle)

4.3 Expenses (organizations/expenses/index.html.erb)

Issue Fix
Month filter pills wrap and reflow Convert to horizontal scroll row
Expense list rows have too much info causing overflow Truncate description on mobile (truncate max-w-[120px] sm:max-w-none)
Summary cards (grid-cols-3) too narrow on 320px Change to grid-cols-1 sm:grid-cols-3
"New Expense" button FAB on mobile

4.4 Investments (organizations/investments/index.html.erb)

Issue Fix
KPI cards grid-cols-2 lg:grid-cols-4 ✅ Already responsive
Tab filter pills (CDB, LCI, etc.) overflow Convert to horizontal scroll row
Investment table (Lista tab) Wrap with _table_scroll_wrapper
Timeline chart Ensure min-h-[200px] on mobile; chart.js maintainAspectRatio: false already needs to be set
"New Investment" button FAB on mobile

4.5 Goals (organizations/goals/index.html.erb)

Issue Fix
grid-cols-1 md:grid-cols-2 goal cards ✅ Good — stacks on mobile
KPI cards in detail section grid-cols-2 lg:grid-cols-4 ✅ Good
Chart section may be too wide Chart wrapper: w-full overflow-hidden

4.6 Analytics (organizations/analytics/index.html.erb)

Issue Fix
4 KPI cards grid-cols-2 lg:grid-cols-4 ✅ Good
Charts grid grid-cols-1 lg:grid-cols-2 ✅ Good — verify chart height minimum
Analytics tables (heatmap etc.) Wrap with _table_scroll_wrapper
Dense data sections need visual breathing room Add mb-6 separator sections on mobile

4.7 Habits (organizations/habits/index.html.erb)

Issue Fix
grid-cols-1 md:grid-cols-3 stat cards ✅ Good
Habit cards list — each habit needs minimum 48px height touch target Ensure py-3 minimum on each habit row
"Check In" toggle buttons Minimum size-10 touch target
Habit analytics page charts Ensure responsive chart heights

4.8 Sports & Workout Sessions

Issue Fix
Sports index — list layout Add overflow-x-auto to any stats table
Workout sessions list Stack sessions vertically — remove any fixed widths
Show page metric cards Verify grid-cols-2 min on mobile

4.9 Notes (organizations/notes/)

Issue Fix
Notes grid Ensure grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
Note show page prose Max width max-w-prose already responsive
Rich text editor (if any) Ensure min-h-[200px] on mobile

4.10 Market Lists (organizations/market_lists/)

Issue Fix
Shopping list items Minimum 44px row height for checkbox tap
Item count badges Readable at text-xs — fine

4.11 Birthdays / Countdowns

Issue Fix
Cards grid Use grid-cols-1 sm:grid-cols-2
Date countdown numbers text-2xl readable on mobile

4.12 Construction Projects

Issue Fix
Project phases table Wrap with _table_scroll_wrapper
Expenses list Stack items on mobile
Progress bar section Full-width bar is already w-full

4.13 Currency Converter

Issue Fix
Converter form layout flex-col on mobile, flex-row on sm+
Input size w-full on mobile

4.14 Gamification Pages

Issue Fix
Achievements grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4
Leaderboard table Wrap with _table_scroll_wrapper
XP progress bars Already percentage-based — fine

4.15 Simulator & Planning

Issue Fix
Simulator sliders Ensure w-full input range elements
Result chart Responsive height: h-[250px] sm:h-[400px]
Planning table Wrap with _table_scroll_wrapper
Projection inputs stacked inside card flex-col on mobile

4.16 Finance Settings (organizations/finance_settings/show.html.erb)

Issue Fix
Settings form sections Already likely max-w-2xl — good
Input groups flex-col sm:flex-row for label+input pairs
Family management section Stack member list vertically

4.17 Memberships / Invitations

Issue Fix
Members list Already uses card-based layout — verify 44px row heights
Invite form w-full email input

4.18 Auth Pages (Devise)

Issue Fix
Login / Register Centred card already responsive — verify px-4 on mobile
Magic link page Same treatment
Invitation accept Form inside a card — mx-4 or px-4 safety margin needed

4.19 Landing Page / Static Pages

Issue Fix
Landing hero section Verify text-4xl sm:text-6xl headings
Pricing cards grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
Feature sections Alternating layout: flex-col sm:flex-row
Navbar (static layout) Hamburger menu for mobile — verify _navbar.html.erb in static/

5. Bottom Navigation — Detailed Spec

Items (5 tabs + More)

Tab Icon (SVG) Route
Home House / dashboard icon dashboard_path
Expenses Receipt / money icon expenses_path
Habits Checkmark circle habits_path
Invest Chart / trending up investments_path
More Grid 2x2 / apps icon Opens "More" drawer

"More" Drawer — Items

Categorised in two rows of 4:

Row 1: Goals · Analytics · Accounts · Simulator

Row 2: Sports · Notes · Market Lists · Gamification

Footer links: Planning · Birthdays · Countdowns · Construction · Converter · Settings

Visual Design

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐             │
│  │  🏠  │  │  💸  │  │  ✅  │  │  📈  │  │  ⋯⋯  │             │
│  │ Home │  │Spend │  │Habits│  │Invest│  │ More │             │
│  └──────┘  └──────┘  └──────┘  └──────┘  └──────┘             │
│                  safe-area-inset-bottom                           │
└─────────────────────────────────────────────────────────────────┘
  • Active tab: icon uses text-blue-400, label text-[11px] font-semibold text-white
  • Inactive: text-gray-500 dark:text-gray-600 for both
  • Active indicator: 2px top border border-t-2 border-blue-400 on the tab
  • Background: var(--bg-sidebar) with border-t: 1px solid var(--sidebar-border)
  • Height: ~52px + env(safe-area-inset-bottom) padding

Stimulus Controller: bottom-nav-controller.js

// app/javascript/controllers/bottom_nav_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["drawer", "backdrop"]

  openMore() {
    this.drawerTarget.classList.remove("translate-y-full")
    this.backdropTarget.classList.remove("hidden")
  }

  closeMore() {
    this.drawerTarget.classList.add("translate-y-full")
    this.backdropTarget.classList.add("hidden")
  }
}

"More" drawer — slides up from below the bottom nav:

┌─────────────────────────────────────┐
│ ╌╌ (drag handle) ╌╌                 │  ← rounded-t-2xl
│                                     │
│  🎯 Goals      📊 Analytics         │
│  🏦 Accounts   📐 Simulator         │
│                                     │
│  ⚽ Sports     📝 Notes             │
│  🛒 Market     ⚡ Gamification      │
│                                     │
│  ─ Planning · Birthdays · Converter │
└─────────────────────────────────────┘

6. Implementation Order (Sprints)

Sprint 1 — Foundation (no visual regressions)

  1. Add pb-24 lg:pb-6 to main in layouts/application.html.erb
  2. Create _table_scroll_wrapper.html.erb partial
  3. Apply table scroll wrapper to: accounts, balance registries, investments, analytics
  4. Add .scrollbar-none CSS utility to application.css
  5. Convert all filter pill rows to overflow-x-auto scrollbar-none (expenses, investments, analytics, habits)

Sprint 2 — Modal Bottom Sheet

  1. Update _turbo_modal.html.erb — bottom sheet on mobile, centred on desktop
  2. Update modal animation classes
  3. Add safe-area padding to modal body
  4. Test all forms that open in modals: expenses, investments, goals, habits, accounts, balance registries

Sprint 3 — Bottom Navigation Bar

  1. Rewrite _footer_nav.html.erb with 5-tab layout + "More" drawer
  2. Create bottom_nav_controller.js Stimulus controller
  3. Register controller in app/javascript/controllers/index.js
  4. Update nav_link helper (or create bottom_nav_item helper) to render icon + label + active state

Sprint 4 — Page Header & FAB

  1. Update _page_header.html.erb to support mobile_actions: + stack layout
  2. Create _fab.html.erb shared partial
  3. Create fab_controller.js Stimulus controller (speed-dial toggle)
  4. Apply FAB to: accounts, dashboard, expenses, investments, goals

Sprint 5 — Feature Page Fixes

  1. Dashboard: remove min-w-[280px], wrap KPI table
  2. Expenses: fix summary cards grid, fix overflow
  3. Investments: wrap table, fix filter pills
  4. Analytics: wrap tables, verify chart heights
  5. Goals: verify chart container overflow
  6. Habits: verify touch targets
  7. Sports / Notes / Market Lists / Birthdays / Countdowns: grid fixes

Sprint 6 — Auth & Static Pages

  1. Devise pages: verify padding and form widths
  2. Landing page: hero heading sizes, pricing grid, feature alternating layout
  3. Static navbar hamburger

Sprint 7 — Polish & QA

  1. Safe-area insets audit on iPhone SE (375px) and iPhone 14 (390px)
  2. Touch target audit — all interactive elements ≥ 44px
  3. Horizontal scroll regression test — no page should scroll horizontally
  4. Dark mode regression test on mobile
  5. Turbo navigation regression — bottom nav active state updates on navigate

7. CSS Tokens & Shared Utilities to Add

In app/assets/stylesheets/application.css (or app/assets/tailwind/application.css):

/* Safe-area bottom padding */
.pb-safe {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Hide scrollbar but keep functionality */
.scrollbar-none {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.scrollbar-none::-webkit-scrollbar {
  display: none;
}

/* Bottom sheet slide-up animation */
.slide-up-enter {
  transform: translateY(100%);
}
.slide-up-enter-active {
  transform: translateY(0);
  transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1);
}

8. DRY Pattern Summary

Pattern Implementation Reused In
Page header with responsive actions shared/_page_header.html.erb All feature index pages
Table with horizontal scroll shared/_table_scroll_wrapper.html.erb Accounts, Investments, Analytics, Balance Registries, Construction, Leaderboard, Planning
Bottom sheet (modal) shared/_turbo_modal.html.erb All turbo frame modal forms
FAB (floating action button) shared/_fab.html.erb + fab_controller.js Accounts, Dashboard, Expenses, Investments, Goals
Bottom nav item bottom_nav_item helper method _footer_nav.html.erb
Filter pill row shared/_filter_pills.html.erb (new partial) Expenses, Investments, Habits, Analytics
Scrollable filter row .scrollbar-none overflow-x-auto utility Same as above

9. Files Changed — Complete Reference

File Type Change
app/views/layouts/application.html.erb Edit pb-24 lg:pb-6 on main
app/views/shared/_footer_nav.html.erb Rewrite Full bottom nav with More drawer
app/views/shared/_turbo_modal.html.erb Edit Bottom sheet on mobile
app/views/shared/_page_header.html.erb Edit Stack layout + mobile_actions
app/views/shared/_table_scroll_wrapper.html.erb New Reusable table scroll wrapper
app/views/shared/_fab.html.erb New Floating action button
app/views/shared/_filter_pills.html.erb New Horizontal scrollable filter pills
app/javascript/controllers/bottom_nav_controller.js New "More" drawer toggle
app/javascript/controllers/fab_controller.js New Speed-dial FAB toggle
app/javascript/controllers/index.js Edit Register new controllers
app/assets/stylesheets/application.css Edit pb-safe, scrollbar-none, slide-up-*
app/helpers/application_helper.rb Edit Add bottom_nav_item helper
app/views/organizations/dashboard/index.html.erb Edit Remove min-w, wrap KPI table, use FAB
app/views/organizations/accounts/index.html.erb Edit Table wrapper, FAB, header stack
app/views/organizations/expenses/index.html.erb Edit Filter pills, summary grid, FAB
app/views/organizations/investments/index.html.erb Edit Filter pills, table wrapper, FAB
app/views/organizations/goals/index.html.erb Edit Chart container overflow
app/views/organizations/analytics/index.html.erb Edit Table wrappers, chart heights
app/views/organizations/habits/index.html.erb Edit Touch target sizes
app/views/organizations/sports/index.html.erb Edit Table wrapper
app/views/organizations/notes/index.html.erb Edit Grid layout
app/views/organizations/gamification/leaderboard.html.erb Edit Table wrapper
app/views/organizations/planning/index.html.erb Edit Table wrapper, simulator layout
app/views/organizations/simulator/index.html.erb Edit Full-width inputs, chart height
app/views/organizations/construction_projects/show.html.erb Edit Table wrapper
app/views/organizations/balance_registries/index.html.erb Edit Table wrapper
app/views/landing_page/index.html.erb Edit Hero size, pricing grid, feature flex
app/views/static/pricing.html.erb Edit Cards grid responsive
app/views/devise/sessions/new.html.erb Edit Verify padding
app/views/devise/registrations/new.html.erb Edit Verify padding

10. Non-Goals (Out of Scope)

  • Rearchitecting Stimulus controllers for touch-drag reordering (future enhancement)
  • PWA offline improvements (separate concern)
  • Admin views — staff-only tool, desktop-first is acceptable, excluded from this plan
  • Adding swipe gestures to modals (can follow this plan)
  • Changing the overall design language or colour palette

May the Force of clean, responsive HTML be with you, always.