History Section

Displays filtered records with date range and tag filtering.

Structure

  1. 408-date-range-picker - Preset buttons + From/To pickers
  2. 409-tag-filter - Tag toggle buttons in FlowLayout
  3. Total time display
  4. Share button (opens 410-share-export-sheet)
  5. Month rows (HistoryMonthRow) - Period > Day > Record hierarchy

Month rows (HistoryMonthRow)

HistorySection emits one List row per month (HistoryMonthRow). Because the enclosing List reuses cells natively, only on-screen months are built — the nested SwiftUI Grids below are not lazy, but each is bounded to a single month. Columns still align across months because every label column has a fixed width (HistoryColumnSizes).

The record editor sheet is presented by the month row that held the record when it was tapped: the row stores it (presentedRecord) when editingRecordId changes, and keeps presenting it while the id is set, even if the record has left the month. A save that moves a record into another month in the range publishes the move before the editor clears editingRecordId; a sheet derived from the current month contents closed in the old row and opened again in the new one.

Level 1: Month Row

  • One List row per year-month
  • Period label column: vertical text (rotated 90 degrees)
  • Contains Level 2 (Days Grid) for the month

Level 2: Days Grid

  • Rows grouped by day within each month
  • Day label column: large monospaced day number (01-31)
  • Contains Level 3 (Records Grid) for each day

Level 3: Records Grid

  • Individual record rows with columns:
    • Duration (compact format, monospaced, right-aligned)
    • Time range (start - end, monospaced)
    • Tag (color indicator + name) - always shown
  • Comment shown on separate row when present (spans all columns)

Smart Column Hiding (HistoryVisibility)

Columns are intelligently hidden based on filtered data:

  • Period column: Hidden if all records are in current month of current year
  • Year in period label: Shown only if multiple years or non-current year
  • Day column: Hidden if all records are on the same day

The tag column is always shown so records stay identifiable regardless of the active filter.

This reduces visual noise when filtering to specific time periods.

Sticky Labels

Period and day labels use sticky positioning:

  • Labels stay visible at top of viewport when scrolling through long lists
  • Offset is clamped to keep label within its cell bounds
  • Threshold: labels start sticking when cell Y goes below 100pt

Interactions

  • Tap row: Expand inline 406-record-editor (spans record columns)
  • Context menu: Delete option
  • Long press (iOS): Same as context menu

Filtering

Records filtered by:

  1. Date range (inclusive)
  2. Selected tags (if any)
  3. Untagged toggle

Running timers are excluded from history.

Export

Share button opens 410-share-export-sheet with:

  • Current date range
  • Current tag filters
  • All filtered records

Uses snapshot pattern to prevent sheet re-render issues.