ADR-063: Work Lanes as Routes
Replaces the CSS-only radio filter on the work index with one static page per lane, so a lane can be linked to and the selected lane is exposed to assistive technology. Written after the change shipped; accepted on review.
Last updated
Status
Accepted (2026-10-11, retroactive — the change shipped in PR #101 on 2026-10-10 before this record existed; accepted by the owner on review)
Context
The work index (/work/) filtered case studies by lane with a radio group and
:has() sibling selectors. It needed no JavaScript, but it had two problems:
- A lane had no URL. The homepage names three lanes and could not link to any of them, and a filtered view could not be shared.
- The selected lane was visual state only on a set of visually hidden radios. The
cards that did not match were hidden with
display: none.
At the same time the Web & Brand lane was advertised on the homepage, the About page and the work index, and led nowhere: its only case study was a draft and the sites gallery was disabled.
This record was written after the fact. The change altered route shapes and
component props, which ADR-038 names as triggers for the three-pass workflow, and
.claude/workflow.md says architectural calls are not made inline. Neither was
followed in PR #101. Issue #103 tracks the gap.
Decision Drivers
- Linkable lanes: the homepage and other pages need a destination per lane
- Zero JavaScript: constitution rule 7 prefers CSS and static HTML over script
- Accessible state: the selected lane should be announced, not only painted
- No empty views: a lane with no published work must not produce a blank page
Considered Options
Option 1: Keep the radio filter
Leave /work/ as it was and link the homepage lanes to /work/ unfiltered.
Pros: no change; one page. Cons: lanes stay unlinkable; the homepage strip cannot route.
Option 2: Anchor links with :target
Turn each pill into a link to a fragment (#lane-tools) and filter with
:has(:target).
Pros: linkable; still one page; CSS only. Cons: the selected state cannot be expressed in markup, so it is not exposed to assistive technology; hidden cards remain in the DOM.
Option 3: Radios plus :target
Keep the radios and add :target as a second trigger for deep links.
Pros: keeps the announced radio state for in-page use. Cons: the two triggers conflict. Arriving on a fragment and then choosing a different radio applies both filters and hides every card.
Option 4: One static route per lane (chosen)
Generate /work/lane/<lane>/ for each lane that has published work, rendered by
a component shared with /work/.
Pros: real URLs; aria-current="page" on the selected pill; only matching
cards are rendered; no script.
Cons: more pages in the build; a navigation instead of an in-page filter.
Decision
Option 4.
Implementation Details
src/components/structural/WorkIndex.astrorenders the listing for the index and for every lane page. It takesactiveLane, defaulting toall.src/pages/work/lane/[lane].astrogenerates one page per lane with at least one published case study. A lane with none gets neither a page nor a pill.src/utils/work.tsholds the lane labels,laneHref()andpickRelatedProjects(). “More work” on a case study prefers the same lane, thensortOrder.- The homepage lanes strip links AI Engineering and Dev Tools to their lane
pages and Web & Brand to
/sites/. - The sites gallery (
/sites/) is a live route again. A site entry may name acaseStudyslug; the card links to it only when that case study is published.
Consequences
Positive
- A lane can be linked to, shared, and indexed as its own page
- The selected lane is announced through
aria-current - The Web & Brand lane has a destination
Negative
- Two more pages in the build today (
ai,tools), three once the web lane has a published case study - Choosing a lane is a page navigation; the view transition softens it but it is not instant
- The listing markup now lives in a shared component, one step removed from the page files
Neutral
- The lane route sits under
/work/lane/so it cannot collide with a case-study slug under/work/
Validation
src/utils/__tests__/work.test.tscoverslaneHref()andpickRelatedProjects()e2e/work.spec.tscovers the lane link, the rendered cards,aria-current, and the related-work ordere2e/index.spec.tscovers the homepage lane linkse2e/sites.spec.tscovers the gallery and the case-study link gating- The decision is wrong if lane pages draw no visits once the site is indexed, or if the extra pages push a build or Lighthouse budget
References
- PR #101: the change itself
- Issue #96: the discovery review that prompted it
- Issue #103: this record, and the workflow that was skipped (closed on acceptance)
- ADR-038: the three-pass workflow