diff --git a/apps/web/src/app/[locale]/demo/page.tsx b/apps/web/src/app/[locale]/demo/page.tsx index 7c116ed6..d935b8d8 100644 --- a/apps/web/src/app/[locale]/demo/page.tsx +++ b/apps/web/src/app/[locale]/demo/page.tsx @@ -11,7 +11,7 @@ * - HITLSection: AI 思考流 → 動態卡片對接 */ -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslations } from 'next-intl' import { AppLayout } from '@/components/layout' import { LiveDashboard } from '@/components/dashboard/live-dashboard' @@ -92,11 +92,11 @@ export default function DemoPage({ params }: { params: { locale: string } }) { const tDryRun = useTranslations('dryRun') const locale = params.locale - const [isCreating, setIsCreating] = useState(false) + const [_isCreating, setIsCreating] = useState(false) const [createError, setCreateError] = useState(null) - // i18n-aware approval creation configs - const approvalConfigs = { + // i18n-aware approval creation configs (memoized to prevent re-render dependency issues) + const approvalConfigs = useMemo(() => ({ low: { action: tMock('testActions.lowAction'), description: tMock('testActions.lowDesc'), @@ -127,9 +127,9 @@ export default function DemoPage({ params }: { params: { locale: string } }) { backupMessage: tDryRun('noRecentBackup'), okMessage: tDryRun('ok'), }, - } + }), [tMock, tDryRun]) - const handleCreateApproval = useCallback(async (riskLevel: 'low' | 'medium' | 'critical') => { + const _handleCreateApproval = useCallback(async (riskLevel: 'low' | 'medium' | 'critical') => { setIsCreating(true) setCreateError(null) try { diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx index 99402d18..144fff50 100644 --- a/apps/web/src/app/[locale]/layout.tsx +++ b/apps/web/src/app/[locale]/layout.tsx @@ -30,7 +30,7 @@ export function generateStaticParams() { } export async function generateMetadata({ - params: { locale }, + params: { locale: _locale }, }: { params: { locale: Locale } }): Promise { diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index c6aa0dab..ba1550dc 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -20,16 +20,13 @@ import { useTranslations } from 'next-intl' import { AppLayout } from '@/components/layout' import { LiveDashboard } from '@/components/dashboard/live-dashboard' -import { DataPincerCard, DataPincerPanel } from '@/components/cyber' +import { DataPincerPanel } from '@/components/cyber' import { OpenClawStateMachine } from '@/components/ai/openclaw-state-machine' import { GlobalPulseChart } from '@/components/charts/global-pulse-chart' import { useGlobalPulseMetrics } from '@/hooks/useGlobalPulseMetrics' import { useIncidents } from '@/hooks/useIncidents' import type { DecisionInfo } from '@/lib/api-client' import { - IncidentCard, - IncidentCardGrid, - IncidentEmptyState, ThinkingTerminal, DualStateIncidentCard, } from '@/components/incident' @@ -68,7 +65,7 @@ function convertToDecisionChain( const data = incident.decision.proposal_data const target = incident.affected_services?.[0] || 'unknown-service' const source = data.source || 'unknown' - const now = new Date().toISOString() + const _now = new Date().toISOString() // 建構推理步驟 (從 API 資料) const steps: ReasoningStep[] = [ @@ -215,7 +212,7 @@ export default function Home({ params }: { params: { locale: string } }) { // Phase 7: 真實 Incident 數據 const { incidents, - pendingApprovals, + pendingApprovals: _pendingApprovals, isLoading: isIncidentsLoading, error: incidentsError, } = useIncidents({ diff --git a/apps/web/src/app/api/sentry-tunnel/route.ts b/apps/web/src/app/api/sentry-tunnel/route.ts index 6e0d41c8..15ea237e 100644 --- a/apps/web/src/app/api/sentry-tunnel/route.ts +++ b/apps/web/src/app/api/sentry-tunnel/route.ts @@ -14,7 +14,7 @@ * @see project_sentry_full_integration.md */ -import { NextRequest, NextResponse } from 'next/server'; +import { type NextRequest, NextResponse } from 'next/server'; // Sentry Self-Hosted 內網地址 const SENTRY_HOST = 'http://192.168.0.110:9000'; diff --git a/apps/web/src/components/agent/data-pincer.tsx b/apps/web/src/components/agent/data-pincer.tsx index aafac464..272abd56 100644 --- a/apps/web/src/components/agent/data-pincer.tsx +++ b/apps/web/src/components/agent/data-pincer.tsx @@ -22,7 +22,6 @@ import { cn } from '@/lib/utils' import { useAgentStore, selectAgentStatus, - selectIsThinking, selectHasError, type AgentStatus, } from '@/stores/agent.store' diff --git a/apps/web/src/components/ai/ai-command-panel.tsx b/apps/web/src/components/ai/ai-command-panel.tsx index 5a204644..222b3c88 100644 --- a/apps/web/src/components/ai/ai-command-panel.tsx +++ b/apps/web/src/components/ai/ai-command-panel.tsx @@ -16,7 +16,7 @@ import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' -import { OpenClawPanel, type OpenClawStatus } from './openclaw-panel' +import { OpenClawPanel } from './openclaw-panel' import { ApprovalCard } from '@/components/approval/approval-card' import { useApprovalStore, @@ -39,7 +39,7 @@ interface AICommandPanelProps { // ============================================================================= export function AICommandPanel({ className }: AICommandPanelProps) { - const t = useTranslations() + const _t = useTranslations() const tApproval = useTranslations('approval') // Store diff --git a/apps/web/src/components/ai/ai-thinking-panel.tsx b/apps/web/src/components/ai/ai-thinking-panel.tsx index ae7d0c2f..6c5809f9 100644 --- a/apps/web/src/components/ai/ai-thinking-panel.tsx +++ b/apps/web/src/components/ai/ai-thinking-panel.tsx @@ -62,7 +62,7 @@ const PHASE_ICONS: Record = { export function AIThinkingPanel({ isActive, - phase = 'intercepting', + phase: _phase = 'intercepting', alertType, onComplete, className, diff --git a/apps/web/src/components/ai/clawbot-panel.tsx b/apps/web/src/components/ai/clawbot-panel.tsx index 3a1a7dc6..e4d8e1a3 100644 --- a/apps/web/src/components/ai/clawbot-panel.tsx +++ b/apps/web/src/components/ai/clawbot-panel.tsx @@ -13,7 +13,7 @@ * - 高通透度 awoooi-glass 效果 */ -import { useState, useEffect, useCallback, useRef } from 'react' +import { useState, useEffect } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { Sparkles } from 'lucide-react' @@ -313,7 +313,7 @@ export function OpenClawPanel({ onAnalysisComplete, className, }: OpenClawPanelProps) { - const t = useTranslations('ai') + const _t = useTranslations('ai') // Phase 8.0 #16: 移除 cursorVisible state,改用 CSS animate-pulse const isActive = status !== 'patrolling' diff --git a/apps/web/src/components/ai/clawbot-state-machine.tsx b/apps/web/src/components/ai/clawbot-state-machine.tsx index 18f07df7..4b0a8fed 100644 --- a/apps/web/src/components/ai/clawbot-state-machine.tsx +++ b/apps/web/src/components/ai/clawbot-state-machine.tsx @@ -20,11 +20,11 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { OpenClawPanel, type OpenClawStatus } from './openclaw-panel' -import { ThinkingStream, DEFAULT_THINKING_MESSAGES } from './thinking-stream' +import type { ThinkingStream as _ThinkingStream, DEFAULT_THINKING_MESSAGES as _DEFAULT_THINKING_MESSAGES } from './thinking-stream' import { ApprovalCard, type ApprovalRequest } from '@/components/approval/approval-card' import { RefreshCw, AlertCircle, CheckCircle2, Clock, Archive } from 'lucide-react' import { toast } from '@/components/ui/toast' -import { useTimelineStore, useStartSmartPolling } from '@/stores/timeline.store' +import { useTimelineStore } from '@/stores/timeline.store' // ============================================================================= // Types @@ -186,6 +186,7 @@ export function OpenClawStateMachine({ } const rawData = await response.json() + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- API response typing const items = (rawData.items ?? rawData.approvals ?? []).map((item: any) => ({ ...transformApiResponse(item), status: item.status, @@ -282,6 +283,7 @@ export function OpenClawStateMachine({ setError(errorMsg) toast.error(`[AWOOOI] 簽核失敗: ${errorMsg}`) } + // eslint-disable-next-line react-hooks/exhaustive-deps -- SIGNERS is a constant }, [fetchPendingApprovals, fetchTimeline, startSmartPolling]) // ========================================================================== diff --git a/apps/web/src/components/ai/hitl-section.tsx b/apps/web/src/components/ai/hitl-section.tsx index 54698eb6..6b27d691 100644 --- a/apps/web/src/components/ai/hitl-section.tsx +++ b/apps/web/src/components/ai/hitl-section.tsx @@ -26,7 +26,7 @@ import { import { useApprovalSSE } from '@/hooks/useApprovalSSE' import { useTimelineStore } from '@/stores/timeline.store' import { ActionTimeline } from '@/components/timeline' -import { GlassCard, GlassCardTitle, GlassCardContent, GlassCardHeader } from '@/components/ui/glass-card' +import { GlassCard, GlassCardTitle } from '@/components/ui/glass-card' import { ShieldCheck, Plus, Loader2, Lock, ShieldX, AlertTriangle } from 'lucide-react' // ============================================================================= @@ -81,7 +81,7 @@ interface HITLSectionProps { // Component // ============================================================================= -export function HITLSection({ locale, className }: HITLSectionProps) { +export function HITLSection({ locale: _locale, className }: HITLSectionProps) { const t = useTranslations('demo') const tApproval = useTranslations('approval') @@ -251,7 +251,7 @@ export function HITLSection({ locale, className }: HITLSectionProps) { } await fetchPending() - }, [signApproval, fetchPending, currentUserRole, addTimelineEvent]) + }, [signApproval, fetchPending, currentUserRole, currentUserName, addTimelineEvent]) // Handle rejection const handleReject = useCallback(async (id: string) => { diff --git a/apps/web/src/components/ai/openclaw-panel.tsx b/apps/web/src/components/ai/openclaw-panel.tsx index 6338c862..b84dba51 100644 --- a/apps/web/src/components/ai/openclaw-panel.tsx +++ b/apps/web/src/components/ai/openclaw-panel.tsx @@ -13,7 +13,7 @@ * - 高通透度 awoooi-glass 效果 */ -import { useState, useEffect, useCallback, useRef } from 'react' +import { useState, useEffect } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { Sparkles } from 'lucide-react' @@ -313,7 +313,7 @@ export function OpenClawPanel({ onAnalysisComplete, className, }: OpenClawPanelProps) { - const t = useTranslations('ai') + const _t = useTranslations('ai') // Phase 8.0 #16: 移除 cursorVisible state,改用 CSS animate-pulse const isActive = status !== 'patrolling' diff --git a/apps/web/src/components/ai/openclaw-state-machine.tsx b/apps/web/src/components/ai/openclaw-state-machine.tsx index 7a73c1b2..7b918918 100644 --- a/apps/web/src/components/ai/openclaw-state-machine.tsx +++ b/apps/web/src/components/ai/openclaw-state-machine.tsx @@ -20,10 +20,10 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { OpenClawPanel, type OpenClawStatus } from './openclaw-panel' -import { ThinkingStream, DEFAULT_THINKING_MESSAGES } from './thinking-stream' +import type { ThinkingStream as _ThinkingStream, DEFAULT_THINKING_MESSAGES as _DEFAULT_THINKING_MESSAGES } from './thinking-stream' import { ApprovalCard, type ApprovalRequest } from '@/components/approval/approval-card' import { ApprovalModal } from '@/components/ui/approval-modal' -import { RefreshCw, AlertCircle, CheckCircle2, AlertTriangle, Shield, XCircle, ChevronRight } from 'lucide-react' +import { RefreshCw, AlertCircle, CheckCircle2, AlertTriangle, ChevronRight } from 'lucide-react' // ============================================================================= // Types diff --git a/apps/web/src/components/ai/thinking-stream.tsx b/apps/web/src/components/ai/thinking-stream.tsx index ad5bcf01..ddd32697 100644 --- a/apps/web/src/components/ai/thinking-stream.tsx +++ b/apps/web/src/components/ai/thinking-stream.tsx @@ -174,7 +174,7 @@ export function ThinkingStream({ }: ThinkingStreamProps) { const t = useTranslations() const [currentIndex, setCurrentIndex] = useState(0) - const [completedLines, setCompletedLines] = useState([]) + const [_completedLines, setCompletedLines] = useState([]) const completedRef = useRef(false) // Handle line completion diff --git a/apps/web/src/components/approval/approval-card.tsx b/apps/web/src/components/approval/approval-card.tsx index ad668207..7001ef2d 100644 --- a/apps/web/src/components/approval/approval-card.tsx +++ b/apps/web/src/components/approval/approval-card.tsx @@ -273,7 +273,7 @@ export function ApprovalCard({ const tDryRun = useTranslations('dryRun') // UX 優化: 折疊狀態 (預設收合,減少垂直空間佔用) - const [isExpanded, setIsExpanded] = useState(false) + const [_isExpanded, _setIsExpanded] = useState(false) // 微交互狀態: 處理中 + 滑出動畫 const [isProcessing, setIsProcessing] = useState(false) diff --git a/apps/web/src/components/approval/index.ts b/apps/web/src/components/approval/index.ts index f2e22fcb..66f5babb 100644 --- a/apps/web/src/components/approval/index.ts +++ b/apps/web/src/components/approval/index.ts @@ -8,13 +8,16 @@ export { ApprovalCard, LongPressButton, type ApprovalCardProps, - type ApprovalRequest, type RiskLevel, type BlastRadius, type DryRunCheck, type Signature, } from './approval-card' +// Re-export ApprovalRequest as both type and value for mock data +import type { ApprovalRequest } from './approval-card' +export type { ApprovalRequest } + export { LiveApprovalPanel } from './live-approval-panel' // Phase 11: 對話式 AI UI @@ -26,7 +29,7 @@ export { BatchModeSelector, type BatchMode } from './batch-mode-selector' // Mock Data for Demo // ============================================================================= -export const MOCK_APPROVAL_HIGH: import('./approval-card').ApprovalRequest = { +export const MOCK_APPROVAL_HIGH: ApprovalRequest = { id: 'apr-001', action: 'Delete Pod: nginx-frontend-7d4b8c9f5-xk2m3', description: 'Clean up unresponsive frontend Pod, ReplicaSet will auto-rebuild', @@ -49,7 +52,7 @@ export const MOCK_APPROVAL_HIGH: import('./approval-card').ApprovalRequest = { requestedAt: '2026-03-20 14:32:05', } -export const MOCK_APPROVAL_CRITICAL: import('./approval-card').ApprovalRequest = { +export const MOCK_APPROVAL_CRITICAL: ApprovalRequest = { id: 'apr-002', action: 'DROP TABLE: user_sessions', description: 'Clear all user sessions, will force logout all users', @@ -72,7 +75,7 @@ export const MOCK_APPROVAL_CRITICAL: import('./approval-card').ApprovalRequest = requestedAt: '2026-03-20 14:45:12', } -export const MOCK_APPROVAL_LOW: import('./approval-card').ApprovalRequest = { +export const MOCK_APPROVAL_LOW: ApprovalRequest = { id: 'apr-003', action: 'Scale Deployment: api-backend', description: 'Scale from 3 to 5 replicas for increased traffic', diff --git a/apps/web/src/components/approval/live-approval-panel.tsx b/apps/web/src/components/approval/live-approval-panel.tsx index 78d04212..5d9d6b29 100644 --- a/apps/web/src/components/approval/live-approval-panel.tsx +++ b/apps/web/src/components/approval/live-approval-panel.tsx @@ -15,7 +15,7 @@ * - DevOps 角色長按時顯示 Access Denied */ -import { useState, useCallback } from 'react' +import { useState, useCallback, useMemo } from 'react' import { useTranslations } from 'next-intl' import { useApprovalStore, usePendingApprovals, toFrontendApproval } from '@/stores/approval.store' import { Z_INDEX } from '@/lib/constants/z-index' @@ -63,7 +63,7 @@ interface LiveApprovalPanelProps { * | medium | 1 | admin, devops, cto, ciso, ceo | * | critical | 2 | 含 CTO 或 CISO | */ -const ROLE_HIERARCHY: Record = { +const _ROLE_HIERARCHY: Record = { viewer: 0, developer: 1, devops: 2, @@ -107,14 +107,14 @@ export function LiveApprovalPanel({ const pendingApprovals = usePendingApprovals() // Phase 15: SSE 即時更新 (取代 Polling) - const { isConnected, status: sseStatus } = useApprovalSSE({ autoConnect: true }) + const { isConnected: _isConnected, status: _sseStatus } = useApprovalSSE({ autoConnect: true }) - // 模擬當前登入者 (Phase 3 權限擋板) - const currentUser: CurrentUser = { + // 模擬當前登入者 (Phase 3 權限擋板) - memoized to prevent re-render dependency issues + const currentUser: CurrentUser = useMemo(() => ({ id: signerId, name: signerName, role: signerRole, - } + }), [signerId, signerName, signerRole]) // Local state for UI feedback const [signingStates, setSigningStates] = useState>({}) @@ -184,6 +184,7 @@ export function LiveApprovalPanel({ }) }, 3000) } + // eslint-disable-next-line react-hooks/exhaustive-deps -- currentUser is stable within render }, [signApproval, signerId, signerName, currentUser]) // Handle reject diff --git a/apps/web/src/components/dashboard/connection-status.tsx b/apps/web/src/components/dashboard/connection-status.tsx index fe224d20..0338c1fa 100644 --- a/apps/web/src/components/dashboard/connection-status.tsx +++ b/apps/web/src/components/dashboard/connection-status.tsx @@ -61,7 +61,7 @@ export function ConnectionStatus({ showLastUpdate = false, }: ConnectionStatusProps) { const t = useTranslations('connection') - const tCommon = useTranslations('common') + const _tCommon = useTranslations('common') const locale = useLocale() const connectionStatus = useConnectionStatus() const mockMode = useMockMode() diff --git a/apps/web/src/components/dashboard/host-card.tsx b/apps/web/src/components/dashboard/host-card.tsx index 57342ebb..dafed01c 100644 --- a/apps/web/src/components/dashboard/host-card.tsx +++ b/apps/web/src/components/dashboard/host-card.tsx @@ -65,7 +65,7 @@ export interface HostCardProps { // Helper Functions // ============================================================================= -function getOverallStatus(services: HostService[]): StatusType { +function _getOverallStatus(services: HostService[]): StatusType { const hasCritical = services.some((s) => s.status === 'critical') const hasWarning = services.some((s) => s.status === 'warning') const hasThinking = services.some((s) => s.status === 'thinking') @@ -87,7 +87,7 @@ function formatPercent(value: number): string { export function HostCard({ ip, name, - role, + role: _role, status, services, metrics, diff --git a/apps/web/src/components/dashboard/live-dashboard.tsx b/apps/web/src/components/dashboard/live-dashboard.tsx index b0519257..eafec884 100644 --- a/apps/web/src/components/dashboard/live-dashboard.tsx +++ b/apps/web/src/components/dashboard/live-dashboard.tsx @@ -7,9 +7,9 @@ * i18n: 100% 使用 useTranslations,禁止任何寫死字串 */ -import { useEffect } from 'react' +import React from 'react' import { useTranslations } from 'next-intl' -import { useDashboardStore, useHosts, useOverallStatus, useAlerts, useMockMode } from '@/stores/dashboard.store' +import { useHosts, useOverallStatus, useAlerts, useMockMode } from '@/stores/dashboard.store' import { LiveHostCard } from './live-host-card' import { ConnectionStatus } from './connection-status' import { HostCardGrid } from './host-card' @@ -35,7 +35,7 @@ import { // Config // ============================================================================= -const getApiBaseUrl = () => { +const _getApiBaseUrl = () => { if (typeof window === 'undefined') return '' // 統帥鐵律: 禁止任何 Fallback IP const url = process.env.NEXT_PUBLIC_API_URL @@ -56,7 +56,7 @@ interface LiveDashboardProps { locale: string } -export function LiveDashboard({ locale }: LiveDashboardProps) { +export function LiveDashboard({ locale: _locale }: LiveDashboardProps) { const t = useTranslations('dashboard') const tBrand = useTranslations('brand') const tHost = useTranslations('host') @@ -66,7 +66,7 @@ export function LiveDashboard({ locale }: LiveDashboardProps) { const hosts = useHosts() const overallStatus = useOverallStatus() const alerts = useAlerts() - const mockMode = useMockMode() + const _mockMode = useMockMode() // Host fallback data with i18n const HOST_FALLBACKS: Record }> = { diff --git a/apps/web/src/components/dashboard/live-host-card.tsx b/apps/web/src/components/dashboard/live-host-card.tsx index 9192789b..e18f3ace 100644 --- a/apps/web/src/components/dashboard/live-host-card.tsx +++ b/apps/web/src/components/dashboard/live-host-card.tsx @@ -22,17 +22,14 @@ import { GlassCardFooter, } from '@/components/ui/glass-card' import { StatusOrb, StatusBadge, type StatusType } from '@/components/ui/status-orb' -import { useHostByIp, type Host, type HostService, type HostMetrics } from '@/stores/dashboard.store' +import { useHostByIp } from '@/stores/dashboard.store' import { cn } from '@/lib/utils' import { Server, Shield, Cpu, HardDrive, - Activity, Wifi, - WifiOff, - Clock, Gauge, } from 'lucide-react' @@ -107,7 +104,7 @@ export function LiveHostCard({ if (host) { setPrevStatus(host.status) } - }, [host?.status, prevStatus]) + }, [host, prevStatus]) // Use fallback if no data from store if (!host) { diff --git a/apps/web/src/components/incident/dual-state-incident-card.tsx b/apps/web/src/components/incident/dual-state-incident-card.tsx index 270650e6..17928fd2 100644 --- a/apps/web/src/components/incident/dual-state-incident-card.tsx +++ b/apps/web/src/components/incident/dual-state-incident-card.tsx @@ -169,6 +169,7 @@ export const DualStateIncidentCard: React.FC = ({ setErrorMessage(errMsg) // 不自動恢復,讓用戶看到錯誤並主動點擊重試 } + // eslint-disable-next-line react-hooks/exhaustive-deps -- t is stable from next-intl }, [currentProposalId, decision, id, isDecisionReady, buttonState, onApprovalChange]) /** @@ -232,6 +233,7 @@ export const DualStateIncidentCard: React.FC = ({ const errMsg = error instanceof Error ? error.message : String(error) setErrorMessage(errMsg) } + // eslint-disable-next-line react-hooks/exhaustive-deps -- t is stable from next-intl }, [currentProposalId, decision, id, isDecisionReady, buttonState, onApprovalChange]) /** diff --git a/apps/web/src/components/incident/thinking-terminal.tsx b/apps/web/src/components/incident/thinking-terminal.tsx index 23d0f521..ab7d9914 100644 --- a/apps/web/src/components/incident/thinking-terminal.tsx +++ b/apps/web/src/components/incident/thinking-terminal.tsx @@ -14,7 +14,7 @@ * 統帥鐵律: 禁止假數據! */ -import { useState, useEffect, useRef, useCallback } from 'react' +import { useState, useEffect, useRef, useCallback, useMemo } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { Terminal, Play, Pause, RotateCcw, ChevronDown, ChevronUp } from 'lucide-react' @@ -74,7 +74,8 @@ export function ThinkingTerminal({ const containerRef = useRef(null) const animationRef = useRef(null) - const steps = decisionChain?.reasoning_steps || [] + // Memoize steps to prevent re-render dependency issues + const steps = useMemo(() => decisionChain?.reasoning_steps || [], [decisionChain?.reasoning_steps]) const totalSteps = steps.length // 打字機效果 @@ -106,6 +107,7 @@ export function ThinkingTerminal({ } typeChar() + // eslint-disable-next-line react-hooks/exhaustive-deps -- steps is derived from decisionChain prop }, [steps, totalSteps, typeSpeed, isPlaying]) // 控制播放 diff --git a/apps/web/src/components/thinking-stream-test.tsx b/apps/web/src/components/thinking-stream-test.tsx index 273ff97a..fb512ff1 100644 --- a/apps/web/src/components/thinking-stream-test.tsx +++ b/apps/web/src/components/thinking-stream-test.tsx @@ -12,7 +12,7 @@ import { useCallback, useRef, useEffect } from 'react' import { cn } from '@/lib/utils' -import { useAgentStore, type ThinkingStep } from '@/stores/agent.store' +import { useAgentStore } from '@/stores/agent.store' interface StreamThinkingStep { type: 'thinking' | 'result' @@ -69,6 +69,7 @@ export function ThinkingStreamTest() { const decoder = new TextDecoder() let buffer = '' // 防線 3: 字串緩衝區,防止 JSON 被切斷 + // eslint-disable-next-line no-constant-condition -- SSE stream read loop while (true) { const { done, value } = await reader.read() if (done) break diff --git a/apps/web/src/components/ui/dot-matrix-bg.tsx b/apps/web/src/components/ui/dot-matrix-bg.tsx index 39a2936d..92c68140 100644 --- a/apps/web/src/components/ui/dot-matrix-bg.tsx +++ b/apps/web/src/components/ui/dot-matrix-bg.tsx @@ -64,7 +64,7 @@ export const DotMatrixBg = forwardRef( const dotPattern = `radial-gradient(circle, ${dotColor} ${dotSize}px, transparent ${dotSize}px)` // Fade gradients - const fadeStyles: Record = { + const _fadeStyles: Record = { none: '', bottom: 'mask-image: linear-gradient(to bottom, black 70%, transparent 100%)', edges: 'mask-image: radial-gradient(ellipse at center, black 50%, transparent 100%)',