fix(client): consistent back navigation via goBackOr history-or-parent helper #310

Merged
james merged 1 commit from 308-back-nav-consistency into main 2026-06-27 01:26:34 +00:00
Owner

Back affordances on the network detail screens used a bare router.back(), which is a no-op on cold start, deep link, or web refresh — leaving the user stranded with no way back to the list.

Fix

Adds lib/nav/goBack.ts exporting goBackOr(router, fallback): returns to the true previous screen when in-app history exists, otherwise replaces with the screen's logical parent.

Audit (every back affordance in apps/client)

File:line Old New
network/[id].tsx:268 (person-detail Back) router.back() — no-op without history goBackOr(router, "/network")
network/orgs/[id].tsx:201 (org-detail Back) router.back() — same goBackOr(router, "/network")

These two were the only true router back controls. Left unchanged (correctly):

  • experience.tsx "← Back to jobs/positions/contracts" — drive in-page sub-view state (setSelectedId(null)), not the router.
  • Post-delete router.replace("/network") in the delete handlers — you can't return to a just-deleted entity; the list is the right destination.
  • Auth-flow router.replace (login/register/server-setup) and the sidebar ChevronLeft collapse toggle — not back-to-previous controls.

Cross-navigation (org→person, person→org via push) builds real history, so goBackOr returns to the actual originating detail screen; the /network fallback only engages when there's genuinely no history.

Conventions / verification

  • Labels already come from i18n (t("back") / t("organizationDetail.back")) — no copy changes.
  • pnpm -F @carol/client typecheck — clean
  • pnpm -F @carol/client lint — clean
  • pnpm -F @carol/client test — 133 pass (added tests/goBack.test.ts covering both branches)

Closes #308

🤖 Generated with Claude Code

Back affordances on the network detail screens used a bare `router.back()`, which is a no-op on cold start, deep link, or web refresh — leaving the user stranded with no way back to the list. ## Fix Adds `lib/nav/goBack.ts` exporting `goBackOr(router, fallback)`: returns to the true previous screen when in-app history exists, otherwise replaces with the screen's logical parent. ## Audit (every back affordance in `apps/client`) | File:line | Old | New | |---|---|---| | `network/[id].tsx:268` (person-detail Back) | `router.back()` — no-op without history | `goBackOr(router, "/network")` | | `network/orgs/[id].tsx:201` (org-detail Back) | `router.back()` — same | `goBackOr(router, "/network")` | These two were the only true router back controls. Left unchanged (correctly): - `experience.tsx` "← Back to jobs/positions/contracts" — drive **in-page sub-view state** (`setSelectedId(null)`), not the router. - Post-delete `router.replace("/network")` in the delete handlers — you can't return to a just-deleted entity; the list is the right destination. - Auth-flow `router.replace` (login/register/server-setup) and the sidebar `ChevronLeft` collapse toggle — not back-to-previous controls. Cross-navigation (org→person, person→org via `push`) builds real history, so `goBackOr` returns to the actual originating detail screen; the `/network` fallback only engages when there's genuinely no history. ## Conventions / verification - Labels already come from i18n (`t("back")` / `t("organizationDetail.back")`) — no copy changes. - `pnpm -F @carol/client typecheck` — clean - `pnpm -F @carol/client lint` — clean - `pnpm -F @carol/client test` — 133 pass (added `tests/goBack.test.ts` covering both branches) Closes #308 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(client): consistent back navigation via goBackOr history-or-parent helper
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 12s
PR / OSV-Scanner (pull_request) Successful in 1m37s
PR / pnpm audit (pull_request) Successful in 2m26s
PR / Client (web export smoke) (pull_request) Successful in 2m36s
PR / Static analysis (pull_request) Successful in 2m36s
PR / Typecheck (pull_request) Successful in 2m46s
PR / Lint (pull_request) Successful in 2m53s
PR / Build (pull_request) Successful in 3m3s
PR / OpenAPI (pull_request) Successful in 3m10s
PR / Test (sqlite) (pull_request) Successful in 3m20s
PR / Package age policy (soft) (pull_request) Successful in 51s
PR / Test (postgres) (pull_request) Successful in 3m30s
Secrets / gitleaks (pull_request) Successful in 53s
PR / Trivy (image) (pull_request) Successful in 2m3s
PR / Coverage (soft) (pull_request) Successful in 2m16s
0366454878
Back affordances on the network detail screens used a bare router.back(),
which is a no-op on cold start, deep link, or web refresh — leaving the
user stranded with no way back to the list.

Add lib/nav/goBack.ts exporting goBackOr(router, fallback): returns to the
true previous screen when in-app history exists, otherwise replaces with
the screen's logical parent. Applied to the person- and org-detail back
buttons (fallback /network for both). Forward/lateral links and
post-delete redirects are unchanged.

Closes #308

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

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

Metric Value Soft target
Lines 81.6% ≥ 50%
Branches 72.8% ⚠️ ≥ 75%
Functions 91.1% 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 | 81.6% ✅ | ≥ 50% | | Branches | 72.8% ⚠️ | ≥ 75% | | Functions | 91.1% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 4c7156cbb2 into main 2026-06-27 01:26:34 +00:00
james deleted branch 308-back-nav-consistency 2026-06-27 01:26:35 +00:00
Sign in to join this conversation.
No description provided.