AGENTS.md
4989 bytes
kaiwari — iOS client
IMPORTANT
- Never edit generated files:
xtool/(includingxtool/kaiwari.app/Info.plist) and.build/are wiped and regenerated on every build. The Info.plist source of truth isResources/Info.plist, merged at build time viainfoPathinxtool.yml. - Do not write any tests; there is no test suite in this project.
- Never start, stop, or restart the live model services (
llama-server,audiocpp_server) orkaiwari-server, and never load models into VRAM. They are user-managed; integration testing is manual and user-run only.
Architecture
Kaiwari is a spoken-Japanese learning game. This repo is the thin iOS client for
kaiwari-server (sibling repo ../server). The app is a terminal for the game: it
renders the adventure log, captures push-to-talk audio, and plays NPC speech. All game
logic, ASR, judging, and TTS live on the server; the client's only state is the session
and UI phase.
Tech stack: SwiftUI (Liquid Glass), Swift 6, SPM + xtool (no Xcode project), AVFoundation (record/play), URLSession.
Layers:
- Wire:
APIClient.swiftmust match../server/internal/server/handlers.goexactly (DTOs, endpoints, bearer auth).TurnResponseis a flat struct = log entry fields +location/talk/audio/speakError/judgeError. Kana never crosses the wire; the UI is romaji-only by construction. Plain HTTP is intentional (VPN-only use): scheme check inkaiwariShared/ServerConfiguration.swift, ATS opt-out (NSAllowsArbitraryLoads) inResources/Info.plist. - State:
GameStore(@Observable @MainActor) owns the phase machineidle/recording/processing: session create + client-fired opening turn "Look around.", reconnect on launch via persisted session id (UserDefaults) + log fetch (any failure = fresh start), 15 s record cap with auto-stop. Bare?/!prefixes are rejected and keep the input buffer (TUI parity). - UI:
GameView(bottom-pinned log list, empty state, toolbar: replay / new adventure / settings, Liquid Glass input bar) andEntryView(per-turn card: action, desc, romaji + translation reveal chips, judge block, ask block). - Audio:
Recorder→ 16 kHz mono 16-bit WAV temp file (~480 KB at the cap, under the server's 2 MB upload limit).Playerauto-plays the NPC line on turn completion; replay serves the client-side cache (the server only sends audio in turn responses, never in logs). - Config:
SettingsStore+KeychainStore; server URL and bearer token, token in the device keychain.
Project Structure
ios/
├── Package.swift # SPM manifest; deployment targets (iOS/macOS 26)
├── xtool.yml # bundleID, Info.plist path
├── Resources/Info.plist # plist source of truth (ATS, mic usage string)
└── Sources/
├── kaiwari/ # app target
│ ├── KaiwariApp.swift # @main entry
│ ├── ContentView.swift # settings gate → game
│ ├── SettingsStore.swift / SettingsView.swift
│ ├── KeychainStore.swift
│ ├── GameStore.swift # phase machine, session lifecycle
│ ├── GameView.swift # log list, toolbar, input bar
│ ├── EntryView.swift # per-turn card with reveal chips
│ ├── Recorder.swift / Player.swift
│ └── APIClient.swift # wire DTOs + HTTP client
└── kaiwariShared/
└── ServerConfiguration.swift # URL/token, scheme check
Development Workflow
- Build/verify:
xtool dev buildfrom this directory. Zero warnings is the bar. There is no simulator on this host (Linux cross-compile); visual and audio behavior are verified manually by the user on device. - Identity: bundle id lives in
xtool.yml, keychain service name inKeychainStore.swift. Changing either orphans installed-app state (reinstall / one-time reconfigure). - Liquid Glass (
.glassEffect,GlassEffectContainer,.buttonStyle(.glass...)) requires the iOS 26 deployment target — keep both platform entries inPackage.swiftat 26.0. Note: a glass effect applied to a button label only hit-tests the icon glyph; add.contentShape(.interaction, <shape>)covering the full control so the whole control is tappable. - Server contract: after changing anything in
../server/internal/server/handlers.go, port the DTO/endpoint changes intoAPIClient.swift.
Subagents
- Run at most one worker subagent at a time, sequenced; never spawn two in parallel.
- Sequence work in dependency-ordered batches; wait for each agent to finish before starting the next.
- Give workers a plain-language description of the change (goal, behavior, constraints, how to verify), not a full file to transcribe. The main context does the design and thinking; the worker turns that into code.
- Workers commit their changes when done: one commit per batch with a short, polished message describing the change.