Thanks for helping improve vocaphone. Changes should keep the privacy-first architecture, the documented network-exposure controls, and the iOS keyboard constraints intact.
By participating, you agree to follow the Code of Conduct. For usage questions and support routing, see SUPPORT.md. Report security issues privately via SECURITY.md.
- Report reproducible bugs with the bug report template
- Propose focused improvements with the feature request template
- Improve docs in
README.mdordocs/ - Fix bugs or add tests for gateway, iOS, or Android behavior
- Review pull requests for privacy, security, and platform-constraint regressions
Look for issues labeled good first issue or help wanted when those labels
are available.
Coding-agent instructions (IME frame budget, InputConnection, benchmarks)
live in AGENTS.md and android/AGENTS.md.
- Clone with submodules:
git clone --recurse-submodules …(orgit submodule update --init --recursiveon an existing clone). The gateway is the vocagateway submodule atgateway/. - For iOS, run
just ios fetch(orbash ios/ThirdParty/SherpaOnnx/fetch.sh) after cloning. That downloads the pinned sherpa-onnx iOS no-TTS xcframeworks from GitHub Releases. They are not stored in git. - Install
just, Xcode, XcodeGen,uv, and FFmpeg. - For Android work, install a recent Android Studio / SDK and JDK 21 (the exact major version matters: F-Droid rebuilds the APK on JDK 21, so reproducible builds require the same javac).
- Run
just ios genafter changingios/project.yml, and commit the regenerated project. The other iOS recipes regenerate it for you; this one matters because CI fails when the checked-in project is stale. - Gateway-only changes belong in vocagateway (open the PR there, then bump the submodule pin here if this repo needs the new revision).
- Never commit microphone recordings, bearer tokens, signing material, tailnet hostnames, local database files, or Apple provisioning profiles.
The shared ios/VocaPhone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
file pins WhisperKit and its transitive Swift packages for reproducible iOS
builds. Keep it committed when Swift package dependencies change. Do not
commit Xcode user data under xcuserdata/.
Each application owns a justfile, and the repository root aggregates them, so recipes work from either place:
just --list # cross-cutting recipes and the three modules
just --list gateway # one application's recipes
cd gateway && just test # same as `just gateway test` from the root
just doctor # what each application's toolchain is missingjust doctor is the fastest way to find out what a fresh machine still needs;
it reports all three toolchains and never fails, because nobody has all of them
installed at once.
The parent repository records a fixed gateway/ SHA so clones and release builds
are reproducible. That pin is what CI and shipped apps use.
For day-to-day gateway work you can move the local checkout to the tip of
main without changing what this repo ships:
just gateway-pin-status # pin vs working tree vs origin/main
just gateway-sync # git submodule update --remote gateway (main tip)
just gateway install # after a sync, if dependencies moved
just gateway rungateway-sync only updates your working tree. It does not commit a new pin.
When this repo should adopt a newer gateway (or a release tag), do it on purpose:
just gateway-sync # or: cd gateway && git fetch --tags && git checkout vX.Y.Z
git add gateway
git commit -m "build: pin vocagateway to <sha or tag>"Phone-only work can leave the recorded pin alone. Shipping builds must never run
gateway-sync in CI; they check out the committed pin only.
direnv is set up but not required; every command in this
repository works without it. With direnv installed, run direnv allow once
after cloning. The checked-in .envrc then puts the gateway virtualenv and the
Android SDK's platform-tools on PATH, so pytest, ruff and adb work
without uv run or a full path, and exports ANDROID_HOME.
Machine-specific settings belong in .envrc.local, which is gitignored and
sourced automatically:
export VOCAPHONE_SIM='iPhone 17 Pro' # which simulator ios/justfile uses
export ANDROID_SERIAL=emulator-5554 # which device android/justfile targets.envrc deliberately does not load gateway/.env. That file holds the Compose
bearer token, and the gateway reads VOCAGATEWAY_TOKEN straight from the
environment, so exporting it would make a natively run gateway serve the
container's token instead of the one in ~/.config/vocagateway/token — silently,
because both are valid. Compose reads that file by itself. For the same reason,
only the repository-root .envrc is tracked; any nested one is gitignored,
since the quickest way to make gateway/.envrc is to copy gateway/.env into
it, secret and all.
Each application has one recipe that runs everything its workflow gates on. Run the one for what you changed:
just gateway install # once, and after dependency changes (submodule)
just gateway test # lint, types, dependency audit, unit tests, Compose
just ios ci # regenerates the project, builds, runs the unit tests
just android ci # assembles, unit tests, lint, Room schema freshnessjust ci from the repository root runs all three and skips any whose toolchain
is absent, which is what a contributor with only one platform installed wants.
iOS/Android recipes match the workflows in .github/workflows/. Gateway quality
and container CI run in vocagateway; use just gateway test / just gateway image
against the submodule when you change the pin or work on the gateway itself.
When changing documentation, check local links and commands against the current
repository layout, then run git diff --check. Do not publish machine-specific
paths, real tailnet hostnames, tokens, recordings, or transcript samples.
Keyboard, microphone, background-audio, and insertion changes must also be
verified on a physical iPhone — just ios device builds and installs onto a
connected phone, and docs/device-setup.md has the
acceptance sequence. Describe the tested app, iOS version, and exact
interaction sequence in the pull request.
For Android changes, note whether the floating bubble was exercised on a
physical device; just android run installs and launches on one, and
just android permissions grants what the bubble needs.
- Keep changes focused and document user-visible behavior.
- Add or update tests for state transitions, gateway behavior, and regressions.
- Update README or
docs/when setup, privacy, security, or architecture changes. - Do not weaken loopback gateway binding, bearer authentication, upload limits, retention, or explicit microphone indicators without discussing the tradeoff.
- Use the pull request template checklist; skip checks that truly do not apply and say why.
Maintainers can comment one of these on a pull request (same idea as VocaMac's
DMG /build):
| Comment | Result |
|---|---|
/build |
Android release APK + iOS ad-hoc IPA (IPA only if signing secrets are set) |
/build android |
Android release APK only |
/build ios |
iOS ad-hoc IPA only |
/build-quick |
Android debug APK only (no release keystore) |
The workflow reacts with a rocket, posts a started note, uploads artifacts, and
replies with download links. Only OWNER / MEMBER / COLLABORATOR comments
trigger it, so fork PRs need a maintainer to run the build.
Android uses the same release keystore as beta tags, so the APK replaces an
installed beta. Install with adb install -r … or by opening the APK on the
device.
iOS needs these repository secrets for an IPA (ad-hoc profiles must include the tester's device UDID):
IOS_CERTIFICATE_P12_BASE64IOS_CERTIFICATE_PASSWORDIOS_PROVISION_PROFILE_APP_BASE64IOS_PROVISION_PROFILE_KEYBOARD_BASE64IOS_PROVISION_PROFILE_LIVEACTIVITY_BASE64
Without those secrets, /build still produces the Android APK and notes that
iOS was skipped. You can also run the workflow manually under Actions → PR Build.
Version tags are platform-prefixed: android/v0.1.1 publishes Android,
ios/v1.0.21 uploads iOS to TestFlight. They can share a commit but never a
tag. See releasing.md.
Discord is the fastest place to talk with maintainers and other people building VocaPhone. Follow @vocahq on X for release notes.
Contributions are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), the same license used by VocaMac and VocaLinux (both AGPL-3.0). By opening a pull request, you agree that your contribution may be distributed under that license.