# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

IceGlass is a macOS menu bar application that provides NHL game situational awareness. It shows recent scores, upcoming games, and game states for all NHL teams in a yesterday/today/tomorrow window. The app fetches live data from the NHL API and displays it in a dropdown menu.

## Building and Testing

### Build Commands
```bash
# Generate Xcode project (after modifying project.yml).
# Never hand-edit IceGlass.xcodeproj — it is generated and will be overwritten.
xcodegen generate

# Build the project
xcodebuild -scheme IceGlass -configuration Debug build

# Build for release
xcodebuild -scheme IceGlass -configuration Release build

# Clean build folder
xcodebuild -scheme IceGlass clean
```

### Running the App
Open `IceGlass.xcodeproj` in Xcode and run the scheme, or build and run the resulting .app bundle from the build directory.

### Debug vs Release Builds
- **DEBUG** builds include a "Developer" menu with "Force Refresh"

## Architecture

### Core Services Pattern

The app uses a singleton-based architecture following the same pattern as the Ovi app:

1. **MainService** (`Services/MainService.swift`) - Central coordinator that:
   - Manages a single polling timer for the scoreboard API
   - Dynamically adjusts polling intervals based on game state (7s live, 180s pre-game, 600s game day, 3600s idle)
   - Filters API response to yesterday/today/tomorrow window in Eastern Time

2. **StatusItemManager** (`Managers/StatusItemManager.swift`) - Manages the menu bar item:
   - Displays NHL shield icon (template image for dark/light mode)
   - Creates and manages the NSStatusItem

3. **MenuManager** (`Managers/MenuManager.swift`) - Builds and updates the dropdown menu:
   - Displays games grouped by date (YESTERDAY/TODAY/TOMORROW headers)
   - Game rows show: away/home teams, scores, game state/time
   - Click opens NHL GameCenter; option-click opens NHL Videocast
   - Menu updates safely (skips during mouse clicks to prevent crashes)

### API Integration

**Single endpoint**: `https://api-web.nhle.com/v1/scoreboard/now`
- Returns games grouped by date with scores, covering a multi-day window
- Response includes team names, abbreviations, scores, game state, period info

**ApiService** (`Services/ApiService.swift`) - Generic URL+callback fetcher

### Game States (from NHL API)

Defined in `Models/GameState.swift`:
- **FUT** (Future) - More than 30 minutes before game start
- **PRE** (Pre-game) - Less than 30 minutes before puck drop
- **LIVE** - Game in progress
- **CRIT** - Last 5 minutes of regulation, OT, or SO
- **OVER** - Soft final (game over, not yet confirmed)
- **FINAL** - Hard final (confirmed by arena scorers)
- **OFF** - Official (confirmed by NHL statisticians)

### Polling Intervals

Defined in `Services/PollingInterval.swift`:
- **idle**: 3600s (1 hour) - No games today
- **bootstrap**: 42s - Initial app launch
- **gameDay**: 600s (10 minutes) - Games scheduled today but not starting soon
- **preGame**: 180s (3 minutes) - Game starting within 30 minutes
- **liveGame**: 7s - Any game currently live
- **everyMinute**: 60s - Intermissions, post-game

### Notification System

**NotificationManager** (`Managers/NotificationManager.swift`) - Handles macOS notifications:
- **Game start notifications**: triggered when game state transitions from FUT/PRE to LIVE
- **Goal scored notifications**: triggered when a team's score increases, with scoring team's logo attached
- Team logo PNGs (80x80) bundled in `TeamLogos/` directory for `UNNotificationAttachment`
- Deduplicates via Set tracking: game IDs for starts, "gameId-awayScore-homeScore" keys for goals
- First fetch suppressed (no notifications on app startup)
- AppDelegate handles notification clicks to open URLs in browser

**Change Detection** (in MainService):
- `previousGameStates: [Int: GameSnapshot]` tracks game state and scores between polls
- `detectChanges(in:)` compares current vs previous to fire notifications

### Team Logos

Downloaded from NHL CDN via `Scripts/download_team_logos.sh`:
- Source: `https://assets.nhle.com/logos/nhl/svg/{TEAM}_light.svg?season={SEASON}`
- SVGs stored in `Assets.xcassets/TeamLogo_{TEAM}.imageset/` for future UI use
- PNGs (80x80) stored in `TeamLogos/` as bundle resources for notification attachments
- Update logos each season by running the script with the new season parameter

### Settings & Persistence

**AppSettings** (`Managers/AppSettings.swift`) - UserDefaults-backed:
- Launch at login (via SMAppService)
- Selected team filter (nil = all teams)

### Eastern Time Handling

NHL operates on Eastern Time. Date extensions handle timezone conversions:
- `Date+easternTimeZone.swift` - ET timezone constant
- `Date+etCalendar.swift` - Calendar configured for ET
- `Date+formatDateET.swift` - ET date formatters
- `Date+gameWindow.swift` - Yesterday/today/tomorrow date computation in ET

### Important Implementation Notes

- All services use singleton pattern with private initializers
- All singleton classes use `@unchecked Sendable` for Swift 6 strict concurrency
- Menu updates are skipped during mouse clicks (`isSafe()` check) to prevent crashes
- Timer rescheduling includes tolerance checks to avoid unnecessary invalidation
- Weak self references used throughout to prevent retain cycles
- XcodeGen is used to generate the Xcode project from `project.yml`
- LSUIElement=true in Info.plist (no dock icon, menu bar only)

### Dependencies

- **IndieAbout** (SPM) - About window UI (`https://git.rzen.dev/rzen/indie-about.git`)

### File Organization

```
IceGlass/
├── Services/          - Core business logic (MainService, ApiService, PollingInterval)
├── Managers/          - UI coordination (StatusItemManager, MenuManager, NotificationManager, AppSettings)
├── Models/            - Codable data models (ScoreboardModel, StandingsModel, GameState)
├── Extensions/        - Date/Timer/NSMenuItem/TimeInterval helpers
├── Lib/               - Utilities (IceGlassLogger, AppTerminator)
├── About/             - About window (AboutWindow with IndieAbout)
├── Assets.xcassets/   - AppIcon, NHLShield, TeamLogo_*.imageset (32 SVGs)
├── TeamLogos/         - 32 team logo PNGs (80x80, for notification attachments)
├── IceGlassApp.swift  - App entry point (@main)
└── AppDelegate.swift  - NSApplicationDelegate, notification click handler
```
