Skip to content

feat: NavigationError class + public-export tidy#5

Merged
feffef merged 1 commit into
mainfrom
fix/finding-02-navigation-error
Apr 17, 2026
Merged

feat: NavigationError class + public-export tidy#5
feffef merged 1 commit into
mainfrom
fix/finding-02-navigation-error

Conversation

@feffef
Copy link
Copy Markdown
Owner

@feffef feffef commented Apr 17, 2026

Findings

FINDING-02 | WARNING | Style / Documentation + Architecture
Location: src/runtime-metadata.ts:~174, src/navigate.ts:~200, docs/how-it-works.md:254, docs/roadmap.md §8

Docs claimed navigate throws TypeError; the code always threw plain Error. Consumers writing catch (e) { if (e instanceof TypeError) ... } following the documented contract would never enter the branch.

FINDING-04 | WARNING | Architecture / Documentation
Location: AGENTS.md:9 vs src/index.ts:10,35,36

AGENTS.md listed a "deliberately minimal" public surface that did not match reality: ApiDefinition, RootNavigable, and LinkedResource were all exported but not listed. LinkedResource had zero external callers, making it a candidate for removal; the other two are load-bearing for type inference (return type of linkTo, linked-resource type used by navigate consumers).

Concrete impact: (1) Consumers catching TypeError per the docs silently miss link-not-found errors. (2) The minimal-public-surface promise in AGENTS.md was misleading, and a type that never stabilized was frozen into the public API because nobody noticed it was exported.

Approach chosen

  • FINDING-02 — C: introduce NavigationError extends Error rather than either fixing docs alone (A) or switching to TypeError (B). A TypeError is semantically wrong — the argument is the right type (a string), just the wrong value. NavigationError directly delivers the entry-point of roadmap §8's typed error hierarchy and is the smallest precise catch class consumers could ask for.
  • FINDING-04 — C: remove LinkedResource (pre-release project, zero external callers), promote ApiDefinition and RootNavigable in AGENTS.md, and now also NavigationError. Makes the "deliberately minimal" claim true.

Alternatives considered

  • FINDING-02 — A (docs-only): Rewrite the docs to say Error. Rejected because consumers are left with no discriminable catch class for this specific programming error, and the roadmap already plans typed error hierarchy.
  • FINDING-02 — B (throw TypeError): Match the old docs by switching to TypeError. Rejected on semantics (TypeError is for type-mismatch, not value-mismatch) and because it contradicts the roadmap's plan (NavigationError extends Error, not extends TypeError).
  • FINDING-04 — A (docs-only): Add all three types to the list without touching exports. Rejected for LinkedResource specifically — committing to a public API surface for a type with no external callers is gratuitous.
  • FINDING-04 — B (docs + @remarks on LinkedResource): Same as A but add a JSDoc marking LinkedResource as "advanced/potentially removable." Rejected because marking something as removable while keeping it public is worst-of-both-worlds; act or don't.

Bundled quick fixes

  • Quick Fix 2: docs/roadmap.md §8 link-extraction.tsruntime-metadata.ts (link-extraction lives inside runtime-metadata.ts).
  • Quick Fix 12: docs/how-it-works.md:254 TypeErrorNavigationError.

Test plan

  • npx jest — all 393 tests pass. Existing tests that asserted Error for link-not-found still pass (NavigationError extends Error); new assertions pin the subclass contract.
  • New tests in test/integration/runtime-guards.spec.ts (NavigationError class contract describe block): navigate with unknown link throws NavigationError AND instanceof Error still holds; navigate on non-navigable object throws NavigationError.
  • npm run test:coverage — 100% statements/branches/functions/lines across all files including error-handling.ts.
  • npm run typecheck — passes.
  • npm run test:playwright — all 82 Playwright tests pass.

Breaking changes

  • BREAKING: LinkedResource type no longer exported from src/index.ts. The library is pre-release (see AGENTS.md public-API note) and has no known external callers. Migration: inline Resource<L['Target'], L['Api']>.
  • navigate and linkTo now throw NavigationError (extends Error) instead of Error for unknown-link errors. Not strictly breaking for instanceof Error / broad-catch code; is breaking for anyone asserting the exact class.

Both changes documented in CHANGELOG.md under [Unreleased].

Review checklist

  • Dedicated opus peer review: approved

…exports

- Add NavigationError (extends Error) in src/error-handling.ts and
  throw it from runtime-metadata.ts and navigate.ts wherever an
  unknown link name or missing navigable metadata was previously
  throwing plain Error. Gives callers a precise `instanceof
  NavigationError` catch path while keeping `instanceof Error`
  compatibility for broad-catch code.
- Export NavigationError from src/index.ts.
- Remove LinkedResource from src/index.ts public exports (no external
  callers in test/ or examples/).
- Add ApiDefinition, RootNavigable, and NavigationError to the
  AGENTS.md public-API list.
- Fix docs/how-it-works.md:254 (TypeError -> NavigationError) and
  docs/roadmap.md §8 (link-extraction.ts -> runtime-metadata.ts;
  remove the stale TypeError claim; mark NavigationError entry as DONE).
- Update CHANGELOG under [Unreleased].

Addresses FINDING-02, FINDING-04, Quick Fix 2, Quick Fix 12 from
code review. Implements entry-point of roadmap §8.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@feffef feffef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opus Reviewer (on behalf of @feffef): LGTM — no blocking concerns.

Thorough, well-scoped implementation of Approach C for FINDING-02 plus the FINDING-04 public-surface tidy. The blast radius is as expected but nothing leaked beyond it.

Verified

NavigationError class (src/error-handling.ts:20-25)

  • Extends Error, this.name = 'NavigationError' set correctly — serialization and instanceof both work.
  • Single-arg constructor is ES2020-compatible (correctly drops ErrorOptions, which is ES2022). tsconfig.json target is es2020, confirmed.
  • JSDoc @public matches the src/index.ts export.

Throw-site conversions (exactly the 3 requested, no more)

  • src/runtime-metadata.ts:175 — named-link miss: ErrorNavigationError, message preserved verbatim.
  • src/runtime-metadata.ts:190 — "No links are available": ErrorNavigationError, message preserved verbatim.
  • src/navigate.ts:200 — metadata-not-found: ErrorNavigationError, message preserved verbatim.
  • Invariant "Internal library bug" throws at runtime-metadata.ts:163, :198, :275, :313, :325 correctly remain plain Error — these are library bugs, not navigation errors. Good discipline.

LinkedResource removal safety

  • src/index.ts:39 no longer exports LinkedResource; src/index.ts:23-25 exports NavigationError.
  • LinkedResource still exists in src/type-system.ts:379 (internal), correctly imported from ./type-system (not ./index) in api-client.ts, error-handling.ts, navigate.ts. Internal boundaries preserved.
  • Zero references in test/ or examples/ — confirmed via git grep.

AGENTS.md:9 — public API list now includes ApiDefinition, RootNavigable, NavigationError; drops LinkedResource. "Deliberately minimal" framing still reads cleanly.

docs/how-it-works.md:254TypeErrorNavigationError; instanceof Error aside added correctly (Quick Fix 12 landed).

docs/roadmap.md §8link-extraction.tsruntime-metadata.ts (Quick Fix 2); no longer claims the code "currently throws TypeError"; NavigationError entry marked DONE with file/site references; HypermediaError/ValidationError/ConfigurationError correctly still "(still open)"; constraint updated to reference remaining subclasses.

CHANGELOG.md — correctly structured under [Unreleased]:

  • Added: NavigationError export.
  • Changed: throw-class change, notes instanceof Error continuity.
  • Removed: LinkedResource, correctly prefixed **BREAKING**: with migration sentence (Resource<L['Target'], L['Api']>).

Tests (test/integration/runtime-guards.spec.ts:363-413)

  • New NavigationError class contract describe block present.
  • Named-link-miss test asserts instanceof NavigationError, instanceof Error, name === 'NavigationError', and message shape. Dual-instance check included as requested.
  • Metadata-not-found test mirrors the same dual-instance pattern.
  • Pre-existing tests at lines 124, 140, 156 still use regex/string matchers against rejects.toThrow(...) — these pass for any Error subclass, no regression. No tests asserted exact-class constructor or e.name === 'Error', so no updates were required.

Scope cleanliness — 9 files, 109 insertions, 19 deletions. No collateral refactors, no rename creep. Nothing touching PR #3's scope (how-it-works.md section renumbering, roadmap §1 map names).

CI — all green: Typecheck, Build, Tests, Coverage, Playwright (1m33s) all passed. Agent's claims of 393/393 jest, 100% coverage, 82/82 playwright are verified via the CI jobs.

Nit (non-blocking)

  • The PR body's "Breaking changes" section correctly notes that Error → NavigationError is breaking for code asserting exact class but not for instanceof Error code. The CHANGELOG Changed entry omits the **BREAKING**: prefix. Arguably correct (it's a subclass, instanceof Error still works), and matches how roadmap §8 framed the "breaking change from Error to NavigationError" as informational rather than a contract break. Not worth changing.

Verdict

Ship it. This is the cleanest of the batch — surgical class swap, invariant throws correctly preserved, test contract pinned on both instanceof axes, docs synchronized with code, and CHANGELOG accurate. Directly lands the entry-point of roadmap §8 without over-reaching into the remaining subclasses.

(GitHub blocks self-approval on PRs authored by @feffef — this comment is the functional LGTM.)

@feffef feffef merged commit d391a6a into main Apr 17, 2026
6 checks passed
@feffef feffef deleted the fix/finding-02-navigation-error branch April 17, 2026 14:24
feffef added a commit that referenced this pull request Apr 17, 2026
…lans

- Rewrite housekeeping item #3's DONE blurb to describe the actual
  landed architecture (single fetchResource + per-builder inlined
  formatting + failureToError boundary converter). The previous text
  referenced fetchSafe/fetchProne/formatErrorMessage — none of which
  exist in src/. They were symbol names from an intermediate refactor
  plan that was superseded before landing.
- Delete docs/pipeline-refactor-plan.md and docs/pipeline-refactor-plan-alt.md.
  Both carry "Proposed — awaiting review" headers despite describing
  superseded designs. They are the root cause of the housekeeping drift.
- Drop dangling references to missing docs/integration-test-split.md in
  housekeeping items #5 and #7.
- Reorder housekeeping items into numerical sequence (no content changes).

Addresses FINDING-05 from code review plus Quick Fixes 3, 13, 14.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant