nvrsion: promote trunk to dev
Nucleic-Promote: 1 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -128,10 +128,14 @@ final class RemoteStore: ObservableObject {
|
|||||||
let cal = Calendar.current
|
let cal = Calendar.current
|
||||||
let today = cal.startOfDay(for: Date())
|
let today = cal.startOfDay(for: Date())
|
||||||
let activity = (0..<40).map { i -> ActivityDay in
|
let activity = (0..<40).map { i -> ActivityDay in
|
||||||
ActivityDay(day: cal.date(byAdding: .day, value: -i, to: today)!, count: (i * 7) % 6)
|
let count = (i * 7) % 6
|
||||||
|
// Sample tokens roughly track messages so the preview grid shades by usage.
|
||||||
|
ActivityDay(day: cal.date(byAdding: .day, value: -i, to: today)!,
|
||||||
|
count: count, tokens: count * 8_500 + (i * 137) % 4_000)
|
||||||
}
|
}
|
||||||
dashboard = DashboardSnapshot(
|
dashboard = DashboardSnapshot(
|
||||||
counts: DashboardCounts(projects: 2, chats: 5, activeChats: 2, messages: 142, activeDays: 9),
|
counts: DashboardCounts(
|
||||||
|
projects: 2, chats: 5, activeChats: 2, messages: 142, activeDays: 9, tokens: 1_284_000),
|
||||||
activity: activity,
|
activity: activity,
|
||||||
projects: [
|
projects: [
|
||||||
WireProject(id: p1, name: "nucleic", defaultBranch: "main", sessionCount: 3, activeCount: 2),
|
WireProject(id: p1, name: "nucleic", defaultBranch: "main", sessionCount: 3, activeCount: 2),
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ struct HomeView: View {
|
|||||||
Dictionary(store.dashboard.activity.map { (Calendar.current.startOfDay(for: $0.day), $0.count) },
|
Dictionary(store.dashboard.activity.map { (Calendar.current.startOfDay(for: $0.day), $0.count) },
|
||||||
uniquingKeysWith: +)
|
uniquingKeysWith: +)
|
||||||
}
|
}
|
||||||
|
/// Tokens used per day — the activity grid's primary intensity signal (falls back to message
|
||||||
|
/// count for days without recorded usage). Mirrors the Mac home.
|
||||||
|
private var tokensByDay: [Date: Int] {
|
||||||
|
Dictionary(store.dashboard.activity.map { (Calendar.current.startOfDay(for: $0.day), $0.tokens) },
|
||||||
|
uniquingKeysWith: +)
|
||||||
|
}
|
||||||
|
|
||||||
/// Current activity streak with streak freezes folded in — the run of consecutive active
|
/// Current activity streak with streak freezes folded in — the run of consecutive active
|
||||||
/// days, where a banked freeze (one earned per five days) bridges a missed day. Shared with
|
/// days, where a banked freeze (one earned per five days) bridges a missed day. Shared with
|
||||||
@@ -55,7 +61,9 @@ struct HomeView: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Text("Activity").font(.headline)
|
Text("Activity").font(.headline)
|
||||||
ActivityGrid(activityByDay: activityByDay, frozenDays: streak.frozenDays)
|
ActivityGrid(
|
||||||
|
activityByDay: activityByDay, tokensByDay: tokensByDay,
|
||||||
|
frozenDays: streak.frozenDays)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.card()
|
.card()
|
||||||
@@ -88,6 +96,7 @@ struct HomeView: View {
|
|||||||
StatCard(value: running.count, label: "In progress", icon: "circle.lefthalf.filled")
|
StatCard(value: running.count, label: "In progress", icon: "circle.lefthalf.filled")
|
||||||
StatCard(value: counts.activeChats, label: "Active", icon: "bolt")
|
StatCard(value: counts.activeChats, label: "Active", icon: "bolt")
|
||||||
StatCard(value: counts.messages, label: "Messages", icon: "paperplane")
|
StatCard(value: counts.messages, label: "Messages", icon: "paperplane")
|
||||||
|
StatCard(value: TokenCount.abbreviated(counts.tokens), label: "Tokens", icon: "number")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,13 +128,26 @@ private struct InProgressSessions: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct StatCard: View {
|
private struct StatCard: View {
|
||||||
let value: Int
|
let value: String
|
||||||
let label: String
|
let label: String
|
||||||
let icon: String
|
let icon: String
|
||||||
|
|
||||||
|
init(value: Int, label: String, icon: String) {
|
||||||
|
self.value = "\(value)"
|
||||||
|
self.label = label
|
||||||
|
self.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
init(value: String, label: String, icon: String) {
|
||||||
|
self.value = value
|
||||||
|
self.label = label
|
||||||
|
self.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Image(systemName: icon).foregroundStyle(Palette.accent)
|
Image(systemName: icon).foregroundStyle(Palette.accent)
|
||||||
Text("\(value)").font(.title2.weight(.semibold).monospacedDigit())
|
Text(value).font(.title2.weight(.semibold).monospacedDigit())
|
||||||
Text(label).font(.caption).foregroundStyle(.secondary)
|
Text(label).font(.caption).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -172,6 +194,9 @@ struct StreakBadge: View {
|
|||||||
/// GitHub-style activity grid: columns = weeks (old→new), rows = Sun…Sat.
|
/// GitHub-style activity grid: columns = weeks (old→new), rows = Sun…Sat.
|
||||||
struct ActivityGrid: View {
|
struct ActivityGrid: View {
|
||||||
let activityByDay: [Date: Int]
|
let activityByDay: [Date: Int]
|
||||||
|
/// Tokens used per day — the primary intensity signal; days absent here fall back to their
|
||||||
|
/// message count (mirrors the Mac grid via `ActivityShading`).
|
||||||
|
var tokensByDay: [Date: Int] = [:]
|
||||||
/// Missed days a streak freeze kept alive — drawn in the frozen blue rather than the
|
/// Missed days a streak freeze kept alive — drawn in the frozen blue rather than the
|
||||||
/// empty-day gray (mirrors the Mac grid).
|
/// empty-day gray (mirrors the Mac grid).
|
||||||
var frozenDays: Set<Date> = []
|
var frozenDays: Set<Date> = []
|
||||||
@@ -193,8 +218,9 @@ struct ActivityGrid: View {
|
|||||||
let startOfWeek = cal.date(byAdding: .day, value: -(weekday - 1), to: today)!
|
let startOfWeek = cal.date(byAdding: .day, value: -(weekday - 1), to: today)!
|
||||||
let gridStart = cal.date(byAdding: .day, value: -7 * (weeks - 1), to: startOfWeek)!
|
let gridStart = cal.date(byAdding: .day, value: -7 * (weeks - 1), to: startOfWeek)!
|
||||||
// Brightness relative to *this user's* activity, outliers excluded so a few marathon
|
// Brightness relative to *this user's* activity, outliers excluded so a few marathon
|
||||||
// days don't crush the everyday range. Computed once for the whole grid (mirrors Mac).
|
// days don't crush the everyday range. Shades by tokens-per-day, falling back to message
|
||||||
let scale = ActivityScale(activityByDay: activityByDay)
|
// count for days without recorded usage. Computed once for the whole grid (mirrors Mac).
|
||||||
|
let shading = ActivityShading(tokensByDay: tokensByDay, messagesByDay: activityByDay)
|
||||||
return HStack(alignment: .top, spacing: gap) {
|
return HStack(alignment: .top, spacing: gap) {
|
||||||
ForEach(0..<weeks, id: \.self) { col in
|
ForEach(0..<weeks, id: \.self) { col in
|
||||||
VStack(spacing: gap) {
|
VStack(spacing: gap) {
|
||||||
@@ -205,7 +231,8 @@ struct ActivityGrid: View {
|
|||||||
RoundedRectangle(cornerRadius: 2)
|
RoundedRectangle(cornerRadius: 2)
|
||||||
.fill(isFuture ? Color.clear
|
.fill(isFuture ? Color.clear
|
||||||
: isFrozen ? Palette.frozen
|
: isFrozen ? Palette.frozen
|
||||||
: Palette.activity(intensity: scale.intensity(for: activityByDay[date] ?? 0)))
|
: Palette.activity(intensity: shading.intensity(
|
||||||
|
tokens: tokensByDay[date] ?? 0, messages: activityByDay[date] ?? 0)))
|
||||||
.frame(width: cell, height: cell)
|
.frame(width: cell, height: cell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user