chore(eslint): update eslint config and downgrade strict rules

This commit is contained in:
Fatih Kadir Akın
2026-02-03 13:12:56 +03:00
parent 72fb2c1662
commit 9d0141538c
17 changed files with 139 additions and 118 deletions

View File

@@ -12,7 +12,35 @@ const eslintConfig = defineConfig([
"out/**", "out/**",
"build/**", "build/**",
"next-env.d.ts", "next-env.d.ts",
// Compiled outputs
"packages/*/dist/**",
// Packages with their own ESLint config
"packages/raycast-extension/**",
// Scripts - may use CommonJS
"scripts/**",
// Prisma scripts
"prisma/**",
]), ]),
// Downgrade strict rules to warnings for gradual adoption
{
rules: {
// React hooks compiler rules - many false positives in complex state patterns
"react-hooks/set-state-in-effect": "warn",
"react-hooks/immutability": "warn",
"react-hooks/refs": "warn",
"react-hooks/preserve-manual-memoization": "warn",
// JSX entity escaping - affects many existing components
"react/no-unescaped-entities": "warn",
// Function type - affects test mocks
"@typescript-eslint/no-unsafe-function-type": "warn",
// Display name - affects anonymous components
"react/display-name": "warn",
// HTML links - sometimes needed for external/special navigation
"@next/next/no-html-link-for-pages": "warn",
// Children as props - used in some component patterns
"react/no-children-prop": "warn",
},
},
]); ]);
export default eslintConfig; export default eslintConfig;

View File

@@ -39,6 +39,7 @@ function getJSDocComment(node: ts.Node, sourceFile: ts.SourceFile): { descriptio
const examples: string[] = []; const examples: string[] = [];
let description: string | undefined; let description: string | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript AST doesn't expose jsDoc in types
const jsDocNodes = (node as any).jsDoc as ts.JSDoc[] | undefined; const jsDocNodes = (node as any).jsDoc as ts.JSDoc[] | undefined;
if (jsDocNodes && jsDocNodes.length > 0) { if (jsDocNodes && jsDocNodes.length > 0) {
const jsDoc = jsDocNodes[0]; const jsDoc = jsDocNodes[0];

View File

@@ -220,7 +220,7 @@ export function PromptList({
return ( return (
<Box flexDirection="column" height={terminalHeight} padding={1}> <Box flexDirection="column" height={terminalHeight} padding={1}>
<Text color="red">Error: {error}</Text> <Text color="red">Error: {error}</Text>
<Text dimColor>Press 'r' to retry, 'q' to quit</Text> <Text dimColor>Press &apos;r&apos; to retry, &apos;q&apos; to quit</Text>
</Box> </Box>
); );
} }

View File

@@ -55,7 +55,7 @@ function parseSimpleYaml(content: string): Record<string, unknown> {
const lines = content.split('\n'); const lines = content.split('\n');
let currentKey: string | null = null; let currentKey: string | null = null;
let currentValue: unknown = null; const _currentValue: unknown = null; // Placeholder for future use
let inArray = false; let inArray = false;
let inMultiline = false; let inMultiline = false;
let multilineContent = ''; let multilineContent = '';

View File

@@ -16,21 +16,24 @@ interface LevelContentWrapperProps {
levelNumber: string; levelNumber: string;
} }
export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelContentWrapperProps) { export function LevelContentWrapper({ children, levelSlug, levelNumber: _levelNumber }: LevelContentWrapperProps) {
const t = useTranslations("kids"); const t = useTranslations("kids");
const setLevelSlug = useSetLevelSlug(); const setLevelSlug = useSetLevelSlug();
const { const {
currentSection, currentSection,
setCurrentSection, setCurrentSection,
completedSections, completedSections: _completedSections,
markSectionComplete, markSectionComplete,
isSectionComplete, isSectionComplete: _isSectionComplete,
sectionRequiresCompletion, sectionRequiresCompletion,
} = useSectionNavigation(); } = useSectionNavigation();
// Track section completion state from localStorage // Track section completion state from localStorage
const [sectionCompletionState, setSectionCompletionState] = useState<Record<number, boolean>>({}); const [sectionCompletionState, setSectionCompletionState] = useState<Record<number, boolean>>({});
// Track the highest section the user has visited (moved before early returns)
const [highestVisitedSection, setHighestVisitedSection] = useState(0);
// Check localStorage for section completion on mount and when section changes // Check localStorage for section completion on mount and when section changes
const checkSectionCompletion = useCallback(() => { const checkSectionCompletion = useCallback(() => {
const newState: Record<number, boolean> = {}; const newState: Record<number, boolean> = {};
@@ -39,27 +42,40 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
} }
setSectionCompletionState(newState); setSectionCompletionState(newState);
}, [levelSlug]); }, [levelSlug]);
useEffect(() => { useEffect(() => {
checkSectionCompletion(); checkSectionCompletion();
// Re-check periodically to catch component completions // Re-check periodically to catch component completions
const interval = setInterval(checkSectionCompletion, 500); const interval = setInterval(checkSectionCompletion, 500);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [checkSectionCompletion, currentSection]); }, [checkSectionCompletion, currentSection]);
// Set the level slug in context when component mounts // Set the level slug in context when component mounts
useEffect(() => { useEffect(() => {
setLevelSlug(levelSlug); setLevelSlug(levelSlug);
// Track level view // Track level view
const level = getLevelBySlug(levelSlug); const level = getLevelBySlug(levelSlug);
if (level) { if (level) {
analyticsKids.viewLevel(levelSlug, level.world); analyticsKids.viewLevel(levelSlug, level.world);
} }
return () => setLevelSlug(""); // Clear when unmounting return () => setLevelSlug(""); // Clear when unmounting
}, [levelSlug, setLevelSlug]); }, [levelSlug, setLevelSlug]);
// Update highest visited when current section changes
useEffect(() => {
setHighestVisitedSection(prev => Math.max(prev, currentSection));
}, [currentSection]);
// Reset to first section and visited state when level changes
useEffect(() => {
setCurrentSection(0);
setHighestVisitedSection(0);
setSectionCompletionState({});
// eslint-disable-next-line react-hooks/exhaustive-deps -- setCurrentSection is stable
}, [levelSlug]);
// Extract Section components from children // Extract Section components from children
const sections: ReactElement[] = []; const sections: ReactElement[] = [];
let hasExplicitSections = false; let hasExplicitSections = false;
@@ -104,19 +120,11 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
const totalSections = sections.length; const totalSections = sections.length;
const isFirstSection = currentSection === 0; const isFirstSection = currentSection === 0;
const isLastSection = currentSection === totalSections - 1; const isLastSection = currentSection === totalSections - 1;
// Check if current section is complete (from localStorage) OR doesn't require completion // Check if current section is complete (from localStorage) OR doesn't require completion
const currentSectionRequiresCompletion = sectionRequiresCompletion(currentSection); const currentSectionRequiresCompletion = sectionRequiresCompletion(currentSection);
const isCurrentSectionComplete = !currentSectionRequiresCompletion || sectionCompletionState[currentSection] || false; const isCurrentSectionComplete = !currentSectionRequiresCompletion || sectionCompletionState[currentSection] || false;
// Track the highest section the user has visited
const [highestVisitedSection, setHighestVisitedSection] = useState(0);
// Update highest visited when current section changes
useEffect(() => {
setHighestVisitedSection(prev => Math.max(prev, currentSection));
}, [currentSection]);
// Can navigate to a section if it's: // Can navigate to a section if it's:
// 1. The current section // 1. The current section
// 2. A previously visited section (but NOT future sections) // 2. A previously visited section (but NOT future sections)
@@ -139,27 +147,20 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
setCurrentSection((prev) => prev - 1); setCurrentSection((prev) => prev - 1);
} }
}; };
const handleDotClick = (targetSection: number) => { const handleDotClick = (targetSection: number) => {
if (canNavigateToSection(targetSection)) { if (canNavigateToSection(targetSection)) {
setCurrentSection(targetSection); setCurrentSection(targetSection);
} }
}; };
// Mark section as complete manually (for sections without interactive elements) // Mark section as complete manually (for sections without interactive elements)
const handleMarkComplete = () => { const _handleMarkComplete = () => {
markSectionCompleted(levelSlug, currentSection); markSectionCompleted(levelSlug, currentSection);
markSectionComplete(currentSection); markSectionComplete(currentSection);
checkSectionCompletion(); checkSectionCompletion();
}; };
// Reset to first section and visited state when level changes
useEffect(() => {
setCurrentSection(0);
setHighestVisitedSection(0);
setSectionCompletionState({});
}, [levelSlug]);
return ( return (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
{/* Content area */} {/* Content area */}

View File

@@ -2,9 +2,7 @@
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { useTranslations, useLocale } from "next-intl"; import { useTranslations, useLocale } from "next-intl";
import { formatDistanceToNow } from "@/lib/date";
import { getPromptUrl } from "@/lib/urls"; import { getPromptUrl } from "@/lib/urls";
import { ArrowBigUp, Lock, Copy, ImageIcon, Download, Play, BadgeCheck, Volume2, Link2 } from "lucide-react"; import { ArrowBigUp, Lock, Copy, ImageIcon, Download, Play, BadgeCheck, Volume2, Link2 } from "lucide-react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -91,7 +89,7 @@ export interface PromptCardProps {
export function PromptCard({ prompt, showPinButton = false, isPinned = false }: PromptCardProps) { export function PromptCard({ prompt, showPinButton = false, isPinned = false }: PromptCardProps) {
const t = useTranslations("prompts"); const t = useTranslations("prompts");
const tCommon = useTranslations("common"); const tCommon = useTranslations("common");
const locale = useLocale(); const _locale = useLocale();
const outgoingCount = prompt._count?.outgoingConnections || 0; const outgoingCount = prompt._count?.outgoingConnections || 0;
const incomingCount = prompt._count?.incomingConnections || 0; const incomingCount = prompt._count?.incomingConnections || 0;
const isFlowStart = outgoingCount > 0 && incomingCount === 0; const isFlowStart = outgoingCount > 0 && incomingCount === 0;
@@ -104,7 +102,7 @@ export function PromptCard({ prompt, showPinButton = false, isPinned = false }:
const isVideo = prompt.type === "VIDEO"; const isVideo = prompt.type === "VIDEO";
const hasMediaBackground = prompt.type === "IMAGE" || isVideo || (isStructuredInput && !!prompt.mediaUrl && !isAudio); const hasMediaBackground = prompt.type === "IMAGE" || isVideo || (isStructuredInput && !!prompt.mediaUrl && !isAudio);
const videoRef = useRef<HTMLVideoElement>(null); const videoRef = useRef<HTMLVideoElement>(null);
const [isVisible, setIsVisible] = useState(false); const [_isVisible, setIsVisible] = useState(false);
// Autoplay video when visible in viewport // Autoplay video when visible in viewport
useEffect(() => { useEffect(() => {

View File

@@ -794,6 +794,7 @@ function FlowGraph({ nodes, edges, currentPromptId, currentUserId, isAdmin, onNo
return ( return (
<div ref={containerRef} className="relative"> <div ref={containerRef} className="relative">
<svg ref={svgRef} className="w-full" /> <svg ref={svgRef} className="w-full" />
{/* eslint-disable-next-line react-hooks/refs -- Container dimensions needed for tooltip positioning */}
{hoveredNode && (() => { {hoveredNode && (() => {
// Calculate position with viewport awareness // Calculate position with viewport awareness
const tooltipWidth = 320; const tooltipWidth = 320;

View File

@@ -6,7 +6,7 @@ import { useTranslations } from "next-intl";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { Loader2, Upload, X, ArrowDown, Play, Image as ImageIcon, Video, Volume2, Paperclip, Search, Sparkles, BookOpen, ExternalLink, ChevronDown, Settings2 } from "lucide-react"; import { Loader2, Upload, X, ArrowDown, Image as ImageIcon, Video, Volume2, Paperclip, Search, Sparkles, BookOpen, ExternalLink, ChevronDown, Settings2 } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { VariableToolbar } from "./variable-toolbar"; import { VariableToolbar } from "./variable-toolbar";
import { VariableWarning } from "./variable-warning"; import { VariableWarning } from "./variable-warning";
@@ -21,17 +21,10 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { import {
parseSkillFiles,
serializeSkillFiles,
getLanguageFromFilename,
validateFilename,
suggestFilename,
generateSkillContentWithFrontmatter, generateSkillContentWithFrontmatter,
updateSkillFrontmatter, updateSkillFrontmatter,
validateSkillFrontmatter, validateSkillFrontmatter,
DEFAULT_SKILL_FILE, DEFAULT_SKILL_FILE,
DEFAULT_SKILL_CONTENT,
type SkillFile,
} from "@/lib/skill-files"; } from "@/lib/skill-files";
import { import {
Form, Form,
@@ -61,7 +54,7 @@ import { toast } from "sonner";
import { prettifyJson } from "@/lib/format"; import { prettifyJson } from "@/lib/format";
import { analyticsPrompt } from "@/lib/analytics"; import { analyticsPrompt } from "@/lib/analytics";
import { getPromptUrl } from "@/lib/urls"; import { getPromptUrl } from "@/lib/urls";
import { AI_MODELS, getModelsByProvider, type PromptMCPConfig } from "@/lib/works-best-with"; import { AI_MODELS, getModelsByProvider } from "@/lib/works-best-with";
interface MediaFieldProps { interface MediaFieldProps {
form: ReturnType<typeof useForm<PromptFormValues>>; form: ReturnType<typeof useForm<PromptFormValues>>;
@@ -1324,6 +1317,7 @@ export function PromptForm({ categories, tags, initialData, initialContributors
</div> </div>
{/* Code output content */} {/* Code output content */}
<div className="p-4 text-xs space-y-1" style={{ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }}> <div className="p-4 text-xs space-y-1" style={{ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }}>
{/* eslint-disable-next-line react/jsx-no-comment-textnodes -- Intentional code preview text */}
<div><span className="text-[#6a9955]">// Code generated by skill...</span></div> <div><span className="text-[#6a9955]">// Code generated by skill...</span></div>
<div><span className="text-[#c586c0]">export</span> <span className="text-[#569cd6]">function</span> <span className="text-[#dcdcaa]">handler</span><span className="text-[#d4d4d4]">()</span> <span className="text-[#d4d4d4]">{'{'}</span></div> <div><span className="text-[#c586c0]">export</span> <span className="text-[#569cd6]">function</span> <span className="text-[#dcdcaa]">handler</span><span className="text-[#d4d4d4]">()</span> <span className="text-[#d4d4d4]">{'{'}</span></div>
<div><span className="text-[#d4d4d4]"> </span><span className="text-[#c586c0]">return</span> <span className="text-[#ce9178]">&quot;...&quot;</span><span className="text-[#d4d4d4]">;</span></div> <div><span className="text-[#d4d4d4]"> </span><span className="text-[#c586c0]">return</span> <span className="text-[#ce9178]">&quot;...&quot;</span><span className="text-[#d4d4d4]">;</span></div>

View File

@@ -1,6 +1,6 @@
import Link from "next/link"; import Link from "next/link";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Sparkles, ArrowBigUp } from "lucide-react"; import { ArrowBigUp } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { getPromptUrl } from "@/lib/urls"; import { getPromptUrl } from "@/lib/urls";

View File

@@ -316,6 +316,7 @@ export function RunPromptButton({
if (url.startsWith("http://") || url.startsWith("https://")) { if (url.startsWith("http://") || url.startsWith("https://")) {
window.open(url, "_blank"); window.open(url, "_blank");
} else { } else {
// eslint-disable-next-line react-hooks/immutability -- Valid browser navigation for custom URL schemes
window.location.href = url; window.location.href = url;
} }
analyticsPrompt.run(promptId, platform.name); analyticsPrompt.run(promptId, platform.name);

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useCallback, useMemo, useEffect } from "react"; import { useState, useCallback, useMemo } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { DiffEditor } from "@monaco-editor/react"; import { DiffEditor } from "@monaco-editor/react";

View File

@@ -32,7 +32,6 @@ import {
validateFilename, validateFilename,
suggestFilename, suggestFilename,
DEFAULT_SKILL_FILE, DEFAULT_SKILL_FILE,
DEFAULT_SKILL_CONTENT,
type SkillFile, type SkillFile,
} from "@/lib/skill-files"; } from "@/lib/skill-files";
@@ -357,6 +356,7 @@ export function SkillEditor({ value, onChange, className }: SkillEditorProps) {
// Only update if the value changed externally // Only update if the value changed externally
if (value !== currentSerialized) { if (value !== currentSerialized) {
// eslint-disable-next-line react-hooks/set-state-in-effect -- Intentional sync from external prop
setFiles(parsed); setFiles(parsed);
// Ensure active file exists // Ensure active file exists
if (!parsed.some((f) => f.filename === activeFile)) { if (!parsed.some((f) => f.filename === activeFile)) {
@@ -372,7 +372,7 @@ export function SkillEditor({ value, onChange, className }: SkillEditorProps) {
// File icon based on extension // File icon based on extension
const getFileIcon = (filename: string) => { const getFileIcon = (filename: string) => {
const ext = filename.split(".").pop()?.toLowerCase(); const _ext = filename.split(".").pop()?.toLowerCase();
// Could add more specific icons here // Could add more specific icons here
return <File className="h-4 w-4 text-muted-foreground" />; return <File className="h-4 w-4 text-muted-foreground" />;
}; };

View File

@@ -2,7 +2,7 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { AlertTriangle, Braces, FileCode } from "lucide-react"; import { Braces, FileCode } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";

View File

@@ -101,12 +101,12 @@ export function CodeView({ content, language = "json", className, maxLines, font
</div> </div>
)} )}
{viewMode === "tree" && isValidJson ? ( {viewMode === "tree" && isValidJson ? (
<JsonTreeViewWrapper <JsonTreeViewWrapper
content={content} content={content}
className={className} className={className}
fontSize={fontSize} fontSize={fontSize}
onExpandAll={expandAllRef} onExpandAllRef={expandAllRef}
onCollapseAll={collapseAllRef} onCollapseAllRef={collapseAllRef}
/> />
) : ( ) : (
<pre suppressHydrationWarning className={cn("font-mono bg-muted rounded p-2", preview ? "overflow-hidden" : "overflow-y-auto max-h-[500px]", { <pre suppressHydrationWarning className={cn("font-mono bg-muted rounded p-2", preview ? "overflow-hidden" : "overflow-y-auto max-h-[500px]", {

View File

@@ -2,10 +2,8 @@
import { useState, useMemo, useCallback, useEffect } from "react"; import { useState, useMemo, useCallback, useEffect } from "react";
import type React from "react"; import type React from "react";
import { ChevronRight, ChevronDown, ChevronsDown, ChevronsUp } from "lucide-react"; import { ChevronRight, ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { useTranslations } from "next-intl";
interface JsonNode { interface JsonNode {
key: string | null; key: string | null;
@@ -14,17 +12,46 @@ interface JsonNode {
path: string; path: string;
} }
// Pure helper function - defined outside component to avoid recreation
const getNodeType = (value: unknown): JsonNode["type"] => {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
if (typeof value === "object") return "object";
return typeof value as "string" | "number" | "boolean";
};
// Recursive helper function - defined outside component
const collectExpandablePaths = (value: unknown, path: string, maxDepth: number, depth: number = 0): string[] => {
const paths: string[] = [];
const type = getNodeType(value);
if ((type === "object" || type === "array") && depth < maxDepth) {
paths.push(path);
if (type === "array") {
(value as unknown[]).forEach((item, index) => {
paths.push(...collectExpandablePaths(item, `${path}.${index}`, maxDepth, depth + 1));
});
} else {
Object.entries(value as Record<string, unknown>).forEach(([k, v]) => {
paths.push(...collectExpandablePaths(v, `${path}.${k}`, maxDepth, depth + 1));
});
}
}
return paths;
};
interface JsonTreeViewProps { interface JsonTreeViewProps {
data: unknown; data: unknown;
className?: string; className?: string;
fontSize?: "xs" | "sm" | "base"; fontSize?: "xs" | "sm" | "base";
maxDepth?: number; maxDepth?: number;
onExpandAll?: React.MutableRefObject<(() => void) | undefined>; onExpandAllRef?: React.MutableRefObject<(() => void) | undefined>;
onCollapseAll?: React.MutableRefObject<(() => void) | undefined>; onCollapseAllRef?: React.MutableRefObject<(() => void) | undefined>;
} }
function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpandAll, onCollapseAll }: JsonTreeViewProps) { function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpandAllRef, onCollapseAllRef }: JsonTreeViewProps) {
const t = useTranslations("common");
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(["root"])); const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(["root"]));
const togglePath = (path: string) => { const togglePath = (path: string) => {
@@ -39,38 +66,9 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
}); });
}; };
const getNodeType = (value: unknown): JsonNode["type"] => {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
if (typeof value === "object") return "object";
return typeof value as "string" | "number" | "boolean";
};
// Collect all expandable paths recursively
const collectExpandablePaths = useCallback((value: unknown, path: string, depth: number = 0): string[] => {
const paths: string[] = [];
const type = getNodeType(value);
if ((type === "object" || type === "array") && depth < maxDepth) {
paths.push(path);
if (type === "array") {
(value as unknown[]).forEach((item, index) => {
paths.push(...collectExpandablePaths(item, `${path}.${index}`, depth + 1));
});
} else {
Object.entries(value as Record<string, unknown>).forEach(([k, v]) => {
paths.push(...collectExpandablePaths(v, `${path}.${k}`, depth + 1));
});
}
}
return paths;
}, [maxDepth, getNodeType]);
const allExpandablePaths = useMemo(() => { const allExpandablePaths = useMemo(() => {
return collectExpandablePaths(data, "root"); return collectExpandablePaths(data, "root", maxDepth);
}, [data, collectExpandablePaths]); }, [data, maxDepth]);
const expandAll = useCallback(() => { const expandAll = useCallback(() => {
setExpandedPaths(new Set(allExpandablePaths)); setExpandedPaths(new Set(allExpandablePaths));
@@ -109,7 +107,7 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
} }
}; };
const renderNode = (node: JsonNode, depth: number = 0, isLast: boolean = true): React.ReactNode => { const renderNode = (node: JsonNode, depth: number = 0, _isLast: boolean = true): React.ReactNode => {
const { key, value, type, path } = node; const { key, value, type, path } = node;
const isExpanded = expandedPaths.has(path); const isExpanded = expandedPaths.has(path);
const isComplex = type === "object" || type === "array"; const isComplex = type === "object" || type === "array";
@@ -239,13 +237,13 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
// Expose expand/collapse functions via useEffect // Expose expand/collapse functions via useEffect
useEffect(() => { useEffect(() => {
if (onExpandAll) { if (onExpandAllRef) {
onExpandAll.current = expandAll; onExpandAllRef.current = expandAll;
} }
if (onCollapseAll) { if (onCollapseAllRef) {
onCollapseAll.current = collapseAll; onCollapseAllRef.current = collapseAll;
} }
}, [expandAll, collapseAll, onExpandAll, onCollapseAll]); }, [expandAll, collapseAll, onExpandAllRef, onCollapseAllRef]);
return ( return (
<div <div
@@ -264,18 +262,18 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
); );
} }
export function JsonTreeViewWrapper({ export function JsonTreeViewWrapper({
content, content,
className, className,
fontSize = "xs", fontSize = "xs",
onExpandAll, onExpandAllRef,
onCollapseAll onCollapseAllRef
}: { }: {
content: string; content: string;
className?: string; className?: string;
fontSize?: "xs" | "sm" | "base"; fontSize?: "xs" | "sm" | "base";
onExpandAll?: React.MutableRefObject<(() => void) | undefined>; onExpandAllRef?: React.MutableRefObject<(() => void) | undefined>;
onCollapseAll?: React.MutableRefObject<(() => void) | undefined>; onCollapseAllRef?: React.MutableRefObject<(() => void) | undefined>;
}) { }) {
const parsedData = useMemo(() => { const parsedData = useMemo(() => {
try { try {
@@ -294,12 +292,12 @@ export function JsonTreeViewWrapper({
} }
return ( return (
<JsonTreeView <JsonTreeView
data={parsedData} data={parsedData}
className={className} className={className}
fontSize={fontSize} fontSize={fontSize}
onExpandAll={onExpandAll} onExpandAllRef={onExpandAllRef}
onCollapseAll={onCollapseAll} onCollapseAllRef={onCollapseAllRef}
/> />
); );
} }

View File

@@ -3,7 +3,6 @@ import { PrismaAdapter } from "@auth/prisma-adapter";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { getConfig } from "@/lib/config"; import { getConfig } from "@/lib/config";
import { initializePlugins, getAuthPlugin } from "@/lib/plugins"; import { initializePlugins, getAuthPlugin } from "@/lib/plugins";
import type { User } from "@prisma/client";
import type { Adapter, AdapterUser } from "next-auth/adapters"; import type { Adapter, AdapterUser } from "next-auth/adapters";
// Initialize plugins before use // Initialize plugins before use

View File

@@ -11,7 +11,7 @@ import { z } from "zod";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { isValidApiKeyFormat } from "@/lib/api-key"; import { isValidApiKeyFormat } from "@/lib/api-key";
import { improvePrompt } from "@/lib/ai/improve-prompt"; import { improvePrompt } from "@/lib/ai/improve-prompt";
import { parseSkillFiles, serializeSkillFiles, DEFAULT_SKILL_FILE, DEFAULT_SKILL_CONTENT } from "@/lib/skill-files"; import { parseSkillFiles, serializeSkillFiles, DEFAULT_SKILL_FILE } from "@/lib/skill-files";
interface AuthenticatedUser { interface AuthenticatedUser {
id: string; id: string;