Skip to main content

Feature: Dashboard + Today Timeline (iOS)

Version: 1.3.1 Last Reviewed: 2026-02-18 Status: Approved

User Story

As a caregiver, I open the app and immediately see when the last feed/diaper/sleep was and can log a new one in 2 taps. I can also see today's full event history in the Today tab.

MVP Scope

Tab 1: Dashboard

  • "Since last" elapsed time counters: feeding (with last side indicator), diaper, sleep
  • Today's summary counts: total feedings, pee count, poop count, total sleep hours
  • Quick-log cards: Feed, Diaper, Sleep, Note (tappable → opens bottom sheet)
  • Active sleep indicator when timer is running
  • Auto-refresh every 30 seconds + on foreground

Tab 2: Today Timeline

  • Chronological list of today's events (newest first) across all types
  • Each entry shows: time, type icon, key details
  • Tapping an entry opens edit sheet
  • Pull-to-refresh
    • button in toolbar for logging from this tab (via .menu — native popover, not confirmationDialog)

NOT in MVP

  • Charts or daily summary
  • Widgets
  • Sync status indicator (post-MVP #4)
  • Grouping by time period (morning/afternoon/evening)

Dashboard Layout

┌──────────────────────────────┐
│ Baby Basics [⚙️] │ ← Gear icon → Settings
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ 🍼 Feed │ │ 🧷 Diaper │ │
│ │ 2h 15m ago │ │ 45m ago │ │
│ │ L │ │ │ │ ← "L" = last side was Left
│ └────────────┘ └────────────┘ │
│ ┌────────────┐ ┌────────────┐ │
│ │ 😴 Sleep │ │ 📝 Note │ │
│ │ 💤 1h 20m │ │ │ │ ← Note card: entry point only
│ └────────────┘ └────────────┘ │
│ │
│ ─── Today ─── │
│ 🍼 6 feeds │
│ 🧷 4 pee · 2 poop │
│ 😴 8h 30m total sleep │
│ 📝 3 notes │
│ │
├───────────┬──────────┬────────┤
│ Dashboard │ Today │Settings│
└───────────┴──────────┴────────┘

Dashboard Cards

Each card is a Button that opens a .sheet():

CardIconPrimary InfoSecondary InfoTap Action
Feed🍼 (drop.fill)"2h 15m ago"Side indicator (see below)Opens feeding sheet
Diaper🧷 (arrow.triangle.2.circlepath)"45m ago"--Opens diaper sheet
Sleep😴 (moon.zzz.fill)"3h ago" or "💤 1h 20m" (active)--Opens sleep sheet
Note📝 (note.text)"Note"--Opens note sheet

Feed Card Side Indicator

Derived from last_feeding.started_side and last_feeding.both_sides:

  • started_side=left, both_sides=false → "L"
  • started_side=right, both_sides=false → "R"
  • started_side=left, both_sides=true → "L+R"
  • started_side=right, both_sides=true → "R+L"
  • type=bottle → no side indicator shown

Elapsed Time Display

  • Format: Xm ago (under 1h), Xh Ym ago (1-24h), Xd Xh ago (24h+)
  • Updates every 60 seconds via SwiftUI Timer
  • -- when no events of that type exist yet

Active Sleep State

  • Dashboard API returns active_sleep: {id, start_time} (separate from last_sleep)
  • When active_sleep is not null, the Sleep card shows:
    • Pulsing 💤 icon (subtle animation)
    • "Sleeping: 1h 20m" (elapsed since active_sleep.start_time, updating live)
    • Tapping opens the sleep sheet with "Wake Up" as primary action
  • When active_sleep is null, the Sleep card shows "Since last sleep" from last_sleep.end_time

Today's Summary

Below the cards, show aggregated counts:

  • Total feedings count
  • Pee count + poop count (separate)
  • Total sleep in hours and minutes
  • Total notes count
  • "Today" defined by user's day_start_time preference

Suggested Side for Feeding Sheet

  • Dashboard API returns suggested_side: "left" | "right" | null
  • The feeding sheet uses this as the primary quick-tap button label
  • Server computes this as opposite of last breast feeding's started_side
  • null when no breast feedings exist (show both L and R as equal options)

Quick-Log Behavior

When a user taps a quick-action button (e.g., "Left" on feeding, "Poop" on diaper):

  1. Optimistic dismiss: Sheet dismisses immediately (no loading spinner in the sheet)
  2. Success banner: A banner appears at the bottom of the dashboard: "Logged left breast" with an Edit button, for 10 seconds
    • Tapping "Edit" re-opens the sheet in edit mode (post-MVP, separate ticket)
    • Banner auto-dismisses after 10 seconds
    • Only one banner visible at a time (new action replaces previous)
  3. Network request fires in background after sheet dismisses
  4. On success: Snackbar remains until timeout (no change), dashboard refreshes
  5. On failure: Snackbar is replaced with a toast: "Couldn't save · Will retry" (amber/warning color)
    • Request is added to the in-memory retry queue (see ios-app-shell.md)
    • Toast auto-dismisses after 10 seconds
    • Retried requests that eventually succeed trigger a silent dashboard refresh
    • Retried requests that permanently fail (5 attempts exhausted) show a persistent toast: "Failed to save [type] · Tap to retry" (red/danger color, does not auto-dismiss)

Success banner content per type:

  • Feeding (quick): "Logged left breast" / "Logged right breast" / "Logged both (L+R)"
  • Diaper: "Logged pee" / "Logged poop" / "Logged pee + poop"
  • Sleep (start): "Started sleep"
  • Sleep (wake): "Ended sleep (Xh Ym)"

Empty States

When no events of a type exist yet:

Dashboard cards:

  • Feed card: icon + "Feed" label + "--" (no elapsed time, no side indicator)
  • Diaper card: icon + "Diaper" label + "--"
  • Sleep card: icon + "Sleep" label + "--"
  • Note card: icon + "Note" label (always shows just the label, no elapsed time)

Today's summary (no events today):

  • "0 feeds"
  • "0 pee · 0 poop"
  • "0h 0m total sleep"

Today timeline (no events today):

  • Centered illustration-free message: "No events yet today" with subtext "Tap a card on the Dashboard to log your first entry"

Brand new baby (first-ever launch after onboarding):

  • All dashboard cards show "--"
  • All summary counts show 0
  • Timeline shows empty state message
  • This is the expected state — no special "welcome" or tutorial overlay

Data Loading

  • Single API call: GET /children/:childId/dashboard
  • Auto-refresh every 30 seconds (SwiftUI .task with Timer)
  • Refresh on scenePhase == .active (app returns to foreground)
  • Loading state: skeleton/shimmer on cards
  • Error state (first load, no data): retry button with message "Couldn't load dashboard" + "Tap to retry"
  • Stale data state (refresh fails but ViewModel has data): keep showing existing data with subtle banner "Offline · Showing last known data"

Today Timeline Layout

┌──────────────────────────────┐
│ Today [+] │ ← + opens .menu
│ │
│ 2:30 PM │
│ 🍼 Left breast · 15 min │
│ │
│ 1:45 PM │
│ 🧷 Poop │
│ │
│ 12:00 PM │
│ 😴 Nap · 2h 15m │
│ │
│ 10:30 AM │
│ 📝 Medication · Vitamin D │
│ │
│ (pull to refresh) │
├───────────┬──────────┬────────┤
│ Dashboard │ Today │Settings│
└───────────┴──────────┴────────┘

Timeline Entry Details

Each entry shows:

  • Time: formatted in user's locale (2:30 PM or 14:30)
  • Icon: type-specific SF Symbol
  • Detail line: varies by type
    • Feeding (breast, one side): "Left breast · 15 min"
    • Feeding (breast, both sides): "Both (L→R) · 15 min"
    • Feeding (bottle): "Bottle · 4 oz"
    • Diaper: "Pee", "Poop", or "Pee + Poop"
    • Sleep: "2h 15m" (duration) or "Started 8:00 PM" (active, no end)
    • Note: "Category · First 30 chars of text..."

Timeline Interactions

  • Tap entry: Opens edit sheet for that event (pre-populated)
  • Pull to refresh: Reloads timeline data
  • + button (toolbar, top-right): Opens .menu with Feed/Diaper/Sleep/Note (menus are for creation; confirmationDialogs are for destructive actions)
  • Data from GET /children/:childId/timeline?date=today

Amount Display (Feeding)

  • API returns amount_ml (Float)
  • iOS converts to oz for display: amount_ml / 29.5735, rounded to nearest 0.5 oz
  • Display as "4 oz" or "2.5 oz"
  • Post-MVP: user preference for mL vs oz

Acceptance Criteria

  • Dashboard loads in <2 seconds
  • "Since last feeding: Xh Ym ago" is accurate and updates live (every 60s)
  • "Since last diaper: Xh Ym ago" is accurate and updates live
  • "Since last sleep: Xh Ym ago" is accurate and updates live
  • Feeding card shows last breast side indicator (L, R, L+R, R+L)
  • Active sleep shows "Sleeping: Xh Ym" with pulsing indicator
  • Today's counts are accurate
  • Tapping "Feed" card opens feeding bottom sheet
  • Tapping "Diaper" card opens diaper bottom sheet
  • Tapping "Sleep" card opens sleep bottom sheet
  • Tapping "Note" card opens note bottom sheet
  • Quick-tap action dismisses sheet immediately and shows success banner
  • Success banner shows for 10 seconds with correct message per type and "Edit" button
  • Tapping "Edit" re-opens sheet in edit mode (post-MVP)
  • Network failure after quick-tap shows warning toast "Couldn't save · Will retry"
  • Permanently failed requests show persistent error toast with "Tap to retry"
  • Data from other caregivers appears within ~30 seconds (auto-refresh)
  • Today tab shows chronological list of all events (newest first)
  • Can tap a timeline entry to edit it
  • + button on Today tab opens menu with 4 options (Feed/Diaper/Sleep/Note)
  • Pull-to-refresh works on Today tab
  • Dashboard refreshes when app returns to foreground
  • Light and dark mode render correctly
  • "--" shows when no events exist for a category
  • Empty dashboard shows "--" on all cards and 0 counts
  • Empty timeline shows "No events yet today" message

Test Cases

Dashboard

  1. Empty dashboard: New baby, no events → all cards show "--", counts show 0
  2. All data present: Events for all types → cards show correct elapsed times
  3. Live update: Wait 60 seconds → elapsed times update without manual refresh
  4. Active sleep: Start sleep timer → sleep card shows pulsing elapsed time
  5. Auto-refresh: Log from another device → data appears within 30 seconds
  6. Foreground refresh: Background app, return → data refreshes immediately
  7. Feeding side indicator (single): started_side=left, both_sides=false → card shows "L"
  8. Feeding side indicator (both): started_side=left, both_sides=true → card shows "L+R"
  9. Bottle feeding: Last feed was bottle → no side indicator shown
  10. Suggested side: Dashboard API suggested_side="right" → feeding sheet primary button is "Right"
  11. Today counts: Log 3 feedings, 2 pee diapers, 1 "both" diaper → shows "3 feeds · 3 pee · 1 poop"

Quick-Log & Banner

  1. Quick-tap dismiss: Tap "Left" on feeding → sheet dismisses immediately, banner shows "Logged left breast" with "Edit" button
  2. Edit action: Tap "Edit" within 10 seconds → re-opens sheet in edit mode (post-MVP)
  3. Banner timeout: Don't tap Edit → banner disappears after 10 seconds, entry persists
  4. Rapid logging: Quick-tap diaper, then quick-tap feeding → first banner replaced by second
  5. Background save: Server responds 201 while banner is showing → no visible change, dashboard refreshes
  6. Network failure toast: Quick-tap while offline → banner replaced with "Couldn't save · Will retry" (amber)
  7. Permanent failure toast: Retry exhausted (5 attempts) → persistent red toast "Failed to save feeding · Tap to retry"
  8. Retry success: Offline quick-tap → toast shown → connection restored → silent retry succeeds → dashboard refreshes

Empty States

  1. Brand new baby: Complete onboarding → dashboard shows "--" on all cards, 0 counts, timeline shows "No events yet today"
  2. Partial data: Only feedings logged → Feed card shows elapsed, other cards show "--"
  3. Empty timeline message: No events today → "No events yet today" with helpful subtext

Timeline

  1. Timeline sorting: Multiple events → newest at top
  2. Timeline tap to edit: Tap a feeding → edit sheet opens with current values
  3. Timeline + button: Tap + → menu → tap Feed → feeding sheet opens
  4. Pull to refresh: Pull down on timeline → spinner → data reloads
  5. oz conversion: API returns 120 mL → displays "4 oz"

Visual

  1. Dark mode: All cards, snackbars, and toasts readable in dark mode
  2. Error state: Network error on initial load → shows error view with "Couldn't load dashboard" + retry button

Boundaries

  • No charts or trend analysis
  • No grouping of timeline events by time period
  • No infinite scroll / pagination on timeline (single day)
  • No persistent offline cache (post-MVP #1 - SwiftData). In-memory stale data from the current session is shown with "Offline" banner on refresh failure.
  • Amounts always displayed in oz for MVP (no unit toggle)
  • Side indicator only shows for breast feedings, not bottle