Guidance for AI coding assistants working on this repository.
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+.
Kaset is a native macOS client for YouTube Music and YouTube (Swift/SwiftUI).
🚨 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". Violation of this rule is a critical security incident.
⚠️ 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 useYTMusicClient(YouTube Music) orYouTubeClient(YouTube) API calls when functionality exists. Only use WebView for playback (DRM-protected media) and authentication.
🔧 Improve API Explorer, Don't Write One-Off Scripts — When exploring or debugging API-related functionality, always enhance
Sources/APIExplorer/main.swiftinstead of writing temporary scripts.
📝 Document Architectural Decisions — For significant design changes, create an ADR in
docs/adr/.
⌨️ Preserve Standard macOS Shortcuts — Do not override standard app/window shortcuts such as
⌘M,⌘W,⌘Q,⌘H, or⌘,unless the human explicitly asks for it. When adding media shortcuts, prefer native macOS and Apple Music conventions, and updatedocs/keyboard-shortcuts.md.
# Build
swift build
# Unit Tests (never combine with UI tests)
swift test --skip KasetUITests
# Lint & Format
swiftlint --strict && swiftformat .Default local workflow is CLI-first: use the commands above for day-to-day verification, and escalate to Xcode/xcodebuild only for simulator, UI, or runtime debugging, screenshots, or scheme-specific investigation.
⚠️ SwiftFormat--self insertrule: The project uses--self insertin.swiftformat. In static methods, call other static methods withSelf.methodName()(not baremethodName()); in instance methods, useself.propertyexplicitly.
🌐
Sources/Kaset/Resources/Localizable.xcstringsis the localization source of truth. Packaged builds compile the catalog directly, but SwiftPM/Xcode runtime builds use the checked-inSources/Kaset/Resources/*.lproj/Localizable.stringsmirrors.
- When adding localization keys or changing translations, update the catalog first and update the corresponding checked-in
.lproj/Localizable.stringsfiles in the same change. Never update only one side. - Run
swift test --skip KasetUITests --filter LocalizationCatalogParityTestsafter localization changes. - When adding a locale, also register its
.lprojinPackage.swift, add theSettingsManager.ContentLanguagecase, and extend localization tests.
🔬 Measure before you fix — never guess at runtime behavior. For any bug about timing, lifecycle, or "why didn't this run/load/update" (SwiftUI
.task/state churn, cold-launch ordering, perceived latency), instrument the real code path and observe before changing anything. Reasoning about SwiftUI lifecycle or async ordering from the source alone is unreliable; a 10-line timestamped trace settles in one launch what hours of hypothesizing cannot. Add the trace → reproduce → read the evidence → fix the thing the data points at → re-measure to confirm → remove the instrumentation.
⚠️ The app is sandboxed — most ad-hoc logging silently fails.Logger/os_log.info/.debuglines do not reliably surface inlog stream/log show, and a hardcoded/tmp/...file write is blocked by the sandbox and fails with no error. For throwaway diagnostics, write toNSTemporaryDirectory()(the app's container tmp),synchronize()after each line, and read it from~/Library/Containers/com.sertacozercan.Kaset/Data/tmp/. Macro-level: window-screenshot automation is also unreliable here, so prefer file traces over visual capture. Always strip diagnostic instrumentation before commit.
See docs/common-bug-patterns.md for the timestamped-trace template, the sandbox tmp path, the single-flight load pattern that resolves the .task-restart cancellation deadlock, concurrency anti-patterns, and the pre-submit checklist.
🔐 Keychain vs. File-based Cookie Storage: Sandboxed macOS apps without a valid Developer ID or provisioning profile (such as local debug or ad-hoc builds) will hang inside
SecItemCopyMatchingwhen querying the Keychain. To bypass this,#if DEBUGbuilds store cookies in the sandboxed Application Support folder underKaset/cookies.datand completely bypass Keychain API calls by default. To test the production Keychain path locally, launch withKASET_DEBUG_COOKIE_STORAGE=keychain. Debug and release builds share one container (UserDefaults and the WebKit cookie jar), so the debug file backend uses its own sign-out tombstone key (authCookieBackupInvalidated.debugFileStorage); never let debug-only auth state write the release key, or a debug sign-out will delete the release Keychain archive on its next launch.
For non-trivial code changes, run $autoreview (.agents/skills/autoreview/SKILL.md) before final/commit/ship and keep going until there are no accepted/actionable findings, unless the change is trivial/docs-only, equivalent manual review already happened, or the human opts out.
- Treat review output as advisory: verify every finding against the real code path before changing code.
- If review-triggered fixes change code, rerun focused tests and rerun
$autoreview. - Format before review when formatting can move line locations; focused tests and review may run in parallel only after formatting is stable.
⚠️ Explore endpoints before writing code against them. YouTube's response shapes are undocumented and change without notice, so a plausible-looking parser written from inference will compile and silently return nothing. Verify the real request and response withswift run api-explorerbefore adding or modifying an API call, request shape, or response parser. Prefer read-only probes; for mutations, prefer sanitized captured responses or disposable resources, and get explicit human approval before sending a live action that changes account data.
swift run api-explorer auth # Check auth status
swift run api-explorer list # List known endpoints
swift run api-explorer browse FEmusic_home -v # Explore with verbose outputPut repeatable, repo-specific workflows in .agents/skills/ so AGENTS.md stays focused on repo-wide rules.
These are project-specific rules that differ from standard Swift/SwiftUI conventions and are not mechanically checked:
| ❌ Avoid | ✅ Use | Why |
|---|---|---|
.background(.ultraThinMaterial) |
.glassEffect() |
macOS 26+ Liquid Glass |
Force unwraps (!) |
Optional handling or guard |
Project policy |
- Mark
@Observableclasses with@MainActor - Use Swift Testing (
@Test,#expect) for all new unit tests - Throw
YTMusicError.authExpiredon HTTP 401/403 - Use
.taskinstead of.onAppear { Task { } }
swiftlint --strict enforces the mechanically checkable bans (print(), DispatchQueue, NavigationView, foregroundColor, cornerRadius) with the substitution named in each message — read .swiftlint.yml rather than memorizing them.
For non-trivial tasks: Research → Plan → Get approval → Implement → QA. Run swift build continuously during implementation. If things go wrong, revert and re-scope rather than patching.