mcpbeat Sign in

Streak Tracker Skill for Claude

Generates a streak tracking system with timezone-aware day boundaries, streak freeze protection, and streak-at-risk push notifications. Use when user wants daily/weekly engagement streaks, consecutive day tracking, or habit tracking.

10k tokens
context cost
the whole folder, loaded on every use
2
files
instructions only
0
copies elsewhere
how many repositories repackaged it
585
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill streak-tracker

The instruction itself

30 sections, as written by the author

Streak Tracker Generator

Generate a production streak tracking system that records consecutive days of user activity, calculates current and longest streaks, handles timezone-aware day boundaries, supports streak freeze/protection passes, and schedules streak-at-risk local notifications.

When This Skill Activates

Use this skill when the user:

  • Asks to "add streaks" or "daily streak" tracking
  • Wants "streak tracking" or "consecutive days" counting
  • Mentions "engagement streaks" or "habit tracking"
  • Asks about "streak freeze" or "streak protection"
  • Wants "streak-at-risk" notifications or reminders
  • Mentions "login streak" or "activity streak"

Pre-Generation Checks

1. Project Context Detection

  • [ ] Check Swift version (requires Swift 5.9+)
  • [ ] Check deployment target (iOS 17+ / macOS 14+ for @Observable and SwiftData)
  • [ ] Check for SwiftData availability and existing model container setup
  • [ ] Identify source file locations

2. Conflict Detection

Search for existing streak or habit tracking:

Glob: **/*Streak*.swift, **/*Habit*.swift, **/*DailyTrack*.swift
Grep: "streak" or "consecutiveDays" or "habitTrack" or "dailyStreak"

If existing streak/habit system found:

  • Ask if user wants to replace or extend it
  • If extending, generate only the missing components

3. Platform Detection

Determine if generating for iOS (UNUserNotificationCenter) or macOS (UNUserNotificationCenter available on macOS 11+) or both.

Configuration Questions

Ask user via AskUserQuestion:

  • Streak type?
  • Daily (consecutive calendar days) -- recommended
  • Weekly (at least one activity per calendar week)
  • Custom interval (every N hours/days)
  • Storage backend?
  • SwiftData (recommended for iOS 17+ / macOS 14+)
  • UserDefaults (lightweight, no model container needed)
  • Include streak freeze/protection?
  • Yes — users get limited freeze passes to preserve streaks on missed days
  • No — strict consecutive tracking only
  • Streak-at-risk notifications?
  • Yes — schedule a local notification (e.g., 8 PM) if no activity recorded today
  • No — no notifications
  • Additional features? (multi-select)
  • Calendar heat map view (visual grid of activity days)
  • Streak badge view (compact count with animation)
  • Milestone celebrations (7-day, 30-day, 100-day, etc.)

Generation Process

Step 1: Read Templates

Read templates.md for production Swift code.

Step 2: Create Core Files

Generate these files:

  • StreakRecord.swift — SwiftData @Model for activity records
  • StreakManager.swift — @Observable class: record activity, calculate streaks, manage freezes
  • StreakError.swift — Error types for streak operations

Step 3: Create UI Files

  • StreakCalendarView.swift — Grid showing days with/without activity
  • StreakBadgeView.swift — Compact badge with streak count and animation

Step 4: Create Optional Files

Based on configuration:

  • StreakFreeze.swift — If streak freeze selected
  • StreakNotificationScheduler.swift — If notifications selected

Step 5: Determine File Location

Check project structure:

  • If Sources/ exists -> Sources/StreakTracking/
  • If App/ exists -> App/StreakTracking/
  • Otherwise -> StreakTracking/

Output Format

After generation, provide:

Files Created

StreakTracking/
├── StreakRecord.swift               # SwiftData model for activity records
├── StreakManager.swift              # Core streak calculation engine
├── StreakError.swift                # Error types
├── StreakCalendarView.swift         # Calendar heat map view
├── StreakBadgeView.swift            # Compact animated badge
├── StreakFreeze.swift               # Freeze/protection passes (optional)
└── StreakNotificationScheduler.swift # Streak-at-risk reminders (optional)

Integration Steps

Set up the model container (SwiftData):

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [StreakRecord.self, StreakFreeze.self])
    }
}

Record activity on user action:

struct LessonCompleteView: View {
    @Environment(\.modelContext) private var modelContext
    @State private var streakManager: StreakManager?

    var body: some View {
        Button("Complete Lesson") {
            Task {
                try await streakManager?.recordActivity(type: "lesson")
            }
        }
        .onAppear {
            streakManager = StreakManager(modelContext: modelContext)
        }
    }
}

Display the current streak:

struct ProfileView: View {
    @Environment(\.modelContext) private var modelContext
    @State private var streakManager: StreakManager?

    var body: some View {
        VStack {
            if let manager = streakManager {
                StreakBadgeView(streak: manager.currentStreak)
                Text("Longest: \(manager.longestStreak) days")
                    .foregroundStyle(.secondary)
            }
        }
        .onAppear {
            streakManager = StreakManager(modelContext: modelContext)
            Task { await streakManager?.refresh() }
        }
    }
}

Show the calendar heat map:

struct StatsView: View {
    @Environment(\.modelContext) private var modelContext
    @State private var streakManager: StreakManager?

    var body: some View {
        if let manager = streakManager {
            StreakCalendarView(manager: manager)
        }
    }
}

Use a streak freeze:

Button("Use Streak Freeze") {
    if streakManager.useStreakFreeze() {
        // Freeze applied — streak preserved
    } else {
        // No freezes available
    }
}

Testing

import Testing
import SwiftData

@Test
func recordingActivityIncrementsStreak() async throws {
    let container = try ModelContainer(
        for: StreakRecord.self, StreakFreeze.self,
        configurations: ModelConfiguration(isStoredInMemoryOnly: true)
    )
    let context = ModelContext(container)
    let manager = StreakManager(modelContext: context)

    // Record activity for today
    try await manager.recordActivity(type: "workout")
    await manager.refresh()

    #expect(manager.currentStreak == 1)
}

@Test
func consecutiveDaysBuildStreak() async throws {
    let container = try ModelContainer(
        for: StreakRecord.self, StreakFreeze.self,
        configurations: ModelConfiguration(isStoredInMemoryOnly: true)
    )
    let context = ModelContext(container)
    let manager = StreakManager(modelContext: context)

    // Simulate 3 consecutive days
    let calendar = Calendar.current
    for daysAgo in (0...2).reversed() {
        let date = calendar.date(byAdding: .day, value: -daysAgo, to: .now)!
        try await manager.recordActivity(type: "workout", date: date)
    }
    await manager.refresh()

    #expect(manager.currentStreak == 3)
    #expect(manager.longestStreak == 3)
}

@Test
func missedDayBreaksStreak() async throws {
    let container = try ModelContainer(
        for: StreakRecord.self, StreakFreeze.self,
        configurations: ModelConfiguration(isStoredInMemoryOnly: true)
    )
    let context = ModelContext(container)
    let manager = StreakManager(modelContext: context)

    let calendar = Calendar.current
    // Day 3 ago and Day 2 ago (streak of 2), then skip Day 1, record today
    let threeDaysAgo = calendar.date(byAdding: .day, value: -3, to: .now)!
    let twoDaysAgo = calendar.date(byAdding: .day, value: -2, to: .now)!
    try await manager.recordActivity(type: "workout", date: threeDaysAgo)
    try await manager.recordActivity(type: "workout", date: twoDaysAgo)
    try await manager.recordActivity(type: "workout", date: .now)
    await manager.refresh()

    #expect(manager.currentStreak == 1) // Gap broke the streak
    #expect(manager.longestStreak == 2) // Previous streak preserved
}

@Test
func streakFreezePreservesStreak() async throws {
    let container = try ModelContainer(
        for: StreakRecord.self, StreakFreeze.self,
        configurations: ModelConfiguration(isStoredInMemoryOnly: true)
    )
    let context = ModelContext(container)
    let manager = StreakManager(modelContext: context)

    let calendar = Calendar.current
    // Record 2 days ago and today — gap of 1 day
    let twoDaysAgo = calendar.date(byAdding: .day, value: -2, to: .now)!
    try await manager.recordActivity(type: "workout", date: twoDaysAgo)
    manager.addStreakFreeze(count: 1)
    let used = manager.useStreakFreeze()
    try await manager.recordActivity(type: "workout", date: .now)
    await manager.refresh()

    #expect(used == true)
    #expect(manager.currentStreak == 3) // 2 days ago + freeze + today
}

Common Patterns

Check In for Today

// Record a single check-in per day (idempotent)
try await streakManager.recordActivity(type: "daily-check-in")
// StreakManager deduplicates — calling twice on the same day is safe

Query Streak State

let current = streakManager.currentStreak    // Consecutive days up to today
let longest = streakManager.longestStreak    // All-time best
let atRisk = streakManager.isStreakAtRisk    // No activity today yet
let hasActivity = streakManager.hasActivityToday

Streak Freeze

// Grant freeze passes (e.g., as a reward or purchase)
streakManager.addStreakFreeze(count: 2)

// Use a freeze to cover a missed day
let success = streakManager.useStreakFreeze()
// Returns false if no freezes remaining or no gap to fill

Gotchas & Edge Cases

Timezone and Day Boundary Handling

Always use Calendar.current.startOfDay(for:) to normalize dates. Never compare raw Date values for "same day" checks — a user active at 11:59 PM and 12:01 AM has activity on two different calendar days but should not get a gap. The StreakManager uses the user's local calendar for all day boundary calculations.

Midnight Edge Cases

If a user completes an activity exactly at midnight, startOfDay(for:) may place it on the new day. The templates handle this by recording the timestamp as well as the normalized date, so the raw data is preserved for debugging.

Device Clock Manipulation

Users can change their device clock to fake streak activity. Mitigations:

  • Store createdAt timestamps alongside activityDate and flag anomalies (e.g., createdAt is before a previously recorded createdAt)
  • For server-synced apps, validate streaks server-side
  • For local-only apps, accept that determined users can game it — focus on honest users

Calendar vs Gregorian Day Changes

Different calendars (Islamic, Hebrew, Japanese) have different day boundaries and week structures. The templates use Calendar.current which respects the user's locale. If your app requires a fixed calendar (e.g., Gregorian for global leaderboards), pass an explicit Calendar(identifier: .gregorian) to StreakManager.

Duplicate Activity on Same Day

StreakManager.recordActivity is idempotent per calendar day per activity type. Calling it multiple times on the same day creates only one StreakRecord. This prevents accidental double-counting.

App Reinstall / Data Loss

SwiftData stores persist across app updates but are lost on reinstall. For critical streak data, consider syncing to CloudKit or a backend. The templates include a lastSyncDate hook in StreakManager for this purpose.

App Store Guidelines

  • Guideline 4.5.4: Streak-reminder pushes ("don't lose your 12-day streak!") are marketing notifications — they require explicit opt-in consent in your UI plus an in-app opt-out, not just the system permission prompt.
  • Guideline 3.1.1: If streak freezes or repairs are sold, they are digital content and must go through IAP — never an external purchase link. The one sanctioned exception is the US storefront with the StoreKit External Purchase Link entitlement (see monetization/external-purchases); everywhere else the rule stands.
  • Guideline 4.3 (Spam): A streak mechanic doesn't differentiate a thin app. If this is one of several similar apps on your account, run app-store/originality-check before submitting.

References

  • templates.md — All production Swift templates for streak tracking
  • Related: generators/push-notifications — Push notification setup for streak reminders
  • Related: generators/milestone-celebration — Celebrate streak milestones with animations

Other skills for the same job

different authors, same section of the catalogue
Protocolsio Integration
by christophacham
×4

Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.

16k tokens
Tailored Resume Generator
by frostant
×4

Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances

3k tokens
Excalidraw Diagram Generator
by github
vendor ×3

Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.

36k tokens scripts
Expo Dev Client
by openai
vendor ×3

Build and distribute Expo development clients locally or via TestFlight

961 tokens
Executing Plans
by ZhanlinCui
×3

Use when you have a written implementation plan to execute in a separate session with review checkpoints

542 tokens
Anndata
by christophacham
×3

Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.

16k tokens
Benchling Integration
by christophacham
×3

Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.

14k tokens
Biopython
by christophacham
×3

Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.

24k tokens

How to use it

Copy the folder

Take rshankras/streak-tracker from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.