refactor(dashboard,desktop,engine): remove unused imports, props, and locals

Clears the remaining no-unused-vars warnings across the dashboard app and
server, desktop main, and engine sources. Dead React state destructures are
collapsed to setter-only, unused props are underscore-prefixed to preserve
API shape, and unreferenced catch bindings are dropped. No behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-23 17:41:33 -07:00
parent cbdc164c8d
commit 200ebfcd39
54 changed files with 70 additions and 200 deletions

View File

@@ -52,7 +52,7 @@ import { useRemoteNodeData } from "./hooks/useRemoteNodeData";
import { useRemoteNodeEvents } from "./hooks/useRemoteNodeEvents";
import { NodeProvider, useNodeContext } from "./context/NodeContext";
import type { AiSessionSummary } from "./api";
import { fetchAiSession, fetchUnreadCount, reportDashboardPerf } from "./api";
import { fetchUnreadCount, reportDashboardPerf } from "./api";
import { getScopedItem, setScopedItem } from "./utils/projectStorage";
import { subscribeSse } from "./sse-bus";
@@ -63,7 +63,7 @@ function AppInner() {
const isElectron = typeof window !== "undefined" && Boolean((window as Window & { electronAPI?: unknown }).electronAPI);
// Project management hooks - MUST be called before any conditional logic
const { projects, loading: projectsLoading, error: projectsError, refresh: refreshProjects, register: registerProject, update: updateProjectHook, unregister: unregisterProjectHook } = useProjects();
const { projects, loading: projectsLoading, refresh: refreshProjects } = useProjects();
const { nodes } = useNodes();
// Node context for local/remote node switching - must be called before useCurrentProject
@@ -109,11 +109,10 @@ function AppInner() {
// Remote node data and events when in remote mode (pass searchQuery for server-side filtering)
const remoteData = useRemoteNodeData(currentNodeId, { projectId: currentProject?.id, searchQuery: searchQuery || undefined });
const remoteEvents = useRemoteNodeEvents(currentNodeId);
useRemoteNodeEvents(currentNodeId);
// Use remote data when in remote mode, local data otherwise
const effectiveProjects = isRemote && remoteData.projects.length > 0 ? remoteData.projects : projects;
const effectiveTasks = isRemote && remoteData.tasks.length > 0 ? remoteData.tasks : [];
// Theme management - required before useViewState
const { themeMode, colorTheme, setThemeMode, setColorTheme } = useTheme();
@@ -131,7 +130,7 @@ function AppInner() {
});
// View state must be defined before useTasks since useTasks depends on taskView for SSE gating
const { viewMode, setViewMode, taskView, handleChangeTaskView, handleToggleTheme } = useViewState({
const { viewMode, setViewMode, taskView, handleChangeTaskView } = useViewState({
projectsLoading,
currentProjectLoading,
currentProject,
@@ -270,7 +269,6 @@ function AppInner() {
// Settings state
const {
maxConcurrent,
rootDir,
autoMerge,
globalPaused,
enginePaused,
@@ -401,12 +399,6 @@ function AppInner() {
setNodesOpen((prev) => !prev);
}, [nodesEnabled]);
const handleOpenMissionsView = useCallback(() => {
setMissionTargetId(undefined);
setMissionResumeSessionId(undefined);
handleChangeTaskView("missions");
}, [handleChangeTaskView]);
const handleOpenMission = useCallback((missionId: string) => {
setMissionTargetId(missionId);
setMissionResumeSessionId(undefined);

View File

@@ -57,10 +57,8 @@ import type {
Insight,
InsightCategory,
InsightStatus,
InsightListOptions,
InsightRun,
InsightRunTrigger,
InsightRunCreateInput,
} from "@fusion/core";
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
import type { ScheduledTask, ScheduledTaskCreateInput, ScheduledTaskUpdateInput, AutomationRunResult, Routine, RoutineCreateInput, RoutineUpdateInput, RoutineExecutionResult } from "@fusion/core";
@@ -2523,7 +2521,7 @@ export async function fetchDevServers(projectId?: string): Promise<DevServerSess
}
: undefined,
previewUrl: legacy.previewUrl ?? legacy.detectedUrl ?? undefined,
logHistory: (legacy.logs ?? []).map<DevServerLogEntry>((text, i) => ({
logHistory: (legacy.logs ?? []).map<DevServerLogEntry>((text) => ({
timestamp: new Date().toISOString(),
stream: text.startsWith("[stderr]") ? "stderr" : "stdout",
text: text.replace(/^\[stderr\]\s*/, ""),

View File

@@ -1,7 +1,6 @@
import { memo, useCallback, useMemo } from "react";
import {
GitPullRequest,
GitMerge,
GitMerge,
CheckCircle,
XCircle,
Plus,

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect } from "react";
import { X, History, Trash2, Filter, RefreshCw, CheckCircle, XCircle, ArrowRight, Plus, Settings, AlertCircle, Loader2, Folder } from "lucide-react";
import { clearActivityLog, type ActivityLogEntry, type ActivityEventType, type ActivityFeedEntry } from "../api";
import { useActivityLog } from "../hooks/useActivityLog";
@@ -72,10 +72,10 @@ function formatTimestamp(timestamp: string): string {
* - Real-time updates via useActivityLog hook
*/
export function ActivityLogModal({
isOpen,
onClose,
tasks,
onOpenTaskDetail,
isOpen,
onClose,
tasks: _tasks,
onOpenTaskDetail,
projectId,
projects = [],
onProjectFilterChange,
@@ -136,7 +136,7 @@ export function ActivityLogModal({
await clearActivityLog();
refresh();
setShowConfirmClear(false);
} catch (err) {
} catch {
// Error handled by hook
setShowConfirmClear(false);
}

View File

@@ -149,7 +149,7 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
}
};
const handleRoleKeyDown = (e: React.KeyboardEvent, agentId: string) => {
const handleRoleKeyDown = (e: React.KeyboardEvent, _agentId: string) => {
if (e.key === "Escape") {
setEditingRoleForAgent(null);
}

View File

@@ -2,7 +2,7 @@ import { useState, useCallback, useEffect, useRef } from "react";
import { BUILTIN_AGENT_PROMPTS, PROMPT_KEY_CATALOG } from "../utils/builtinPrompts";
import type { AgentPromptTemplate, AgentPromptsConfig, AgentCapability } from "@fusion/core";
import type { PromptKey } from "@fusion/core";
import { X, Plus, Pencil, Trash2, BookOpen, Users, Settings2, ChevronDown, ChevronUp, Maximize2, Minimize2 } from "lucide-react";
import { Plus, Pencil, Trash2, BookOpen, Users, Settings2, ChevronDown, ChevronUp, Maximize2, Minimize2 } from "lucide-react";
/**
* Props for the AgentPromptsManager component.
@@ -807,7 +807,6 @@ export function AgentPromptsManager({
(t) => t.id === currentAssignment,
);
const isOverriding = !!currentAssignment;
const isOverridingBuiltin = isBuiltinId(currentAssignment);
return (
<div

View File

@@ -464,9 +464,8 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
const [mentionStartPos, setMentionStartPos] = useState(-1);
// File mention state and hook
const [fileMentionPopupVisible, setFileMentionPopupVisible] = useState(false);
const [, setFileMentionPopupVisible] = useState(false);
const [fileMentionPosition, setFileMentionPosition] = useState({ top: 0, left: 0 });
const fileMentionRef = useRef<HTMLDivElement>(null);
const fileMention = useFileMention({ projectId });

View File

@@ -95,11 +95,6 @@ export function CustomModelDropdown({
}, [favoriteModels, filteredModels]);
// Sort providers: favorites first (in order), then alphabetically
// Exclude providers that are already favorited as models (they appear at top as pinned rows)
const favoritedProviderSet = new Set(favoriteModels.map((fullId) => {
const idx = fullId.indexOf("/");
return idx !== -1 ? fullId.slice(0, idx) : fullId;
}));
const sortedProviderEntries = useMemo(() => {
const entries = Object.entries(modelsByProvider);
const favoritesSet = new Set(favoriteProviders);

View File

@@ -80,8 +80,6 @@ function truncateCommand(command: string): string {
export function DevServerView({ addToast, projectId }: DevServerViewProps) {
const {
session,
sessions,
logs,
detectedCommands,
previewUrl,
isLoading,
@@ -294,7 +292,6 @@ export function DevServerView({ addToast, projectId }: DevServerViewProps) {
}
const fallbackCwd = normalizeSourceToCwd(selectedSource) ?? ".";
const scriptName = selectedCandidate?.scriptName ?? selectedScript ?? "custom";
const cwd = selectedCandidate?.cwd ?? fallbackCwd;
void runAction(

View File

@@ -1,6 +1,6 @@
import { useMemo } from "react";
import type { Task } from "@fusion/core";
import { Activity, AlertTriangle, Clock, Pause, Play, Zap } from "lucide-react";
import { AlertTriangle, Clock, Pause, Play, Zap } from "lucide-react";
import { useExecutorStats } from "../hooks/useExecutorStats";
import type { ExecutorState, AiSessionSummary } from "../api";
import { BackgroundTasksIndicator } from "./BackgroundTasksIndicator";

View File

@@ -16,7 +16,7 @@ function getIssueModifierClass(state: string, stateReason?: string): string {
return "card-github-badge--closed";
}
export function GitHubBadge({ prInfo, issueInfo, onIssueRefresh }: GitHubBadgeProps) {
export function GitHubBadge({ prInfo, issueInfo, onIssueRefresh: _onIssueRefresh }: GitHubBadgeProps) {
const handlePrClick = () => {
if (prInfo?.url) {
window.open(prInfo.url, "_blank", "noopener,noreferrer");

View File

@@ -319,7 +319,6 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
// Determine state flags
const hasRemotes = remotes.length > 0;
const singleRemote = remotes.length === 1;
const multipleRemotes = remotes.length > 1;
// Tab-specific counts
const importedIssueCount = issues.filter((issue) => importedUrls.has(issue.html_url)).length;

View File

@@ -168,7 +168,7 @@ interface GitManagerModalProps {
// ── Main Component ────────────────────────────────────────────────
export function GitManagerModal({ isOpen, onClose, tasks, addToast, projectId }: GitManagerModalProps) {
export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, projectId }: GitManagerModalProps) {
const [activeSection, setActiveSection] = useState<SectionId>("status");
const [loading, setLoading] = useState(false);
const [sectionError, setSectionError] = useState<string | null>(null);

View File

@@ -23,15 +23,13 @@ import {
Users,
LineChart,
TrendingUp,
MoreVertical,
ExternalLink,
Archive,
Clock,
} from "lucide-react";
import { useInsights, INSIGHT_CATEGORIES, CATEGORY_LABELS, type InsightSection } from "../hooks/useInsights";
import { useInsights, type InsightSection } from "../hooks/useInsights";
import type { InsightCategory } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { createTask } from "../api";
interface InsightsViewProps {
projectId?: string;

View File

@@ -1,5 +1,5 @@
import { useState, useCallback, useMemo, Fragment, useEffect, useRef } from "react";
import { LayoutGrid, List as ListIcon, ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight } from "lucide-react";
import { ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight } from "lucide-react";
import type { Task, TaskDetail, Column, TaskCreateInput } from "@fusion/core";
import { COLUMN_LABELS, COLUMNS } from "@fusion/core";
import { batchUpdateTaskModels } from "../api";
@@ -163,7 +163,7 @@ export function ListView({
onSubtaskBreakdown,
onTasksUpdated,
projectId,
projectName,
projectName: _projectName,
taskStuckTimeoutMs,
searchQuery = "",
lastFetchTimeMs,

View File

@@ -87,11 +87,7 @@ export function MilestoneSliceInterviewModal({
const trackedLockSessionRef = useRef<string | null>(null);
const [lockSessionId, setLockSessionId] = useState<string | null>(null);
const sessionTabId = useMemo(() => getSessionTabId(), []);
const {
isLockedByOther,
takeControl,
isLoading: isLockLoading,
} = useSessionLock(isOpen ? lockSessionId : null);
useSessionLock(isOpen ? lockSessionId : null);
const {
activeTabMap,
broadcastUpdate,

View File

@@ -39,16 +39,11 @@ import type {
MilestoneStatus,
SliceStatus,
FeatureStatus,
MilestoneWithSlices,
SliceWithFeatures,
MissionHealth,
MissionEvent,
MissionEventType,
FeatureLoopState,
MissionAssertionStatus,
MissionContractAssertion,
ContractAssertionCreateInput,
ContractAssertionUpdateInput,
MilestoneValidationRollup,
MilestoneValidationTelemetry,
MissionFeatureLoopSnapshot,
@@ -79,17 +74,13 @@ import {
stopMission,
startMission,
updateMissionAutopilot,
fetchMissionHealth,
fetchMissionsHealth,
fetchMissionEvents,
fetchAssertions,
createAssertion,
updateAssertion,
deleteAssertion,
reorderAssertions,
linkFeatureToAssertion,
unlinkFeatureFromAssertion,
fetchAssertionsForFeature,
fetchFeaturesForAssertion,
fetchMilestoneValidation,
fetchMilestoneValidationTelemetry,
@@ -97,12 +88,11 @@ import {
fetchValidationLoopState,
fetchValidationRuns,
fetchValidationRun,
fetchAssertion,
fetchAiSessions,
fetchAiSession,
type AiSessionSummary,
} from "../api";
import type { AutopilotStatus as AutopilotStatusType, AutopilotState } from "./mission-types";
import type { AutopilotState } from "./mission-types";
interface MissionManagerProps {
isOpen: boolean;
@@ -157,16 +147,6 @@ const autopilotStateColors: Record<AutopilotState, { bg: string; text: string }>
completing: { bg: "var(--autopilot-completing-bg)", text: "var(--autopilot-completing-text)" },
};
/** Loop state colors for feature execution loop */
const loopStateColors: Record<FeatureLoopState, { bg: string; text: string; indicator: string }> = {
idle: { bg: "var(--loop-idle-bg)", text: "var(--loop-idle-text)", indicator: "var(--loop-idle-indicator)" },
implementing: { bg: "var(--loop-implementing-bg)", text: "var(--loop-implementing-text)", indicator: "var(--loop-implementing-indicator)" },
validating: { bg: "var(--loop-validating-bg)", text: "var(--loop-validating-text)", indicator: "var(--loop-validating-indicator)" },
needs_fix: { bg: "var(--loop-needs-fix-bg)", text: "var(--loop-needs-fix-text)", indicator: "var(--loop-needs-fix-indicator)" },
passed: { bg: "var(--loop-passed-bg)", text: "var(--loop-passed-text)", indicator: "var(--loop-passed-indicator)" },
blocked: { bg: "var(--loop-blocked-bg)", text: "var(--loop-blocked-text)", indicator: "var(--loop-blocked-indicator)" },
};
/** Assertion status colors */
const assertionStatusColors: Record<MissionAssertionStatus, { bg: string; text: string }> = {
pending: { bg: "var(--assertion-pending-bg)", text: "var(--assertion-pending-text)" },
@@ -570,7 +550,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
// Assertion panel state
const [assertionsByMilestone, setAssertionsByMilestone] = useState<Map<string, MissionContractAssertion[]>>(new Map());
const [assertionsLoading, setAssertionsLoading] = useState(false);
const [editingAssertionId, setEditingAssertionId] = useState<string | null>(null);
const [assertionForm, setAssertionForm] = useState<{ title: string; assertion: string; status: MissionAssertionStatus }>({
title: "",
@@ -929,7 +908,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
};
const handleSliceUpdated = (rawEvent: Event) => {
const handleSliceUpdated = (_rawEvent: Event) => {
refreshHealth();
// Reload the selected mission detail to reflect updated slice status
if (selectedMissionRef.current) {
@@ -1560,7 +1539,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(milestoneId, assertions);
return next;
});
} catch (err: any) {
} catch {
// Silently fail - assertions are optional
}
}, [projectId]);
@@ -1573,7 +1552,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(milestoneId, rollup);
return next;
});
} catch (err: any) {
} catch {
// Silently fail
}
}, [projectId]);
@@ -1640,18 +1619,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
}, [assertionForm, addToast, loadAssertionsForMilestone, loadValidationRollup, handleCancelAssertion, projectId]);
const handleDeleteAssertion = useCallback(async (assertionId: string, milestoneId: string) => {
try {
await deleteAssertion(assertionId, projectId);
addToast("Assertion deleted", "success");
await loadAssertionsForMilestone(milestoneId);
await loadValidationRollup(milestoneId);
setDeleteConfirmId(null);
} catch (err: any) {
addToast(err.message || "Failed to delete assertion", "error");
}
}, [addToast, loadAssertionsForMilestone, loadValidationRollup, projectId]);
const loadLinkedFeaturesForAssertion = useCallback(async (assertionId: string) => {
try {
const features = await fetchFeaturesForAssertion(assertionId, projectId);
@@ -1660,7 +1627,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(assertionId, features);
return next;
});
} catch (err: any) {
} catch {
// Silently fail
}
}, [projectId]);
@@ -1753,7 +1720,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(featureId, snapshot);
return next;
});
} catch (err: any) {
} catch {
// Silently fail
}
}, [projectId]);
@@ -1767,7 +1734,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(featureId, runs);
return next;
});
} catch (err: any) {
} catch {
// Silently fail
}
}, [projectId]);
@@ -1821,7 +1788,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(runId, detail);
return next;
});
} catch (err: any) {
} catch {
// Silently fail
}
}, [projectId]);

View File

@@ -406,7 +406,6 @@ function ApiKeyEntryForm({
import {
getOnboardingState,
saveOnboardingState,
clearOnboardingState,
markOnboardingCompleted,
markStepSkipped,
getSkippedSteps,
@@ -1523,7 +1522,6 @@ export function ModelOnboardingModal({
const connectedCount = authProviders.filter(p => p.id !== "github" && p.authenticated).length;
const totalAiProviders = authProviders.filter(p => p.id !== "github").length;
const skippedCount = Object.keys(skippedProviders).filter(id => !authProviders.find(p => p.id === id)?.authenticated).length;
const connectedProviders = authProviders.filter(p => p.id !== "github" && p.authenticated);
if (totalAiProviders === 0) return null;

View File

@@ -174,7 +174,7 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
try {
await updateGlobalSettings({ favoriteProviders: newFavorites, favoriteModels });
} catch (err) {
} catch {
// Revert on error
setFavoriteProviders(currentFavorites);
addToast("Failed to update favorites", "error");
@@ -193,7 +193,7 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
try {
await updateGlobalSettings({ favoriteProviders, favoriteModels: newFavorites });
} catch (err) {
} catch {
// Revert on error
setFavoriteModels(currentFavorites);
addToast("Failed to update model favorites", "error");

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Activity, Download, Key, Pencil, Save, Shield, Upload, X } from "lucide-react";
import { Activity, Download, Pencil, Save, Shield, Upload, X } from "lucide-react";
import type { NodeInfo, NodeUpdateInput, ProjectInfo } from "../api";
import type { ToastType } from "../hooks/useToast";
import { getProjectsForNode } from "../utils/nodeProjectAssignment";
@@ -66,7 +66,7 @@ export function NodeDetailModal({
// Conflict resolution modal state
const [showConflictModal, setShowConflictModal] = useState(false);
const [conflicts, setConflicts] = useState<SettingsConflictEntry[]>([]);
const [conflicts] = useState<SettingsConflictEntry[]>([]);
useEffect(() => {
isMountedRef.current = true;

View File

@@ -88,8 +88,6 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
return;
}
const currentPlugins = pluginsRef.current;
switch (payload.transition) {
case "installing":
case "enabled":

View File

@@ -1,5 +1,5 @@
import { memo, useCallback, useState } from "react";
import { Play, Pause, AlertCircle, Loader2, MoreHorizontal, Trash2, Folder, ArrowRight } from "lucide-react";
import { Play, Pause, AlertCircle, Loader2, Trash2, Folder, ArrowRight } from "lucide-react";
import type { RegisteredProject, ProjectHealth, ProjectStatus } from "@fusion/core";
import type { NodeInfo } from "../api";

View File

@@ -203,16 +203,6 @@ export function ProjectOverview({
}));
}, [projects, nodes]);
// Handle sort change
const handleSort = useCallback((option: SortOption) => {
if (sortBy === option) {
setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"));
} else {
setSortBy(option);
setSortDirection(option === "name" ? "asc" : "desc");
}
}, [sortBy]);
// Handle project selection
const handleSelectProject = useCallback((project: ProjectInfo) => {
// Update recent projects in localStorage

View File

@@ -437,7 +437,7 @@ export function QuickChatFAB({
const [mentionStartPos, setMentionStartPos] = useState(-1);
// File mention state and hook
const [fileMentionPopupVisible, setFileMentionPopupVisible] = useState(false);
const [, setFileMentionPopupVisible] = useState(false);
const [fileMentionPosition, setFileMentionPosition] = useState({ top: 0, left: 0 });
const fileMention = useFileMention({ projectId });

View File

@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect, useRef } from "react";
import { Plus, Pencil, Trash2, Check, X, GripVertical, Sparkles, Download, Copy, Loader, ChevronLeft, ArrowLeft, ChevronUp } from "lucide-react";
import { Plus, Pencil, Trash2, Check, X, GripVertical, Sparkles, Download, Copy, Loader, ArrowLeft, ChevronUp } from "lucide-react";
import type { ToastType } from "../hooks/useToast";
import { useRoadmaps, type FeatureSuggestion, type MilestoneSuggestion, type SuggestionDraftPatch } from "../hooks/useRoadmaps";
import { useViewportMode } from "../hooks/useViewportMode";
@@ -466,11 +466,11 @@ function MilestoneCard({
onCancelMilestoneEdit,
onSaveMilestoneEdit,
featureEdit,
onStartFeatureEdit,
onStartFeatureEdit: _onStartFeatureEdit,
onCancelFeatureEdit,
onSaveFeatureEdit,
projectId,
addToast,
projectId: _projectId,
addToast: _addToast,
// Milestone drag-and-drop props
isMilestoneDragging,
isMilestoneDropTarget,
@@ -631,7 +631,7 @@ function MilestoneCard({
type="text"
className="roadmaps-view__inline-input"
value={milestoneEdit.value}
onChange={(e) =>
onChange={() =>
onStartMilestoneEdit()
}
onKeyDown={handleMilestoneTitleKeyDown}
@@ -659,7 +659,7 @@ function MilestoneCard({
<textarea
className="roadmaps-view__inline-textarea"
value={milestoneEdit.field === "description" ? milestoneEdit.value : milestone.description || ""}
onChange={(e) => {
onChange={() => {
// Update the edit state with description
}}
onKeyDown={handleMilestoneDescKeyDown}
@@ -1623,9 +1623,6 @@ export function RoadmapsView({ projectId, addToast }: RoadmapsViewProps) {
const isCrossMilestone = draggingMilestoneId !== targetMilestoneId;
if (isCrossMilestone) {
// Cross-milestone move
const targetFeatures = featuresByMilestoneId[targetMilestoneId] || [];
// No-op check: if moving to same position in same milestone (shouldn't happen but safety check)
if (draggingMilestoneId === targetMilestoneId) {
handleFeatureDragEnd();
@@ -1907,7 +1904,7 @@ export function RoadmapsView({ projectId, addToast }: RoadmapsViewProps) {
// Feature handlers
const handleStartFeatureEdit = useCallback(
(featureId: string, currentTitle: string, currentDescription?: string) => {
(featureId: string, currentTitle: string, _currentDescription?: string) => {
setFeatureEdit({
featureId,
field: "title",

View File

@@ -3,7 +3,6 @@ import { Calendar, Webhook, Code, Zap, Globe, Folder } from "lucide-react";
import type {
Routine,
RoutineCreateInput,
RoutineUpdateInput,
RoutineTrigger,
RoutineTriggerType,
RoutineCronTrigger,
@@ -126,13 +125,6 @@ function extractTriggerFields(routine: Routine) {
}
}
const TRIGGER_TYPE_LABELS: Record<RoutineTriggerType, string> = {
cron: "Cron Schedule",
webhook: "Webhook",
api: "API",
manual: "Manual",
};
const EXECUTION_POLICY_OPTIONS: { value: RoutineExecutionPolicy; label: string }[] = [
{ value: "parallel", label: "Allow concurrent runs" },
{ value: "queue", label: "Queue after current (one at a time)" },

View File

@@ -1,9 +1,9 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { Globe, Folder } from "lucide-react";
import { THINKING_LEVELS, PROMPT_KEY_CATALOG, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, PromptKey, AgentPromptsConfig } from "@fusion/core";
import { THINKING_LEVELS, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, AgentPromptsConfig } from "@fusion/core";
import { fetchSettings, fetchSettingsByScope, updateSettings, updateGlobalSettings, fetchAuthStatus, loginProvider, logoutProvider, saveApiKey, clearApiKey, fetchModels, testNtfyNotification, fetchBackups, createBackup, exportSettings, importSettings, fetchMemoryFile, fetchMemoryFiles, saveMemoryFile, compactMemory, fetchGlobalConcurrency, updateGlobalConcurrency, installQmd, testMemoryRetrieval, fetchGitRemotesDetailed } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData, MemoryBackendCapabilities, MemoryFileInfo, MemoryRetrievalTestResult, GitRemoteDetailed } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData, MemoryFileInfo, MemoryRetrievalTestResult, GitRemoteDetailed } from "../api";
import { useMemoryBackendStatus } from "../hooks/useMemoryBackendStatus";
import type { ToastType } from "../hooks/useToast";
import { ThemeSelector } from "./ThemeSelector";

View File

@@ -31,7 +31,7 @@ interface SettingsSyncLogProps {
* with filtering by direction and node name.
*/
export function SettingsSyncLog({
nodeId,
nodeId: _nodeId,
entries,
loading = false,
singleNode = false,

View File

@@ -507,7 +507,7 @@ function TaskCardComponent({
try {
const detail = await fetchTaskDetail(depId, projectId);
onOpenDetail(detail);
} catch (err: any) {
} catch {
addToast(`Failed to load dependency ${depId}`, "error");
}
}, [onOpenDetail, addToast]);

View File

@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Pencil, Bot, X, ChevronDown } from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, PrInfo, Settings, AgentLogEntry, Agent } from "@fusion/core";
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, Settings, AgentLogEntry, Agent } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS } from "@fusion/core";
import { uploadAttachment, deleteAttachment, updateTask, pauseTask, unpauseTask, fetchTaskDetail, fetchSettings, requestSpecRevision, rebuildTaskSpec, approvePlan, rejectPlan, refineTask, fetchWorkflowResults, assignTask, fetchAgents, fetchAgent } from "../api";
import type { WorkflowStepResult } from "@fusion/core";
@@ -27,23 +27,7 @@ interface ModelSelection {
modelId?: string;
}
function normalizeModelField(value: string | null | undefined): string | undefined {
return value ?? undefined;
}
function getExecutorSelection(task: Task | TaskDetail): ModelSelection {
return {
provider: normalizeModelField(task.modelProvider),
modelId: normalizeModelField(task.modelId),
};
}
function getValidatorSelection(task: Task | TaskDetail): ModelSelection {
return {
provider: normalizeModelField(task.validatorModelProvider),
modelId: normalizeModelField(task.validatorModelId),
};
}
/**
* Resolve the effective executor model following the engine's resolution order:
@@ -996,7 +980,7 @@ export function TaskDetailModal({
try {
const detail = await fetchTaskDetail(depId, projectId);
onOpenDetail(detail);
} catch (err: any) {
} catch {
addToast(`Failed to load dependency ${depId}`, "error");
}
}, [onOpenDetail, addToast]);

View File

@@ -1,12 +1,11 @@
import { useCallback, useEffect, useState } from "react";
import { FileText, ChevronDown, ChevronUp, Plus, Trash2, History, X } from "lucide-react";
import { FileText, ChevronDown, ChevronUp, Plus, Trash2, History } from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { Task, TaskDocument, TaskDocumentRevision } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import {
fetchTaskDocuments,
fetchTaskDocument,
fetchTaskDocumentRevisions,
putTaskDocument,
deleteTaskDocument,
@@ -37,7 +36,7 @@ function getContentPreview(content: string, maxLength: number = MAX_CONTENT_PREV
export function TaskDocumentsTab({
taskId,
addToast,
onTaskUpdated,
onTaskUpdated: _onTaskUpdated,
projectId,
canEdit = false,
}: TaskDocumentsTabProps) {

View File

@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect, useRef, type ReactNode } from "react";
import type { Task, ModelPreset, Settings, WorkflowStep } from "@fusion/core";
import type { Task, Settings, WorkflowStep } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { fetchModels, fetchSettings, fetchWorkflowSteps, refineText, getRefineErrorMessage, updateGlobalSettings, type RefinementType, type ModelInfo } from "../api";
import { applyPresetToSelection, getRecommendedPresetForSize } from "../utils/modelPresets";

View File

@@ -87,7 +87,7 @@ interface UsageWindowRowProps {
/**
* Single usage window row with progress bar
*/
function UsageWindowRow({ window, viewMode, providerName }: UsageWindowRowProps) {
function UsageWindowRow({ window, viewMode, providerName: _providerName }: UsageWindowRowProps) {
const colorClass = getUsageColorClass(window.percentUsed);
const isRemainingMode = viewMode === 'remaining';

View File

@@ -143,7 +143,7 @@ export function useBatchBadgeFetch(projectId?: string): UseBatchBadgeFetchResult
batchBadgeStore.data.set(getScopedTaskKey(taskId, projectId), { result, timestamp });
}
batchBadgeStore.lastFetchTime = timestamp;
} catch (err) {
} catch {
// Even on error, we don't throw - the hook handles errors gracefully
// and partial results are still stored
} finally {

View File

@@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
import {
detectDevServerCommands,
fetchDevServer,
fetchDevServerLogs,
fetchDevServers,
getDevServerLogsStreamUrl,
getDevServerSessionLogsStreamUrl,
@@ -26,8 +25,6 @@ import { subscribeSse } from "../sse-bus";
const MAX_LOG_LINES = 500;
const POLL_INTERVAL_MS = 3000;
let resetVersion = 0;
function normalizeError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
@@ -161,7 +158,7 @@ export interface UseDevServerReturn {
}
export function __resetUseDevServerForTests(): void {
resetVersion += 1;
// no-op: reserved hook for future test reset coordination.
}
export function useDevServer(projectId?: string): UseDevServerReturn {

View File

@@ -210,7 +210,7 @@ export function useFileMention(options: UseFileMentionOptions = {}): UseFileMent
* Supports ArrowUp/ArrowDown to navigate, Enter/Tab to select, Escape to dismiss.
*/
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>, currentText: string): boolean => {
(event: React.KeyboardEvent<HTMLElement>, _currentText: string): boolean => {
if (!mentionActive || files.length === 0) {
return false;
}

View File

@@ -121,7 +121,7 @@ export interface ModalManager {
* and cross-modal transitions (for example, script runner -> terminal handoff).
*/
export function useModalManager(options: UseModalManagerOptions): ModalManager {
const { projectId, planningSessions } = options;
const { planningSessions } = options;
const [newTaskModalOpen, setNewTaskModalOpen] = useState(false);
const [isPlanningOpen, setIsPlanningOpen] = useState(false);

View File

@@ -156,7 +156,7 @@ export function useNodeSettingsSync(): UseNodeSettingsSyncResult {
* Fetch sync status for a single node and update state.
* Does NOT set loading=true (called during polling and initial fetch).
*/
const fetchNodeStatus = useCallback(async (nodeId: string, isInitial: boolean): Promise<void> => {
const fetchNodeStatus = useCallback(async (nodeId: string, _isInitial: boolean): Promise<void> => {
try {
const status = await fetchNodeSettingsSyncStatus(nodeId);
setSyncStatusMap((prev) => ({

View File

@@ -49,7 +49,7 @@ export function usePreviewEmbed(url: string | null, options: UsePreviewEmbedOpti
const [embedStatus, setEmbedStatusState] = useState<EmbedStatus>("unknown");
const [blockReason, setBlockReason] = useState<string | null>(null);
const [detectionMethod, setDetectionMethod] = useState<EmbedDetectionMethod>(initialDetectionMethod);
const [detectionMethod] = useState<EmbedDetectionMethod>(initialDetectionMethod);
const clearLoadingTimeout = useCallback(() => {
if (timeoutRef.current !== null) {

View File

@@ -25,11 +25,6 @@ export interface TerminalTab {
createdAt: number;
}
interface StoredTab extends TerminalTab {
/** Marked as unverified during server validation */
_verified?: boolean;
}
interface UseTerminalSessionsReturn {
/** All terminal tabs */
tabs: TerminalTab[];

View File

@@ -23,8 +23,6 @@ import type {
import { summarizeTitle } from "@fusion/core";
import { EventEmitter } from "node:events";
import { join, resolve, relative } from "node:path";
import { readFile } from "node:fs/promises";
import { SessionEventBuffer } from "./sse-buffer.js";
// Dynamic import for @fusion/engine to avoid resolution issues in test environment

View File

@@ -1,4 +1,4 @@
import { Router, type Request, type Response } from "express";
import { Router, type Response } from "express";
import { badRequest, conflict, ApiError, sendErrorResponse } from "./api-error.js";
import { detectDevServerScripts } from "./dev-server-detect.js";
import {

View File

@@ -26,10 +26,8 @@ import type { AiSessionStore, AiSessionRow } from "./ai-session-store.js";
import { SessionEventBuffer, type SessionBufferedEvent } from "./sse-buffer.js";
import {
createSessionDiagnostics,
setDiagnosticsSink,
resetDiagnosticsSink,
nonfatal,
nonfatalAsync,
} from "./ai-session-diagnostics.js";
// Dynamic import for @fusion/engine to avoid resolution issues in test environment

View File

@@ -455,9 +455,7 @@ export function createRoadmapRouter(store: TaskStore): Router {
router.post("/:roadmapId/suggestions/milestones", async (req, res) => {
// Route-level timeout as safety net (slightly longer than internal timeout)
const ROUTE_TIMEOUT_MS = SUGGESTION_TIMEOUT_MS + 10_000;
let routeTimedOut = false;
const routeTimeoutId = setTimeout(() => {
routeTimedOut = true;
if (!res.headersSent) {
res.status(503).json({ error: "Request timed out" });
}
@@ -528,9 +526,7 @@ export function createRoadmapRouter(store: TaskStore): Router {
router.post("/milestones/:milestoneId/suggestions/features", async (req, res) => {
// Route-level timeout as safety net (slightly longer than internal timeout)
const ROUTE_TIMEOUT_MS = SUGGESTION_TIMEOUT_MS + 10_000;
let routeTimedOut = false;
const routeTimeoutId = setTimeout(() => {
routeTimedOut = true;
if (!res.headersSent) {
res.status(503).json({ error: "Request timed out" });
}

View File

@@ -28,7 +28,7 @@ const {
writeFile: fsWriteFile,
} = fsPromises;
import type { TaskStore, Column, ScheduleType, ActivityEventType, ModelPreset, MessageType, ParticipantType, RoutineTriggerType, ProjectSettings, EnrichedChatSession, PlanningSummary } from "@fusion/core";
import { COLUMNS, VALID_TRANSITIONS, GLOBAL_SETTINGS_KEYS, type BatchStatusEntry, type BatchStatusResponse, type BatchStatusResult, type IssueInfo, type PrInfo, type Task, type PiExtensionEntry, type PiExtensionSettings, getCurrentRepo, isGhAvailable, isGhAuthenticated, AutomationStore, validateBackupSchedule, validateBackupRetention, validateBackupDir, syncBackupRoutine, exportSettings, importSettings, validateImportData, MessageStore, validateMessageMetadata, RoutineStore, isWebhookTrigger, resolveMemoryBackend, getMemoryBackendCapabilities, listMemoryBackendTypes, listProjectMemoryFiles, readProjectMemoryFile, readProjectMemoryFileContent, writeProjectMemoryFile, listAgentMemoryFiles, readAgentMemoryFile, writeAgentMemoryFile, readMemory, writeMemory, searchProjectMemory, isQmdAvailable, installQmd, refreshQmdProjectMemoryIndex, QMD_INSTALL_COMMAND, MemoryBackendError, scheduleQmdProjectMemoryRefresh, discoverPiExtensions, updatePiExtensionDisabledIds, getFusionAgentDir, getLegacyPiAgentDir, ensureMemoryFileWithBackend, readInsightsMemory, writeInsightsMemory, generateMemoryAudit, buildInsightExtractionPrompt, parseInsightExtractionResponse, processAndAuditInsightExtraction } from "@fusion/core";
import { COLUMNS, VALID_TRANSITIONS, GLOBAL_SETTINGS_KEYS, type BatchStatusEntry, type BatchStatusResponse, type BatchStatusResult, type IssueInfo, type PrInfo, type Task, type PiExtensionEntry, type PiExtensionSettings, getCurrentRepo, isGhAvailable, isGhAuthenticated, AutomationStore, validateBackupSchedule, validateBackupRetention, validateBackupDir, syncBackupRoutine, exportSettings, importSettings, validateImportData, MessageStore, validateMessageMetadata, RoutineStore, isWebhookTrigger, resolveMemoryBackend, getMemoryBackendCapabilities, listMemoryBackendTypes, listProjectMemoryFiles, readProjectMemoryFile, readProjectMemoryFileContent, writeProjectMemoryFile, listAgentMemoryFiles, readAgentMemoryFile, writeAgentMemoryFile, readMemory, writeMemory, searchProjectMemory, isQmdAvailable, installQmd, refreshQmdProjectMemoryIndex, QMD_INSTALL_COMMAND, MemoryBackendError, scheduleQmdProjectMemoryRefresh, discoverPiExtensions, updatePiExtensionDisabledIds, getFusionAgentDir, getLegacyPiAgentDir, ensureMemoryFileWithBackend, readInsightsMemory, writeInsightsMemory, generateMemoryAudit, buildInsightExtractionPrompt, processAndAuditInsightExtraction } from "@fusion/core";
import type { ServerOptions } from "./server.js";
import { probeClaudeCli } from "./claude-cli-probe.js";
import { GitHubClient, parseBadgeUrl } from "./github.js";
@@ -16353,9 +16353,6 @@ async function persistImportedSkills(
const globalSettingsStore = store.getGlobalSettingsStore();
const globalSettings = await globalSettingsStore.getSettings();
// Get local node ID for source tracking
const localPeerInfo = await central.getLocalPeerInfo();
// Build sync payload
const payload = {
global: globalSettings,