feat(FN-1748): merge fusion/fn-1748

This commit is contained in:
gsxdsm
2026-04-14 22:45:59 -07:00
parent 6e2236c0ae
commit 42da5e80cb
34 changed files with 133 additions and 151 deletions

View File

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

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";
@@ -81,6 +81,7 @@ export function ActivityLogModal({
onProjectFilterChange,
currentProject,
}: ActivityLogModalProps) {
// tasks parameter reserved for future use when filtering activity by task
const [filteredType, setFilteredType] = useState<ActivityEventType | "all">("all");
const [filteredProjectId, setFilteredProjectId] = useState<string | "all">(projectId || "all");
const [showConfirmClear, setShowConfirmClear] = useState(false);
@@ -136,7 +137,7 @@ export function ActivityLogModal({
await clearActivityLog();
refresh();
setShowConfirmClear(false);
} catch (err) {
} catch {
// Error handled by hook
setShowConfirmClear(false);
}

View File

@@ -109,7 +109,7 @@ export function AddNodeModal({ isOpen, onClose, onSubmit, addToast }: AddNodeMod
try {
await onSubmit(input);
addToast(`Node \"${input.name}\" registered`, "success");
addToast(`Node "${input.name}" registered`, "success");
closeModal();
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to register node";

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import type { JSX } from "react";
import { X, Plus, Play, Pause, Square, Activity, Heart, Trash2, RefreshCw, Bot, LayoutGrid, List, Filter } from "lucide-react";
import { X, Plus, Play, Pause, Square, Trash2, RefreshCw, Bot, LayoutGrid, List, Filter } from "lucide-react";
import type { Agent, AgentCapability, AgentState } from "../api";
import { fetchAgents, createAgent, updateAgent, updateAgentState, deleteAgent } from "../api";
import { getScopedItem, setScopedItem } from "../utils/projectStorage";
@@ -158,7 +158,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

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import type { JSX } from "react";
import { Plus, Play, Pause, Square, Activity, Heart, Trash2, RefreshCw, Bot, LayoutGrid, List, ChevronRight, ChevronDown, GitBranch, Filter, Upload, Network } from "lucide-react";
import { Plus, Play, Pause, Square, Activity, Trash2, RefreshCw, Bot, LayoutGrid, List, ChevronRight, ChevronDown, GitBranch, Filter, Upload, Network } from "lucide-react";
import type { Agent, AgentCapability, AgentState, OrgTreeNode } from "../api";
import { fetchAgents, updateAgent, updateAgentState, deleteAgent, startAgentRun, fetchOrgTree } from "../api";
import { AgentDetailView } from "./AgentDetailView";

View File

@@ -1,5 +1,5 @@
import type { ProjectInfo } from "../api";
import type { ColorTheme, Column, MergeResult, Task, TaskCreateInput, ThemeMode } from "@fusion/core";
import type { ColorTheme, Column, MergeResult, Task, ThemeMode } from "@fusion/core";
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
import type { ModalManager } from "../hooks/useModalManager";
import type { UseTaskHandlersResult } from "../hooks/useTaskHandlers";

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

@@ -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

@@ -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

@@ -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, TaskStep, TaskCreateInput } from "@fusion/core";
import { COLUMN_LABELS, COLUMNS } from "@fusion/core";
import { batchUpdateTaskModels } from "../api";
@@ -122,8 +122,6 @@ interface ListViewProps {
onTasksUpdated?: (updatedTasks: Task[]) => void;
/** Project ID for multi-project context (optional) */
projectId?: string;
/** Project name for display (optional) */
projectName?: string;
/** Project-level stuck task timeout in milliseconds (undefined = disabled) */
taskStuckTimeoutMs?: number;
/** External search query from header search (defaults to "") */
@@ -161,7 +159,6 @@ export function ListView({
onSubtaskBreakdown,
onTasksUpdated,
projectId,
projectName,
taskStuckTimeoutMs,
searchQuery = "",
lastFetchTimeMs,

View File

@@ -117,8 +117,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

@@ -102,12 +102,13 @@ export function ProjectOverview({
case "name":
comparison = a.project.name.localeCompare(b.project.name);
break;
case "activity":
case "activity": {
const aTime = a.project.lastActivityAt || a.health?.lastActivityAt || a.project.updatedAt;
const bTime = b.project.lastActivityAt || b.health?.lastActivityAt || b.project.updatedAt;
comparison = new Date(bTime).getTime() - new Date(aTime).getTime();
break;
case "status":
}
case "status": {
const statusOrder: Record<ProjectStatus, number> = {
errored: 0,
initializing: 1,
@@ -116,6 +117,7 @@ export function ProjectOverview({
};
comparison = statusOrder[a.project.status] - statusOrder[b.project.status];
break;
}
}
return sortDirection === "asc" ? comparison : -comparison;

View File

@@ -3,7 +3,6 @@ import { Calendar, Webhook, Code, Zap } from "lucide-react";
import type {
Routine,
RoutineCreateInput,
RoutineUpdateInput,
RoutineTrigger,
RoutineTriggerType,
RoutineCronTrigger,
@@ -22,7 +21,7 @@ function isLikelyCron(expr: string): boolean {
const parts = expr.trim().split(/\s+/);
if (parts.length !== 5) return false;
// Each field should contain digits, *, /, -, or ,
return parts.every((p) => /^[\d*,/\-]+$/.test(p));
return parts.every((p) => /^[\d*,/-]+$/.test(p));
}
/**

View File

@@ -38,7 +38,7 @@ function isLikelyCron(expr: string): boolean {
const parts = expr.trim().split(/\s+/);
if (parts.length !== 5) return false;
// Each field should contain digits, *, /, -, or ,
return parts.every((p) => /^[\d*,/\-]+$/.test(p));
return parts.every((p) => /^[\d*,/-]+$/.test(p));
}
type ScheduleMode = "simple" | "advanced";

View File

@@ -431,8 +431,6 @@ export function ScheduledTasksModal({ onClose, addToast }: ScheduledTasksModalPr
// Determine if we're in "list" view for showing the "New" button
const isShowingList =
activeTab === "schedules" ? view === "list" && schedules.length > 0 : routineView === "list" && routines.length > 0;
const isShowingEmptyState =
activeTab === "schedules" ? view === "list" && schedules.length === 0 && !loading : routineView === "list" && routines.length === 0;
return (
<div className="modal-overlay open" onClick={handleOverlayClick}>

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, updateSettings, updateGlobalSettings, fetchAuthStatus, loginProvider, logoutProvider, saveApiKey, clearApiKey, fetchModels, testNtfyNotification, fetchBackups, createBackup, exportSettings, importSettings, fetchMemory, saveMemory, fetchGlobalConcurrency, updateGlobalConcurrency } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData, MemoryBackendCapabilities } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData } from "../api";
import { useMemoryBackendStatus } from "../hooks/useMemoryBackendStatus";
import type { ToastType } from "../hooks/useToast";
import { ThemeSelector } from "./ThemeSelector";

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";
@@ -23,24 +23,10 @@ interface ModelSelection {
modelId?: string;
}
function normalizeModelField(value: string | null | undefined): string | undefined {
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:
* 1. Per-task modelProvider/modelId (both must be set)
@@ -359,7 +345,7 @@ export function TaskDetailModal({
})
.catch((err: any) => {
if (!cancelled) {
addToast(`Failed to load workflow results: ${err.message}`, "error");
addToast(`Failed to load workflow results: ${err instanceof Error ? err.message : String(err)}`, "error");
}
})
.finally(() => {
@@ -557,8 +543,8 @@ export function TaskDetailModal({
setEditPendingImages([]);
addToast(`Updated ${task.id}`, "success");
setIsEditing(false);
} catch (err: any) {
addToast(`Failed to update ${task.id}: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to update ${task.id}: ${err instanceof Error ? err.message : String(err)}`, "error");
} finally {
if (mountedRef.current) {
setIsSaving(false);
@@ -570,8 +556,8 @@ export function TaskDetailModal({
try {
await updateTask(task.id, { description }, projectId);
addToast("Description saved", "success");
} catch (err: any) {
addToast(`Failed to save: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to save: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, addToast, projectId]);
@@ -620,8 +606,8 @@ export function TaskDetailModal({
await onMoveTask(task.id, column);
onClose();
addToast(`Moved to ${COLUMN_LABELS[column]}`, "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
},
[task.id, onMoveTask, onClose, addToast],
@@ -633,8 +619,8 @@ export function TaskDetailModal({
await onDeleteTask(task.id);
onClose();
addToast(`Deleted ${task.id}`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onDeleteTask, onClose, addToast]);
@@ -650,7 +636,7 @@ export function TaskDetailModal({
addToast(msg, "success");
})
.catch((err: any) => {
addToast(err.message, "error");
addToast(err instanceof Error ? err.message : String(err), "error");
});
}, [task.id, onMergeTask, onClose, addToast]);
@@ -660,8 +646,8 @@ export function TaskDetailModal({
await onRetryTask(task.id);
onClose();
addToast(`Retrying ${task.id}...`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onRetryTask, onClose, addToast]);
@@ -672,8 +658,8 @@ export function TaskDetailModal({
const newTask = await onDuplicateTask(task.id);
onClose();
addToast(`Duplicated ${task.id}${newTask.id}`, "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onDuplicateTask, onClose, addToast]);
@@ -687,8 +673,8 @@ export function TaskDetailModal({
addToast(`Paused ${task.id}`, "success");
}
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, task.paused, onClose, addToast]);
@@ -697,8 +683,8 @@ export function TaskDetailModal({
await approvePlan(task.id, projectId);
addToast(`Plan approved — ${task.id} moved to Todo`, "success");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -708,8 +694,8 @@ export function TaskDetailModal({
await rejectPlan(task.id, projectId);
addToast(`Plan rejected — ${task.id} returned to Triage for re-specification`, "info");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -719,8 +705,8 @@ export function TaskDetailModal({
await rebuildTaskSpec(task.id, projectId);
onClose();
addToast(`Respecifying ${task.id}...`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, projectId, onClose, addToast]);
@@ -771,8 +757,8 @@ export function TaskDetailModal({
const newTask = await refineTask(task.id, refineFeedback.trim(), projectId);
addToast(`Refinement task created: ${newTask.id}`, "success");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
} finally {
setIsRefining(false);
}
@@ -784,8 +770,8 @@ export function TaskDetailModal({
const attachment = await uploadAttachment(task.id, file, projectId);
setAttachments((prev) => [...prev, attachment]);
addToast("Screenshot attached", "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
} finally {
setUploading(false);
}
@@ -839,8 +825,8 @@ export function TaskDetailModal({
await deleteAttachment(task.id, filename, projectId);
setAttachments((prev) => prev.filter((a) => a.filename !== filename));
addToast("Attachment deleted", "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, addToast]);
@@ -852,9 +838,9 @@ export function TaskDetailModal({
const updatedTask = await updateTask(task.id, { enabledWorkflowSteps }, projectId);
addToast("Workflow steps updated", "success");
onTaskUpdated?.(updatedTask);
} catch (err: any) {
} catch (err: unknown) {
setWorkflowEnabledSteps(previousSteps);
addToast(`Failed to update workflow steps: ${err.message}`, "error");
addToast(`Failed to update workflow steps: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, workflowEnabledSteps, onTaskUpdated, addToast]);
@@ -864,8 +850,8 @@ export function TaskDetailModal({
const loadedAgents = await fetchAgents(undefined, projectId);
setAgents(loadedAgents);
setShowAgentPicker(true);
} catch (err: any) {
addToast(`Failed to load agents: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to load agents: ${err instanceof Error ? err.message : String(err)}`, "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);
@@ -884,8 +870,8 @@ export function TaskDetailModal({
setShowAgentPicker(false);
onTaskUpdated?.(updatedTask);
addToast("Assigned agent updated", "success");
} catch (err: any) {
addToast(`Failed to assign agent: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to assign agent: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, agents, onTaskUpdated, addToast]);
@@ -896,8 +882,8 @@ export function TaskDetailModal({
setShowAgentPicker(false);
onTaskUpdated?.(updatedTask);
addToast("Agent unassigned", "success");
} catch (err: any) {
addToast(`Failed to unassign agent: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to unassign agent: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, onTaskUpdated, addToast]);
@@ -906,9 +892,10 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err: unknown) {
setDependencies(dependencies);
addToast(err.message, "error");
const message = err instanceof Error ? err instanceof Error ? err.message : String(err) : String(err);
addToast(message, "error");
}
}, [task.id, dependencies, addToast]);
@@ -918,9 +905,10 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err: unknown) {
setDependencies(dependencies);
addToast(err.message, "error");
const message = err instanceof Error ? err instanceof Error ? err.message : String(err) : String(err);
addToast(message, "error");
}
}, [task.id, dependencies, addToast]);
@@ -928,7 +916,7 @@ export function TaskDetailModal({
try {
const detail = await fetchTaskDetail(depId, projectId);
onOpenDetail(detail);
} catch (err: any) {
} catch (err: unknown) {
addToast(`Failed to load dependency ${depId}`, "error");
}
}, [onOpenDetail, addToast]);
@@ -943,8 +931,8 @@ export function TaskDetailModal({
if (fullDetail) {
fullDetail.prompt = newContent;
}
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
throw err;
} finally {
setIsSavingSpec(false);
@@ -958,11 +946,12 @@ export function TaskDetailModal({
addToast("AI revision requested. Task moved to triage.", "success");
// Task has been moved to triage, close modal
onClose();
} catch (err: any) {
if (err.message?.includes("in-review") || err.message?.includes("done")) {
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
if (message?.includes("in-review") || message?.includes("done")) {
addToast("Cannot request revision: Task must be in 'todo' or 'in-progress' column.", "error");
} else {
addToast(err.message, "error");
addToast(message, "error");
}
} finally {
setIsRequestingRevision(false);

View File

@@ -1,10 +1,9 @@
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 type { Task, TaskDocument, TaskDocumentRevision } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import {
fetchTaskDocuments,
fetchTaskDocument,
fetchTaskDocumentRevisions,
putTaskDocument,
deleteTaskDocument,

View File

@@ -69,8 +69,9 @@ export async function retryDynamicImport<T>(
/** Whether the current device is likely mobile (touch-primary, small viewport). */
function isMobileDevice(): boolean {
if (typeof window === "undefined") return false;
const nav = navigator as Navigator & { maxTouchPoints?: number };
const hasTouchScreen =
"ontouchstart" in window || (navigator as any).maxTouchPoints > 0;
"ontouchstart" in window || (nav.maxTouchPoints ?? 0) > 0;
const isNarrow = window.innerWidth <= 768;
return hasTouchScreen && isNarrow;
}
@@ -601,7 +602,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
if (xtermRef.current) {
const currentSize = xtermRef.current.options.fontSize || 14;
xtermRef.current.options.fontSize = Math.min(currentSize + 1, 32);
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
if (fitAddonRef.current) {
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
}
}
return;
}
@@ -612,7 +615,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
if (xtermRef.current) {
const currentSize = xtermRef.current.options.fontSize || 14;
xtermRef.current.options.fontSize = Math.max(currentSize - 1, 8);
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
if (fitAddonRef.current) {
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
}
}
return;
}
@@ -622,7 +627,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
e.preventDefault();
if (xtermRef.current) {
xtermRef.current.options.fontSize = 14;
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
if (fitAddonRef.current) {
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
}
}
return;
}
@@ -726,8 +733,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
// Restart the active tab's session
try {
await restartActiveTab();
} catch (err: any) {
setError(err.message || "Failed to restart terminal session");
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
setError(message || "Failed to restart terminal session");
}
}, [restartActiveTab]);

View File

@@ -209,8 +209,8 @@ export interface MissionWithHierarchy extends Mission {
export type MissionEventType = CoreMissionEventType;
/** Mission lifecycle event persisted in the mission event log. */
export interface MissionEvent extends CoreMissionEvent {}
export type MissionEvent = CoreMissionEvent;
/** Computed mission health snapshot returned by observability APIs. */
export interface MissionHealth extends CoreMissionHealth {}
export type MissionHealth = CoreMissionHealth;