Skip to content

feat: add ctx.IsRoute for active-nav highlighting - #122

Merged
acoshift merged 1 commit into
masterfrom
feat/is-route
Jun 10, 2026
Merged

feat: add ctx.IsRoute for active-nav highlighting#122
acoshift merged 1 commit into
masterfrom
feat/is-route

Conversation

@acoshift

@acoshift acoshift commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

The final Tier-2 item. ctx.IsRoute(name) reports whether the named route is the most specific (deepest) registered route matching the current request path — for highlighting the active nav link.

return ctx.View("page", ctx) // pass ctx as the view data
<a href="{{route "users"}}" class="{{if .IsRoute "users"}}active{{end}}">Users</a>

Matching (best-match)

Routes Home=/, Admin=/admin, Users=/admin/users; on /admin/users/42:

IsRoute(...) result
"users" true (most specific)
"admin" false (a deeper route matches)
"home" false
  • Path-segment aware: /admin matches /admin/x but not /administrators.
  • / is active only on exactly /.
  • A section (/admin) stays active on its sub-paths (/admin/settings) when no deeper route matches.
  • A route's query string and trailing slash are normalized away (/docs/ behaves like /docs), via a small routePath helper.

Why a method, not a {{isRoute}} template func

A bare {{isRoute}} func can't see the request: hime's template funcs bind at parse time on the shared app.parent, and rebinding per request would force a template clone on every render (required for thread-safety) plus awkward nested-{{component}} handling. html/template already calls methods on the data, so a Context method used as {{.IsRoute "x"}} (pass ctx as the data) gives request-aware highlighting with zero clone, zero core-render change, thread-safe, and it works inside components too. Bonus: it makes .IsHTMX, .CookieValue, … usable from templates the same way.

Note

Best-match scans the route table per call (O(routes)) and couples a route's result to the whole table (registering a deeper route can change a shallower one's highlight) — that coupling is the intended "most specific wins" behavior. Negligible cost for realistic route tables.

Test plan

  • go vet ./...
  • go test ./...100% coverage on IsRoute, routePath, pathUnder; covers exact / sub-path / section / segment-aware / root / query / trailing-slash / panic cases, plus an in-template test (View("nav", ctx){{.IsRoute}})
  • go test -race ./...
  • gofmt clean
  • runnable ExampleContext_IsRoute; CLAUDE.md updated

Completes Tier 2: signed cookies → flash → FormState → IsRoute.

🤖 Generated with Claude Code

The final Tier-2 website-DX item. ctx.IsRoute(name) reports whether the
named route is the most specific (deepest) registered route matching the
current request path, so the active nav link is highlighted without its
parent sections also lighting up: on /admin/users/42, IsRoute("users") is
true but IsRoute("admin") is false. Matching is path-segment aware
("/admin" matches "/admin/x" but not "/administrators"), so "/" is active
only on exactly "/"; any query string or trailing slash in the route path
is normalized away.

It is a Context method (not a bare template func), so templates use it as
{{.IsRoute "home"}} by passing ctx as the view data. This avoids
per-request template-func binding (and its template-clone cost and
thread-safety concerns) entirely: html/template already calls methods on
the data, and the data flows into components too.

Panics on an unknown route, like Route. stdlib only, 100% covered.
Verified with go vet, go test -race, gofmt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@acoshift
acoshift merged commit 4a68b90 into master Jun 10, 2026
2 checks passed
@acoshift
acoshift deleted the feat/is-route branch June 10, 2026 01:45
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