Skip to content

Latest commit

Β 

History

History
343 lines (244 loc) Β· 14.5 KB

File metadata and controls

343 lines (244 loc) Β· 14.5 KB

AGENTS.md

Guidance for AI coding assistants (Claude, GitHub Copilot, Cursor, etc.) working on this repository.

Role

You are a Senior Swift Engineer specializing in SwiftUI, Swift Concurrency, and macOS development. Your code must adhere to Apple's Human Interface Guidelines. Target Swift 6.0+ and macOS 26.0+.

What is Kaset?

A native macOS YouTube Music client built with Swift and SwiftUI.

  • Apple Music-style UI: Liquid Glass player bar, clean sidebar navigation
  • Browser-cookie authentication: Auto-extracts cookies from an in-app login WebView
  • Hidden WebView playback: Singleton WebView for YouTube Music Premium (DRM content)
  • Background audio: Audio continues when window is closed, stops on quit
  • Native UI: SwiftUI sidebar navigation, player bar, and content views
  • System integration: Now Playing in Control Center, media keys, Dock menu

Project Structure

App/                β†’ App entry point, AppDelegate (window lifecycle)
Core/
  β”œβ”€β”€ Models/       β†’ Data models (Song, Playlist, Album, Artist, etc.)
  β”œβ”€β”€ Services/
  β”‚   β”œβ”€β”€ API/      β†’ YTMusicClient, Parsers/ (response parsing)
  β”‚   β”œβ”€β”€ Auth/     β†’ AuthService (login state machine)
  β”‚   β”œβ”€β”€ Player/   β†’ PlayerService, NowPlayingManager (playback, media keys)
  β”‚   └── WebKit/   β†’ WebKitManager (cookie store, persistent login)
  β”œβ”€β”€ ViewModels/   β†’ HomeViewModel, LibraryViewModel, SearchViewModel
  └── Utilities/    β†’ DiagnosticsLogger, extensions
Views/
  └── macOS/        β†’ SwiftUI views (MainWindow, Sidebar, PlayerBar, etc.)
Tests/              β†’ Unit tests (KasetTests/)
Tools/              β†’ Standalone CLI tools (api-explorer.swift)
docs/               β†’ Detailed documentation
  └── adr/          β†’ Architecture Decision Records

Documentation

For detailed information, see the docs/ folder:

Before You Start

  1. Read PLAN.md β€” Contains the phased implementation plan
  2. Understand the playback architecture β€” See docs/playback.md
  3. Check ADRs for past decisions β€” See docs/adr/ before proposing architectural changes
  4. Consult API documentation before implementing API features β€” See docs/api-discovery.md for endpoint reference

API Discovery Workflow

⚠️ MANDATORY: Before implementing ANY feature that requires a new or modified API call, you MUST explore the endpoint first using the API Explorer tool. Do NOT guess or assume API response structures.

Step 1: Explore with Standalone Tool (Required)

Use the standalone CLI tool to explore endpoints before writing any code:

# Check authentication status
./Tools/api-explorer.swift auth

# List all known endpoints
./Tools/api-explorer.swift list

# Explore public browse endpoints
./Tools/api-explorer.swift browse FEmusic_charts
./Tools/api-explorer.swift browse FEmusic_moods_and_genres

# Explore authenticated endpoints (requires Kaset sign-in)
./Tools/api-explorer.swift browse FEmusic_liked_playlists
./Tools/api-explorer.swift browse FEmusic_history

# Explore with verbose output to see raw JSON
./Tools/api-explorer.swift browse FEmusic_home -v

# Explore action endpoints
./Tools/api-explorer.swift action search '{"query":"taylor swift"}'
./Tools/api-explorer.swift action player '{"videoId":"dQw4w9WgXcQ"}'

The tool automatically uses cookies from the Kaset app for authenticated endpoints.

Step 2: Check Documentation

Review docs/api-discovery.md to see if the endpoint is already documented with its auth requirements and response structure.

Step 3: For Authenticated Endpoints (πŸ”)

If the endpoint requires authentication:

  1. Run ./Tools/api-explorer.swift auth to check cookie status
  2. If no cookies, run the Kaset app and sign in to YouTube Music
  3. The app saves cookies to ~/Library/Application Support/Kaset/cookies.dat
  4. Run the API explorer again

Step 4: Document Findings

If you discover new response structures or endpoint behaviors, update docs/api-discovery.md with your findings.

⚠️ Do NOT guess API structures β€” Always verify with the API Explorer tool or documentation before writing parsers. Incorrect assumptions lead to runtime failures.

Critical Rules

🚨 NEVER leak secrets, cookies, API keys, or tokens β€” Under NO circumstances include real cookies, authentication tokens, API keys, SAPISID values, or any sensitive credentials in code, comments, logs, documentation, test fixtures, or any output. Always use placeholder values like "REDACTED", "mock-token", or "test-cookie" in examples and tests. This applies to all files including tests, docs, and ADRs. Violation of this rule is a critical security incident.

⚠️ NEVER run git commit or git push β€” Always leave committing and pushing to the human.

⚠️ ALWAYS confirm before running UI tests β€” UI tests launch the app and can be disruptive. Ask the human for permission before executing any UI test.

⚠️ No Third-Party Frameworks β€” Do not introduce third-party dependencies without asking first.

⚠️ Prefer API over WebView β€” Always use YTMusicClient API calls when functionality exists. Only use WebView for playback (DRM-protected audio) and authentication. API calls are faster, more testable, and reduce WebView complexity.

πŸ“ Document Architectural Decisions β€” For significant design changes, create an ADR in docs/adr/ following the format in docs/adr/README.md.

⚑ Performance Awareness β€” For non-trivial features, run performance tests and verify no anti-patterns. When adding parsers or API calls, include measure {} tests.

πŸ”§ Improve API Explorer, Don't Write One-Off Scripts β€” When exploring or debugging API-related functionality, always enhance Tools/api-explorer.swift instead of writing temporary scripts. This ensures the tool grows with the project, maintains consistency, and provides reusable functionality for future API work. If you need to fetch raw JSON, test a new endpoint, or debug response parsing, add that capability to the API explorer.

Build & Verify

After modifying code, verify the build:

xcodebuild -scheme Kaset -destination 'platform=macOS' build

Code Quality

swiftlint --strict && swiftformat .

⚠️ SwiftFormat --self insert rule: The project uses --self insert in .swiftformat. This means:

  • In static methods, call other static methods with Self.methodName() (not bare methodName())
  • In instance methods, use self.property explicitly

Always run swiftformat . before completing work to auto-fix these issues.

Modern SwiftUI APIs

❌ Avoid βœ… Use
.foregroundColor() .foregroundStyle()
.cornerRadius() .clipShape(.rect(cornerRadius:))
onChange(of:) { newValue in } onChange(of:) { _, newValue in }
Task.sleep(nanoseconds:) Task.sleep(for: .seconds())
NavigationView NavigationSplitView or NavigationStack
onTapGesture() Button (unless tap location needed)
tabItem() Tab API
AnyView Concrete types or @ViewBuilder
print() DiagnosticsLogger
DispatchQueue Swift concurrency (async/await)
String(format: "%.2f", n) Text(n, format: .number.precision(...))
Force unwraps (!) Optional handling or guard
Image-only buttons without labels Add .accessibilityLabel()
.background(.ultraThinMaterial) .glassEffect() for macOS 26+

Liquid Glass UI (macOS 26+)

See docs/architecture.md#ui-design-macos-26 for detailed patterns.

Quick Rules:

  • Use .glassEffect(.regular.interactive(), in: .capsule) for interactive elements
  • Wrap multiple glass elements in GlassEffectContainer
  • Add PlayerBar via safeAreaInset to every navigable view
  • Avoid glass-on-glass (no .buttonStyle(.glass) inside glass containers)

Swift Testing (Preferred)

βœ… Use Swift Testing for all new unit tests β€” See docs/testing.md and ADR-0006 for full patterns.

Quick Reference:

  • Use @Suite struct + @Test func (not XCTest)
  • Use #expect(a == b) (not XCTAssertEqual)
  • Use .serialized for @MainActor test suites
  • Keep performance tests (measure {}) and UI tests in XCTest

Swift Concurrency

  • Mark @Observable classes with @MainActor
  • Never use DispatchQueue β€” use async/await, MainActor

WebKit Patterns

  • Use WebKitManager's shared WKWebsiteDataStore for cookie persistence
  • Use SingletonPlayerWebView.shared for playback (never create multiple WebViews)
  • Compute SAPISIDHASH fresh per request using current cookies

Error Handling

  • Throw YTMusicError.authExpired on HTTP 401/403
  • Use DiagnosticsLogger for all logging (not print())
  • Show user-friendly error messages with retry options

Key Files

File Purpose
Tools/api-explorer.swift Standalone API explorer CLI (run before implementing API features)
App/AppDelegate.swift Window lifecycle, background audio support
Core/Services/WebKit/WebKitManager.swift Cookie store & persistence
Core/Services/Auth/AuthService.swift Login state machine
Core/Services/Player/PlayerService.swift Playback state & control
Views/macOS/MiniPlayerWebView.swift Singleton WebView, playback UI
Views/macOS/MainWindow.swift Main app window
Core/Utilities/DiagnosticsLogger.swift Logging

Quick Reference

See docs/testing.md for full test commands and patterns.

Build Commands

# Build
xcodebuild -scheme Kaset -destination 'platform=macOS' build

# Unit Tests
xcodebuild -scheme Kaset -destination 'platform=macOS' test -only-testing:KasetTests

# Lint & Format
swiftlint --strict && swiftformat .

Test Execution Rules

⚠️ NEVER run unit tests and UI tests together β€” Always execute them separately.

UI Tests β€” Ask permission first, run ONE at a time:

xcodebuild -scheme Kaset -destination 'platform=macOS' test \
  -only-testing:KasetUITests/TestClassName/testMethodName

Architecture Overview

See docs/architecture.md and docs/playback.md for detailed flows.

Key Concepts:

  • Singleton WebView for playback (DRM requires WebKit)
  • Background audio via windowShouldClose returning false (hides instead of closes)
  • Cookie-based auth with __Secure-3PAPISID extracted from WebView
  • API-first β€” use YTMusicClient for data, WebView only for playback/auth

Performance Checklist

Before completing non-trivial features, verify:

  • No await calls inside loops or ForEach
  • Lists use LazyVStack/LazyHStack for large datasets
  • Network calls cancelled on view disappear (.task handles this)
  • Parsers have measure {} tests if processing large payloads
  • Images use ImageCache with appropriate targetSize (not loading inline)
  • Search input is debounced (not firing on every keystroke)
  • ForEach uses stable identity (avoid Array(enumerated()) unless rank is needed)
  • Frequently updating UI (e.g., progress) caches formatted strings

πŸ“š See docs/architecture.md#performance-guidelines for detailed patterns.

Task Planning: Phases with Exit Criteria

For any non-trivial task, plan in phases with testable exit criteria before writing code. This ensures incremental progress and early detection of issues.

Phase Structure

Every task should be broken into phases. Each phase must have:

  1. Clear deliverable β€” What artifact or change is produced
  2. Testable exit criteria β€” How to verify the phase is complete
  3. Rollback point β€” The phase should leave the codebase in a working state

Standard Phases

Phase 1: Research & Understanding

Deliverable Exit Criteria
Identify affected files and dependencies List all files to modify/create
Understand existing patterns Can explain how similar features work
Read relevant docs Confirmed patterns in docs/ apply

Exit gate: Can articulate the implementation plan without ambiguity.

Phase 2: Interface Design

Deliverable Exit Criteria
Define new types/protocols Type signatures compile
Plan public API surface No breaking changes to existing callers (or changes identified)

Exit gate: xcodebuild build succeeds with stub implementations.

Phase 3: Core Implementation

Deliverable Exit Criteria
Implement business logic Unit tests pass for new code
Handle error cases Error paths have test coverage
Add logging DiagnosticsLogger calls in place
Performance verified Anti-pattern checklist passed, perf tests added if applicable

Exit gate: xcodebuild test -only-testing:KasetTests passes.

Phase 4: Quality Assurance

Deliverable Exit Criteria
Linting passes swiftlint --strict reports 0 errors
Formatting applied swiftformat . makes no changes
Full test suite passes xcodebuild test succeeds

Exit gate: CI-equivalent checks pass locally.

Example: Adding a New Service

Phase 1: Research
β”œβ”€β”€ Exit: Understand YTMusicClient pattern, confirm no existing solution

Phase 2: Interface
β”œβ”€β”€ Create NewService.swift with protocol + stub
β”œβ”€β”€ Exit: `xcodebuild build` passes

Phase 3: Implementation
β”œβ”€β”€ Implement methods, add error handling
β”œβ”€β”€ Create NewServiceTests.swift
β”œβ”€β”€ Exit: `xcodebuild test -only-testing:KasetTests/NewServiceTests` passes

Phase 4: QA
β”œβ”€β”€ Run swiftlint, swiftformat
β”œβ”€β”€ Exit: Full test suite passes, no lint errors

Checkpoint Communication

After each phase, briefly report:

  • βœ… What was completed
  • πŸ§ͺ Test/verification results
  • ➑️ Next phase plan

This keeps the human informed and provides natural points to course-correct.