技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
add-report【数据与研究】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) Showing usage history in a new way, or reading another tool's usage logs (3) Adding comparison cards that show…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Add report cards that turn a login's usage history into metrics with comparison deltas, shown in the existing card style, test first.
This skill covers report-style features — cards that:
Usage history is data now (daily-usage design). A report is almost always a new view over days the login already reads, not new reading code. Place it before anything else:
| The person wants… | It is | You write |
|---|---|---|
| a new look at usage this app already reads — this week vs last, cost per model, a 30-day chart | a report over days: the page asks account.usageHistory?.days(in: range) and a value in Quotas turns the days into the report | a Quotas value + its tests, a card. No reading code |
| the same history for another tool (its own log files) | a usageHistory block in that provider's definition | JSON (paths, where, fields, prices), its golden tests — no Swift |
| history from a log format no reader understands yet | a new reader, named for the format, in Modules/DataSources/Sources/Internal/Logs/ | the reader, test-first in DataSourcesTests, then the JSON; a row in ENGINE_DESIGN §1 |
| an answer to a different question than "how much did I use?" | a capability (CANONICAL §2.1): declared in the definition, a handle on Account that is nil when not declared | design first; a runner outside the modules is supplied by the Engine (TARGET_ARCHITECTURE §10) |
Never: a field on UsageSnapshot (the kernel is shrinking toward Usage,
CANONICAL_MODEL §6),
an XxxAnalyzer / XxxAnalyzing protocol, a parser in Sources/Infrastructure, or a
vendor-named type in a module.
Reference implementation:
references/daily-usage-pattern.md— TODAY'S USAGE: Claude's logs and prices as data,UsageLogin DataSources,account.usageHistory,DailyUsageReportin Quotas,DailyUsageCardView. The Leaderboard reads 30 days the same way (login.history.days(in:)→DailyTokens.summed).
Check the design first — the docs are the source of truth (AGENTS.md): USER_JOURNEYS, CANONICAL_MODEL, TARGET_ARCHITECTURE, then the feature's
design.md.
<id>.json "usageHistory" ──► UsageLog (DataSources) one per login, built by
reader · PriceList · ProviderFactory from the definition
DayAggregator
│
popover opens (never in the background, #204)
│ ▼
account.usageHistory?.days(in: .last(14)) ──► [DailyUsageStat] (closed days from the ledger)
│
▼
WeeklyReport(days:) a rich value in Modules/Quotas: deltas, percentages, formatting
│
▼
WeeklyCardView renders what the report says — never compares or counts
Phase 0: Place it and design it (get user approval)
↓
Phase 1: The report value + tests (TDD Red→Green)
↓
Phase 2: Only if needed — the definition's usageHistory, a new reader, or a capability
↓
Phase 3: Mockup, card view, reading it on popover open
↓
Phase 4: Verify, screenshots on mock data, docs
DateRange (.last(n), a week, today vs yesterday).Example: this week vs last week, per login
┌────────────────────────────────────────────────────────────────────┐
│ Providers (module) Quotas (module) App │
│ │
│ account.usageHistory ─► WeeklyReport(days:) ─► WeeklyCardView │
│ .days(in: .last(14)) thisWeek / lastWeek × 3 metrics │
│ → [DailyUsageStat] deltas, %, formatted │
│ │
│ No reading code: claude.json's usageHistory already reads the logs│
└────────────────────────────────────────────────────────────────────┘
| Piece | Module | One job | Test |
|---|---|---|---|
WeeklyReport | Quotas | turns days into the comparison the card shows | Tests/DomainTests/<Feature>/ |
WeeklyCardView | App | renders it | AppTests / screenshots |
(only if needed) usageHistory block, a reader, a capability | definition / DataSources / Providers | — | golden tests / DataSourcesTests / ProvidersTests |
A UI change gets a mockup in design-concept/<feature>/ on mock data first
(AGENTS.md). Present the doc change, the diagram, the pieces and the mockup;
use AskUserQuestion and wait for approval.
Name each test should <outcome> [when <situation>], in the person's words, never a method, type or mechanism verb → Naming tests.
Location: Modules/Quotas/Sources/{Name}Report.swift (beside DailyUsageReport),
tests in Tests/DomainTests/{Feature}/{Name}ReportTests.swift (beside DailyUsageReportTests).
It is built from days — [DailyUsageStat] — so it never reads a file:
public struct {Name}Report: Sendable, Equatable {
public let current: [DailyUsageStat] // e.g. this week
public let previous: [DailyUsageStat] // e.g. last week
/// The days split at `boundary`, as the page asks for them.
public init(days: [DailyUsageStat], splitAt boundary: Date) { … }
public var cost: Decimal { current.reduce(0) { $0 + $1.totalCost } }
public var costChangePercent: Double? {
let before = previous.reduce(Decimal(0)) { $0 + $1.totalCost }
guard before > 0 else { return nil } // nil when there was nothing before
…
}
public var formattedCostDelta: String { … } // "+$5.00", "-$1.20": the sign always shown
public var costProgress: Double { … } // current / (current + previous), 0…1
}
Rules:
Decimal for money, TimeInterval for durations; Locale(identifier: "en_US") for currency.nil when the previous period is zero; deltas always carry a sign.isEmpty means all zero (&&, not ||).Write the tests first (state and return values, Chicago school), then the value.
Add a usageHistory block to <id>.json — files, format, where, fields,
prices, sessionGap — the shape is in the daily-usage design §2.
Golden tests in Modules/Providers/Tests/ over fixture logs (see ClaudeUsageHistoryTests).
No Swift.
A reader named for the format (JSONLinesReader, never AcmeLogReader) in
Modules/DataSources/Sources/Internal/Logs/, test-first in
Modules/DataSources/Tests/Logs/ with neutral fixtures, yielding the one
LogRecord every reader yields. Then the JSON uses it. Add its row to
ENGINE_DESIGN §1. Only scan files changed within the range (LogFileFinder).
Design it in the docs first (CANONICAL §2.1, TARGET_ARCHITECTURE): declared in
the definition, its own types in Modules/Providers, an @Observable handle on
Account that is nil when not declared — like usageHistory and
guestPasses. ProviderFactory.make builds it from the definition; a runner
that lives outside the modules (a CLI the App runs, the Keychain) is supplied by
the Engine the App builds once. The App never passes anything per provider.
Location: Sources/App/Views/{Name}CardView.swift. Match the existing
cards; DailyUsageCardView is the reference:
struct {Name}CardView: View {
let metric: {Name}Metric // one case per card
let report: {Name}Report
let delay: Double // cascading entrance
@Environment(\.appTheme) private var theme
var body: some View {
VStack(alignment: .leading, spacing: 6) {
// header: icon + LABEL · the large value · the progress bar · the delta line
}
.padding(12)
.background(/* theme.cardGradient fill + theme.glassBorder stroke */)
}
}
Card styling checklist: .padding(12) · theme.cardGradient + theme.glassBorder (1pt) ·
theme.cardCornerRadius · theme.fontDesign on text · theme.textPrimary/Secondary/Tertiary ·
theme.progressTrack · hover scale 1.015 · progress bar animated with delay.
Usage history is read when the popover opens, never in the background (#204),
and per login — the page asks the selected login, as MenuContentView does:
.task(id: login.id) {
guard let history = login.usageHistory else { return } // nil: this login offers none
let days = await history.days(in: .last(14))
report = {Name}Report(days: days, splitAt: weekStart)
}
The view renders report; it never compares, counts or decides from the days itself.
ProviderFactory.make builds usageHistory from the definition, and every
provider is found by ProviderCatalog.detect() (TARGET_ARCHITECTURE §10). The
App passes nothing per provider.
tuist generatexcodebuild test -workspace ClaudeBar.xcworkspace -scheme ClaudeBar-Workspace -destination 'platform=macOS,arch=arm64' — the full run; tuist test skips cached targetsscripts/demo-screenshots.sh) — screenshots, never real names, emails or usageREADME.md / design.md, one CHANGELOG line (≤300 chars), python3 scripts/gen-docs.py && python3 scripts/check-docs.py --strictusageHistory · new reader · capabilitydesign-concept/<feature>/{Name}ReportTests written first (deltas, nil percent, signs, progress, empty){Name}Report in Modules/Quotas, built from [DailyUsageStat] — greenusageHistory block + golden tests, or a format-named reader in DataSources + its ENGINE_DESIGN row, or a declared capabilityUsageSnapshot field, no analyzer protocol, no vendor-named Swift{Name}CardView matching the card styleClaudeBarAppxcodebuild test green