Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import SwiftUI

/// A day's recovery datum for the heat strip.
public struct RecoveryDay: Identifiable, Sendable {
public let id = UUID()
public var id: Date { date }
public var date: Date
/// Recovery 0...100, or nil if no data for that day.
public var score: Double?
Expand Down Expand Up @@ -79,7 +79,7 @@ public struct YearHeatStrip: View {

// Group days into week columns. weekday 0 = Monday ... 6 = Sunday.
private struct Week: Identifiable {
let id = UUID()
let id: Date
var cells: [RecoveryDay?] // length 7, indexed by weekday row
var monthLabel: String?
}
Expand All @@ -89,7 +89,7 @@ public struct YearHeatStrip: View {
private static func buildWeeks(from days: [RecoveryDay]) -> [Week] {
guard let first = days.first?.date else { return [] }
var weeks: [Week] = []
var current = Week(cells: Array(repeating: nil, count: 7), monthLabel: nil)
var current = Week(id: first, cells: Array(repeating: nil, count: 7), monthLabel: nil)
var lastMonth = -1
// Pad the first week so the first day lands on its weekday row.
let firstRow = weekdayRow(first)
Expand All @@ -100,7 +100,7 @@ public struct YearHeatStrip: View {
let row = weekdayRow(day.date)
if row == 0 && filledThisWeek > 0 {
weeks.append(current)
current = Week(cells: Array(repeating: nil, count: 7), monthLabel: nil)
current = Week(id: day.date, cells: Array(repeating: nil, count: 7), monthLabel: nil)
filledThisWeek = 0
}
current.cells[row] = day
Expand Down
21 changes: 8 additions & 13 deletions Strand/Screens/TrendsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ struct TrendsView: View {
let rhr: ResolvedMetric
let strain: ResolvedMetric
let rest: ResolvedMetric
let recoveryDays: [RecoveryDay]
}

/// Repository status publications can redraw Trends without changing its historical data.
/// Retain the five resolved windows across those redraws; invalidate on the data generation,
/// Retain the resolved windows and calendar dates; invalidate on the data generation,
/// range, Rest-series revision, local day, or locale so readings and captions remain current.
/// This reference does not publish changes of its own, avoiding a body-to-state update loop.
@MainActor private final class ResolvedCache {
Expand Down Expand Up @@ -154,7 +155,10 @@ struct TrendsView: View {
hrv: resolve { $0.avgHrv },
rhr: resolve { $0.restingHr.map(Double.init) },
strain: resolve { $0.strain },
rest: resolve { sleepPerfByDay[$0.day] })
rest: resolve { sleepPerfByDay[$0.day] },
recoveryDays: repo.days.suffix(max(range.days ?? repo.days.count, 365)).compactMap { d in
date(d.day).map { RecoveryDay(date: $0, score: d.recovery) }
})
}
}

Expand Down Expand Up @@ -325,9 +329,7 @@ struct TrendsView: View {
// range window — chronic load is inherently a 42-day horizon. Self-hides its
// chart behind an honest "needs N more days" state until enough history exists.
TrainingLoadCard(days: repo.days)
.staggeredAppear(index: 5)
yearStrip
.staggeredAppear(index: 6)
yearStrip(recoveryDays: metrics.recoveryDays)
exportReportRow
.staggeredAppear(index: 7)
}
Expand Down Expand Up @@ -772,14 +774,7 @@ struct TrendsView: View {

// MARK: Year heat-strip

private var yearStrip: some View {
// Always show at least a full year for context; expand to all history on ALL.
let stripDays = max(range.days ?? repo.days.count, 365)
let recent = repo.days.suffix(stripDays)
let recoveryDays: [RecoveryDay] = recent.compactMap { d in
guard let dt = date(d.day) else { return nil }
return RecoveryDay(date: dt, score: d.recovery)
}
private func yearStrip(recoveryDays: [RecoveryDay]) -> some View {
let title = (range == .all && repo.days.count > 365) ? String(localized: "Charge (all history)") : String(localized: "Charge (past year)")
return NoopCard {
VStack(alignment: .leading, spacing: NoopMetrics.cardInnerSpacing) {
Expand Down
Loading