Browse / github.com/storytold/artcraft-services / patch
fix: mark un-awaited promises explicitly (SonarCloud S9383)
fix: mark un-awaited promises explicitly (SonarCloud S9383) Resolves the 165 `typescript:S9383` findings across the dashboard, webapp, genhub and shared libs. Each flagged expression is a fire-and-forget call (`handleConfirm()`, `loadData()`, a generated API client, `navigator.clipboard.writeText(...).then(...)`, or an async IIFE). `void` marks the ignored promise explicitly, which is the fix the rule documents; it is deliberately not `.catch()` everywhere, because the components already surface failures through their own error state and adding empty handlers would hide them. Two shapes were handled by hand: the twelve async IIFEs (`void (async () =>
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- dylanmtaylor (imported from pull request #179, open)
- Base
e57ee3d63ac478a24149384bf1473ef97aa067f8- Size
- +227 −383 in 99 files
- Review
- pending
- Shared
- 2026-10-28
- Id
90f39c59b1de7ae7f62a497b
How it went
No other operator has reported using it yet.
Diff
raw · frontend/apps/artcraft-dashboard/src/components/BanUserDialog.tsx frontend/apps/artcraft-dashboard/src/components/BulkBanUsersDialog.tsx frontend/apps/artcraft-dashboard/src/components/ChangeUserEmailDialog.tsx frontend/apps/artcraft-dashboard/src/components/GiveCreditsDialog.tsx frontend/apps/artcraft-dashboard/src/components/ImpersonateUserDialog.tsx frontend/apps/artcraft-dashboard/src/components/JobsTable.tsx frontend/apps/artcraft-dashboard/src/components/MediaDetailModal.tsx frontend/apps/artcraft-dashboard/src/components/UserBillingSection.tsx frontend/apps/artcraft-dashboard/src/components/UserFeatureFlagsDialog.tsx frontend/apps/artcraft-dashboard/src/context/AuthContext.tsx frontend/apps/artcraft-dashboard/src/hooks/useInfiniteScroll.ts frontend/apps/artcraft-dashboard/src/pages/DashboardHome.tsx frontend/apps/artcraft-dashboard/src/pages/DebugLogs.tsx frontend/apps/artcraft-dashboard/src/pages/DebugLogsAll.tsx frontend/apps/artcraft-dashboard/src/pages/ExploreMedia.tsx frontend/apps/artcraft-dashboard/src/pages/FeatureFlags.tsx frontend/apps/artcraft-dashboard/src/pages/Impersonation.tsx frontend/apps/artcraft-dashboard/src/pages/JobHistory.tsx frontend/apps/artcraft-dashboard/src/pages/JobInfo.tsx frontend/apps/artcraft-dashboard/src/pages/ReengagementList.tsx frontend/apps/artcraft-dashboard/src/pages/ReferralsAnalytics.tsx frontend/apps/artcraft-dashboard/src/pages/SendPager.tsx frontend/apps/artcraft-dashboard/src/pages/SpendEvents.tsx frontend/apps/artcraft-dashboard/src/pages/StaffAuditLogs.tsx frontend/apps/artcraft-dashboard/src/pages/SubscriberSignups.tsx frontend/apps/artcraft-dashboard/src/pages/TopSpenders.tsx frontend/apps/artcraft-dashboard/src/pages/UserCreations.tsx frontend/apps/artcraft-dashboard/src/pages/UserDebugLogs.tsx frontend/apps/artcraft-dashboard/src/pages/UserEmailChanges.tsx frontend/apps/artcraft-dashboard/src/pages/UserProfile.tsx frontend/apps/artcraft-dashboard/src/pages/UserReferrals.tsx frontend/apps/artcraft-dashboard/src/pages/UserSearch.tsx frontend/apps/artcraft-dashboard/src/pages/UserSignups.tsx frontend/apps/artcraft-dashboard/src/pages/UserSpendHistory.tsx frontend/apps/artcraft-dashboard/src/pages/UserSpendSummary.tsx frontend/apps/artcraft-dashboard/src/pages/WalletHistory.tsx frontend/apps/artcraft-webapp/src/components/auth/signup-form.tsx frontend/apps/artcraft-webapp/src/components/lightbox/LightboxDetails.tsx frontend/apps/artcraft-webapp/src/components/prompt-box/MediaReferenceRow.tsx frontend/apps/artcraft-webapp/src/components/settings-modal/ApiKeySection.tsx frontend/apps/artcraft-webapp/src/components/settings-modal/BillingSection.tsx frontend/apps/artcraft-webapp/src/components/topbar/TopBarActions.tsx frontend/apps/artcraft-webapp/src/components/topbar/task-queue.tsx frontend/apps/artcraft-webapp/src/lib/session.ts frontend/apps/artcraft-webapp/src/pages/checkout/success.tsx frontend/apps/artcraft-webapp/src/pages/create-object/components/mesh-upload.ts frontend/apps/artcraft-webapp/src/pages/create-vfx/create-vfx.tsx frontend/apps/artcraft-webapp/src/pages/forgot-password/forgot-password.tsx frontend/apps/artcraft-webapp/src/pages/forgot-password/verify-reset.tsx frontend/apps/artcraft-webapp/src/pages/frame-extractor/frame-extractor.tsx frontend/apps/artcraft-webapp/src/pages/library/library.tsx frontend/apps/artcraft-webapp/src/pages/login/login.tsx frontend/apps/artcraft-webapp/src/pages/onboarding/onboarding.tsx frontend/apps/artcraft-webapp/src/pages/pagescene/demo-output-overlay.tsx frontend/apps/artcraft-webapp/src/pages/pagescene/splash/SceneSplashCard.tsx frontend/apps/artcraft-webapp/src/pages/pagescene/use-prompt-prefill-from-output.ts frontend/apps/artcraft-webapp/src/pages/pricing/pricing.tsx frontend/apps/artcraft-webapp/src/pages/set-password/set-password.tsx frontend/apps/artcraft-website-next/src/components/media/media-details.tsx frontend/apps/artcraft-website-next/src/lib/use-account.ts frontend/apps/genhub/src/components/feed/media-detail.tsx frontend/apps/genhub/src/components/layout/navbar.tsx frontend/libs/components/create-3d-modal/src/lib/create-3d-modal.tsx frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx frontend/libs/components/generation-list/src/lib/media-prompt-token-cache.ts frontend/libs/components/generation-list/src/lib/prompts-cache.ts frontend/libs/components/help-menu/src/lib/help-menu.tsx frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx frontend/libs/components/lightbox-modal/src/lib/tags/TagsSection.tsx frontend/libs/components/pagedraw/src/lib/PageDraw.tsx frontend/libs/components/pagedraw/src/lib/PaintSurface.tsx frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx frontend/libs/components/pagescene/src/lib/actions/toggleObjectVisibility.ts frontend/libs/components/pagescene/src/lib/engine/editor.ts frontend/libs/components/pagescene/src/lib/engine/editor/ModalTransformController.ts frontend/libs/components/pagescene/src/lib/engine/placeholder_manager.ts frontend/libs/components/pagescene/src/lib/engine/scene.ts frontend/libs/components/pricing-modal/src/lib/use-currency.ts frontend/libs/components/pricing-table/src/lib/pricing-table.tsx frontend/libs/components/promptbox/src/lib/ImagePromptRow.tsx frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx frontend/libs/components/promptbox/src/lib/PromptBoxAudio.tsx frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx frontend/libs/components/promptbox/src/lib/deck/DeckCard.tsx frontend/libs/components/promptbox/src/lib/deck/useDeckMedia.tsx frontend/libs/components/upload-modal/src/lib/UploadModal3D/UploadFiles3D.tsx frontend/libs/components/upload-modal/src/lib/UploadModalImage/UploadFilesImage.tsx frontend/libs/components/upload-modal/src/lib/UploadModalSplat/UploadFilesSplat.tsx frontend/libs/components/video-editor/src/lib/actions/use-editor-actions.ts frontend/libs/components/video-editor/src/lib/fonts/use-font-atlas.ts frontend/libs/components/video-editor/src/lib/hooks/use-fullscreen.ts frontend/libs/components/video-editor/src/lib/timeline/bookmarks/components/bookmarks.tsx frontend/libs/components/video-editor/src/lib/timeline/bookmarks/hooks/use-bookmark-drag.ts frontend/libs/tauri-api/src/lib/events/CanvasBgRemovedEvent.ts frontend/libs/tauri-api/src/lib/sora/useSoraLoginListener.ts frontend/libs/tauri-utils/src/lib/useWindowControls.ts
@@ -194,7 +194,7 @@ export function BanUserDialog({ disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleConfirm();+ void handleConfirm(); }} > {isSubmitting ? (@@ -204,7 +204,7 @@ export function BulkBanUsersDialog({ disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleConfirm();+ void handleConfirm(); }} > {isSubmitting ? (@@ -189,7 +189,7 @@ export function ChangeUserEmailDialog({ disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleConfirm();+ void handleConfirm(); }} > {isSubmitting ? (@@ -156,7 +156,7 @@ export function GiveCreditsDialog({ disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleConfirm();+ void handleConfirm(); }} > {isSubmitting ? (@@ -55,7 +55,7 @@ export function ImpersonateUserDialog({ setImpersonationToken(null); const modApi = new ModerationApi();- modApi.ImpersonateUser({ user_token: userToken }).then((resp) => {+ void modApi.ImpersonateUser({ user_token: userToken }).then((resp) => { if (cancelled) return; if (resp.success && resp.data) { setImpersonationToken(resp.data.password_token);@@ -71,7 +71,7 @@ export function ImpersonateUserDialog({ }, [open, userToken]); const copy = (text: string, field: string) => {- navigator.clipboard.writeText(text).then(() => {+ void navigator.clipboard.writeText(text).then(() => { setCopiedField(field); setTimeout(() => setCopiedField(null), 2000); });@@ -71,7 +71,7 @@ export function JobsTable({ const [copiedToken, setCopiedToken] = useState<string | null>(null); const copyToClipboard = (text: string) => {- navigator.clipboard.writeText(text).then(() => {+ void navigator.clipboard.writeText(text).then(() => { setCopiedToken(text); setTimeout(() => setCopiedToken(null), 2000); });@@ -130,14 +130,14 @@ export function MediaDetailModal({ if (!open || !item) return; let cancelled = false; const api = new MediaApi();- api.GetMediaFile(item.token).then((res) => {+ void api.GetMediaFile(item.token).then((res) => { if (cancelled) return; const promptToken = res.data?.maybe_prompt_token; if (!promptToken) { setPromptFailed(true); return; }- api.GetPrompt(promptToken).then((promptRes) => {+ void api.GetPrompt(promptToken).then((promptRes) => { if (cancelled) return; if (promptRes.success && promptRes.data) { setPromptInfo(promptRes.data);@@ -47,7 +47,7 @@ export function UserBillingSection({ userToken }: { userToken: string }) { }, [userToken]); const copyToClipboard = (text: string, id: string) => {- navigator.clipboard.writeText(text).then(() => {+ void navigator.clipboard.writeText(text).then(() => { setCopiedStates((prev) => ({ ...prev, [id]: true })); setTimeout(() => { setCopiedStates((prev) => ({ ...prev, [id]: false }));@@ -80,7 +80,7 @@ export function UserFeatureFlagsDialog({ setSelectedFlags(new Set()); const api = new ModerationApi();- Promise.all([+ void Promise.all([ api.ListAvailableFeatureFlags(), api.ListUserFeatureFlags(usernameOrToken), ])@@ -360,7 +360,7 @@ export function UserFeatureFlagsDialog({ disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleClearAll();+ void handleClearAll(); }} > {isSubmitting ? (@@ -59,7 +59,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { }; useEffect(() => {- checkSession();+ void checkSession(); }, []); const loginState = (userData: UserInfo) => {new file mode 100644@@ -0,0 +1,26 @@+import { useEffect, useRef } from "react";++/**+ * Calls `onLoadMore` when the element the returned ref is attached to (at the end of a list)+ * scrolls into view.+ */+export function useInfiniteScroll<T extends Element>(onLoadMore: () => void) {+ const sentinelRef = useRef<T | null>(null);++ useEffect(() => {+ const observer = new IntersectionObserver(+ (entries) => {+ if (entries[0].isIntersecting) onLoadMore();+ },+ { threshold: 0.1 },+ );++ if (sentinelRef.current) {+ observer.observe(sentinelRef.current);+ }++ return () => observer.disconnect();+ }, [onLoadMore]);++ return sentinelRef;+}@@ -16,7 +16,7 @@ export function DashboardHome() { useEffect(() => { let cancelled = false; const api = new ModerationApi();- api.ListDataboxDashboards().then((resp) => {+ void api.ListDataboxDashboards().then((resp) => { if (cancelled) return; setDataboards(resp.success && resp.data ? resp.data : []); setIsLoading(false);@@ -32,7 +32,7 @@ export function DebugLogs() { const [filter, setFilter] = useState(""); const copy = (value: string, id: string) => {- navigator.clipboard.writeText(value).then(() => {+ void navigator.clipboard.writeText(value).then(() => { setCopiedId(id); setTimeout(() => setCopiedId((c) => (c === id ? null : c)), 2000); });@@ -59,7 +59,7 @@ export function DebugLogs() { }; useEffect(() => {- loadLogs();+ void loadLogs(); }, [eventToken]); const filteredLogs = useMemo(() => {@@ -53,7 +53,7 @@ export function DebugLogsAll() { const loadingRef = useRef(false); const copy = (value: string, id: string) => {- navigator.clipboard.writeText(value).then(() => {+ void navigator.clipboard.writeText(value).then(() => { setCopiedId(id); setTimeout(() => setCopiedId((c) => (c === id ? null : c)), 2000); });@@ -21,6 +21,7 @@ import { IconPhoto, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; const MEDIA_CLASS_OPTIONS = [ { value: "", label: "All" },@@ -52,9 +53,7 @@ export function ExploreMedia() { const [selectedItem, setSelectedItem] = useState<ExploreMediaFile | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLDivElement | null>(null);-+ // Stable filter key to detect changes const filterKey = useMemo( () => `${filterMediaClasses}|${filterMediaType}|${includeUserUploads}`,@@ -122,22 +121,7 @@ export function ExploreMedia() { }, [nextCursor, isLoadingMore, loadData]); // IntersectionObserver for infinite scroll- useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLDivElement>(loadMore); // Sync URL mediaToken → selectedItem useEffect(() => {@@ -152,7 +136,7 @@ export function ExploreMedia() { } else { // Fetch single media file for direct URL access const api = new MediaApi();- api.GetMediaFile(mediaToken).then((res) => {+ void api.GetMediaFile(mediaToken).then((res) => { if (res.success && res.data) { setSelectedItem(res.data as unknown as ExploreMediaFile); }@@ -49,7 +49,7 @@ export function FeatureFlags() { useEffect(() => { let cancelled = false; const api = new ModerationApi();- api+ void api .ListAvailableFeatureFlags() .then((resp) => { if (cancelled) return;@@ -23,6 +23,7 @@ import { IconArrowRight, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; export function Impersonation() { usePageTitle("Impersonation Logs");@@ -33,9 +34,7 @@ export function Impersonation() { const [nextCursor, setNextCursor] = useState<string | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: string | null, append = false) => { if (!append) { setIsLoading(true);@@ -74,12 +73,12 @@ export function Impersonation() { const handleRefresh = () => { setRequests([]); setNextCursor(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -87,26 +86,11 @@ export function Impersonation() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -64,7 +64,7 @@ export function JobHistory() { useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -162,7 +162,7 @@ export function JobInfo() { const [copiedId, setCopiedId] = useState<string | null>(null); const copy = (value: string, id: string) => {- navigator.clipboard.writeText(value).then(() => {+ void navigator.clipboard.writeText(value).then(() => { setCopiedId(id); setTimeout(() => setCopiedId((c) => (c === id ? null : c)), 2000); });@@ -189,7 +189,7 @@ export function JobInfo() { }; useEffect(() => {- loadJob();+ void loadJob(); }, [jobToken]); const badge = job ? getJobStatusBadgeProps(job.status) : null;@@ -23,6 +23,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; const COLUMN_COUNT = 7; @@ -53,9 +54,7 @@ export function ReengagementList() { const [nextOffset, setNextOffset] = useState<number | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (offset?: number | null, append = false) => { if (!append) { setIsLoading(true);@@ -90,12 +89,12 @@ export function ReengagementList() { const handleRefresh = () => { setCandidates([]); setNextOffset(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -103,21 +102,11 @@ export function ReengagementList() { const loadMore = useCallback(() => { if (!isLoadingMore && nextOffset != null) {- loadData(nextOffset, true);+ void loadData(nextOffset, true); } }, [nextOffset, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );- if (sentinelRef.current) observerRef.current.observe(sentinelRef.current);- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -24,6 +24,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; function formatDate(iso: string) { return new Date(iso).toLocaleString("en-US", {@@ -48,9 +49,7 @@ export function ReferralsAnalytics() { const [nextCursor, setNextCursor] = useState<string | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: string | null, append = false) => { if (!append) { setIsLoading(true);@@ -87,12 +86,12 @@ export function ReferralsAnalytics() { const handleRefresh = () => { setReferrals([]); setNextCursor(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -100,26 +99,11 @@ export function ReferralsAnalytics() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -232,7 +232,7 @@ export function SendPager() { disabled={isSubmitting} onClick={(e) => { e.preventDefault();- handleConfirm();+ void handleConfirm(); }} > {isSubmitting ? (@@ -24,6 +24,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; const COLUMN_COUNT = 8; @@ -84,9 +85,7 @@ export function SpendEvents() { const [nextOffset, setNextOffset] = useState<number | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (offset?: number | null, append = false) => { if (!append) { setIsLoading(true);@@ -123,12 +122,12 @@ export function SpendEvents() { const handleRefresh = () => { setEvents([]); setNextOffset(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -136,26 +135,11 @@ export function SpendEvents() { const loadMore = useCallback(() => { if (!isLoadingMore && nextOffset != null) {- loadData(nextOffset, true);+ void loadData(nextOffset, true); } }, [nextOffset, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -22,6 +22,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; export function StaffAuditLogs() { usePageTitle("Staff Audit Logs");@@ -32,9 +33,7 @@ export function StaffAuditLogs() { const [nextCursor, setNextCursor] = useState<string | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: string | null, append = false) => { if (!append) { setIsLoading(true);@@ -71,12 +70,12 @@ export function StaffAuditLogs() { const handleRefresh = () => { setLogs([]); setNextCursor(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -84,26 +83,11 @@ export function StaffAuditLogs() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -23,6 +23,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; function getSubscriptionStatusBadge(status: string | null) { if (!status) return { className: "text-muted-foreground", label: "Unknown" };@@ -49,9 +50,7 @@ export function SubscriberSignups() { const [nextCursor, setNextCursor] = useState<number | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: number | null, append = false) => { if (!append) { setIsLoading(true);@@ -86,12 +85,12 @@ export function SubscriberSignups() { const handleRefresh = () => { setUsers([]); setNextCursor(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -99,26 +98,11 @@ export function SubscriberSignups() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -23,6 +23,7 @@ import { IconTrophy, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; const COLUMN_COUNT = 7; @@ -58,9 +59,7 @@ export function TopSpenders() { const [nextOffset, setNextOffset] = useState<number | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async ( timeWindow: string, offset?: number | null,@@ -102,18 +101,18 @@ export function TopSpenders() { setWindow(value); setSpenders([]); setNextOffset(null);- loadData(value);+ void loadData(value); }; const handleRefresh = () => { setSpenders([]); setNextOffset(null);- loadData(window);+ void loadData(window); }; useEffect(() => { cancelledRef.current = false;- loadData(DEFAULT_WINDOW);+ void loadData(DEFAULT_WINDOW); return () => { cancelledRef.current = true; };@@ -121,26 +120,11 @@ export function TopSpenders() { const loadMore = useCallback(() => { if (!isLoadingMore && nextOffset != null) {- loadData(window, nextOffset, true);+ void loadData(window, nextOffset, true); } }, [window, nextOffset, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -110,7 +110,7 @@ export function UserCreations() { setIsLoading(false); } - init();+ void init(); return () => { cancelledRef.current = true; };@@ -51,7 +51,7 @@ export function UserDebugLogs() { const loadingRef = useRef(false); const copy = (value: string, id: string) => {- navigator.clipboard.writeText(value).then(() => {+ void navigator.clipboard.writeText(value).then(() => { setCopiedId(id); setTimeout(() => setCopiedId((c) => (c === id ? null : c)), 2000); });@@ -102,7 +102,7 @@ export function UserDebugLogs() { if (userToken) { const api = new ModerationApi();- api.UserLookup(userToken).then((resp) => {+ void api.UserLookup(userToken).then((resp) => { if (resp.success && resp.data?.maybe_user) { setUser({ display_name: resp.data.maybe_user.display_name,@@ -32,6 +32,7 @@ import { IconLoader2, IconPencil, } from "@tabler/icons-react";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; function gravatarUrl(hash?: string): string | undefined { return hash ? `https://gravatar.com/avatar/${hash}?s=48&d=404` : undefined;@@ -80,9 +81,7 @@ export function UserEmailChanges() { const [nextCursor, setNextCursor] = useState<string | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadHistory = async ( username: string, cursor?: string | null,@@ -123,40 +122,25 @@ export function UserEmailChanges() { const loadMore = useCallback(() => { if (user && !isLoadingMore && nextCursor != null) {- loadHistory(user.username, nextCursor, true);+ void loadHistory(user.username, nextCursor, true); } }, [user, nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const handleSelect = (next: ModeratorUserLookupResponse | null) => { setUser(next); setCurrentEmail(next?.email_address ?? null); setChanges([]); setNextCursor(null);- if (next) loadHistory(next.username);+ if (next) void loadHistory(next.username); }; const handleChangeSuccess = (newEmail: string) => { setCurrentEmail(newEmail); setUser((prev) => (prev ? { ...prev, email_address: newEmail } : prev)); toast.success("Email address changed");- if (user) loadHistory(user.username);+ if (user) void loadHistory(user.username); }; const { ref: tableRef, height: tableHeight } = useTableHeight();@@ -150,7 +150,7 @@ export function UserProfile() { ); const copyToClipboard = (text: string, id: string) => {- navigator.clipboard.writeText(text).then(() => {+ void navigator.clipboard.writeText(text).then(() => { setCopiedStates((prev) => ({ ...prev, [id]: true })); setTimeout(() => { setCopiedStates((prev) => ({ ...prev, [id]: false }));@@ -185,7 +185,7 @@ export function UserProfile() { setUser(fetchedUser); setIsWalletsLoading(true);- modApi.ListUserWallets(fetchedUser.token).then((resp) => {+ void modApi.ListUserWallets(fetchedUser.token).then((resp) => { if (cancelled) return; if (resp.success && resp.data) { setWallets(resp.data.wallets);@@ -194,7 +194,7 @@ export function UserProfile() { }); setIsJobsLoading(true);- modApi.ListUserJobs(fetchedUser.token).then((resp) => {+ void modApi.ListUserJobs(fetchedUser.token).then((resp) => { if (cancelled) return; if (resp.success && resp.data) { setJobs(resp.data.jobs);@@ -220,7 +220,7 @@ export function UserProfile() { } } - loadData();+ void loadData(); return () => { cancelled = true; };@@ -25,6 +25,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; function formatDate(iso: string) { return new Date(iso).toLocaleString("en-US", {@@ -52,9 +53,7 @@ export function UserReferrals() { const [nextCursor, setNextCursor] = useState<string | null>(null); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: string | null, append = false) => { if (!username) return; if (!append) {@@ -92,12 +91,12 @@ export function UserReferrals() { const handleRefresh = () => { setReferrals([]); setNextCursor(null);- loadData();+ void loadData(); }; useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -105,26 +104,11 @@ export function UserReferrals() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore, username]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -104,7 +104,7 @@ export function UserSearch() { setSearchResult(foundUser); setIsWalletsLoading(true);- api.ListUserWallets(foundUser.token).then((walletResp) => {+ void api.ListUserWallets(foundUser.token).then((walletResp) => { if (walletResp.success && walletResp.data) { setWallets(walletResp.data.wallets); }@@ -122,7 +122,7 @@ export function UserSearch() { useEffect(() => { if (queryParam) {- performSearch(queryParam);+ void performSearch(queryParam); } }, []); @@ -137,7 +137,7 @@ export function UserSearch() { const copyProfileLink = () => { if (!searchResult) return; const profileUrl = `${window.location.origin}/user/profile/${searchResult.username}`;- navigator.clipboard.writeText(profileUrl).then(() => {+ void navigator.clipboard.writeText(profileUrl).then(() => { setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); });@@ -382,7 +382,7 @@ export function UserSearch() { onOpenChange={setGiveCreditsOpen} onSuccess={() => { const api = new ModerationApi();- api.ListUserWallets(searchResult.token).then((resp) => {+ void api.ListUserWallets(searchResult.token).then((resp) => { if (resp.success && resp.data) { setWallets(resp.data.wallets); }@@ -27,6 +27,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; export function UserSignups() { usePageTitle("User Signups");@@ -41,9 +42,7 @@ export function UserSignups() { const [banDialogOpen, setBanDialogOpen] = useState(false); const cancelledRef = useRef(false);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadData = async (cursor?: number | null, append = false) => { if (!append) { setIsLoading(true);@@ -81,7 +80,7 @@ export function UserSignups() { setUsers([]); setNextCursor(null); setSelectedUsernames(new Set());- loadData();+ void loadData(); }; const toggleUser = (username: string, checked: boolean) => {@@ -112,7 +111,7 @@ export function UserSignups() { useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -120,26 +119,11 @@ export function UserSignups() { const loadMore = useCallback(() => { if (!isLoadingMore && nextCursor != null) {- loadData(nextCursor, true);+ void loadData(nextCursor, true); } }, [nextCursor, isLoadingMore]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();-- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );-- if (sentinelRef.current) {- observerRef.current.observe(sentinelRef.current);- }-- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -26,6 +26,7 @@ import { IconLoader2, } from "@tabler/icons-react"; import { usePageTitle } from "@/hooks/usePageTitle";+import { useInfiniteScroll } from "@/hooks/useInfiniteScroll"; const PAGE_SIZE = 200; const COLUMN_COUNT = 8;@@ -62,9 +63,7 @@ export function UserSpendHistory() { const cancelledRef = useRef(false); const tokenRef = useRef<string | null>(null);- const observerRef = useRef<IntersectionObserver | null>(null);- const sentinelRef = useRef<HTMLTableRowElement | null>(null);-+ const loadPage = useCallback(async (offset: number, append: boolean) => { const token = tokenRef.current; if (!token) return;@@ -94,7 +93,7 @@ export function UserSpendHistory() { if (!username) return; cancelledRef.current = false; - (async () => {+ void (async () => { setIsLoading(true); setError(null); setRows([]);@@ -130,17 +129,7 @@ export function UserSpendHistory() { } }, [nextOffset, isLoadingMore, loadPage]); - useEffect(() => {- if (observerRef.current) observerRef.current.disconnect();- observerRef.current = new IntersectionObserver(- (entries) => {- if (entries[0].isIntersecting) loadMore();- },- { threshold: 0.1 },- );- if (sentinelRef.current) observerRef.current.observe(sentinelRef.current);- return () => observerRef.current?.disconnect();- }, [loadMore]);+ const sentinelRef = useInfiniteScroll<HTMLTableRowElement>(loadMore); const { ref: tableRef, height: tableHeight } = useTableHeight(); @@ -108,7 +108,7 @@ export function UserSpendSummary() { if (!username) return; cancelledRef.current = false; - (async () => {+ void (async () => { setIsLoading(true); setError(null); try {@@ -105,7 +105,7 @@ export function WalletHistory() { const totalCredits = totalMonthly + totalBanked; const copyToClipboard = (text: string) => {- navigator.clipboard.writeText(text).then(() => {+ void navigator.clipboard.writeText(text).then(() => { setCopiedToken(text); setTimeout(() => setCopiedToken(null), 2000); });@@ -170,7 +170,7 @@ export function WalletHistory() { useEffect(() => { cancelledRef.current = false;- loadData();+ void loadData(); return () => { cancelledRef.current = true; };@@ -110,7 +110,7 @@ export const SignupForm = ({ className="space-y-4" onSubmit={(e) => { e.preventDefault();- handleSignup();+ void handleSignup(); }} > {error && (@@ -120,7 +120,7 @@ export function LightboxDetails({ audioPlayerRef.current = null; }; audioPlayerRef.current = audio;- audio.play();+ void audio.play(); setPlayingAudioToken(token); } },@@ -381,7 +381,7 @@ const AudioRefTile = ({ el.volume = 0.2; audioElRef.current = el; el.onended = () => setIsPlaying(false);- el.play();+ void el.play(); setIsPlaying(true); } },@@ -73,7 +73,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) { }; useEffect(() => {- load(true);+ void load(true); // Load once on mount; pagination drives subsequent loads explicitly. // eslint-disable-next-line react-hooks/exhaustive-deps }, []);@@ -82,7 +82,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) { setCreating(false); // Reveal the full secret once; the truncated key then appears in the list. setCreatedKey(created);- load(true);+ void load(true); }; const handleDeleteConfirmed = async () => {@@ -36,7 +36,7 @@ export function BillingSection({ onCloseModal }: BillingSectionProps) { if (!cancelled) setIsLoading(false); } };- fetchSubscription();+ void fetchSubscription(); return () => { cancelled = true; };@@ -73,8 +73,8 @@ export function TopBarActions({ className }: { className?: string }) { useEffect(() => { if (user) {- fetchCredits().then(setCredits);- fetchHasPaidPlan().then(setHasPaidPlan);+ void fetchCredits().then(setCredits);+ void fetchHasPaidPlan().then(setHasPaidPlan); } else { setCredits(null); setHasPaidPlan(null);@@ -83,7 +83,7 @@ export function TopBarActions({ className }: { className?: string }) { useEffect(() => { const handler = () => {- fetchCredits().then(setCredits);+ void fetchCredits().then(setCredits); }; window.addEventListener("credits-change", handler); return () => window.removeEventListener("credits-change", handler);@@ -195,7 +195,7 @@ const CopyPromptButton = ({ prompt }: { prompt: string }) => { aria-label="Copy prompt" onClick={(e) => { e.stopPropagation();- navigator.clipboard.writeText(prompt);+ void navigator.clipboard.writeText(prompt); setCopied(true); setTimeout(() => setCopied(false), 3000); }}@@ -930,11 +930,11 @@ export const TaskQueue = () => { } }; - load();+ void load(); const intervalId = setInterval(load, 5000); const handleTaskUpdate = () => {- if (!cancelled) load();+ if (!cancelled) void load(); }; window.addEventListener("task-queue-update", handleTaskUpdate); @@ -90,7 +90,7 @@ function ensureAuthChangeListener(): void { listenerAttached = true; window.addEventListener("auth-change", () => { invalidateSession();- refreshSession(true);+ void refreshSession(true); }); } @@ -114,7 +114,7 @@ export function useSession(): UseSessionResult { ensureAuthChangeListener(); if (!bootRequested) { bootRequested = true;- refreshSession();+ void refreshSession(); } }, []); @@ -64,7 +64,7 @@ const CheckoutSuccess = () => { setIsCheckingOnboarding(false); }; - checkOnboarding();+ void checkOnboarding(); }, [searchParams]); if (isCheckingOnboarding) {@@ -25,7 +25,7 @@ export async function uploadViewImage( ): Promise<RefImage | null> { const url = await readAsDataUrl(file); return new Promise((resolve) => {- uploadImage({+ void uploadImage({ title: `${label.toLowerCase()}-view-${Math.random().toString(36).substring(2, 10)}`, assetFile: file, progressCallback: (state) => {@@ -88,7 +88,7 @@ export default function CreateVFX() { pollers.set(resultId, cancel); }; - (async () => {+ void (async () => { try { const sessionJobs = await listSessionBackgroundChangeJobs(); if (cancelled) return;@@ -63,7 +63,7 @@ const ForgotPassword = () => { className="space-y-4" onSubmit={(e) => { e.preventDefault();- handleRequestReset();+ void handleRequestReset(); }} > {error && (@@ -130,7 +130,7 @@ const VerifyReset = () => { className="space-y-4" onSubmit={(e) => { e.preventDefault();- handleRedeemReset();+ void handleRedeemReset(); }} > {error && (@@ -133,7 +133,7 @@ export default function FrameExtractor() { if (!token) return; setSearchParams({}, { replace: true }); - (async () => {+ void (async () => { try { const response = await new MediaFilesApi().GetMediaFileByToken({ mediaFileToken: token,@@ -348,7 +348,7 @@ export default function Library() { // ── Auth ────────────────────────────────────────────────────────────────── useEffect(() => {- (async () => {+ void (async () => { const usersApi = new UsersApi(); const response = await usersApi.GetSession(); if (response.success && response.data?.loggedIn && response.data.user) {@@ -82,7 +82,7 @@ const Login = () => { className="space-y-4" onSubmit={(e) => { e.preventDefault();- handleLogin();+ void handleLogin(); }} > {error && (@@ -36,7 +36,7 @@ const Onboarding = () => { const [error, setError] = useState(""); useEffect(() => {- checkOnboardingStatus();+ void checkOnboardingStatus(); }, []); const getSuccessUrl = (afterUsernameStep = false) => {@@ -72,7 +72,7 @@ export function DemoOutputOverlay({ setVideo(null); setView(videoToken ? "video" : "image"); - (async () => {+ void (async () => { const [imageResult, videoResult] = await Promise.all([ imageToken ? resolveAsset(imageToken) : Promise.resolve(null), videoToken ? resolveAsset(videoToken) : Promise.resolve(null),@@ -135,7 +135,7 @@ function useSceneCardImages(outputToken: string): { setPreviewUrl(null); setHoverUrl(null); - (async () => {+ void (async () => { try { const mediaResp = await new MediaFilesApi().GetMediaFileByToken({ mediaFileToken: outputToken,@@ -36,7 +36,7 @@ export function usePromptPrefillFromOutput( if (!outputToken) return; let cancelled = false; - (async () => {+ void (async () => { try { const mediaResp = await new MediaFilesApi().GetMediaFileByToken({ mediaFileToken: outputToken,@@ -35,7 +35,7 @@ const Pricing = () => { // not logged in } };- check();+ void check(); }, []); return (@@ -83,7 +83,7 @@ const SetPassword = () => { className="space-y-4" onSubmit={(e) => { e.preventDefault();- handleSetPassword();+ void handleSetPassword(); }} > {error && (@@ -105,7 +105,7 @@ export default function MediaDetails({ media, prompt, promptLoading, dimensions {prompt?.maybe_positive_prompt && ( <button type="button" className="text-muted hover:text-ink" aria-label={copied === "prompt" ? "Prompt copied" : "Copy prompt"} onClick={() => {- copy(prompt.maybe_positive_prompt!, "prompt");+ void copy(prompt.maybe_positive_prompt!, "prompt"); trackEvent("copy_prompt", { media_type: kind }); }}> {copied === "prompt" ? <CheckIcon className="h-4 w-4" /> : <CopyIcon className="h-4 w-4" />}@@ -149,7 +149,7 @@ export default function MediaDetails({ media, prompt, promptLoading, dimensions <ArrowDownToLineIcon aria-hidden className="h-4 w-4" />{downloading ? "Downloading" : "Download file"} </Button> <Button variant="secondary" className="w-full" onClick={() => {- copy(`${SHARE_URL_BASE}${encodeURIComponent(media.token)}`, "link");+ void copy(`${SHARE_URL_BASE}${encodeURIComponent(media.token)}`, "link"); trackEvent("share", { method: "copy_link", content_type: kind, item_id: media.token }); }}> {copied === "link" ? <CheckIcon aria-hidden className="h-4 w-4" /> : <CopyIcon aria-hidden className="h-4 w-4" />}@@ -50,7 +50,7 @@ export function useAccount(): Account { useEffect(() => { let live = true;- loadAccount().then((a) => {+ void loadAccount().then((a) => { if (live) setAccount(a); }); return () => {@@ -39,7 +39,7 @@ export function MediaDetail({ item }: MediaDetailProps) { const likeCount = liked ? item.likes + 1 : item.likes; function handleCopy() {- navigator.clipboard.writeText(item.prompt);+ void navigator.clipboard.writeText(item.prompt); setCopied(true); setTimeout(() => setCopied(false), 2000); }@@ -126,7 +126,7 @@ export function Navbar() { variant="ghost" className="justify-start" onClick={() => {- logout();+ void logout(); setMobileOpen(false); }} >@@ -94,16 +94,16 @@ export const Create3dModal = ({ onModelComplete }: Create3dModalProps = {}) => { }); if (isUnmounted) {- unlisten.then((f) => f()); // Unsubscribe if unmounted early.+ void unlisten.then((f) => f()); // Unsubscribe if unmounted early. } }; - setup();+ void setup(); return () => { isUnmounted = true; if (unlisten) {- unlisten.then((f) => f());+ void unlisten.then((f) => f()); } }; }, []);@@ -948,7 +948,7 @@ export const GalleryModal = React.memo( // couldn't see it — making the library button appear broken. galleryModalDraggingUnder.value = false; let cancelled = false;- (async () => {+ void (async () => { setUsernameError(false); const name = await fetchCurrentUsername(); if (cancelled) return;@@ -90,7 +90,7 @@ export function useMediaPromptTokens( const key = [...new Set(cleaned)].sort().join(","); useEffect(() => {- if (key.length > 0) fetchMissing(key.split(","));+ if (key.length > 0) void fetchMissing(key.split(",")); }, [key]); const result = new Map<string, string>();@@ -63,7 +63,7 @@ export function usePrompts( const key = [...new Set(cleaned)].sort().join(","); useEffect(() => {- if (key.length > 0) fetchMissing(key.split(","));+ if (key.length > 0) void fetchMissing(key.split(",")); }, [key]); const result = new Map<string, Prompts>();@@ -42,7 +42,7 @@ const NewsView = ({ onBack }: { onBack: () => void }) => { console.warn("Failed to fetch news feed, using bundled content.", e); } };- fetchNews();+ void fetchNews(); }, []); const selectedPost = useMemo(@@ -312,7 +312,7 @@ export function HelpMenuButton({ }); }; - run();+ void run(); return () => { if (progressTimerRef.current) {@@ -179,7 +179,7 @@ export function LightboxModal({ audioPlayerRef.current = null; }; audioPlayerRef.current = audio;- audio.play();+ void audio.play(); setPlayingAudioToken(token); } },@@ -83,7 +83,7 @@ export function TagsSection({ useEffect(() => { if (currentUsername !== undefined) return; let cancelled = false;- resolveSessionUsername().then((username) => {+ void resolveSessionUsername().then((username) => { if (!cancelled) setResolvedUsername(username); }); return () => {@@ -133,7 +133,7 @@ export function TagsSection({ } ownTagsLoadedRef.current = true; let cancelled = false;- (async () => {+ void (async () => { const all: TagDetails[] = []; let cursor: string | undefined = undefined; for (let page = 0; page < 50; page++) {@@ -1040,7 +1040,7 @@ const PageDraw = ({ await tryFit(); }; - autoFitCanvas();+ void autoFitCanvas(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [baseImageBitmap, baseImageInfo?.isBlankCanvas]); @@ -1501,7 +1501,7 @@ export const PaintSurface = ({ // Load the checkboard image for transparency background useEffect(() => {- loadImageFromUrl(checkerboard).then(setCheckerImage);+ void loadImageFromUrl(checkerboard).then(setCheckerImage); }, []); // Separate function to render transformers on their own layer@@ -1167,7 +1167,7 @@ export const useSceneStore = create<SceneState>((set, get, store) => ({ console.error("Failed to restore image:", error); } };- loadImage();+ void loadImage(); } return node;@@ -386,7 +386,7 @@ export const Stage3DBody = ({ useEffect(() => { const handler = onImageDrop( (item: GalleryItem, position: { x: number; y: number }) => {- (async () => {+ void (async () => { if (!editor) { console.warn("Cannot drop asset: editor engine not ready"); return;@@ -14,7 +14,7 @@ export function toggleObjectVisibility(editor: Editor, uuid: string): void { if (!obj) return; const before = obj.visible; editor.bus.emit(new OutlinerItemVisibilityToggledEvent(uuid));- editor.sceneManager?.hideObject(uuid);+ void editor.sceneManager?.hideObject(uuid); editor.history.record( new VisibilityAction(editor, uuid, before, !before), );@@ -190,7 +190,7 @@ class Editor { // Ammo.js WASM is a global side-effect script the physics path // depends on. The singleton ensures it's appended to document.body // exactly once, even across Editor re-construction.- ensureAmmoLoaded();+ void ensureAmmoLoaded(); // Version and name. this.version = 2.0;@@ -667,9 +667,9 @@ class Editor { }; if (!this.utils.isEmpty(cacheJson)) {- this.save_manager.loadCache(cacheJson).then(applyIfLoaded);+ void this.save_manager.loadCache(cacheJson).then(applyIfLoaded); } else if (!this.utils.isEmpty(sceneToken)) {- this.save_manager.loadScene(sceneToken).then(applyIfLoaded);+ void this.save_manager.loadScene(sceneToken).then(applyIfLoaded); } else { this.adapter.onSceneTitleChange?.({ title: "Untitled New Scene",@@ -1065,7 +1065,7 @@ class Editor { } this.lastFrameTime = frameTime;- this.renderSingleFrame();+ void this.renderSingleFrame(); } startRenderLoop() {@@ -151,7 +151,7 @@ export class ModalTransformController { } } this.target.position.copy(this.startPos).add(delta);- this.editor.renderScene();+ void this.editor.renderScene(); } private axisVector(axis: "x" | "y" | "z"): THREE.Vector3 {@@ -190,7 +190,7 @@ export class ModalTransformController { private cancel() { if (this.target) this.target.position.copy(this.startPos);- this.editor.renderScene();+ void this.editor.renderScene(); this.cleanup(); } @@ -77,7 +77,7 @@ export class LoadingPlaceHolderManager { if (result === undefined) { return; }- result.destroyWithScene(this.scene);+ void result.destroyWithScene(this.scene); this.messages.delete(id); } @@ -112,7 +112,7 @@ class Scene { initialize() { this.scene = new THREE.Scene(); this.placeholder_manager = new LoadingPlaceHolderManager(this.scene);- this.placeholder_manager.initialize();+ void this.placeholder_manager.initialize(); this._createGrid(); this._create_base_lighting();@@ -170,7 +170,7 @@ export function useCurrency(): UseCurrencyResult { useEffect(() => { let cancelled = false;- fetchRates().then((r) => {+ void fetchRates().then((r) => { if (!cancelled) { setRates(r); setRatesLoaded(true);@@ -130,7 +130,7 @@ const PricingTable = ({ } }; - fetchUserData();+ void fetchUserData(); }, []); const plans = includeFree@@ -102,7 +102,7 @@ const AudioRefTile = ({ el.volume = 0.2; audioRef.current = el; el.onended = () => setIsPlaying(false);- el.play();+ void el.play(); setIsPlaying(true); } },@@ -347,7 +347,7 @@ export const PromptBox2D = ({ const isSubmitCombo = enterToGenerate && !e.shiftKey; if (isSubmitCombo) { e.preventDefault();- handleGenerate();+ void handleGenerate(); } }; @@ -543,7 +543,7 @@ export const PromptBox3D = ({ const isSubmitCombo = enterToGenerate && !e.shiftKey; if (isSubmitCombo) { e.preventDefault();- handleEnqueue();+ void handleEnqueue(); } }; @@ -352,7 +352,7 @@ export const PromptBoxAudio = ({ if (e.key !== "Enter") return; if (enterToGenerate && !e.shiftKey) { e.preventDefault();- handleEnqueue();+ void handleEnqueue(); } }; @@ -454,7 +454,7 @@ export const PromptBoxImage = ({ const isSubmitCombo = enterToGenerate && !e.shiftKey; if (isSubmitCombo) { e.preventDefault();- handleEnqueue();+ void handleEnqueue(); } }; @@ -1211,7 +1211,7 @@ export const PromptBoxVideo = ({ console.error("PromptBoxVideo - enqueue failed", failure.reason); toast.error(commandErrorMessage(failure.reason, "Failed to start video generation. Please try again.")); }- if (acceptedIds.length) onEnqueuePressed?.(prompt, acceptedIds);+ if (acceptedIds.length) void onEnqueuePressed?.(prompt, acceptedIds); setIsEnqueueing(false); };@@ -1262,7 +1262,7 @@ export const PromptBoxVideo = ({ return; } - handleEnqueue();+ void handleEnqueue(); } }; @@ -1374,7 +1374,7 @@ export const PromptBoxVideo = ({ ) return; if (!prompt.trim()) return;- handleEnqueue();+ void handleEnqueue(); } }} onFocus={() => setIsFocused(true)}@@ -68,7 +68,7 @@ export const DeckCard = ({ el.volume = 0.2; audioRef.current = el; el.onended = () => setIsPlaying(false);- el.play();+ void el.play(); setIsPlaying(true); } }, [isPlaying, item.url, stopAudio]);@@ -524,8 +524,8 @@ export function useDeckMedia< ); if (images.length > 0) processImageFiles(images, "start");- if (videos.length > 0 && setReferenceVideos) processVideoFiles(videos);- if (audios.length > 0 && setReferenceAudios) processAudioFiles(audios);+ if (videos.length > 0 && setReferenceVideos) void processVideoFiles(videos);+ if (audios.length > 0 && setReferenceAudios) void processAudioFiles(audios); }; // maxImages of 0 means the page's model takes no image refs at all.@@ -270,7 +270,7 @@ export const UploadFiles3D = ({ // B's key. useEffect(() => { if (previewStatus.type === "OK" && canvasRef.current) {- snapshotCanvasAsThumbnail({+ void snapshotCanvasAsThumbnail({ targetNode: canvasRef.current, resultCallback: (blob) => { const currentFile = fileEntries[previewIndex]?.file;@@ -503,7 +503,7 @@ export const UploadFiles3D = ({ // can only ever be a clean single-file submission; the excludedCount // check makes that invariant explicit rather than incidental. if (excludedCount === 0 && files.length === 1 && fileEntries.length === 1) {- upload3DObjects({+ void upload3DObjects({ title: files[0].name.slice(0, files[0].name.lastIndexOf(".")), assetFile: files[0], engineCategory,@@ -521,7 +521,7 @@ export const UploadFiles3D = ({ setIsUploading(true); setOverallProgress({ current: 0, total: files.length }); - upload3DObjectsBatch({+ void upload3DObjectsBatch({ files, thumbnails: pendingThumbnails, engineCategory,@@ -581,7 +581,7 @@ export const UploadFiles3D = ({ setIsUploading(true); setOverallProgress({ current: 0, total: failedFiles.length }); - upload3DObjectsBatch({+ void upload3DObjectsBatch({ files: failedFiles, thumbnails: failedThumbnails, engineCategory,@@ -742,7 +742,7 @@ export const UploadFiles3D = ({ className="hidden items-center text-xs text-white/60 transition-colors hover:text-white group-hover:inline-flex" onClick={(e) => { e.stopPropagation();- retrySingleFile(i);+ void retrySingleFile(i); }} title="Retry" >@@ -117,7 +117,7 @@ export const UploadFilesImage = ({ .filter((i) => i !== -1); if (files.length === 1 && fileEntries.length === 1) {- uploadImage({+ void uploadImage({ title: files[0].name.slice(0, files[0].name.lastIndexOf(".")), assetFile: files[0], progressCallback: onUploadProgress,@@ -128,7 +128,7 @@ export const UploadFilesImage = ({ setIsUploading(true); setOverallProgress({ current: 0, total: files.length }); - uploadImagesBatch({+ void uploadImagesBatch({ files, onFileStatusChange: (batchIndex, status, errorMessage) => updateFileStatus(originalIndices[batchIndex], status, errorMessage),@@ -159,7 +159,7 @@ export const UploadFilesImage = ({ setIsUploading(true); setOverallProgress({ current: 0, total: failedFiles.length }); - uploadImagesBatch({+ void uploadImagesBatch({ files: failedFiles, onFileStatusChange: (batchIndex, status, errorMessage) => updateFileStatus(failedIndices[batchIndex], status, errorMessage),@@ -248,7 +248,7 @@ export const UploadFilesImage = ({ className="hidden group-hover:inline-flex items-center text-xs text-white/60 hover:text-white transition-colors" onClick={(e) => { e.stopPropagation();- retrySingleFile(i);+ void retrySingleFile(i); }} title="Retry" >@@ -127,7 +127,7 @@ export const UploadFilesSplat = ({ const canvas = canvasRef.current; const currentFile = fileEntries[previewIndex]?.file; const rafId = requestAnimationFrame(() => {- snapshotCanvasAsThumbnail({+ void snapshotCanvasAsThumbnail({ targetNode: canvas, resultCallback: (blob) => { if (blob && currentFile) {@@ -222,7 +222,7 @@ export const UploadFilesSplat = ({ ); if (files.length === 1 && fileEntries.length === 1) {- upload3DObjects({+ void upload3DObjects({ title: files[0].name.slice(0, files[0].name.lastIndexOf(".")), assetFile: files[0], engineCategory,@@ -236,7 +236,7 @@ export const UploadFilesSplat = ({ setIsUploading(true); setOverallProgress({ current: 0, total: files.length }); - upload3DObjectsBatch({+ void upload3DObjectsBatch({ files, thumbnails: pendingThumbnails, engineCategory,@@ -277,7 +277,7 @@ export const UploadFilesSplat = ({ setIsUploading(true); setOverallProgress({ current: 0, total: failedFiles.length }); - upload3DObjectsBatch({+ void upload3DObjectsBatch({ files: failedFiles, thumbnails: failedThumbnails, engineCategory,@@ -375,7 +375,7 @@ export const UploadFilesSplat = ({ className="hidden group-hover:inline-flex items-center text-xs text-white/60 hover:text-white transition-colors" onClick={(e) => { e.stopPropagation();- retrySingleFile(i);+ void retrySingleFile(i); }} title="Retry" >@@ -441,7 +441,7 @@ export function useEditorActions() { useActionHandler( "toggle-bookmark", () => {- editor.scenes.toggleBookmark({ time: editor.playback.getCurrentTime() });+ void editor.scenes.toggleBookmark({ time: editor.playback.getCurrentTime() }); }, undefined, );@@ -21,7 +21,7 @@ export function useFontAtlas({ open }: { open: boolean }) { if (!open || atlas) return; setStatus("loading");- loadFontAtlas().then((data) => {+ void loadFontAtlas().then((data) => { if (data) { setAtlas(data); setStatus("idle");@@ -34,7 +34,7 @@ export function useFontAtlas({ open }: { open: boolean }) { const retry = useCallback(() => { clearFontAtlasCache(); setStatus("loading");- loadFontAtlas().then((data) => {+ void loadFontAtlas().then((data) => { if (data) { setAtlas(data); setStatus("idle");@@ -20,7 +20,7 @@ export function useFullscreen({ const toggleFullscreen = useCallback(() => { if (!containerRef.current) return; if (document.fullscreenElement) {- document.exitFullscreen();+ void document.exitFullscreen(); } else { containerRef.current.requestFullscreen(); }@@ -313,7 +313,7 @@ function BookmarkPopoverContent({ }, [bookmark.color]); const handleRemove = () => {- editor.scenes.removeBookmark({ time });+ void editor.scenes.removeBookmark({ time }); onPopoverClose(); }; @@ -334,7 +334,7 @@ function BookmarkPopoverContent({ updates.duration = duration; } if (Object.keys(updates).length === 0) return;- editor.scenes.updateBookmark({ time, updates });+ void editor.scenes.updateBookmark({ time, updates }); }; const maxDuration = mediaTimeToSeconds({ time:@@ -240,7 +240,7 @@ export function useBookmarkDrag({ const clampedTime = dragState.currentTime > duration ? duration : dragState.currentTime; - editor.scenes.moveBookmark({+ void editor.scenes.moveBookmark({ fromTime: dragState.bookmarkTime, toTime: clampedTime, });@@ -22,15 +22,15 @@ export const useCanvasBgRemovedEvent = (asyncCallback: (event: CanvasBgRemovedEv }); if (isUnmounted) {- unlisten.then(f => f()); // Unsubscribe if unmounted early.+ void unlisten.then(f => f()); // Unsubscribe if unmounted early. } }; - setup();+ void setup(); return () => { isUnmounted = true;- unlisten.then(f => f());+ void unlisten.then(f => f()); }; }, []);@@ -21,7 +21,7 @@ export function useSoraLoginListener( unlisten = stop; }; - setupListener();+ void setupListener(); return () => { if (unlisten) unlisten();@@ -16,7 +16,7 @@ export function useTauriWindowControls() { let unlistenUnmax: (() => void) | undefined; let unlistenResize: (() => void) | undefined; - (async () => {+ void (async () => { try { const win = await getCurrentWindowSafe(); setIsMaximized(await win.isMaximized());