Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
ab606a6
plan
ptgamr Sep 16, 2026
2945f73
ios: scaffold the XcodeGen project and core package
ptgamr Sep 16, 2026
e42d1e9
ios: pin simulator CI and record the FND-01 decisions
ptgamr Sep 16, 2026
435ee55
ios: fix findings from the first codex review
ptgamr Sep 16, 2026
1eedb60
ios: port the palette, type and icon contract
ptgamr Sep 16, 2026
478cac0
ios: port the station model and cliamp radio seed
ptgamr Sep 16, 2026
f95e8f5
licenses: record the bundled Poppins face
ptgamr Sep 16, 2026
a87a4bd
licenses: add the Poppins row to the notices table
ptgamr Sep 16, 2026
15c5988
ios: add the tab shell, stations, settings and player
ptgamr Sep 16, 2026
eceaf1e
ios: port the brick meter and its smoothing core
ptgamr Sep 16, 2026
6f6c15a
ios: drive the meter from real playback FFT
ptgamr Sep 16, 2026
d68c28c
ios: port the radio-browser directory and custom stations
ptgamr Sep 16, 2026
0ea81c5
ios: add the directory and custom sections to Stations
ptgamr Sep 16, 2026
faab09f
docs: record the meter, directory and custom-station slices
ptgamr Sep 16, 2026
3eace94
ios: allow HTTP streams and log player failures
ptgamr Sep 16, 2026
f106f7e
ios: fix the audio-tap and meter findings from review
ptgamr Sep 16, 2026
2ec1b8d
ios: fix the directory findings from review
ptgamr Sep 16, 2026
cb24734
docs: record the review fixes and the ATS decision
ptgamr Sep 16, 2026
6d0fa5a
ios: load station cover art like Android
ptgamr Sep 16, 2026
7fd74a5
docs: record the artwork slice
ptgamr Sep 16, 2026
b05c4cf
ios: show the ICY stream title and match the player layout
ptgamr Sep 16, 2026
7369a36
docs: record the player parity slice
ptgamr Sep 16, 2026
9a977fb
ios: fix the artwork findings from review
ptgamr Sep 16, 2026
532c9d8
ios: add the URL preview hook and the first-slice evidence
ptgamr Sep 16, 2026
b666447
ios: port all 22 Omarchy palettes and persist settings
ptgamr Sep 16, 2026
042cad1
docs: record the Omarchy palettes and settings persistence
ptgamr Sep 16, 2026
a644283
ios: add the app icon and the launch ground
ptgamr Sep 16, 2026
8ac7ef7
docs: record the app icon and launch ground
ptgamr Sep 16, 2026
6a711f4
ios: persist favourites, history and the last station
ptgamr Sep 16, 2026
37a2c2d
ios: navigate prev/next through history and favourites
ptgamr Sep 16, 2026
a21222f
ios: drive the lock screen and interruptions from one playback owner
ptgamr Sep 16, 2026
e6447aa
ios: reconnect live radio with backoff, a stall watchdog and network …
ptgamr Sep 16, 2026
4546445
ios: fix the radio review findings
ptgamr Sep 16, 2026
13b8c3f
ios: follow m3u and pls playlist links one hop
ptgamr Sep 16, 2026
4827fce
ios: seek and speed follow what the source can do
ptgamr Sep 16, 2026
b233be4
ios: marquee long titles, split the landscape player and match the so…
ptgamr Sep 16, 2026
3875678
ios: fix the second radio review findings
ptgamr Sep 16, 2026
01222e1
docs: record the radio close-out and its review rounds
ptgamr Sep 16, 2026
7bd9254
ios: keep the now-playing artwork handler off the main actor
ptgamr Sep 16, 2026
ecb7809
docs: record the artwork-handler crash fix from the iPad
ptgamr Sep 16, 2026
b0681fc
ios: port the podcast models, feed parser and store
ptgamr Sep 16, 2026
22ac118
ios: browse, play and download podcasts
ptgamr Sep 16, 2026
3931214
docs: record the podcast journey
ptgamr Sep 16, 2026
a04ce6a
ios: keep stale podcast pages from writing state
ptgamr Sep 16, 2026
546a395
ios: fix the podcast review findings
ptgamr Sep 16, 2026
7e87243
docs: record the podcast review fixes
ptgamr Sep 16, 2026
792bb5b
ios: keep podcast details inside the shell and match the list chrome
ptgamr Sep 16, 2026
5c4e6f1
docs: record the podcast shell alignment
ptgamr Sep 16, 2026
505e791
ios: match the podcast panes, show header and search screen to Android
ptgamr Sep 16, 2026
b8edf72
ios: align the podcast and search screens with Android
ptgamr Sep 16, 2026
eeeab6f
ios: track queue occurrences and close the podcast alignment gaps
ptgamr Sep 16, 2026
cfe4306
ios: close the final podcast alignment regressions
ptgamr Sep 16, 2026
1be7336
docs: record the podcast alignment passes
ptgamr Sep 16, 2026
e24375c
ios: use the full podcast tab glyph in the show header
ptgamr Sep 16, 2026
f1e6f48
ios: index the managed music folder for the library
ptgamr Sep 16, 2026
d70c2ed
ios: open the Library tab over the managed music folder
ptgamr Sep 16, 2026
bc2ba4d
ios: harden the library scan against packages, hidden flags and bogus…
ptgamr Sep 16, 2026
d320f2e
ios: align the library panes, picker and playlist details with Android
ptgamr Sep 16, 2026
d2ecc76
ios: fix the library review follow-ups
ptgamr Sep 16, 2026
556ba03
ios: order playlists by UTF-8 bytes and serialize cache writes
ptgamr Sep 16, 2026
67211b0
docs: record the library pass and its review fixes
ptgamr Sep 16, 2026
aebb0a7
ios: add the SSH/SFTP provider core
ptgamr Sep 16, 2026
755b037
ios: verify the sftp provider against a live server
ptgamr Sep 16, 2026
03bba05
ios: add the provider accounts, wizard, browse and sftp playback
ptgamr Sep 16, 2026
8ca2a9c
ios: harden the ssh session and scan against the review findings
ptgamr Sep 16, 2026
043a3b5
ios: own the ssh transport per session and classify scan failures
ptgamr Sep 16, 2026
e6f632d
ios: separate sftp read and listing channels
ptgamr Sep 16, 2026
ce235f8
docs: record the sftp provider slice
ptgamr Sep 16, 2026
5b07294
ios: prove listing-channel recycling with a live test
ptgamr Sep 16, 2026
a2c71e6
ios: read embedded cover art for local and sftp tracks
ptgamr Sep 16, 2026
bbe4314
ios: harden embedded artwork parsing and sharing
ptgamr Sep 16, 2026
a5221de
ios: buffer id3 tags, bound the byte budget and drop finished extract…
ptgamr Sep 16, 2026
a2d94c1
ios: decode v2.4 unsync per frame and publish art before dropping ext…
ptgamr Sep 16, 2026
62681c1
ios: coordinate artwork cache checks with in-flight extractions
ptgamr Sep 16, 2026
051921f
ios: let the provider browse preview wait for its account
ptgamr Sep 16, 2026
21baeba
ios: join artwork waiters atomically and validate cached bytes
ptgamr Sep 16, 2026
310fb26
docs: record embedded artwork extraction
ptgamr Sep 16, 2026
2f149be
docs: bring the parity tracker up to date for the pull request
ptgamr Sep 17, 2026
3595222
docs: describe the implemented iOS client in the readme
ptgamr Sep 17, 2026
712238f
ios: fix the tab bar inset and the hosted test bundle's package links
ptgamr Sep 17, 2026
1c57703
ios: measure the tab bar inset without the keyboard
ptgamr Sep 17, 2026
c7f2a0a
ios: inset the player header and swap artwork without a cross-fade
ptgamr Sep 17, 2026
996deaa
ios: port android's scrubber for a draggable timeline
ptgamr Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: ios

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ios-${{ github.ref }}
cancel-in-progress: true

env:
# Pinned so the project file and the manifest agree. Bump deliberately.
XCODEGEN_VERSION: 2.45.4
XCODE_APP: /Applications/Xcode_26.6.app

jobs:
simulator:
# macos-26 ships Xcode 26.6 (17F113), the same toolchain the local
# project is developed against. See ios/README.md for the matrix.
runs-on: macos-26
defaults:
run:
working-directory: ios
steps:
- uses: actions/checkout@v4

- name: Select Xcode
run: sudo xcode-select -s "$XCODE_APP"

- name: Install XcodeGen
run: |
curl -sL -o "$RUNNER_TEMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/$XCODEGEN_VERSION/xcodegen.zip"
unzip -q "$RUNNER_TEMP/xcodegen.zip" -d "$RUNNER_TEMP"
echo "$RUNNER_TEMP/xcodegen/bin" >> "$GITHUB_PATH"

- name: Generate project
run: xcodegen generate

# Package logic is plain Swift; `swift test` runs it on the runner
# without a simulator boot.
- name: Test CliampCore
run: swift test --package-path CliampCore

# Device signing is not configured in CI: the simulator build and the
# hosted unit tests run unsigned.
- name: Build app (unsigned)
run: |
xcodebuild build \
-project Cliamp.xcodeproj \
-scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
CODE_SIGNING_ALLOWED=NO

- name: Test app (unsigned)
run: |
xcodebuild test \
-project Cliamp.xcodeproj \
-scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
CODE_SIGNING_ALLOWED=NO
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@ keystore.properties
# test artifact: local Room database dump
kls_database.db

# Xcode: the project is generated from ios/project.yml by XcodeGen
*.xcodeproj/
*.xcworkspace/
xcuserdata/
DerivedData/
.swiftpm/
.build/

# local planning scratchpad, not for upstream (for now)
plan.md
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# OpenCode

This repo is the **Android app**. The player UI lives in **cliamp**, a separate Go project.
This repo holds the **Android app** and the **iOS app**. The player UI lives in **cliamp**, a separate Go project.

## Source map

| Tree | Role |
| --------------------------------------------------- | ------------------------------------------ |
| this repository | Kotlin / Compose Android client |
| `android/` | Kotlin / Compose Android client |
| `ios/` | SwiftUI iOS client (port in progress) |
| [bjarneo/cliamp](https://github.com/bjarneo/cliamp) | TUI source of truth (Bubbletea, Lip Gloss) |

Find cliamp without machine-specific paths:
Expand Down Expand Up @@ -38,10 +39,15 @@ Two local skills own product work. Everything else is generic language guidance.

`go-skills-router`, `go-coding-standards`, `go-cli`, `go-architecture-review`, `go-concurrency-review`

**Swift / SwiftUI** — no local Swift skill set exists. Use standard Swift 6 and SwiftUI conventions, and treat `docs/design.md` plus `docs/ios-parity.md` as the product source of truth. Do not invent or claim Swift skills that are not installed.

Do not add git plugin entries to `opencode.jsonc`.

## Routing

**iOS port**
Read `docs/ios-parity.md` first; it owns scope, IDs, and state. `ios/project.yml` is the project source: run `xcodegen generate` inside `ios/` before building, and never commit the generated project. Build with `xcodebuild` (see `ios/README.md` for pinned commands). When porting behavior, read the Android source as the reference; do not edit `android/` from an iOS task.

**Android cleanup**
Load `android-kotlin-modernize` plus the Kotlin/Compose set. Do not edit cliamp.

Expand Down
1 change: 1 addition & 0 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ not. They are redistributed under their own terms.

| Component | Licence | Notes |
| --- | --- | --- |
| Poppins | SIL Open Font License 1.1 | Full text in `licenses/Poppins-OFL.txt`. Redistributing the font files requires shipping this notice. |
| JetBrains Mono 2.304 | SIL Open Font License 1.1 | Full text in `licenses/JetBrainsMono-OFL.txt`. Redistributing the font files requires shipping this notice. |
| AndroidX (core, lifecycle, activity, compose, datastore) | Apache License 2.0 | Copyright The Android Open Source Project |
| AndroidX Media3 (ExoPlayer, session, HLS, okhttp datasource) | Apache License 2.0 | Copyright The Android Open Source Project |
Expand Down
35 changes: 35 additions & 0 deletions docs/ios-parity-evidence/FND-01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# FND-01 — foundation decisions and runnable project

Task ID / owner: FND-01 / opencode (2026-09-16)

Android baseline commit / build: `21782fd233b4ab089dc1e6ea56e758a2aa2a8477` (inspected only; no device audit yet)

iOS commit / build / device / OS: `d894f39` scaffold + the CI/docs commit that follows it; iPhone 17 simulator, iOS 26.5; Xcode 26.6 (17F113), XcodeGen 2.45.4

Fixture and reproduction steps:

```sh
cd ios
xcodegen generate
swift test --package-path CliampCore
xcodebuild build -project Cliamp.xcodeproj -scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' CODE_SIGNING_ALLOWED=NO
xcodebuild test -project Cliamp.xcodeproj -scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' CODE_SIGNING_ALLOWED=NO
xcrun simctl install <device> "$(find ~/Library/Developer/Xcode/DerivedData/Cliamp-*/Build/Products/Debug-iphonesimulator -maxdepth 1 -name Cliamp.app)"
xcrun simctl launch <device> stream.cliamp.mobile
```

Functional result and test link: package tests 2/2 passed (`swift test`), app tests 2/2 passed (`xcodebuild test`), unsigned simulator build succeeded, app launched to the placeholder shell. Gate F: P (local). Warnings are errors on both paths: app targets via `SWIFT_TREAT_WARNINGS_AS_ERRORS` / `GCC_TREAT_WARNINGS_AS_ERRORS`, CliampCore via `-warnings-as-errors` in its manifest (verified in the `swiftc` invocations for both `swift test` and the Xcode build). CI workflow `.github/workflows/ios.yml` pins `macos-26` + Xcode 26.6 + XcodeGen 2.45.4; its first GitHub run is pending a push from the owner.

Visual result and paired capture link: not applicable — the placeholder shell has no product visuals; VIS work starts in phase 1. Gate V: NA.

Experience result and video/device notes: not applicable — no product interactions exist yet. Gate X: NA.

Persistence / offline / error checks: not applicable to the scaffold.

Decisions recorded (ios/README.md): iOS 18.0 floor (DEC-03 surfaces), universal iPhone + iPad portrait/landscape, SwiftUI + Swift 6, XcodeGen generated project, app + local SPM packages, unsigned simulator CI, automatic device signing with no repo credentials. Device matrix: CI on iPhone 17 simulator; visual review on iPhone 17e / 17 Pro Max / iPad mini / iPad Pro 11; physical iPhone named by owner before `RAD-11`.

Known difference or blocker / decision ID / next action: CI has not executed on GitHub yet; run it on the next push and move FND-01 to `D` if green. XcodeGen 2.45.4 is pinned in CI but not enforced locally against a different version; `minimumXcodeGenVersion` guards only the manifest schema. A codex `gpt-6-astra` review of the first two commits (2026-09-16) found four issues — package warnings-as-errors not enforced, a wrong Clang warnings-as-errors key, device-matrix references pointing nowhere, and stale tracker prose — all fixed in the follow-up commit. FND-02 (Android reference capture) and FND-03 (audio spike) are the next dependency-ready rows.

Reviewer / date: pending
34 changes: 34 additions & 0 deletions docs/ios-parity-evidence/first-view-slice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# First view slice — design contract, shell, Stations, settings, player

Task IDs / owner: VIS-01–06, RAD-01, RAD-06–07, SET-01 (all W) / opencode (2026-09-16)

Android baseline commit / build: `21782fd233b4ab089dc1e6ea56e758a2aa2a8477` (source read for palette, type, icons, chrome, controls, Stations, Settings, Now Playing)

iOS commit / build / device / OS: `ee947cb`, `4c88b38`, `3753476`; iPhone 17 simulator, iOS 26.5; Xcode 26.6

Fixture and reproduction steps:

```sh
cd ios
xcodegen generate
swift test --package-path CliampDesign # 15 tests
swift test --package-path CliampCore # 11 tests
xcodebuild build -project Cliamp.xcodeproj -scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' CODE_SIGNING_ALLOWED=NO
xcodebuild test -project Cliamp.xcodeproj -scheme Cliamp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' CODE_SIGNING_ALLOWED=NO # 3 app tests
```

Preview hooks (DEBUG only) drive the screens for capture: `-cliamp-preview-player` plays the first seed station and opens the player, `-cliamp-preview-playing` does the same without opening it, `-cliamp-preview-url <url>` plays any URL, `-cliamp-preview-directory` / `-cliamp-preview-custom` open those filters (and the custom add form).

Functional result and test link: design suite 15/15 (palette values, alpha-first hex, SVG path arcs/sweep/adjacent decimals, icon shape counts); core suite 11/11 (seed list, m3u parsing, model derived fields, time formatting); app suite 3/3 (package linkage, root construction, all six font faces registered). Stations fetched 15 live channels from `streams.m3u` on the simulator and fell back to the twelve-channel seed when parse/fetch fails (fallback path unit-tested via `parseGarbage`, live offline test still open). AVPlayer played the Lofi stream with ON AIR, buffering and error states observed.

Visual result and paired capture link: simulator captures reviewed during development — Stations in oxide-light and oxide, Now Playing (dark), Settings (dark). Paired Android captures do not exist yet (FND-02); gates stay unchecked.

Experience result and video/device notes: transport press travel renders in both themes; favourite star toggles in place; tab bar and rail switch at the landscape breakpoint. No device feel review, no video, no VoiceOver pass.

Persistence / offline / error checks: favourites, theme choice and all settings are in-memory for this slice; the app returns to "nothing playing" on relaunch. Offline playlist fallback is implemented but not exercised on-device. A failed stream surfaces "STREAM ERROR" text; no backoff yet (RAD-10).

Known difference or blocker / decision ID / next action: at the time of this slice the player had no ICY metadata, artwork, directory, search, queue or persistence; all but queue/search/persistence landed in later commits recorded in the activity log, and the codex review of the shell/model/radio code was resolved before any gate was set.

Reviewer / date: pending
Loading
Loading