feat(client): port profile/skills/experience screens (#184) #201

Merged
james merged 4 commits from 184-career-core-screens into main 2026-06-21 12:24:48 +00:00
Owner

Summary

Ports the career core slice of #184 — profile, skills, and experience — to the Expo Router universal client, consumed exclusively through @carol/api-client per ADR-0027. The screens follow the Notes reference pattern (apps/client/app/(app)/notes.tsx): TanStack Query via the typed hooks, DS tokens via useTheme(), every user-facing string through react-i18next.

  • Profile (profile.tsx) — bundled GET, basics edit, contacts CRUD with kind picker. Picture is read-only this slice (initials avatar + manage-from-web hint); upload needs expo-image-picker and lands in a follow-up.
  • Skills (skills.tsx) — sections + skills with collapse, page-level Edit toggle, per-section rename/move/delete and per-skill move/delete. Collapse state is local-only here (localStorage isn't available on native; a persisted preference can come later).
  • Experience (experience.tsx) — Jobs → Positions → Contributions drill-down on the bundled useJobs() tree. Each level supports create/edit/delete; drill-down state survives refetches via id lookup. Education + Contracts tabs are out of scope this slice.

No new hooks were needed — every operation maps to a hook already exported by @carol/api-client. The drift gate stays green.

Test plan

  • pnpm install --frozen-lockfile
  • pnpm -F @carol/api typecheck
  • pnpm -F @carol/api lint
  • pnpm -F @carol/api test (44 files, 515 passed | 107 skipped — Postgres skips, expected without TEST_POSTGRES_URL)
  • pnpm -F @carol/api openapi:check
  • pnpm -F @carol/api openapi:coverage
  • pnpm -F @carol/api-client typecheck
  • pnpm -F @carol/api-client lint
  • pnpm -F @carol/api-client test
  • pnpm -F @carol/api-client check
  • pnpm -F @carol/client typecheck
  • pnpm -F @carol/client lint
  • pnpm -F @carol/client test
  • pnpm -F @carol/client export:web
  • Manual: log in via the web export, exercise each screen against a running API

Out of scope

  • Account / Network / Education / Projects / Applications / Chat screens — separate slices of #184.
  • Decommissioning the Next.js UI — that's #185.
  • Picture upload on the universal client — needs expo-image-picker, follow-up ticket.
  • Education + Contracts tabs under Experience — separate ticket.

Links: #184, ADR-0027.

## Summary Ports the **career core** slice of #184 — profile, skills, and experience — to the Expo Router universal client, consumed exclusively through `@carol/api-client` per [ADR-0027](../docs/adr/0027-frontend-backend-split-and-universal-client.md). The screens follow the Notes reference pattern (`apps/client/app/(app)/notes.tsx`): TanStack Query via the typed hooks, DS tokens via `useTheme()`, every user-facing string through `react-i18next`. - **Profile** (`profile.tsx`) — bundled GET, basics edit, contacts CRUD with kind picker. Picture is read-only this slice (initials avatar + manage-from-web hint); upload needs `expo-image-picker` and lands in a follow-up. - **Skills** (`skills.tsx`) — sections + skills with collapse, page-level Edit toggle, per-section rename/move/delete and per-skill move/delete. Collapse state is local-only here (localStorage isn't available on native; a persisted preference can come later). - **Experience** (`experience.tsx`) — Jobs → Positions → Contributions drill-down on the bundled `useJobs()` tree. Each level supports create/edit/delete; drill-down state survives refetches via id lookup. Education + Contracts tabs are out of scope this slice. No new hooks were needed — every operation maps to a hook already exported by `@carol/api-client`. The drift gate stays green. ## Test plan - [x] `pnpm install --frozen-lockfile` - [x] `pnpm -F @carol/api typecheck` - [x] `pnpm -F @carol/api lint` - [x] `pnpm -F @carol/api test` (44 files, 515 passed | 107 skipped — Postgres skips, expected without `TEST_POSTGRES_URL`) - [x] `pnpm -F @carol/api openapi:check` - [x] `pnpm -F @carol/api openapi:coverage` - [x] `pnpm -F @carol/api-client typecheck` - [x] `pnpm -F @carol/api-client lint` - [x] `pnpm -F @carol/api-client test` - [x] `pnpm -F @carol/api-client check` - [x] `pnpm -F @carol/client typecheck` - [x] `pnpm -F @carol/client lint` - [x] `pnpm -F @carol/client test` - [x] `pnpm -F @carol/client export:web` - [ ] Manual: log in via the web export, exercise each screen against a running API ## Out of scope - Account / Network / Education / Projects / Applications / Chat screens — separate slices of #184. - Decommissioning the Next.js UI — that's #185. - Picture upload on the universal client — needs `expo-image-picker`, follow-up ticket. - Education + Contracts tabs under Experience — separate ticket. Links: #184, [ADR-0027](../src/branch/main/docs/adr/0027-frontend-backend-split-and-universal-client.md).
The Expo client's career-core screens (#184) need a handful of new
keys the existing PWA catalog doesn't carry — lede copy for profile,
the contact-add form's kind picker + placeholders, a few skills-edit
labels, and a manage-from-web hint for the picture card (native upload
lands later). All sentence-case and in Carol's first-person voice.
Mirrors the Notes reference pattern (apps/client/app/(app)/notes.tsx):
useProfile() + useUpdateProfileBasics() for the basics card, the
useCreate/Update/DeleteContact() trio for the contact rows. View-by-
default — a per-card Edit affordance reveals the form for basics,
and contact rows get inline Edit/Remove tools.

Profile picture is read-only on this slice: the existing PWA wires
it through a file input + multipart upload, which doesn't translate
cleanly to the universal client without expo-image-picker. The card
shows the initials fallback and a "manage from web" hint until a
follow-up wires picture upload through @carol/api-client.

Strings flow through react-i18next ("profile" + "common" namespaces);
colour comes from useTheme() tokens — no hardcoded values.
Mirrors the Notes reference pattern (apps/client/app/(app)/notes.tsx)
against @carol/api-client's skill-sections + skills hooks. View-by-
default chips for read mode; a page-level Edit toggle exposes the
per-section move/rename/delete tools, per-skill move/delete rows,
and the inline add-skill input under each section.

Section collapse state is local-only here — localStorage doesn't
work on native, and the design package treats it as a UI affordance
rather than a persisted preference. A follow-up can promote it to a
settings field if users miss it after the existing PWA is gone.

Strings flow through react-i18next; tokens come from useTheme(). No
hardcoded values, no leakage into @carol/api source.
feat(client): port experience (jobs) screen to expo router (#184)
Some checks failed
Commits / Conventional Commits (pull_request) Successful in 27s
PR / Static analysis (pull_request) Successful in 2m8s
PR / OpenAPI (pull_request) Successful in 3m7s
PR / Client (web export smoke) (pull_request) Successful in 3m18s
PR / OSV-Scanner (pull_request) Failing after 1m11s
PR / Lint (pull_request) Successful in 4m4s
PR / Typecheck (pull_request) Successful in 4m9s
PR / Test (sqlite) (pull_request) Successful in 3m39s
PR / Test (postgres) (pull_request) Successful in 3m51s
PR / pnpm audit (pull_request) Successful in 1m59s
PR / Build (pull_request) Successful in 4m11s
PR / Package age policy (soft) (pull_request) Successful in 1m33s
Secrets / gitleaks (pull_request) Successful in 1m59s
PR / Coverage (soft) (pull_request) Successful in 2m34s
PR / Trivy (image) (pull_request) Failing after 3m30s
ae699bf806
Three-level drill-down — Jobs → Positions → Contributions — backed
by the bundled useJobs() tree from @carol/api-client. Mutations from
each level invalidate keys.jobs.all so the drill-down state survives
a refetch as long as the selected ids stay in the tree.

A page-level Edit toggle exposes the add/edit/delete affordances at
every level; the existing Next.js page does the same and this slice
matches the behaviour. The PositionWithContributions / Contribution
forms reuse the same shape the server schemas accept — empty endDate
strings collapse to null at the call site.

Education + contracts tabs from the existing PWA are out of scope per
the ticket; they ship in a later slice.

Strings flow through react-i18next; tokens come from useTheme(). No
hardcoded values, no leakage into @carol/api source.

OSV-Scanner

Threshold: high  ·  Total findings: 8  ·  At/above threshold: 2

critical high medium low
1 1 6 0
severity id package installed / range fix
critical GHSA-5xrq-8626-4rwp vitest@2.1.9 4.0.0–4.1.0 4.1.0
high GHSA-fx2h-pf6j-xcff vite@5.4.21 8.0.0–8.0.16 8.0.16
<!-- scanner-comment: osv --> ### OSV-Scanner **Threshold:** `high` &nbsp;·&nbsp; **Total findings:** 8 &nbsp;·&nbsp; **At/above threshold:** 2 | critical | high | medium | low | |---:|---:|---:|---:| | 1 | 1 | 6 | 0 | | severity | id | package | installed / range | fix | |---|---|---|---|---| | critical | GHSA-5xrq-8626-4rwp | vitest@2.1.9 | 4.0.0–4.1.0 | `4.1.0` | | high | GHSA-fx2h-pf6j-xcff | vite@5.4.21 | 8.0.0–8.0.16 | `8.0.16` |

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 61.8% ≥ 50%
Branches 80.3% ≥ 75%
Functions 87.9% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 61.8% ✅ | ≥ 50% | | Branches | 80.3% ✅ | ≥ 75% | | Functions | 87.9% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.

Trivy (container image)

Threshold: high  ·  Total findings: 121  ·  At/above threshold: 1

critical high medium low
0 1 50 70
severity id package installed / range fix
high CVE-2026-12151 undici 6.25.0 6.27.0, 7.28.0, 8.5.0
<!-- scanner-comment: trivy --> ### Trivy (container image) **Threshold:** `high` &nbsp;·&nbsp; **Total findings:** 121 &nbsp;·&nbsp; **At/above threshold:** 1 | critical | high | medium | low | |---:|---:|---:|---:| | 0 | 1 | 50 | 70 | | severity | id | package | installed / range | fix | |---|---|---|---|---| | high | [CVE-2026-12151](https://avd.aquasec.com/nvd/cve-2026-12151) | undici | 6.25.0 | `6.27.0, 7.28.0, 8.5.0` |
james merged commit 9cd4f7dc89 into main 2026-06-21 12:24:48 +00:00
james deleted branch 184-career-core-screens 2026-06-21 12:24:48 +00:00
Sign in to join this conversation.
No description provided.