feat(web): scope commit history to current file/directory path

Quentin Gliech and Claude Opus 4.6 (1M context) created

The History button now passes the current path as a search param, so
viewing history from a subdirectory or file shows only commits touching
that path. Also update README for listbox primitives and path-scoped
commit route.

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

Change summary

webui2/README.md                               |  4 ++--
webui2/src/routes/$repo/_code.tsx              |  3 ++-
webui2/src/routes/$repo/_code/commits/$ref.tsx | 11 +++++++++--
3 files changed, 13 insertions(+), 5 deletions(-)

Detailed changes

webui2/README.md 🔗

@@ -26,7 +26,7 @@ Node 22 is required. If you use asdf, `.tool-versions` pins the right version au
 | `/`                            | Repo picker — auto-redirects for single repo   |
 | `/$repo/tree/$ref/...path`     | Code browser — directory listing               |
 | `/$repo/blob/$ref/...path`     | Code browser — file viewer                     |
-| `/$repo/commits/$ref`          | Commit history                                 |
+| `/$repo/commits/$ref?path=...` | Commit history (optionally scoped to a path)   |
 | `/$repo/commit/$hash`          | Commit detail with collapsible file diffs       |
 | `/$repo/issues`                | Issue list with search, filters, pagination     |
 | `/$repo/issues/new`            | New issue form                                 |
@@ -71,7 +71,7 @@ src/
 
 Components are organized in three layers:
 
-- **`ui/`** — Generic primitives managed by shadcn CLI (`npx shadcn add`). No domain knowledge. Examples: button, input, avatar, badge, popover, separator, skeleton, textarea.
+- **`ui/`** — Generic primitives managed by shadcn CLI (`npx shadcn add`) or hand-written. No domain knowledge. Examples: button, input, avatar, badge, listbox (presentational compound components for dropdown menus), popover, separator, skeleton, textarea. Interactive dropdowns use `@floating-ui/react` hooks wired per-consumer with `Listbox.*` presentational primitives.
 
 - **`shared/`** — App-level reusable components. These know about the domain (bug status, labels, identities) but contain no data fetching. They use **composition APIs** (compound components) and are typed against **colocated GraphQL fragments**. Examples: issue-row, label-badge, status-badge, status-tabs, comment-card, pagination, query-input, write-preview, empty-state, section-heading, issue-filters.
 

webui2/src/routes/$repo/_code.tsx 🔗

@@ -54,7 +54,7 @@ function CodeLayout() {
   function handleRefSelect(newRef: GitRef) {
     const refName = newRef.shortName;
     if (viewMode === "commits") {
-      void navigate({ to: "/$repo/commits/$ref", params: { repo, ref: refName } });
+      void navigate({ to: "/$repo/commits/$ref", params: { repo, ref: refName }, search: { path: currentPath || undefined } });
     } else if (viewMode === "blob") {
       void navigate({
         to: "/$repo/blob/$ref/$",
@@ -92,6 +92,7 @@ function CodeLayout() {
             <ButtonLink
               to="/$repo/commits/$ref"
               params={{ repo, ref: currentRef }}
+              search={{ path: currentPath || undefined }}
               variant="outline"
               size="sm"
             >

webui2/src/routes/$repo/_code/commits/$ref.tsx 🔗

@@ -1,17 +1,24 @@
-// Commit history view: /$repo/commits/$ref
+// Commit history view: /$repo/commits/$ref?path=...
 
 import { createFileRoute } from "@tanstack/react-router";
+import * as v from "valibot";
 
 import { CommitList } from "@/components/code/commit-list";
 
+const commitsSearchSchema = v.object({
+  path: v.fallback(v.optional(v.string()), undefined),
+});
+
 export const Route = createFileRoute("/$repo/_code/commits/$ref")({
   component: CommitsView,
   beforeLoad: () => ({ viewMode: "commits" as const }),
+  validateSearch: (search) => v.parse(commitsSearchSchema, search),
 });
 
 function CommitsView() {
   const { ref: currentRef } = Route.useParams();
   const { ref: repoRef } = Route.useRouteContext();
+  const { path } = Route.useSearch();
 
-  return <CommitList repo={repoRef} ref_={currentRef} />;
+  return <CommitList repo={repoRef} ref_={currentRef} path={path} />;
 }