feat(client): port account, network, education screens (#184) #205

Merged
james merged 5 commits from 184-people-account-slice into main 2026-06-21 13:44:17 +00:00
Owner

Scope

Slice 2 of #184 — porting more existing screens to Expo Router on the universal client per ADR-0027. Slice 1 (Profile + Skills + Experience-Jobs) shipped in #201; this slice covers Account, Network, and extends the existing Experience screen with an Education section.

What changed

  • apps/client/app/(app)/account.tsx — Personal Access Tokens management. Lists active tokens, mints a new one (reveals the plaintext exactly once with a copy-to-clipboard affordance on web; native gets manual-copy until expo-clipboard lands), revokes with an inline confirm step. Also surfaces the signed-in identity. Mirrors the behaviour of the Next.js account/tokens/tokens-client.tsx but rebuilt from spec against @carol/api-client.
  • apps/client/app/(app)/network.tsx — Placeholder port. The Next.js PWA's /network is itself a Placeholder today (epic #5 in idea.md) — there are no people/orgs tables, repos, or routes yet, so a working list + drill-down isn't shippable without standing up the backend. This port mirrors that placeholder shape on the universal client using DS tokens + i18n; when the network endpoints land the file's render swaps to the real list + drill-down with the same screen contract.
  • apps/client/app/(app)/experience.tsx — Education tab alongside the existing Jobs → Positions → Contributions drill-down. Education is a flat list via useEducations() (cursor-paginated; the hook fetches one page at limit=200) with inline create / edit / delete. Jobs tree extracted into a JobsTree subcomponent so each tab owns its own loading + error state. Contracts stays out of the segmented control until its ticket lands.
  • packages/api-client/src/hooks/{account-tokens,contacts,educations}.ts — Unwrap the cursor envelope ({ data, next_cursor, has_more }) added by #191 and request ?limit=200 so the existing array-shaped consumers stay working. Without this, all three hooks were silently handing consumers an undefined-shaped value pretending to be a list.
  • packages/i18n/messages/en.json — Catalog keys for the new surfaces: network.comingSoon.*, account.tokens.screen.{confirmRevoke,cancelRevoke}.

DS tokens via useTheme(); strings via react-i18next; no hardcoded colours or copy.

Out of scope

  • Projects, Applications, Chat screens (slice 3).
  • Decommissioning the Next.js UI (#185).
  • People/organisations backend — the API for the Network surface is a separate epic (#5).
  • Native-only expo-clipboard; web's navigator.clipboard covers the PWA target.
  • Per-screen infinite-scroll UX.

Test plan

  • pnpm install --frozen-lockfile
  • pnpm -F @carol/api typecheck
  • pnpm -F @carol/api lint
  • pnpm -F @carol/api test (574 passed, 107 skipped — SQLite + Postgres matrix)
  • 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

Closes part of #184.

## Scope Slice 2 of #184 — porting more existing screens to Expo Router on the universal client per [ADR-0027](docs/adr/0027-frontend-backend-split-and-universal-client.md). Slice 1 (Profile + Skills + Experience-Jobs) shipped in #201; this slice covers Account, Network, and extends the existing Experience screen with an Education section. ## What changed - **`apps/client/app/(app)/account.tsx`** — Personal Access Tokens management. Lists active tokens, mints a new one (reveals the plaintext exactly once with a copy-to-clipboard affordance on web; native gets manual-copy until expo-clipboard lands), revokes with an inline confirm step. Also surfaces the signed-in identity. Mirrors the behaviour of the Next.js `account/tokens/tokens-client.tsx` but rebuilt from spec against `@carol/api-client`. - **`apps/client/app/(app)/network.tsx`** — Placeholder port. The Next.js PWA's `/network` is itself a Placeholder today (epic #5 in `idea.md`) — there are no people/orgs tables, repos, or routes yet, so a working list + drill-down isn't shippable without standing up the backend. This port mirrors that placeholder shape on the universal client using DS tokens + i18n; when the network endpoints land the file's render swaps to the real list + drill-down with the same screen contract. - **`apps/client/app/(app)/experience.tsx`** — Education tab alongside the existing Jobs → Positions → Contributions drill-down. Education is a flat list via `useEducations()` (cursor-paginated; the hook fetches one page at `limit=200`) with inline create / edit / delete. Jobs tree extracted into a `JobsTree` subcomponent so each tab owns its own loading + error state. Contracts stays out of the segmented control until its ticket lands. - **`packages/api-client/src/hooks/{account-tokens,contacts,educations}.ts`** — Unwrap the cursor envelope (`{ data, next_cursor, has_more }`) added by #191 and request `?limit=200` so the existing array-shaped consumers stay working. Without this, all three hooks were silently handing consumers an undefined-shaped value pretending to be a list. - **`packages/i18n/messages/en.json`** — Catalog keys for the new surfaces: `network.comingSoon.*`, `account.tokens.screen.{confirmRevoke,cancelRevoke}`. DS tokens via `useTheme()`; strings via `react-i18next`; no hardcoded colours or copy. ## Out of scope - Projects, Applications, Chat screens (slice 3). - Decommissioning the Next.js UI (#185). - People/organisations backend — the API for the Network surface is a separate epic (#5). - Native-only `expo-clipboard`; web's `navigator.clipboard` covers the PWA target. - Per-screen infinite-scroll UX. ## 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` (574 passed, 107 skipped — SQLite + Postgres matrix) - [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` Closes part of #184.
`useAccountTokens`, `useContacts`, and `useEducations` were still
treating the response as a raw array — after #191 these endpoints
return the cursor envelope (`{ data, next_cursor, has_more }`), and
the hooks were silently handing every consumer an undefined-shaped
value masquerading as the list.

Request `?limit=200` (the API max) and unwrap `data.data`. Education
histories, contact lists, and PATs all sit well under that ceiling,
so a single page is functionally complete; an infinite-scroll
variant can land alongside the per-screen pagination UX follow-up.
Adds the translation keys the new Account, Network, and Education
surfaces consume:
- account.tokens.screen.confirmRevoke / cancelRevoke — confirm step
  on the revoke action; the existing revokeConfirm string is the
  web confirm() copy and not reused on the universal client.
- network.comingSoon.{title,body} — the placeholder copy that mirrors
  the existing Next.js Placeholder layout until the people/orgs
  backend lands.

Sibling locales fall back to English per-key per ADR-0025.
Personal Access Tokens management on the universal client (ADR-0027,
slice 2 of #184). Renders the signed-in identity (read-only on this
slice — full provider-link surface lands in a follow-up) and the PAT
list + create + revoke flow:

- List active tokens via useAccountTokens() at limit=200; PATs are
  capped at 25 per user so one page covers every realistic case.
- Create a token with the name input, reveal the plaintext exactly
  once in a banner with a copy-to-clipboard affordance (web target
  uses navigator.clipboard; native builds will swap in expo-clipboard
  later — until then the plaintext stays on screen for manual copy).
- Revoke with an inline confirm step (no native confirm() on RN).

DS tokens via useTheme(); strings via react-i18next. Mirrors the
behaviour of apps/api/app/(app)/account/tokens/tokens-client.tsx but
is rebuilt from spec against @carol/api-client per ADR-0027.
The PWA's Network surface is currently a Placeholder (epic #5 in
idea.md) — people and organisations have no DB tables, repos, routes,
or zod DTOs yet, so a working list + drill-down isn't shippable
without the backend. This port mirrors that placeholder behaviour on
the universal client using DS tokens + i18n.

When the network endpoints land, this file's render swaps for the
real list + inline drill-down (same shape as Experience's
Jobs → Positions → Contributions tree); the screen contract stays
the same so the nav + auth gate don't churn.
feat(client): add education section to experience screen (#184)
Some checks failed
Commits / Conventional Commits (pull_request) Successful in 8s
PR / OSV-Scanner (pull_request) Successful in 2m8s
PR / Static analysis (pull_request) Successful in 2m29s
PR / Client (web export smoke) (pull_request) Successful in 2m51s
PR / Typecheck (pull_request) Successful in 3m22s
PR / pnpm audit (pull_request) Successful in 3m36s
PR / OpenAPI (pull_request) Successful in 3m36s
PR / Lint (pull_request) Successful in 3m46s
PR / Build (pull_request) Successful in 3m54s
PR / Test (postgres) (pull_request) Successful in 4m2s
PR / Test (sqlite) (pull_request) Successful in 4m9s
PR / Package age policy (soft) (pull_request) Successful in 1m18s
Secrets / gitleaks (pull_request) Successful in 48s
PR / Trivy (image) (pull_request) Failing after 2m1s
PR / Coverage (soft) (pull_request) Successful in 2m0s
06b416bcb2
Adds the Education tab alongside the existing Jobs →
Positions → Contributions drill-down. Mirrors apps/api/app/(app)/
experience/education-section.tsx but is rebuilt from spec against
@carol/api-client per ADR-0027.

Surface:
- Segmented sub-nav at the top (Education / Jobs) — Contracts is
  still pending its own ticket in epic #4, so it stays out of the
  control here rather than carrying a planned-note card forward.
- Education: flat list via useEducations() (cursor-paginated, hook
  fetches one page at limit=200); create / edit / delete inline.
- Jobs tree extracted into a JobsTree subcomponent so each tab owns
  its own loading + error states; behaviour unchanged from slice 1.

Type, institution, focus, start/end date, and description match the
EducationDto. DS tokens + react-i18next strings throughout — no
hardcoded colours or copy.

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` |

📊 Test coverage

Patch coverage: no testable lines changed.

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

Metric Value Soft target
Lines 70.4% ≥ 50%
Branches 61.2% ⚠️ ≥ 75%
Functions 68.0% 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 | 70.4% ✅ | ≥ 50% | | Branches | 61.2% ⚠️ | ≥ 75% | | Functions | 68.0% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit bb3851db44 into main 2026-06-21 13:44:17 +00:00
james deleted branch 184-people-account-slice 2026-06-21 13:44:17 +00:00
Sign in to join this conversation.
No description provided.