fix(web): fix status tab highlight and author filter active state

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

- parseQueryString now returns null status when no status: token is
  present, so tabs don't falsely highlight "Open" for free-text queries
- buildQueryString omits status: prefix when status is null
- Sync search bar draft with URL query changes via useEffect
- Resolve author query value back to humanId so the author filter
  button shows as active with avatar when an author: filter is set

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

Change summary

webui2/src/lib/query-utils.ts                    |  9 +++---
webui2/src/routes/$repo/_issues/issues/index.tsx | 24 ++++++++++++++---
2 files changed, 24 insertions(+), 9 deletions(-)

Detailed changes

webui2/src/lib/query-utils.ts 🔗

@@ -41,13 +41,13 @@ export function tokenizeQuery(input: string): string[] {
 
 // Parse a query string back into structured filter state.
 export function parseQueryString(input: string): {
-  status: StatusFilter;
+  status: StatusFilter | null;
   labels: string[];
   author: string | null;
   freeText: string;
   sort: SortValue;
 } {
-  let status: StatusFilter = "open";
+  let status: StatusFilter | null = null;
   const labels: string[] = [];
   let author: string | null = null;
   let sort: SortValue = "creation-desc";
@@ -83,13 +83,14 @@ export function buildBaseQuery(labels: string[], author: string | null, freeText
 
 // Build the structured query string sent to the GraphQL allBugs(query:) argument.
 export function buildQueryString(
-  status: StatusFilter,
+  status: StatusFilter | null,
   labels: string[],
   author: string | null,
   freeText: string,
   sort: SortValue = "creation-desc",
 ): string {
-  const parts = [`status:${status}`];
+  const parts: string[] = [];
+  if (status) parts.push(`status:${status}`);
   const base = buildBaseQuery(labels, author, freeText);
   if (base) parts.push(base);
   if (sort !== "creation-desc") parts.push(`sort:${sort}`);

webui2/src/routes/$repo/_issues/issues/index.tsx 🔗

@@ -2,7 +2,7 @@ import { useReadQuery } from "@apollo/client/react";
 import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
 import { formatDistanceToNow } from "date-fns";
 import { Search } from "lucide-react";
-import { useMemo, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
 import * as v from "valibot";
 
 import { type BugListQuery, BugListDocument } from "@/__generated__/graphql";
@@ -61,12 +61,14 @@ function RouteComponent() {
     author: selectedAuthorQuery,
     sort,
   } = parsed;
-  // We don't have the humanId from URL — the dropdown will match by query value
-  const selectedAuthorId: string | null = null;
-
   // Draft is the text input value — starts from URL, only committed on submit
   const [draft, setDraft] = useState(q);
 
+  // Sync draft when URL query changes (e.g. tab clicks, filter changes)
+  useEffect(() => {
+    setDraft(q);
+  }, [q]);
+
   const { bugListRef } = Route.useLoaderData();
   const { labelsRef, identitiesRef } = Route.useRouteContext();
   const { data } = useReadQuery(bugListRef);
@@ -79,6 +81,18 @@ function RouteComponent() {
   const validLabels = labelsData?.repository?.validLabels.nodes;
   const allIdentities = identitiesData?.repository?.allIdentities.nodes;
 
+  // Resolve the author query value (login/name) back to a humanId for the filter UI
+  const selectedAuthorId = useMemo(() => {
+    if (!selectedAuthorQuery || !allIdentities) return null;
+    const match = allIdentities.find(
+      (i) =>
+        i.login === selectedAuthorQuery ||
+        i.name === selectedAuthorQuery ||
+        i.humanId === selectedAuthorQuery,
+    );
+    return match?.humanId ?? null;
+  }, [selectedAuthorQuery, allIdentities]);
+
   const completionProviders: CompletionProvider[] = useMemo(
     () => [
       {
@@ -228,7 +242,7 @@ function RouteComponent() {
 
         {/* Bug rows */}
         {bugs?.nodes.length === 0 && (
-          <EmptyState>No {statusFilter} issues found.</EmptyState>
+          <EmptyState>No {statusFilter ?? ""} issues found.</EmptyState>
         )}
 
         {bugs?.nodes.map((bug) => (