fix(ui): resolve eslint errors in dashboard components (#1538)

Clears ~83 pre-existing eslint errors in the UI: auto-fixable rules
(array-type, import/order, sort-imports, type-only imports, redundant
assertions/conditions) plus manual fixes for unnecessary conditions and
banned @ts-nocheck directives. The 8 ported components excluded from
tsconfig are now also ignored by eslint so type-aware linting no longer
fails to parse them.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Johannes du Plessis 2026-06-15 17:29:38 -07:00 • committed by GitHub
parent 3297e799de
commit 0108d764d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 91 additions and 83 deletions

View file

@ -11,6 +11,17 @@ export default [
"dist/**",
"src/routeTree.gen.ts",
"src/components/ui/**",
// Ported from a desktop host app and excluded from tsconfig (not yet
// integrated), so type-aware linting cannot resolve them. Keep this list
// in sync with the `exclude` entries in tsconfig.json.
"src/components/agents/ported/ChatView.tsx",
"src/components/agents/ported/PromptBar.tsx",
"src/components/agents/ported/BranchSelector.tsx",
"src/components/agents/ported/ContextIndicator.tsx",
"src/components/agents/ported/SourceControlPanel.tsx",
"src/components/agents/ported/SourceControlTile.tsx",
"src/components/agents/ported/Footer.tsx",
"src/components/agents/ported/ThreadPicker.tsx",
],
},
...tanstackConfig,

View file

@ -55,7 +55,7 @@ export function AgentInstructionsPanel() {
});
useEffect(() => {
if (detail.data) setDraft(detail.data.instructions ?? "");
if (detail.data) setDraft(detail.data.instructions);
}, [detail.data?.instructions, detail.data?.full_name]);
const create = useMutation({
@ -104,7 +104,7 @@ export function AgentInstructionsPanel() {
const canAdd = normalizedAddRepo !== null && !configured.has(normalizedAddRepo);
const active =
detail.data ?? instructions.data?.find((s) => s.full_name === selected) ?? null;
const dirty = active != null && draft !== (active.instructions ?? "");
const dirty = active != null && draft !== active.instructions;
const handleAdd = () => {
if (!normalizedAddRepo || !canAdd) return;

View file

@ -9,9 +9,11 @@ import {
IoSunnyOutline,
} from "react-icons/io5";
import type { SessionUser } from "@/lib/api";
import type { Theme } from "@/lib/theme";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { api, type SessionUser } from "@/lib/api";
import { useTheme, type Theme } from "@/lib/theme";
import { api } from "@/lib/api";
import { useTheme } from "@/lib/theme";
import { cn } from "@/lib/utils";
const THEME_OPTIONS: Array<{ value: Theme; label: string; icon: typeof IoSunnyOutline }> = [

View file

@ -17,9 +17,9 @@ import type { GitStatus, GitStatusEntry } from "@pierre/trees"
import type { AgentThread, Message } from "@/lib/agents/types"
import type { ThreadPrDiffFile } from "@/lib/agents/api"
import type { ChangedFileSummaryItem } from "@/components/agents/messages"
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
import { buttonVariants } from "@/components/ui/button"
import type { ChangedFileSummaryItem } from "@/components/agents/messages"
import { useDiffOptions } from "@/components/agents/utils/diffUtils"
import { summarizeChangedFiles } from "@/components/agents/ported"
import { Z } from "@/components/agents/z-index"

View file

@ -1,18 +1,18 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { ReplyCard } from "@/components/agents/ported/ReplyCard";
import { ShellCommand } from "@/components/agents/ported/ShellCommand";
import { ToolExecution } from "@/components/agents/ported/ToolExecution";
import type { Message } from "@/lib/agents/types";
import { ChunkRenderer } from "./ChunkRenderer";
import { ReasoningBlock } from "./ReasoningBlock";
import { buildRenderItems, summarizeExploration, type RenderItem } from "./renderItems";
import { SubagentGroup } from "@/components/agents/subagents";
import { buildRenderItems, summarizeExploration } from "./renderItems";
import { summarizeChangedFiles } from "./summarizeChangedFiles";
import { TurnChangedFilesCard } from "./TurnChangedFilesCard";
import { WorkSummary } from "./WorkSummary";
import type { RenderItem } from "./renderItems";
import type { Message } from "@/lib/agents/types";
import type { ApprovalCallbacks, ChangedFileSummaryItem } from "./types";
import { SubagentGroup } from "@/components/agents/subagents";
import { ToolExecution } from "@/components/agents/ported/ToolExecution";
import { ShellCommand } from "@/components/agents/ported/ShellCommand";
import { ReplyCard } from "@/components/agents/ported/ReplyCard";
/**
* Render-item types kept visible (not collapsed) when a turn finishes — the
@ -24,9 +24,9 @@ const REPLY_ITEM_TYPES = new Set<RenderItem["type"]>(["text-chunk", "reply-item"
* Split a finished turn's items into collapsible work and the trailing reply,
* where the reply is the maximal suffix made up solely of reply/text items.
*/
function splitWorkAndReply(items: RenderItem[]): {
workItems: RenderItem[];
replyItems: RenderItem[];
function splitWorkAndReply(items: Array<RenderItem>): {
workItems: Array<RenderItem>;
replyItems: Array<RenderItem>;
} {
let splitIndex = items.length;
while (splitIndex > 0) {

View file

@ -1,9 +1,8 @@
import type { Chunk } from "@/lib/agents/types";
import type { ApprovalCallbacks } from "./types";
import { CodeBlock } from "@/components/agents/ported/CodeBlock";
import { Markdown } from "@/components/agents/ported/Markdown";
import { ToolExecution } from "@/components/agents/ported/ToolExecution";
import type { Chunk } from "@/lib/agents/types";
import type { ApprovalCallbacks } from "./types";
export function ChunkRenderer({
chunk,

View file

@ -1,13 +1,11 @@
// @ts-nocheck — ported from open-swe-app (Electron); strict checks applied when wiring cloud APIs.
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { ChevronDown } from "lucide-react";
import { useLiveMarkdownMessageId } from "@/lib/agents/provider/useLiveMarkdownMessageId";
import { AgentMessage } from "./AgentMessage";
import { ThinkingSpinner } from "./ThinkingSpinner";
import { UserMessage } from "./UserMessage";
import type { MessagesProps } from "./types";
import { useLiveMarkdownMessageId } from "@/lib/agents/provider/useLiveMarkdownMessageId";
const BOTTOM_LOCK_THRESHOLD_PX = 24;

View file

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { formatElapsed } from "@/lib/utils";
const BUSY_TEXTS: { present: string; past: string }[] = [
const BUSY_TEXTS: Array<{ present: string; past: string }> = [
{ present: "vibing...", past: "Vibed" },
{ present: "noodling...", past: "Noodled" },
{ present: "pondering...", past: "Pondered" },

View file

@ -2,9 +2,8 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
import { MultiFileDiff } from "@pierre/diffs/react";
import { useDiffOptions, warmDiffHighlighter } from "@/components/agents/utils/diffUtils";
import type { ChangedFileSummaryItem } from "./types";
import { useDiffOptions, warmDiffHighlighter } from "@/components/agents/utils/diffUtils";
function stripProjectPathForDisplay(path: string, projectPath?: string): string {
if (!projectPath) return path;
@ -84,7 +83,7 @@ export const TurnChangedFilesCard = memo(function TurnChangedFilesCard({
totals,
projectPath,
}: {
files: ChangedFileSummaryItem[];
files: Array<ChangedFileSummaryItem>;
totals: { additions: number; deletions: number };
projectPath?: string;
}) {

View file

@ -5,7 +5,7 @@ import type { Message } from "@/lib/agents/types";
export function UserMessage({ message }: { message: Message }) {
const text = message.chunks
.filter((c) => c.kind === "text")
.map((c) => (c as { kind: "text"; text: string }).text)
.map((c) => c.text)
.join("");
const images = message.chunks.filter((c) => c.kind === "image");
@ -39,14 +39,12 @@ export function UserMessage({ message }: { message: Message }) {
{images.length > 0 && (
<div className="flex gap-2 mb-2 flex-wrap justify-end">
{images.map((img, i) => (
img.kind === "image" && (
<img
key={i}
src={`data:${img.mimeType};base64,${img.base64}`}
alt={img.fileName || "image"}
className="max-w-48 max-h-48 rounded border border-gray-600"
/>
)
<img
key={i}
src={`data:${img.mimeType};base64,${img.base64}`}
alt={img.fileName || "image"}
className="max-w-48 max-h-48 rounded border border-gray-600"
/>
))}
</div>
)}

View file

@ -1,5 +1,6 @@
import { useState, type ReactNode } from "react";
import { useState } from "react";
import { ChevronRight } from "lucide-react";
import type { ReactNode } from "react";
import { formatElapsed } from "@/lib/utils";

View file

@ -3,12 +3,12 @@ import type { Chunk, ToolExecutionChunk } from "@/lib/agents/types";
export type RenderItem =
| { type: "text-chunk"; key: string; chunk: Chunk }
| { type: "reasoning-item"; key: string; chunk: Chunk }
| { type: "explored-group"; key: string; id: string; chunks: ToolExecutionChunk[] }
| { type: "explored-group"; key: string; id: string; chunks: Array<ToolExecutionChunk> }
/**
* One or more `task` (subagent) tool calls collapsed into a single group so
* they can be rendered side by side as a card grid (see subagents/SubagentGroup).
*/
| { type: "subagent-group"; key: string; id: string; chunks: ToolExecutionChunk[] }
| { type: "subagent-group"; key: string; id: string; chunks: Array<ToolExecutionChunk> }
| { type: "edit-item"; key: string; chunk: ToolExecutionChunk }
| { type: "shell-item"; key: string; chunk: ToolExecutionChunk }
| { type: "reply-item"; key: string; chunk: ToolExecutionChunk }
@ -68,11 +68,11 @@ function isSubagentTool(chunk: ToolExecutionChunk): boolean {
return chunk.toolKind === "task";
}
export function buildRenderItems(chunks: Chunk[], messageId?: string): RenderItem[] {
const items: RenderItem[] = [];
let exploredBuffer: ToolExecutionChunk[] = [];
export function buildRenderItems(chunks: Array<Chunk>, messageId?: string): Array<RenderItem> {
const items: Array<RenderItem> = [];
let exploredBuffer: Array<ToolExecutionChunk> = [];
let exploredStartIndex = -1;
let subagentBuffer: ToolExecutionChunk[] = [];
let subagentBuffer: Array<ToolExecutionChunk> = [];
let subagentStartIndex = -1;
const flushExplored = () => {
@ -165,7 +165,7 @@ export function buildRenderItems(chunks: Chunk[], messageId?: string): RenderIte
return items;
}
export function summarizeExploration(chunks: ToolExecutionChunk[]): string {
export function summarizeExploration(chunks: Array<ToolExecutionChunk>): string {
const count = chunks.length;
return `Explored ${count} file${count === 1 ? "" : "s"}`;
}

View file

@ -1,9 +1,9 @@
import { countLineChanges } from "@/components/agents/utils/diffStats";
import type { Chunk, DiffData } from "@/lib/agents/types";
import type { Chunk } from "@/lib/agents/types";
import type { ChangedFileSummaryItem } from "./types";
import { countLineChanges } from "@/components/agents/utils/diffStats";
export function summarizeChangedFiles(chunks: Chunk[]): ChangedFileSummaryItem[] {
export function summarizeChangedFiles(chunks: Array<Chunk>): Array<ChangedFileSummaryItem> {
const byFile = new Map<string, { filePath: string; originalContent: string | null; modifiedContent: string }>();
for (const chunk of chunks) {
@ -12,7 +12,7 @@ export function summarizeChangedFiles(chunks: Chunk[]): ChangedFileSummaryItem[]
const diffEntries = chunk.diffs?.length ? chunk.diffs : (chunk.diffData ? [chunk.diffData] : []);
if (diffEntries.length === 0) continue;
for (const diffData of diffEntries as DiffData[]) {
for (const diffData of diffEntries) {
const existing = byFile.get(diffData.filePath);
if (!existing) {

View file

@ -20,7 +20,7 @@ export type MessagesScrollControl = {
};
export interface MessagesProps extends ApprovalCallbacks {
messages: Message[];
messages: Array<Message>;
isStreaming: boolean;
/** Live run signal from `useStream().isLoading` — drives Streamdown token animation. */
streamIsLoading?: boolean;

View file

@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { getSingletonHighlighter, type ThemedToken } from "shiki";
import { getSingletonHighlighter } from "shiki";
import type { ThemedToken } from "shiki";
import { useResolvedTheme } from "@/lib/theme";
interface CodeBlockProps {
@ -9,7 +10,7 @@ interface CodeBlockProps {
const SHIKI_THEME = { light: "github-light", dark: "github-dark" } as const;
const TOKEN_CACHE = new Map<string, ThemedToken[][]>();
const TOKEN_CACHE = new Map<string, Array<Array<ThemedToken>>>();
function normalizeLanguage(language?: string): string {
const raw = (language || "").toLowerCase().trim();
@ -45,7 +46,7 @@ function languageLabel(language: string): string {
}
export function CodeBlock({ text, language }: CodeBlockProps) {
const [tokens, setTokens] = useState<ThemedToken[][] | null>(null);
const [tokens, setTokens] = useState<Array<Array<ThemedToken>> | null>(null);
const [copied, setCopied] = useState(false);
const resolvedTheme = useResolvedTheme();
const shikiTheme = SHIKI_THEME[resolvedTheme];
@ -67,13 +68,11 @@ export function CodeBlock({ text, language }: CodeBlockProps) {
getSingletonHighlighter({
themes: [shikiTheme],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
langs: [normalizedLanguage as any],
})
.then((highlighter) => {
if (cancelled) return;
const result = highlighter.codeToTokens(text, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
lang: normalizedLanguage as any,
theme: shikiTheme,
});

View file

@ -1,5 +1,5 @@
import { LoaderCircle } from 'lucide-react';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
export function CompactingIndicator() {
const [dots, setDots] = useState(1);

View file

@ -1,8 +1,8 @@
import { useMemo } from "react";
import { MultiFileDiff } from "@pierre/diffs/react";
import type { DiffData } from "@/lib/agents/types";
import { diffOptions } from "@/components/agents/utils/diffUtils";
import { countLineChanges } from "@/components/agents/utils/diffStats";
import type { DiffData } from "@/lib/agents/types";
interface DiffViewProps {
diffData: DiffData;

View file

@ -1,5 +1,6 @@
import { memo, type ReactNode } from "react";
import { memo } from "react";
import { Streamdown } from "streamdown";
import type { ReactNode } from "react";
import "streamdown/styles.css";
interface MarkdownProps {

View file

@ -1,5 +1,7 @@
// @ts-nocheck — ported from open-swe-app (Electron).
import { Children, isValidElement, type ReactNode } from 'react';
import { Children, isValidElement } from 'react';
import type { ReactNode } from 'react';
type ElementWithChildren = { children?: ReactNode };
interface MarkdownTableProps {
children: ReactNode;
@ -9,17 +11,19 @@ function countCells(node: ReactNode): number {
return Children.toArray(node).filter((child) => isValidElement(child) && (child.type === 'th' || child.type === 'td')).length;
}
function collectColumnCounts(node: ReactNode, counts: number[]) {
function collectColumnCounts(node: ReactNode, counts: Array<number>) {
Children.forEach(node, (child) => {
if (!isValidElement(child)) return;
const childProps = child.props as ElementWithChildren;
if (child.type === 'tr') {
const count = countCells(child.props.children);
const count = countCells(childProps.children);
if (count > 0) counts.push(count);
return;
}
collectColumnCounts(child.props.children, counts);
collectColumnCounts(childProps.children, counts);
});
}
@ -29,7 +33,7 @@ function getTableMinWidth(columnCount: number): string {
}
export function MarkdownTable({ children }: MarkdownTableProps) {
const columnCounts: number[] = [];
const columnCounts: Array<number> = [];
collectColumnCounts(children, columnCounts);
const columnCount = Math.max(0, ...columnCounts);

View file

@ -36,7 +36,7 @@ function isSlackTextObject(value: unknown): value is SlackTextObject {
)
}
function isSlackBlockArray(value: unknown): value is SlackBlock[] {
function isSlackBlockArray(value: unknown): value is Array<SlackBlock> {
return (
Array.isArray(value) &&
value.every((block) => !!block && typeof block === "object")
@ -96,7 +96,7 @@ function renderSlackBody(text: string): Array<ReactNode> {
function blocksFromOptions(
message: string,
options: unknown
): SlackBlock[] | null {
): Array<SlackBlock> | null {
if (!Array.isArray(options)) return null
const cleanOptions = options.filter(
(option): option is string =>
@ -115,7 +115,7 @@ function blocksFromOptions(
]
}
function renderSlackBlocks(blocks: SlackBlock[]): ReactNode {
function renderSlackBlocks(blocks: Array<SlackBlock>): ReactNode {
return (
<div className="flex flex-col gap-2">
{blocks.map((block, index) => {

View file

@ -1,4 +1,4 @@
import { memo, useState, useRef, useCallback, useLayoutEffect } from "react";
import { memo, useCallback, useLayoutEffect, useRef, useState } from "react";
import type { ToolExecutionChunk } from "@/lib/agents/types";
interface ShellCommandProps {

View file

@ -1,4 +1,4 @@
import { useRef, useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
CheckCircle2,
ChevronDown,
@ -10,7 +10,7 @@ import {
import type { TodoItem } from "@/lib/agents/types";
interface TodoListProps {
todos: TodoItem[];
todos: Array<TodoItem>;
className?: string;
runActive?: boolean;
}

View file

@ -1,7 +1,7 @@
import { memo, useState, useCallback, useMemo, useRef, useLayoutEffect } from "react";
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import { MultiFileDiff } from "@pierre/diffs/react";
import type { ToolExecutionChunk, AcpToolKind } from "@/lib/agents/types";
import { DiffView } from "./DiffView";
import type { AcpToolKind, ToolExecutionChunk } from "@/lib/agents/types";
import { useDiffOptions } from "@/components/agents/utils/diffUtils";
import { countLineChanges } from "@/components/agents/utils/diffStats";
@ -204,7 +204,7 @@ export const ToolExecution = memo(function ToolExecution({
const editedFileName = editedFilePath ? getFileName(editedFilePath) : "";
const diffStats = diffData ? countLineChanges(diffData.originalContent, diffData.newContent, diffData.filePath) : null;
if (isCompletedEditOp && diffStats && diffData) {
if (isCompletedEditOp && diffStats) {
return (
<InlineDiffCollapsible
filePath={editedFilePath || diffData.filePath}

View file

@ -1,10 +1,9 @@
import { memo } from "react";
import { Bot, Loader2 } from "lucide-react";
import { useIsInAgentThreadStream } from "@/lib/agents/provider/useIsInAgentThreadStream";
import type { ToolExecutionChunk } from "@/lib/agents/types";
import { SubagentActivity } from "./SubagentActivity";
import type { ToolExecutionChunk } from "@/lib/agents/types";
import { useIsInAgentThreadStream } from "@/lib/agents/provider/useIsInAgentThreadStream";
/** Coerce an unknown tool-argument value to a trimmed string, or `""`. */
function asString(value: unknown): string {

View file

@ -1,6 +1,5 @@
import type { ToolExecutionChunk } from "@/lib/agents/types";
import { SubagentCard } from "./SubagentCard";
import type { ToolExecutionChunk } from "@/lib/agents/types";
/** Maximum number of subagent cards rendered per row. */
const MAX_SUBAGENT_COLUMNS = 4;
@ -11,7 +10,7 @@ const MAX_SUBAGENT_COLUMNS = 4;
* {@link MAX_SUBAGENT_COLUMNS}, so 1–4 subagents fill the row evenly and 5+
* wrap onto additional rows.
*/
export function SubagentGroup({ chunks }: { chunks: ToolExecutionChunk[] }) {
export function SubagentGroup({ chunks }: { chunks: Array<ToolExecutionChunk> }) {
const columns = Math.min(Math.max(chunks.length, 1), MAX_SUBAGENT_COLUMNS);
return (
<div

View file

@ -11,7 +11,7 @@ const LIVE_MARKDOWN_CLEAR_MS = 2000;
* keeps markdown streaming until the run has been idle for a beat.
*/
export function useLiveMarkdownMessageId(
visibleMessages: Message[],
visibleMessages: Array<Message>,
streamIsLoading: boolean | undefined,
isStreaming: boolean,
): string | null {

View file

@ -92,7 +92,7 @@ function isFormField(target: EventTarget | null): boolean {
* (Cmd on macOS, Ctrl elsewhere). Accepts one combo or several aliases.
*/
export function useHotkey(
combo: string | string[],
combo: string | Array<string>,
handler: (event: KeyboardEvent) => void,
options: HotkeyOptions = {}
) {

View file

@ -1,10 +1,8 @@
import { createFileRoute } from "@tanstack/react-router"
import type { AgentSource, AgentStatus } from "@/lib/agents/types"
import {
AgentsThreadsPage,
type ThreadsPageFilters,
} from "@/components/agents/AgentsThreadsPage"
import type { ThreadsPageFilters } from "@/components/agents/AgentsThreadsPage";
import { AgentsThreadsPage } from "@/components/agents/AgentsThreadsPage"
const SOURCES: ReadonlyArray<AgentSource> = [
"dashboard",

View file

@ -228,7 +228,7 @@ function CloudAgentsPage() {
label="Default Repository"
description="Used when no repository is specified"
control={
repos.data?.repositories?.length ? (
repos.data?.repositories.length ? (
<div className="w-56">
<RepoSelector
repos={repos.data.repositories}