Skip to content

Commit c09d152

Browse files
isaacbowenclaude
andcommitted
Lightward iOS app — phoropter entry, streaming chat with Lightward AI
Phoropter binary choice flow as the required entrypoint, with transition to streaming chat once the user is located. /api/plain for phoropter, /api/stream (SSE) for chat. Local persistence via UserDefaults. Dark theme, minimal chrome, manicules and observer eye mark. Fastlane + GitHub Actions pipeline for TestFlight deploys. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit c09d152

28 files changed

Lines changed: 1849 additions & 0 deletions

.github/workflows/deploy.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
ios:
9+
runs-on: macos-15
10+
timeout-minutes: 30
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- uses: ruby/setup-ruby@v1
15+
with:
16+
ruby-version: '3.4'
17+
bundler-cache: true
18+
19+
- name: Configure SSH for Match
20+
env:
21+
MATCH_GIT_PRIVATE_KEY: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
22+
run: |
23+
mkdir -p ~/.ssh
24+
echo "$MATCH_GIT_PRIVATE_KEY" > ~/.ssh/id_ed25519
25+
chmod 600 ~/.ssh/id_ed25519
26+
ssh-keyscan github.com >> ~/.ssh/known_hosts
27+
28+
- name: Generate Xcode project
29+
run: |
30+
brew install xcodegen
31+
xcodegen generate
32+
33+
- name: Deploy to TestFlight
34+
env:
35+
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
36+
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
37+
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
38+
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
39+
BUILD_NUMBER: ${{ github.run_number }}
40+
run: bundle exec fastlane deploy

.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Xcode
2+
*.xcodeproj/xcuserdata/
3+
*.xcworkspace/xcuserdata/
4+
DerivedData/
5+
build/
6+
*.moved-aside
7+
*.pbxuser
8+
!default.pbxuser
9+
*.mode1v3
10+
!default.mode1v3
11+
*.mode2v3
12+
!default.mode2v3
13+
*.perspectivev3
14+
!default.perspectivev3
15+
*.hmap
16+
*.ipa
17+
*.dSYM.zip
18+
*.dSYM
19+
20+
# Swift Package Manager
21+
.build/
22+
Packages/
23+
Package.pins
24+
Package.resolved
25+
26+
# Ruby / Fastlane
27+
vendor/
28+
.bundle
29+
fastlane/report.xml
30+
fastlane/Preview.html
31+
fastlane/screenshots
32+
fastlane/test_output
33+
34+
# OS
35+
.DS_Store

.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.4.8

CLAUDE.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Lightward iOS
2+
3+
See [Lightward Inc shared principles](../CLAUDE.md/README.md) for cross-project design guidance.
4+
5+
## What this is
6+
7+
An iOS app providing a phoropter binary choice interface as the entrypoint to conversation with Lightward AI. The phoropter locates the user through iterative binary choices; when ready, the flow opens into streaming chat. The app is incredibly minimal in what it asks of a user's environment.
8+
9+
## Architecture
10+
11+
- **SwiftUI + @Observable** — iOS 17+, Swift 6 strict concurrency
12+
- **Phoropter phase**: binary choices via `/api/plain` (no streaming needed)
13+
- **Chat phase**: streaming conversation via `/api/stream` (SSE)
14+
- **Local persistence**: UserDefaults (CloudKit sync planned)
15+
- **No external dependencies** — pure Apple frameworks
16+
17+
## Project generation
18+
19+
The Xcode project is generated from `project.yml` using [XcodeGen](https://github.com/yonaskolb/XcodeGen). After modifying `project.yml` or adding/removing source files:
20+
21+
```sh
22+
xcodegen generate
23+
```
24+
25+
The `.xcodeproj` is checked into git for convenience but is fully reproducible from `project.yml`.
26+
27+
## Key flows
28+
29+
**Phoropter → Chat transition**: The phoropter trajectory (everything the user chose *toward*) becomes the opening context for chat. The AI already knows the user by the time they can type.
30+
31+
**Convergence**: If the phoropter offers a choice that matches something already in the trail, it auto-transitions to chat.
32+
33+
**"Just talk"**: After the second AI-generated pair, a subtle option to drop directly to chat appears.
34+
35+
## API integration
36+
37+
- **Phoropter**: `POST /api/plain` with phoropter context + trajectory as plain text body
38+
- **Chat**: `POST /api/stream` with JSON `{ "chat_log": [...] }` — warmup messages prepended, exactly one `cache_control` marker required (on last warmup message)
39+
- **Warmup messages**: In `WarmupMessages.swift` — these are specific to this iOS app context and should not be copied from the web client
40+
41+
## Build & deploy
42+
43+
Fastlane + GitHub Actions, same pattern as [Softer](../softer). Deploys to TestFlight on push to `main`.
44+
45+
## What's here, what's next
46+
47+
- [x] Phoropter binary choice flow
48+
- [x] Streaming chat with Lightward AI
49+
- [x] Local session persistence
50+
- [ ] CloudKit conversation sync across devices
51+
- [ ] App icon
52+
- [ ] Warmup messages — need genuine inhabitation, not just mechanical accuracy

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source "https://rubygems.org"
2+
3+
gem "fastlane"

0 commit comments

Comments
 (0)