The iOS & Android companion app for SoftTrack — a self-hostable issue tracker.
A mobile client that mirrors the full SoftTrack web feature set — boards, issues, cycles, reports, notifications, search, and administration — talking to the existing FastAPI backend of any SoftTrack instance. Sign-in takes three inputs: the server link (your instance URL), email, and password.
The design mockups and some issue text say "username". The API only ever resolves the OAuth2
usernamefield against the email column (backend/lib_identity/identity.py), so the app asks for an email. Adding a username lookup would be a backend change.
You need a SoftTrack instance to point the app at. The quickest one is the reference stack:
cd ../soft-track && docker compose up --buildThat serves the API on :8000 with a seeded demo account (demo@softtrack.dev / password123).
Then start the app:
npm install && npx expo startOpen it in Expo Go. At the login screen enter your machine's LAN address as the server link — http://192.168.x.x:8000, not localhost, which on a phone means the phone.
src/app/ expo-router routes — thin wrappers, no logic
(auth)/ signed out: login, register
(app)/ signed in: Home, Board, Search, Inbox, You
invite/[token] an invitation — readable either way, so guarded by neither
[teamKey] /ENG — sets the active team, then hands to Board
src/api/ HTTP client, instance URL, generated client
src/auth/ session store, auth context, deep-link capture
src/team/ team context, switcher, creation, invitations
src/board/ board and list views, filters, drag-to-move
src/issues/ priority and status metadata
src/offline/ connectivity, cache persistence, the mutation queue
src/ui/ design tokens, theme, primitives, navigation chrome
openapi/ the vendored API contract codegen reads
src/ mirrors the web app's frontend/src/ naming wherever the concept is the same, so moving between the two repos is uneventful.
src/api/generated/ is Orval output — typed React Query hooks — built from openapi/openapi.json, a vendored copy of the schema the backend produces. Same config as the web app, so hook names match in both clients. Never edit it by hand:
npm run sync:openapi # refresh the vendored schema
npm run generate:api # regenerate the clientCI fails if the committed client does not match the committed schema, and a weekly job flags when the vendored schema falls behind the backend.
Tokens in src/ui/tokens.ts are transcribed from the web's frontend/src/index.css. Note the neutral ramp is inverted in dark mode, so the two palettes are built explicitly rather than derived. The stored key (softtrack.theme) and its contract — absent means "follow the system" — match the web exactly.
npm run lint && npm run typecheck && npm testTwo integration suites are opt-in because they need a live server. They drive the real client modules against it, and the onboarding one writes real rows (fresh accounts and team keys per run):
SOFTTRACK_LIVE_URL=http://localhost:8000 npm testThe live suites between them register a handful of real accounts per run, and
registration is throttled per IP and charged even on success — so a few full runs
in a row will eventually 429. The counter lives in the API process, so
docker compose restart backend && docker compose up -d clears it without
touching the database.
Note experiments.typedRoutes is a dev-time aid: the route union is written by
expo start, not by expo export, so outside the dev server every path
typechecks permissively. Paths built at runtime go through href() in
src/ui/href.ts rather than being cast at each call site.
- Work is tracked on the Mobile App project.
- Design mockups live in
docs/design/mobile/— one SVG per feature issue, each showing the screen at three window size classes:- Phone — compact, <600dp: single pane, bottom navigation
- Foldable (unfolded) — medium, 600–839dp: two panes split at the hinge
- Tablet — expanded, ≥840dp: nav rail with persistent multi-panel layouts
The mockups reuse the web app's design tokens (brand #6342db, tinted neutrals, status/priority colors) from frontend/src/index.css in the main repo, so mobile and web share one visual language.
Every tracked issue is implemented except push notifications, which have no backend to talk to. Sign in to any instance, register, accept invitations, manage teams, work a board, create and edit issues with sub-issues and links, comment in GitHub-flavored markdown, attach photos and files, search, run cycles, read the four reports, save views, administer a team or the whole instance, and keep working with no connection.
Known gaps, tracked rather than hidden:
- Nothing has been seen on a device yet. The suites cover the logic, the mockup geometry and that the trees render, but no one has run this on hardware. The drag gesture in particular is unverified by touch, which is why every move is also reachable by tapping a card. The medium and expanded layouts would need a tablet or an Android emulator to check for real.
- Invitation links cannot be true universal links. A
https://your-instance/invite/…link can only open the app if that exact domain is declared in the build, which is impossible for arbitrary self-hosted hosts.softtrack://invite/<token>works, and falls back to asking you to sign in first, since a custom-scheme link carries no instance. - Push notifications are not built, because the API has no way to register a
device. Issue #10 anticipated this ("requires a backend addition for device
token registration + push delivery"). There is no
/devicesendpoint and nothing APNs- or FCM-shaped anywhere in the schema, so the in-app inbox and the unread badge are what ship; the badge polls once a minute while the app is in front rather than pretending to be pushed. Real push also needs a dev build and signing credentials, neither of which exists yet. - Member counts cost one request per team.
TeamReadcarries no count, so the teams list asks each team for its members. Amember_countfield upstream would remove the fan-out and help the web too. - Offline is verified as logic, not as behaviour. The queue, the conflict rule and the cache are covered by tests, but nobody has watched a phone lose signal, move a card and come back. That needs a device.
- No aurora or glass blur yet. The web's translucent panels are approximated
with opaque surfaces;
backdrop-filterhas no React Native equivalent and per-surface blur is expensive on Android.