feat(client): scaffold @carol/client — Expo Router + RN Web (#183) #198
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!198
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "183-expo-client-scaffolding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Stands up
apps/client/— the universal Expo Router + React Native Web app. After this lands,pnpm -F @carol/client export:webproduces a static bundle that #186 will copy into the API container,expo run:androidwill boot the app on a real device against a self-hoster's API URL, and one reference screen (/notes) reads + writes data end-to-end through@carol/api-client.Two commits:
f752a5echore(client): scaffold — real package.json (@carol/client), tsconfig (extendsexpo/tsconfig.base), app.json (web + Android stub, expo-router + expo-secure-store + expo-localization plugins, typedRoutes on), eslint flat config (typescript-eslint strict — same baseline as@carol/api-client), vitest config (node env), and a workspace-scoped.npmrcwithnode-linker=hoistedso Metro's resolver doesn't fight pnpm's strict store.1c32b55feat(client): theme + i18n + auth glue + Notes reference + CI gates — all the source. End-to-end verified in a Node 22 container matching CI; the static web bundle ships underapps/client/dist/.Decisions baked in
/notes. Simpler than/skills(which the ticket suggested), exercises cursor pagination throughuseNotes, matches ADR-0012's canonical TanStack reference. Skills + every other screen land in #184.lib/theme/tokens.ts. Mirrorsapps/api/app/themes/{light,dark}.cssverbatim. Skips the legacy--color-*bridge (ADR-0023) — Expo client is greenfield.i18next+react-i18next+expo-localization. Catalog imports unchanged from@carol/i18n. Pure resolution logic ported fromapps/api/lib/i18n/locales.tsminus the env-var coupling (the client bakes its supported set in at build time).useSettings()+useUpdateSettings()round-trip through@carol/api-client.lib/auth/storage.tsis SecureStore on native, no-op on web (cookies cover it).getAuthHeader()returnsBearer cat_...or null per request.(app)routes. Per ADR-0027 each is implemented from the design spec + feature ticket in #184 — the stubs exist so the routing tree is complete.Out of scope (per the approved plan)
Test plan
pnpm installclean.pnpm -F @carol/client typecheckgreen.pnpm -F @carol/client lintgreen.pnpm -F @carol/client testgreen (8 tests covering locale resolution).pnpm -F @carol/client export:websucceeds and producesapps/client/dist/.pnpm -F @carol/api typecheck/lint/test/openapi:check/openapi:coverage,pnpm -F @carol/api-client typecheck/lint/test/check).apps/api: register → /notes → create/edit/delete a note. (Author hasn't run yet; the CI export confirms the bundle compiles.)expo run:androidagainst an emulator — deferred; the scaffolded Android config is untested without an Android SDK.Closes #183. Fifth ticket under epic #176. Unblocks #184 (screen ports), #186 (single-container deployment), #187 (Android build), #188 (Flatpak).
Fills in @carol/client with the source that turns the scaffold into a working app. End-to-end verified in a Node 22 container matching CI: typecheck, lint, test (8 tests), and `expo export --platform web` all green; the static bundle ships under apps/client/dist/. What landed: - **Theme runtime** (`lib/theme/`): DS token constants (light + dark) mirroring apps/api/app/themes/{light,dark}.css verbatim, minus the legacy --color-* bridge — the Expo client is greenfield and ADR-0023's bridge doesn't apply. ThemeProvider reads useSettings() from @carol/api-client for persistence; auto is resolved by RN's useColorScheme(). - **i18n runtime** (`lib/i18n/`): i18next + react-i18next + the resolution chain. Catalogs imported directly from @carol/i18n (en + es). expo-localization supplies the OS candidate list; the resolver mirrors the API's logic without the env-var coupling. - **Auth glue** (`lib/auth/`): expo-secure-store on native (no-op on web — same-origin cookies cover it); getAuthHeader() is the async callback @carol/api-client's createApiClient consumes. login.ts branches on Platform.OS: web POSTs /api/auth/login, native POSTs /api/auth/token and stores the returned pair. - **Expo Router skeleton**: root layout mounts the providers in order (Query → ApiClient → I18n → Theme). The (app) layout reads useMe() and redirects to /login on 401. Login/register are minimal forms; index.tsx redirects to /notes. The remaining (app) screens (profile, skills, experience, …) are stubs per ADR-0027 — implemented from the design spec in #184. - **Notes reference screen** (`app/(app)/notes.tsx`): full CRUD end-to-end against the API via @carol/api-client. Uses theme tokens, translated strings, edit-in-place, optimistic invalidation. Proves the Expo + RN Web + TanStack + typed-client loop. - **CI gates**: @carol/client added to the lint, typecheck, and test jobs. New `client-build` job runs `expo export --platform web` as a smoke. Android export deferred to #187 (needs Android SDK in the runner image). - **Docs**: apps/client/README.md replaces the placeholder. CLAUDE.md "Shape of the system" points at it. .gitignore covers Expo's local dev state (.expo/). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>OSV-Scanner
Threshold:
high· Total findings: 8 · At/above threshold: 24.1.08.0.16📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.
Trivy (container image)
Threshold:
high· Total findings: 121 · At/above threshold: 16.27.0, 7.28.0, 8.5.0