fix(web): resolve all oxlint errors

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

- add `void` to unhandled promises (no-floating-promises)
- wrap async handlers passed to React event props (no-misused-promises)
- stabilize allLabels/allAuthors/allIdentities with useMemo to avoid
  new array references on every render (exhaustive-deps)

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

Change summary

webui2/src/components/bugs/CommentBox.tsx   | 12 ++++++++++--
webui2/src/components/bugs/IssueFilters.tsx | 18 +++++++++++++-----
webui2/src/components/bugs/LabelEditor.tsx  |  4 +++-
webui2/src/components/bugs/QueryInput.tsx   |  7 +++++--
webui2/src/components/bugs/Timeline.tsx     |  2 +-
webui2/src/components/bugs/TitleEditor.tsx  | 10 ++++++++--
webui2/src/components/code/CommitList.tsx   |  2 +-
webui2/src/components/code/FileDiffView.tsx |  2 +-
webui2/src/components/code/FileViewer.tsx   |  4 ++--
webui2/src/components/layout/Header.tsx     |  2 +-
webui2/src/lib/auth.tsx                     |  2 +-
webui2/src/pages/IdentitySelectPage.tsx     | 18 +++++++++++++++---
webui2/src/pages/NewBugPage.tsx             |  7 ++++++-
13 files changed, 67 insertions(+), 23 deletions(-)

Detailed changes

webui2/src/components/bugs/CommentBox.tsx ๐Ÿ”—

@@ -124,13 +124,21 @@ export function CommentBox({ bugPrefix, bugStatus, ref_ }: CommentBoxProps) {
           <Button
             variant="outline"
             size="sm"
-            onClick={handleToggleStatus}
+            onClick={() => {
+              void handleToggleStatus();
+            }}
             disabled={busy}
             className="min-w-[7.5rem]"
           >
             {isOpen ? "Close issue" : "Reopen issue"}
           </Button>
-          <Button size="sm" onClick={handleComment} disabled={!hasMessage || busy}>
+          <Button
+            size="sm"
+            onClick={() => {
+              void handleComment();
+            }}
+            disabled={!hasMessage || busy}
+          >
             Comment
           </Button>
         </div>

webui2/src/components/bugs/IssueFilters.tsx ๐Ÿ”—

@@ -1,5 +1,5 @@
 import { ArrowUpDown, ChevronDown, Tag, User, X, Search, Check } from "lucide-react";
-import { useState } from "react";
+import { useMemo, useState } from "react";
 
 import { useValidLabelsQuery, useAllIdentitiesQuery } from "@/__generated__/graphql";
 import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -79,12 +79,20 @@ export function IssueFilters({
   const [labelSearch, setLabelSearch] = useState("");
   const [authorSearch, setAuthorSearch] = useState("");
 
-  const validLabels = [...(labelsData?.repository?.validLabels.nodes ?? [])].sort((a, b) =>
-    a.name.localeCompare(b.name),
+  const validLabels = useMemo(
+    () =>
+      [...(labelsData?.repository?.validLabels.nodes ?? [])].sort((a, b) =>
+        a.name.localeCompare(b.name),
+      ),
+    [labelsData],
   );
 
-  const allIdentities = [...(authorsData?.repository?.allIdentities.nodes ?? [])].sort((a, b) =>
-    a.displayName.localeCompare(b.displayName),
+  const allIdentities = useMemo(
+    () =>
+      [...(authorsData?.repository?.allIdentities.nodes ?? [])].sort((a, b) =>
+        a.displayName.localeCompare(b.displayName),
+      ),
+    [authorsData],
   );
 
   const filteredLabels = labelSearch.trim()

webui2/src/components/bugs/LabelEditor.tsx ๐Ÿ”—

@@ -64,7 +64,9 @@ export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps
                   return (
                     <button
                       key={label.name}
-                      onClick={() => toggleLabel(label.name)}
+                      onClick={() => {
+                        void toggleLabel(label.name);
+                      }}
                       className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted"
                     >
                       <span

webui2/src/components/bugs/QueryInput.tsx ๐Ÿ”—

@@ -169,8 +169,11 @@ export function QueryInput({ value, onChange, onSubmit, placeholder, className }
   const { data: labelsData } = useValidLabelsQuery({ variables: { ref: repo } });
   const { data: authorsData } = useAllIdentitiesQuery({ variables: { ref: repo } });
 
-  const allLabels = labelsData?.repository?.validLabels.nodes ?? [];
-  const allAuthors = authorsData?.repository?.allIdentities.nodes ?? [];
+  const allLabels = useMemo(() => labelsData?.repository?.validLabels.nodes ?? [], [labelsData]);
+  const allAuthors = useMemo(
+    () => authorsData?.repository?.allIdentities.nodes ?? [],
+    [authorsData],
+  );
 
   // Compute the filtered suggestion list whenever completion info changes.
   const suggestions = useMemo(() => {

webui2/src/components/bugs/Timeline.tsx ๐Ÿ”—

@@ -74,7 +74,7 @@ function CommentItem({ item, bugPrefix }: { item: CommentItem; bugPrefix: string
       setEditing(false);
       return;
     }
-    editComment({
+    void editComment({
       variables: { input: { targetPrefix: item.id, message: editValue } },
     }).then(() => setEditing(false));
   }

webui2/src/components/bugs/TitleEditor.tsx ๐Ÿ”—

@@ -44,7 +44,7 @@ export function TitleEditor({ bugPrefix, title, humanId, ref_ }: TitleEditorProp
   }
 
   function handleKeyDown(e: React.KeyboardEvent) {
-    if (e.key === "Enter") handleSave();
+    if (e.key === "Enter") void handleSave();
     if (e.key === "Escape") {
       setValue(title);
       setEditing(false);
@@ -62,7 +62,13 @@ export function TitleEditor({ bugPrefix, title, humanId, ref_ }: TitleEditorProp
           className="text-xl font-semibold"
           disabled={loading}
         />
-        <Button size="sm" onClick={handleSave} disabled={loading || !value.trim()}>
+        <Button
+          size="sm"
+          onClick={() => {
+            void handleSave();
+          }}
+          disabled={loading || !value.trim()}
+        >
           Save
         </Button>
         <Button

webui2/src/components/code/CommitList.tsx ๐Ÿ”—

@@ -67,7 +67,7 @@ export function CommitList({ ref_, path }: CommitListProps) {
   function loadMore() {
     if (!cursor) return;
     setLoadingMore(true);
-    fetchMore({
+    void fetchMore({
       variables: { after: cursor },
     })
       .then((result) => {

webui2/src/components/code/FileDiffView.tsx ๐Ÿ”—

@@ -63,7 +63,7 @@ export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps)
 
   function toggle() {
     if (!open && !data && !loading) {
-      fetchDiff({ variables: { repo, hash, path } });
+      void fetchDiff({ variables: { repo, hash, path } });
     }
     setOpen((v) => !v);
   }

webui2/src/components/code/FileViewer.tsx ๐Ÿ”—

@@ -23,7 +23,7 @@ export function FileViewer({ blob, loading }: FileViewerProps) {
     }
     setHighlighted(null);
     let cancelled = false;
-    import("highlight.js").then(({ default: hljs }) => {
+    void import("highlight.js").then(({ default: hljs }) => {
       if (cancelled) return;
       const ext = blob.path.split(".").pop() ?? "";
       const result = hljs.getLanguage(ext)
@@ -43,7 +43,7 @@ export function FileViewer({ blob, loading }: FileViewerProps) {
   const { html, lineCount } = highlighted;
 
   function copyToClipboard() {
-    if (blob.text) navigator.clipboard.writeText(blob.text);
+    if (blob.text) void navigator.clipboard.writeText(blob.text);
   }
 
   return (

webui2/src/components/layout/Header.tsx ๐Ÿ”—

@@ -19,7 +19,7 @@ import { cn } from "@/lib/utils";
 // A full reload is the simplest way to reset all Apollo cache + React state.
 function SignOutButton() {
   function handleSignOut() {
-    fetch("/auth/logout", { method: "POST", credentials: "include" }).finally(() =>
+    void fetch("/auth/logout", { method: "POST", credentials: "include" }).finally(() =>
       window.location.assign("/"),
     );
   }

webui2/src/lib/auth.tsx ๐Ÿ”—

@@ -101,7 +101,7 @@ function ExternalAuthProvider({
   const [loading, setLoading] = useState(true);
 
   useEffect(() => {
-    fetch("/auth/user", { credentials: "include" })
+    void fetch("/auth/user", { credentials: "include" })
       .then((res) => {
         if (res.status === 401) return null;
         if (!res.ok) throw new Error(`/auth/user returned ${res.status}`);

webui2/src/pages/IdentitySelectPage.tsx ๐Ÿ”—

@@ -27,7 +27,7 @@ export function IdentitySelectPage() {
   const [working, setWorking] = useState(false);
 
   useEffect(() => {
-    fetch("/auth/identities", { credentials: "include" })
+    void fetch("/auth/identities", { credentials: "include" })
       .then((res) => {
         if (!res.ok) throw new Error(`unexpected status ${res.status}`);
         return res.json() as Promise<IdentityItem[]>;
@@ -90,7 +90,13 @@ export function IdentitySelectPage() {
                 {id.repoSlug} ยท {id.humanId}
               </p>
             </div>
-            <Button size="sm" disabled={working} onClick={() => adopt(id.id)}>
+            <Button
+              size="sm"
+              disabled={working}
+              onClick={() => {
+                void adopt(id.id);
+              }}
+            >
               Adopt
             </Button>
           </div>
@@ -104,7 +110,13 @@ export function IdentitySelectPage() {
               A fresh git-bug identity will be created from your OAuth profile.
             </p>
           </div>
-          <Button size="sm" disabled={working} onClick={() => adopt(null)}>
+          <Button
+            size="sm"
+            disabled={working}
+            onClick={() => {
+              void adopt(null);
+            }}
+          >
             <Plus className="size-4" />
             Create
           </Button>

webui2/src/pages/NewBugPage.tsx ๐Ÿ”—

@@ -45,7 +45,12 @@ export function NewBugPage() {
 
       <h1 className="mb-6 text-xl font-semibold">New issue</h1>
 
-      <form onSubmit={handleSubmit} className="space-y-4">
+      <form
+        onSubmit={(e) => {
+          void handleSubmit(e);
+        }}
+        className="space-y-4"
+      >
         <div>
           <label htmlFor="title" className="mb-1.5 block text-sm font-medium">
             Title