Skip to content

fix(web): decode percent-encoded route params in matchRoute - #25

Merged
TituxMetal merged 3 commits into
developfrom
claude/issue-20-20260625-0310
Jun 25, 2026
Merged

TituxMetal merged 3 commits into
developfrom
claude/issue-20-20260625-0310

Conversation

@TituxMetal

Copy link
Copy Markdown
Owner

Applies decodeURIComponent() to each dynamic segment value in matchRoute so URLs like /coach/white%2Dcross correctly match lesson id white-cross instead of silently rendering LessonNotFound.

Also exports matchRoute for direct testing, adds two tests covering percent-encoded and plain params, and fixes a pre-existing ESLint failure in router.spec.tsx caused by the web ESLint config overriding the shared base's project: true (project service mode) with project: './tsconfig.json' (legacy program mode).

Closes #20

Generated with Claude Code

Applies decodeURIComponent() to each dynamic segment value so URLs like
/coach/white%2Dcross correctly match lesson id white-cross instead of
silently rendering LessonNotFound.

Also exports matchRoute for direct testing, adds two tests covering
percent-encoded and plain params, and fixes the ESLint parserOptions
conflict that was causing router.spec.tsx to fail lint (project:
./tsconfig.json in the web config was overriding project: true from the
shared base, forcing legacy program mode; removing the override lets the
base projectService setting handle all files correctly).

Closes #20

Co-authored-by: Titux Metal <TituxMetal@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 25, 2026 03:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes client-side route matching for percent-encoded dynamic segments (e.g. lesson IDs) by decoding route params during matchRoute, so encoded URLs correctly resolve to existing Coach lessons instead of rendering LessonNotFound. It also makes matchRoute directly testable and adjusts the web ESLint flat config to avoid overriding the shared TypeScript “project service mode” parser settings.

Changes:

  • Decode dynamic route params via decodeURIComponent() inside matchRoute and export matchRoute for direct testing.
  • Add unit tests covering percent-encoded and plain dynamic params.
  • Remove the web ESLint config override of parserOptions.project to restore the shared base config behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
apps/web/src/lib/router.tsx Export matchRoute and decode dynamic route params during route matching.
apps/web/src/lib/router.spec.tsx Add direct matchRoute tests for encoded vs. plain params.
apps/web/eslint.config.mjs Stop overriding parserOptions.project so the shared ESLint TypeScript settings apply.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/web/src/lib/router.tsx Outdated
Comment thread apps/web/src/lib/router.spec.tsx
@TituxMetal

Copy link
Copy Markdown
Owner Author

@claude Please address the two Copilot review comments on this PR:

  1. In apps/web/src/lib/router.tsx line 40: wrap decodeURIComponent(actual) in a try/catch so a malformed percent-encoding (e.g. %E0%A4) returns null from matchRoute instead of throwing a URIError and crashing the router.

  2. In apps/web/src/lib/router.spec.tsx line 144: add a regression test asserting that matchRoute returns null (and does not throw) when given a route with a malformed percent-encoded param.

Follow project conventions: no semicolons, arrow functions only, named exports, bun run format:check + bun run lint:check + bun run typecheck + bun run test must pass.

@claude

claude Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Claude finished @TituxMetal's task in 2m 56s —— View job


Addressing Copilot Review Comments

  • Gather context and read relevant files
  • Wrap decodeURIComponent in try/catch in router.tsx
  • Add regression test for malformed percent-encoded params in router.spec.tsx
  • Run bun run format:check, lint:check, typecheck, and test — 170 pass, 0 fail
  • Commit and push changes

Changes made

apps/web/src/lib/router.tsx — wrapped the decodeURIComponent(actual) call at the dynamic segment assignment in a try/catch. A URIError from a malformed encoding (e.g. %E0%A4) now causes matchRoute to return null immediately rather than propagating the exception:

try {
  params[expected.slice(1)] = decodeURIComponent(actual)
} catch {
  return null
}

apps/web/src/lib/router.spec.tsx — added a third test in the matchRoute describe block:

it('returns null for a malformed percent-encoded param without throwing', () => {
  const result = matchRoute({ '/coach/:id': render }, '/coach/%E0%A4')
  expect(result).toBeNull()
})

All pre-push checks green: format:check ✓ · lint:check ✓ · typecheck ✓ · test ✓ · build ✓
· Branch

Wrap decodeURIComponent in try/catch inside matchRoute so a URIError
from a malformed segment (e.g. %E0%A4) returns null instead of
crashing the router. Adds a regression test to cover this case.

Co-authored-by: Titux Metal <TituxMetal@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread apps/web/src/lib/router.tsx
Comment thread apps/web/src/lib/router.spec.tsx
@TituxMetal
TituxMetal merged commit 54726ce into develop Jun 25, 2026
2 checks passed
@TituxMetal
TituxMetal deleted the claude/issue-20-20260625-0310 branch June 25, 2026 03:57
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.

fix(web): percent-encoded lessonId silently produces LessonNotFound

2 participants