From a4755cda0e510903a8c675b7a1e82622f0f0d876 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 24 Apr 2026 06:59:07 +0700 Subject: [PATCH] chore(lint): no-explicit-any in app (batch 4) Covers composer, message-input, tooltip, workspace-desktop-tabs-row, and the e2e helpers (app, node-ws-factory, terminal-probes). --- packages/app/e2e/helpers/app.ts | 26 ++++++---- packages/app/e2e/helpers/node-ws-factory.ts | 18 +++---- packages/app/e2e/helpers/terminal-probes.ts | 50 +++++++++++++++---- packages/app/src/components/composer.tsx | 14 +++--- packages/app/src/components/message-input.tsx | 31 +++++++----- packages/app/src/components/ui/tooltip.tsx | 44 ++++++++-------- .../workspace/workspace-desktop-tabs-row.tsx | 19 ++++--- 7 files changed, 125 insertions(+), 77 deletions(-) diff --git a/packages/app/e2e/helpers/app.ts b/packages/app/e2e/helpers/app.ts index 7761139e2f..ab4e727d7f 100644 --- a/packages/app/e2e/helpers/app.ts +++ b/packages/app/e2e/helpers/app.ts @@ -33,20 +33,23 @@ async function ensureE2EStorageSeeded(page: Page): Promise { try { const parsed = JSON.parse(raw); if (!Array.isArray(parsed) || parsed.length !== 1) return true; - const entry = parsed[0] as any; + const entry = parsed[0] as { serverId?: string; connections?: unknown }; if (entry?.serverId !== serverId) return true; const connections = entry?.connections; if (!Array.isArray(connections)) return true; if ( connections.some( - (c: any) => + (c: { type?: string; endpoint?: string }) => c?.type === "directTcp" && typeof c?.endpoint === "string" && /:6767\b/.test(c.endpoint), ) ) return true; - return !connections.some((c: any) => c?.type === "directTcp" && c?.endpoint === endpoint); + return !connections.some( + (c: { type?: string; endpoint?: string }) => + c?.type === "directTcp" && c?.endpoint === endpoint, + ); } catch { return true; } @@ -96,7 +99,7 @@ async function assertE2EUsesSeededTestDaemon(page: Page): Promise { throw new Error("E2E expected @paseo:daemon-registry to be set before app load."); } - let registry: any; + let registry: unknown; try { registry = JSON.parse(snapshot.registryRaw); } catch { @@ -109,7 +112,7 @@ async function assertE2EUsesSeededTestDaemon(page: Page): Promise { ); } - const daemon = registry[0]; + const daemon = registry[0] as { serverId?: string; connections?: unknown }; if (typeof daemon?.serverId !== "string" || daemon.serverId.length === 0) { throw new Error( `E2E expected seeded daemon to have a string serverId (got ${String(daemon?.serverId)}).`, @@ -124,7 +127,10 @@ async function assertE2EUsesSeededTestDaemon(page: Page): Promise { const connections: unknown = daemon?.connections; if ( !Array.isArray(connections) || - !connections.some((c: any) => c?.type === "directTcp" && c?.endpoint === expectedEndpoint) + !connections.some( + (c: { type?: string; endpoint?: string }) => + c?.type === "directTcp" && c?.endpoint === expectedEndpoint, + ) ) { throw new Error( `E2E expected seeded daemon connections to include directTcp ${expectedEndpoint} (got ${JSON.stringify(connections)}).`, @@ -133,7 +139,7 @@ async function assertE2EUsesSeededTestDaemon(page: Page): Promise { if ( Array.isArray(connections) && connections.some( - (c: any) => + (c: { type?: string; endpoint?: string }) => c?.type === "directTcp" && typeof c?.endpoint === "string" && /:6767\b/.test(c.endpoint), ) ) { @@ -146,7 +152,7 @@ async function assertE2EUsesSeededTestDaemon(page: Page): Promise { throw new Error("E2E expected @paseo:create-agent-preferences to be set before app load."); } try { - const prefs = JSON.parse(snapshot.prefsRaw) as any; + const prefs = JSON.parse(snapshot.prefsRaw) as { serverId?: string }; if (prefs?.serverId !== daemon.serverId) { throw new Error( `E2E expected create-agent-preferences.serverId to match seeded daemon serverId (${daemon.serverId}) (got ${String(prefs?.serverId)}).`, @@ -343,8 +349,8 @@ export const ensureHostSelected = async (page: Page) => { const registryRaw = localStorage.getItem("@paseo:daemon-registry"); const prefsRaw = localStorage.getItem("@paseo:create-agent-preferences"); if (!registryRaw || !prefsRaw) return { ok: false, reason: "missing storage" } as const; - const registry = JSON.parse(registryRaw) as any[]; - const prefs = JSON.parse(prefsRaw) as any; + const registry = JSON.parse(registryRaw) as Array<{ serverId?: string }>; + const prefs = JSON.parse(prefsRaw) as { serverId?: string }; if (!Array.isArray(registry) || registry.length !== 1) return { ok: false, reason: "registry shape" } as const; const serverId = registry[0]?.serverId; diff --git a/packages/app/e2e/helpers/node-ws-factory.ts b/packages/app/e2e/helpers/node-ws-factory.ts index 888edd8654..01f8f26a32 100644 --- a/packages/app/e2e/helpers/node-ws-factory.ts +++ b/packages/app/e2e/helpers/node-ws-factory.ts @@ -5,15 +5,15 @@ interface WebSocketLike { send: (data: string | Uint8Array | ArrayBuffer) => void; close: (code?: number, reason?: string) => void; binaryType?: string; - on?: (event: string, listener: (...args: any[]) => void) => void; - off?: (event: string, listener: (...args: any[]) => void) => void; - removeListener?: (event: string, listener: (...args: any[]) => void) => void; - addEventListener?: (event: string, listener: (event: any) => void) => void; - removeEventListener?: (event: string, listener: (event: any) => void) => void; - onopen?: ((event: any) => void) | null; - onclose?: ((event: any) => void) | null; - onerror?: ((event: any) => void) | null; - onmessage?: ((event: any) => void) | null; + on?: (event: string, listener: (...args: unknown[]) => void) => void; + off?: (event: string, listener: (...args: unknown[]) => void) => void; + removeListener?: (event: string, listener: (...args: unknown[]) => void) => void; + addEventListener?: (event: string, listener: (event: unknown) => void) => void; + removeEventListener?: (event: string, listener: (event: unknown) => void) => void; + onopen?: ((event: unknown) => void) | null; + onclose?: ((event: unknown) => void) | null; + onerror?: ((event: unknown) => void) | null; + onmessage?: ((event: unknown) => void) | null; } export type NodeWebSocketFactory = ( diff --git a/packages/app/e2e/helpers/terminal-probes.ts b/packages/app/e2e/helpers/terminal-probes.ts index f8ce6be13e..b1c0705c8b 100644 --- a/packages/app/e2e/helpers/terminal-probes.ts +++ b/packages/app/e2e/helpers/terminal-probes.ts @@ -83,8 +83,12 @@ export interface LatencyStats { export async function installTerminalRenderProbe(page: Page): Promise { await page.addInitScript(() => { + interface ProbeTerm { + write?: (data: string | Uint8Array, callback?: () => void) => void; + __paseoRenderProbeWriteWrapped?: boolean; + } interface ProbeState { - term: any; + term: ProbeTerm | undefined; setCount: number; unsetCount: number; writeCount: number; @@ -101,7 +105,10 @@ export async function installTerminalRenderProbe(page: Page): Promise { startSampling: (durationMs: number) => void; } - const win = window as any; + const win = window as unknown as Record & { + __terminalRenderProbe?: ProbeState; + __paseoTerminal?: ProbeTerm; + }; const existingDescriptor = Object.getOwnPropertyDescriptor(win, "__paseoTerminal"); const getExisting = () => existingDescriptor?.get ? existingDescriptor.get.call(win) : existingDescriptor?.value; @@ -182,7 +189,7 @@ export async function installTerminalRenderProbe(page: Page): Promise { get() { return probe.term; }, - set(next: any) { + set(next: ProbeTerm | undefined) { if (next === undefined) { probe.unsetCount += 1; probe.events.push({ at: performance.now(), type: "unset" }); @@ -230,20 +237,30 @@ export async function installTerminalRenderProbe(page: Page): Promise { }); } +interface TerminalRenderProbeWindow { + __terminalRenderProbe: { + reset: () => void; + startSampling: (durationMs: number) => void; + snapshot: () => TerminalRenderProbeSnapshot; + }; +} + export async function resetTerminalRenderProbe(page: Page): Promise { await page.evaluate(() => { - (window as any).__terminalRenderProbe.reset(); + (window as unknown as TerminalRenderProbeWindow).__terminalRenderProbe.reset(); }); } export async function startTerminalFrameSampling(page: Page, durationMs = 2500): Promise { await page.evaluate((ms) => { - (window as any).__terminalRenderProbe.startSampling(ms); + (window as unknown as TerminalRenderProbeWindow).__terminalRenderProbe.startSampling(ms); }, durationMs); } export async function readTerminalRenderProbe(page: Page): Promise { - return page.evaluate(() => (window as any).__terminalRenderProbe.snapshot()); + return page.evaluate(() => + (window as unknown as TerminalRenderProbeWindow).__terminalRenderProbe.snapshot(), + ); } export async function terminalVisibleText(page: Page): Promise { @@ -639,7 +656,10 @@ export async function installTerminalKeystrokeStressProbe(page: Page): Promise void) => void; + __paseoKeystrokeProbeWriteWrapped?: boolean; + }) { terminal = next; if (next?.write && !next.__paseoKeystrokeProbeWriteWrapped) { const originalWrite = next.write.bind(next); @@ -665,9 +685,18 @@ export async function installTerminalKeystrokeStressProbe(page: Page): Promise void; + report: (text: string) => TerminalKeystrokeStressReport; + }; +} + export async function resetTerminalKeystrokeStressProbe(page: Page): Promise { await page.evaluate(() => { - (window as any).__terminalKeystrokeStressProbe.reset(); + ( + window as unknown as TerminalKeystrokeStressProbeWindow + ).__terminalKeystrokeStressProbe.reset(); }); } @@ -676,7 +705,10 @@ export async function readTerminalKeystrokeStressReport( inputText: string, ): Promise { return page.evaluate( - (text) => (window as any).__terminalKeystrokeStressProbe.report(text), + (text) => + ( + window as unknown as TerminalKeystrokeStressProbeWindow + ).__terminalKeystrokeStressProbe.report(text), inputText, ); } diff --git a/packages/app/src/components/composer.tsx b/packages/app/src/components/composer.tsx index 8748ad29be..20708bedab 100644 --- a/packages/app/src/components/composer.tsx +++ b/packages/app/src/components/composer.tsx @@ -842,8 +842,8 @@ export function Composer({ const cancelButtonStyle = useMemo( () => [ - styles.cancelButton as any, - (!isConnected || isCancellingAgent ? styles.buttonDisabled : undefined) as any, + styles.cancelButton as object, + (!isConnected || isCancellingAgent ? styles.buttonDisabled : undefined) as object | undefined, ], [isConnected, isCancellingAgent], ); @@ -1105,9 +1105,9 @@ export function Composer({ const voiceButtonDisabled = !isConnected || isVoiceSwitching; const realtimeVoiceButtonStyle = useCallback( ({ hovered }: PressableStateCallbackType & { hovered?: boolean }) => [ - styles.realtimeVoiceButton as any, - (Boolean(hovered) ? styles.iconButtonHovered : undefined) as any, - (voiceButtonDisabled ? styles.buttonDisabled : undefined) as any, + styles.realtimeVoiceButton as object, + (Boolean(hovered) ? styles.iconButtonHovered : undefined) as object | undefined, + (voiceButtonDisabled ? styles.buttonDisabled : undefined) as object | undefined, ], [voiceButtonDisabled], ); @@ -1250,7 +1250,7 @@ export function Composer({ ); } -const styles = StyleSheet.create(((theme: Theme) => ({ +const styles = StyleSheet.create((theme: Theme) => ({ container: { flexDirection: "column", position: "relative", @@ -1415,6 +1415,6 @@ const styles = StyleSheet.create(((theme: Theme) => ({ color: theme.colors.palette.red[500], fontSize: theme.fontSize.sm, }, -})) as any) as Record; +})) as unknown as Record; const QUEUE_SEND_BUTTON_STYLE = [styles.queueActionButton, styles.queueSendButton]; diff --git a/packages/app/src/components/message-input.tsx b/packages/app/src/components/message-input.tsx index acde47b7c6..240258e063 100644 --- a/packages/app/src/components/message-input.tsx +++ b/packages/app/src/components/message-input.tsx @@ -19,6 +19,7 @@ import { forwardRef, } from "react"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; +import type { Theme } from "@/styles/theme"; import { ArrowUp, Mic, MicOff, CornerDownLeft, Plus, Square } from "lucide-react-native"; import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated"; import { useDictation } from "@/hooks/use-dictation"; @@ -657,8 +658,9 @@ export const MessageInput = forwardRef(funct const getWebTextArea = useCallback((): TextAreaHandle | null => { const current = textInputRef.current; if (!current) return null; - if (typeof (current as any).getNativeRef === "function") { - const native = (current as any).getNativeRef(); + const candidate = current as { getNativeRef?: () => unknown }; + if (typeof candidate.getNativeRef === "function") { + const native = candidate.getNativeRef(); if (isTextAreaLike(native)) return native; } if (isTextAreaLike(current)) return current; @@ -692,11 +694,16 @@ export const MessageInput = forwardRef(funct return; } - const textarea = getWebTextArea(); + const textarea = getWebTextArea() as + | (TextAreaHandle & { + addEventListener?: (type: string, listener: (e: ClipboardEvent) => void) => void; + removeEventListener?: (type: string, listener: (e: ClipboardEvent) => void) => void; + }) + | null; if ( !textarea || - typeof (textarea as any).addEventListener !== "function" || - typeof (textarea as any).removeEventListener !== "function" + typeof textarea.addEventListener !== "function" || + typeof textarea.removeEventListener !== "function" ) { return; } @@ -727,10 +734,10 @@ export const MessageInput = forwardRef(funct }); }; - (textarea as any).addEventListener("paste", handlePaste); + textarea.addEventListener("paste", handlePaste); return () => { disposed = true; - (textarea as any).removeEventListener("paste", handlePaste); + textarea.removeEventListener?.("paste", handlePaste); }; }, [ disabled, @@ -1227,7 +1234,7 @@ export const MessageInput = forwardRef(funct ); }); -const styles = StyleSheet.create(((theme: any) => ({ +const styles = StyleSheet.create((theme: Theme) => ({ container: { position: "relative", }, @@ -1272,11 +1279,11 @@ const styles = StyleSheet.create(((theme: any) => ({ fontWeight: theme.fontWeight.normal, lineHeight: theme.fontSize.base * 1.4, ...(isWeb - ? { - outlineStyle: "none" as const, + ? ({ + outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent", - } + } as object) : {}), }, buttonRow: { @@ -1362,4 +1369,4 @@ const styles = StyleSheet.create(((theme: any) => ({ right: 0, bottom: 0, }, -})) as any) as Record; +})) as unknown as Record; diff --git a/packages/app/src/components/ui/tooltip.tsx b/packages/app/src/components/ui/tooltip.tsx index 1008bedcd4..c5d8619879 100644 --- a/packages/app/src/components/ui/tooltip.tsx +++ b/packages/app/src/components/ui/tooltip.tsx @@ -307,24 +307,24 @@ export function TooltipTrigger({ }, [clearOpenTimer]); const handleHoverIn = useCallback( - (e?: any) => { - onHoverIn?.(e); + (e?: unknown) => { + onHoverIn?.(e as never); scheduleOpen(); }, [onHoverIn, scheduleOpen], ); const handleHoverOut = useCallback( - (e?: any) => { - onHoverOut?.(e); + (e?: unknown) => { + onHoverOut?.(e as never); close(); }, [onHoverOut, close], ); const handleFocus = useCallback( - (e: any) => { - onFocus?.(e); + (e: unknown) => { + onFocus?.(e as never); if (!ctx.enabled || disabled) return; if (!shouldOpenOnFocus()) return; clearOpenTimer(); @@ -334,16 +334,16 @@ export function TooltipTrigger({ ); const handleBlur = useCallback( - (e: any) => { - onBlur?.(e); + (e: unknown) => { + onBlur?.(e as never); close(); }, [close, onBlur], ); const handlePress = useCallback( - (e: any) => { - onPress?.(e); + (e: unknown) => { + onPress?.(e as never); if (!ctx.enabled || disabled) { return; } @@ -372,7 +372,7 @@ export function TooltipTrigger({ onPointerLeave: handleHoverOut, onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut, - } as any) + } as object) : null), }; @@ -382,21 +382,21 @@ export function TooltipTrigger({ throw new Error("TooltipTrigger with asChild expects a single React element child"); } - const childProps = child.props as Record; + const childProps = child.props as Record; const mergedProps = { ...childProps, ...triggerProps, disabled: childProps.disabled || disabled, - onHoverIn: composeEventHandlers(childProps.onHoverIn, handleHoverIn), - onHoverOut: composeEventHandlers(childProps.onHoverOut, handleHoverOut), - onFocus: composeEventHandlers(childProps.onFocus, handleFocus), - onBlur: composeEventHandlers(childProps.onBlur, handleBlur), - onPress: composeEventHandlers(childProps.onPress, handlePress), - onPointerEnter: composeEventHandlers(childProps.onPointerEnter, handleHoverIn), - onPointerLeave: composeEventHandlers(childProps.onPointerLeave, handleHoverOut), - onMouseEnter: composeEventHandlers(childProps.onMouseEnter, handleHoverIn), - onMouseLeave: composeEventHandlers(childProps.onMouseLeave, handleHoverOut), - } as Record; + onHoverIn: composeEventHandlers(childProps.onHoverIn as never, handleHoverIn), + onHoverOut: composeEventHandlers(childProps.onHoverOut as never, handleHoverOut), + onFocus: composeEventHandlers(childProps.onFocus as never, handleFocus), + onBlur: composeEventHandlers(childProps.onBlur as never, handleBlur), + onPress: composeEventHandlers(childProps.onPress as never, handlePress), + onPointerEnter: composeEventHandlers(childProps.onPointerEnter as never, handleHoverIn), + onPointerLeave: composeEventHandlers(childProps.onPointerLeave as never, handleHoverOut), + onMouseEnter: composeEventHandlers(childProps.onMouseEnter as never, handleHoverIn), + onMouseLeave: composeEventHandlers(childProps.onMouseLeave as never, handleHoverOut), + } as Record; const existingRefProp = childProps[triggerRefProp] as Ref | undefined; mergedProps[triggerRefProp] = (node: View | null) => { diff --git a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx index 7df35baaa4..6a87cf6990 100644 --- a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx +++ b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx @@ -30,7 +30,10 @@ import { } from "lucide-react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { SortableInlineList } from "@/components/sortable-inline-list"; -import type { DraggableRenderItemInfo } from "@/components/draggable-list.types"; +import type { + DraggableListDragHandleProps, + DraggableRenderItemInfo, +} from "@/components/draggable-list.types"; import { isNative, isWeb } from "@/constants/platform"; import { ContextMenu, @@ -220,7 +223,7 @@ function TabChip({ setHoveredCloseTabKey: Dispatch>; onNavigateTab: (tabId: string) => void; onCloseTab: (tabId: string) => Promise | void; - dragHandleProps: any; + dragHandleProps: DraggableListDragHandleProps | undefined; }) { const { theme } = useUnistyles(); const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; @@ -243,7 +246,7 @@ function TabChip({ const tabChipStyle = useCallback( () => [ styles.tab, - isWeb && isDragging && ({ cursor: "grabbing" } as const), + isWeb && isDragging && ({ cursor: "grabbing" } as object), { minWidth: resolvedTabWidth, width: resolvedTabWidth, @@ -322,10 +325,10 @@ function TabChip({ >; onNavigateTab: (tabId: string) => void; onCloseTab: (tabId: string) => Promise | void; - dragHandleProps: any; + dragHandleProps: DraggableListDragHandleProps | undefined; showDropIndicatorBefore: boolean; showDropIndicatorAfter: boolean; }) {