.claude/skills/add-report/SKILL.md
Guide for adding new report cards to ClaudeBar that analyze local data sources and display metrics with comparison deltas. Use this skill when: (1) Adding a new report/analytics card (e.g., weekly summary, model breakdown, session stats) (2) Creating data analysis features that read local files and display aggregated metrics (3) Adding comparison cards that show "today vs previous" style deltas (4) Building any feature that follows the DailyUsage pattern (parse → aggregate → report → card)
npx skillsauth add tddworks/claudebar add-reportInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Add new report cards that analyze local data sources, compute metrics with comparison deltas, and display them in the existing card UI style using TDD.
This skill covers adding report-style features — cards that:
Every report feature follows this data flow:
Data Source → Parser → Analyzer → Report Model → UsageSnapshot → Card View
Mapped to the codebase layers:
| Layer | Location | What to Create |
|-------|----------|----------------|
| Domain | Sources/Domain/{Feature}/ | Rich models + @Mockable protocol |
| Infrastructure | Sources/Infrastructure/{Provider}/ | Parser + Analyzer implementation |
| App | Sources/App/Views/ | Card view(s) |
| Integration | Provider class + statsGrid | Wire analyzer → snapshot → UI |
Reference implementation: See
references/daily-usage-pattern.mdfor the complete DailyUsage feature as a working example of this pattern.
Phase 0: Architecture Design (get user approval)
↓
Phase 1: Domain Models + Tests (TDD Red→Green)
↓
Phase 2: Infrastructure Parser + Analyzer + Tests
↓
Phase 3: Card View + Integration
↓
Phase 4: Verify all tests pass
Before writing code, design the feature and get user approval.
Answer these questions:
Example: Adding a weekly cost breakdown report
┌─────────────────────────────────────────────────────────┐
│ Data Source Infrastructure Domain │
│ │
│ ~/.claude/ → WeeklyParser → WeeklyStat │
│ projects/ (reads JSONL) (per-day cost) │
│ *.jsonl │
│ WeeklyAnalyzer → WeeklyReport │
│ (aggregates by (this week vs │
│ week, implements last week) │
│ protocol) │
│ ↓ ↓ │
│ Provider.refresh() → UsageSnapshot │
│ .weeklyReport │
│ ↓ │
│ statsGrid() → WeeklyCardView │
└─────────────────────────────────────────────────────────┘
| Component | Purpose | Inputs | Outputs |
|-----------|---------|--------|---------|
| {Name}Stat | Single period's data | Raw aggregated values | Formatted strings, isEmpty |
| {Name}Report | Period comparison | Two Stats | Deltas, percentages, progress |
| {Name}Analyzing | Protocol | Date/config | Report |
| {Name}Analyzer | Implementation | File paths | Report |
| {Name}CardView | UI card | Report + metric | Glassmorphism card |
Use AskUserQuestion to confirm the design before proceeding.
The stat model represents one period's aggregated data with rich formatting behavior.
Location: Sources/Domain/{Feature}/{Name}Stat.swift
Pattern to follow:
import Foundation
public struct {Name}Stat: Sendable, Equatable {
public let date: Date
// Add your metrics here
public let metricA: Decimal
public let metricB: Int
// Formatting — encapsulate in the model, not the view
public var formattedMetricA: String { /* currency, compact number, duration, etc. */ }
public var formattedMetricB: String { /* ... */ }
public var isEmpty: Bool { /* all zeros check */ }
public static func empty(for date: Date) -> Self { /* zero-valued instance */ }
}
Key rules:
Decimal for monetary values (not Double — floating point errors)TimeInterval for durationsLocale(identifier: "en_US") for currency formatting (not en_US_POSIX which adds a space)isEmpty uses && (all zeros = empty), not ||The report model compares two periods and computes deltas.
Location: Sources/Domain/{Feature}/{Name}Report.swift
Pattern to follow:
public struct {Name}Report: Sendable, Equatable {
public let current: {Name}Stat // e.g., today, this week
public let previous: {Name}Stat // e.g., yesterday, last week
// Delta calculations
public var metricADelta: Decimal { current.metricA - previous.metricA }
public var metricAChangePercent: Double? {
guard previous.metricA > 0 else { return nil } // nil when previous is zero
// ...
}
// Formatted deltas with sign: "+$5.00", "-1.2M"
public var formattedMetricADelta: String { /* ... */ }
// Progress for bar display (0-1 ratio of current vs total)
public var metricAProgress: Double {
let total = /* current + previous */
guard total > 0 else { return 0 }
return current / total
}
}
Key rules:
nil when previous is zero (avoid division by zero)+ or -)current / (current + previous), clamped to 0-1abs() for formatted values, prepend sign separatelyLocation: Sources/Domain/{Feature}/{Name}Analyzing.swift
import Mockable
@Mockable
public protocol {Name}Analyzing: Sendable {
func analyze() async throws -> {Name}Report
}
Location: Tests/DomainTests/{Feature}/
Create two test files following Chicago School TDD (test state, not interactions):
{Name}StatTests.swift — Test formatting, isEmpty, edge cases{Name}ReportTests.swift — Test deltas, percentages, nil cases, progressimport Foundation
import Testing
@testable import Domain
@Suite
struct {Name}StatTests {
@Test func `formats metric as expected`() {
let stat = {Name}Stat(date: Date(), metricA: 14.26, ...)
#expect(stat.formattedMetricA == "$14.26")
}
// ...
}
After writing tests → implement the models → run tests → all green.
If the report reads local files (JSONL, JSON, CSV), create a parser.
Location: Sources/Infrastructure/{Provider}/{Name}Parser.swift
Pattern: Parser is a struct (not protocol) since it's a pure data transformation.
struct {Name}Parser {
func parse(fileURL: URL) throws -> [{Name}Record] { /* ... */ }
func parse(content: String) -> [{Name}Record] { /* for testing */ }
}
Location: Sources/Infrastructure/{Provider}/{Provider}{Name}Analyzer.swift
The analyzer implements the domain protocol and orchestrates:
Performance rule: Only scan files modified within the relevant time window. With 2000+ JSONL files, scanning all of them is too slow.
public struct {Provider}{Name}Analyzer: {Name}Analyzing, Sendable {
public func analyze() async throws -> {Name}Report {
let files = findRecentFiles(since: periodStart) // Performance!
// parse → partition → aggregate → return
}
}
Location: Tests/InfrastructureTests/{Provider}/
Location: Sources/App/Views/{Name}CardView.swift
The card must match the existing glassmorphism style. Use WrappedStatCard as the reference:
struct {Name}CardView: View {
let metric: {Name}Metric // enum for each displayable metric
let report: {Name}Report
let delay: Double // for cascading entrance animation
@Environment(\.appTheme) private var theme
var body: some View {
VStack(alignment: .leading, spacing: 6) {
// 1. Header: icon + LABEL (uppercased)
// 2. Large value (e.g., "$14.26" or "19.5M")
// 3. Progress bar (animated)
// 4. Delta comparison line (e.g., "Vs Mar 10 -$27.47 (4.9%)")
}
.padding(12)
.background(/* theme.cardGradient + theme.glassBorder stroke */)
.scaleEffect(isHovering ? 1.015 : 1.0)
.onHover { isHovering = $0 }
}
}
Card styling checklist:
.padding(12) on the VStacktheme.cardGradient fill + theme.glassBorder stroke (1pt)theme.cardCornerRadius for cornerstheme.fontDesign on all texttheme.textPrimary / theme.textSecondary / theme.textTertiary for text hierarchytheme.progressTrack for bar backgrounddelay parameterAdd an optional field for the report:
// In Sources/Domain/Provider/UsageSnapshot.swift
public let {name}Report: {Name}Report?
// Add to init with default nil
Inject the analyzer into the provider that owns this data:
// In the provider's init:
private let {name}Analyzer: (any {Name}Analyzing)?
// In refresh():
snapshot = await attach{Name}Report(to: newSnapshot)
// Helper method:
private func attach{Name}Report(to snapshot: UsageSnapshot) async -> UsageSnapshot {
guard let analyzer = {name}Analyzer,
let report = try? await analyzer.analyze(),
!report.current.isEmpty else { return snapshot }
return UsageSnapshot(/* copy all fields, add report */)
}
Add to MenuContentView.statsGrid(snapshot:):
if let report = snapshot.{name}Report {
let baseDelay = Double(snapshot.quotas.count + 1) * 0.08
// Render card(s) in LazyVGrid or standalone
}
Pass the analyzer when creating the provider:
{Provider}Provider(
probe: ...,
settingsRepository: settingsRepository,
{name}Analyzer: {Provider}{Name}Analyzer()
)
tuist generatexcodebuild test -scheme ClaudeBar-Workspace ...{Name}StatTests (formatting, isEmpty, edge cases){Name}Stat — make tests green{Name}ReportTests (deltas, percentages, progress){Name}Report — make tests green{Name}Analyzing protocol with @Mockable{Name}CardView matching glassmorphism style{name}Report field to UsageSnapshotrefresh() via attach{Name}ReportstatsGridClaudeBarApptuist generate succeedsdevelopment
Guide for making improvements to existing ClaudeBar functionality using TDD. Use this skill when: (1) Enhancing existing features (not adding new ones) (2) Improving UX, performance, or code quality (3) User asks "improve X", "make Y better", or "enhance Z" (4) Small enhancements that don't require full architecture design For NEW features, use implement-feature skill instead.
development
Guide for implementing features in ClaudeBar following architecture-first design, TDD, rich domain models, and Swift 6.2 patterns. Use this skill when: (1) Adding new functionality to the app (2) Creating domain models that follow user's mental model (3) Building SwiftUI views that consume domain models directly (4) User asks "how do I implement X" or "add feature Y" (5) Implementing any feature that spans Domain, Infrastructure, and App layers
development
Manage ClaudeBar's GitHub Actions CI/CD pipelines: build, test, and release workflows. Use this skill when: (1) Setting up secrets for CI/CD (certificate, API key, Sparkle key, Codecov) (2) Creating a new release — tag-based or manual workflow_dispatch (3) Triggering or explaining the build.yml, tests.yml, or release.yml workflows (4) Debugging release failures (signing, notarization, appcast) (5) Managing beta vs stable channels for Sparkle auto-updates (6) User says "release a new version", "push a tag", "set up CI secrets", "why did the release fail"
development
Guide for fixing bugs in ClaudeBar following Chicago School TDD and rich domain design. Use this skill when: (1) User reports a bug or unexpected behavior (2) Fixing a defect in existing functionality (3) User asks "fix this bug" or "this doesn't work correctly" (4) Correcting behavior that violates the user's mental model