submit ux improvements

This commit is contained in:
2025-07-31 13:53:39 +01:00
parent d712b2de02
commit d6e9a689e3

View File

@@ -1,10 +1,11 @@
"use client"; "use client";
import { useState, useCallback, useMemo } from "react"; import { useState, useCallback, useMemo, useEffect } from "react";
import { CodeEditor } from "./code-editor"; import { CodeEditor } from "./code-editor";
import { TestResults } from "./test-results"; import { TestResults } from "./test-results";
import { usePyodide } from "@/hooks/use-pyodide"; import { usePyodide } from "@/hooks/use-pyodide";
import { submitSolution } from "@/lib/api"; import { submitSolution } from "@/lib/api";
import { markQuestionCompleted, getSavedSolution, isQuestionCompleted } from "@/lib/progress";
import type { QuestionDetail, TestResult, HiddenTestOutput } from "@/types"; import type { QuestionDetail, TestResult, HiddenTestOutput } from "@/types";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Markdown } from "@/components/ui/markdown"; import { Markdown } from "@/components/ui/markdown";
@@ -12,6 +13,19 @@ import { CodeBlock } from "@/components/ui/code-block";
import { Callout, ApproachBox } from "@/components/ui/callout"; import { Callout, ApproachBox } from "@/components/ui/callout";
import { Collapsible } from "@/components/ui/collapsible"; import { Collapsible } from "@/components/ui/collapsible";
import { getDifficultyVariant, capitalize } from "@/lib/utils"; import { getDifficultyVariant, capitalize } from "@/lib/utils";
import {
detectProblemType,
TREE_NODE_CLASS,
LIST_NODE_CLASS,
BUILD_TREE,
TREE_TO_ARRAY,
BUILD_LIST,
LIST_TO_ARRAY,
} from "@/lib/python-helpers";
import {
COMPLEXITY_ANALYZER,
type ComplexityResult,
} from "@/lib/complexity-analyzer";
import { import {
FileText, FileText,
BookOpen, BookOpen,
@@ -24,6 +38,13 @@ import {
Play, Play,
Send, Send,
Loader2, Loader2,
PanelLeftClose,
PanelRightClose,
Columns2,
Gauge,
ChevronRight,
CheckCircle,
XCircle,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
@@ -38,19 +59,60 @@ interface ExecutionResult {
} }
function extractFunctionName(signature: string): string { function extractFunctionName(signature: string): string {
const match = signature.match(/def\s+(\w+)\s*\(/); // Check for class-based signature: "class MinStack" -> "MinStack"
return match ? match[1] : "solution"; const classMatch = signature.match(/class\s+(\w+)/);
if (classMatch) return classMatch[1];
// Function: "def two_sum(" -> "two_sum"
const funcMatch = signature.match(/def\s+(\w+)\s*\(/);
return funcMatch ? funcMatch[1] : "solution";
} }
function generateStarterCode(signature: string): string { function generateStarterCode(signature: string): string {
// Class-based problems need a different starter template
if (signature.startsWith("class ")) {
return `${signature}:
def __init__(self):
# Initialize your data structure here
pass
`;
}
return `${signature} return `${signature}
# Write your solution here # Write your solution here
pass pass
`; `;
} }
type LeftTab = "problem" | "explanation"; /**
* Compare actual and expected outputs with flexible matching.
* Handles unordered array comparison for problems like subsets.
*/
function compareOutputs(actual: unknown, expected: unknown): boolean {
// Direct equality check
if (JSON.stringify(actual) === JSON.stringify(expected)) {
return true;
}
// Handle unordered array comparison (e.g., subsets, word-break-ii)
if (Array.isArray(actual) && Array.isArray(expected)) {
if (actual.length !== expected.length) {
return false;
}
// Sort both arrays by their JSON string representation for comparison
const sortFn = (a: unknown, b: unknown) =>
JSON.stringify(a).localeCompare(JSON.stringify(b));
const sortedActual = [...actual].sort(sortFn);
const sortedExpected = [...expected].sort(sortFn);
return JSON.stringify(sortedActual) === JSON.stringify(sortedExpected);
}
return false;
}
type LeftTab = "problem" | "explanation" | "solutions";
type RightTab = "code" | "results"; type RightTab = "code" | "results";
type ViewMode = "split" | "left" | "right";
export function ProblemWorkspace({ question }: ProblemWorkspaceProps) { export function ProblemWorkspace({ question }: ProblemWorkspaceProps) {
const starterCode = useMemo( const starterCode = useMemo(
@@ -60,6 +122,7 @@ export function ProblemWorkspace({ question }: ProblemWorkspaceProps) {
const [code, setCode] = useState(starterCode); const [code, setCode] = useState(starterCode);
const [leftTab, setLeftTab] = useState<LeftTab>("problem"); const [leftTab, setLeftTab] = useState<LeftTab>("problem");
const [rightTab, setRightTab] = useState<RightTab>("code"); const [rightTab, setRightTab] = useState<RightTab>("code");
const [viewMode, setViewMode] = useState<ViewMode>("split");
const [hasRunTests, setHasRunTests] = useState(false); const [hasRunTests, setHasRunTests] = useState(false);
// Test execution state // Test execution state
@@ -69,6 +132,18 @@ export function ProblemWorkspace({ question }: ProblemWorkspaceProps) {
const [visibleResults, setVisibleResults] = useState<TestResult[]>([]); const [visibleResults, setVisibleResults] = useState<TestResult[]>([]);
const [hiddenPassedCount, setHiddenPassedCount] = useState(0); const [hiddenPassedCount, setHiddenPassedCount] = useState(0);
const [hiddenTotalCount, setHiddenTotalCount] = useState(0); const [hiddenTotalCount, setHiddenTotalCount] = useState(0);
const [complexityResult, setComplexityResult] = useState<ComplexityResult | null>(null);
const [isComplexityExpanded, setIsComplexityExpanded] = useState(false);
const [isCompleted, setIsCompleted] = useState(false);
// Load saved solution and completion status on mount
useEffect(() => {
const saved = getSavedSolution(question.slug);
if (saved) {
setCode(saved);
}
setIsCompleted(isQuestionCompleted(question.slug));
}, [question.slug]);
const functionName = useMemo( const functionName = useMemo(
() => extractFunctionName(question.function_signature!), () => extractFunctionName(question.function_signature!),
@@ -88,9 +163,127 @@ export function ProblemWorkspace({ question }: ProblemWorkspaceProps) {
setCode(starterCode); setCode(starterCode);
}, [starterCode]); }, [starterCode]);
const toggleViewMode = useCallback((panel: "left" | "right") => {
setViewMode((current) => {
if (current === "split") {
return panel; // Maximize the clicked panel
}
return "split"; // Return to split view
});
}, []);
const signature = question.function_signature ?? "";
const generateTestHarness = useCallback( const generateTestHarness = useCallback(
(tests: Array<{ id: number; input: Record<string, unknown> }>) => { (tests: Array<{ id: number; input: Record<string, unknown> }>) => {
// Use first test to detect problem type
const firstInput = tests[0]?.input ?? {};
const problemType = detectProblemType(signature, firstInput);
const testsJson = JSON.stringify(tests); const testsJson = JSON.stringify(tests);
if (problemType === "class-based") {
// Class-based harness: operations/args format
return `
import json
${code}
__tests__ = json.loads('${testsJson.replace(/'/g, "\\'")}')
__results__ = []
for test in __tests__:
try:
operations = test["input"]["operations"]
arguments = test["input"]["args"]
outputs = []
instance = None
for op, args in zip(operations, arguments):
if instance is None:
# First operation is always the constructor
instance = globals()[op](*args)
outputs.append(None)
else:
method_result = getattr(instance, op)(*args)
outputs.append(method_result)
__results__.append({"test_id": test["id"], "output": outputs})
except Exception as e:
__results__.append({"test_id": test["id"], "output": None, "error": str(e)})
json.dumps(__results__)
`;
}
if (problemType === "tree") {
// Tree-based harness: convert arrays to TreeNode, convert result back
return `
import json
${TREE_NODE_CLASS}
${BUILD_TREE}
${TREE_TO_ARRAY}
${code}
__tests__ = json.loads('${testsJson.replace(/'/g, "\\'")}')
__results__ = []
for test in __tests__:
try:
processed = dict(test["input"])
# Convert tree parameters from array to TreeNode
for key in ["root", "tree", "p", "q"]:
if key in processed and isinstance(processed[key], list):
processed[key] = __build_tree(processed[key])
result = ${functionName}(**processed)
# Convert TreeNode result back to array
if isinstance(result, TreeNode):
result = __tree_to_array(result)
__results__.append({"test_id": test["id"], "output": result})
except Exception as e:
__results__.append({"test_id": test["id"], "output": None, "error": str(e)})
json.dumps(__results__)
`;
}
if (problemType === "linkedlist") {
// Linked list harness: convert arrays to ListNode, convert result back
return `
import json
${LIST_NODE_CLASS}
${BUILD_LIST}
${LIST_TO_ARRAY}
${code}
__tests__ = json.loads('${testsJson.replace(/'/g, "\\'")}')
__results__ = []
for test in __tests__:
try:
processed = dict(test["input"])
# Convert list parameters from array to ListNode
for key in ["head", "l1", "l2", "list1", "list2", "node"]:
if key in processed and isinstance(processed[key], list):
processed[key] = __build_list(processed[key])
result = ${functionName}(**processed)
# Convert ListNode result back to array
if isinstance(result, ListNode):
result = __list_to_array(result)
__results__.append({"test_id": test["id"], "output": result})
except Exception as e:
__results__.append({"test_id": test["id"], "output": None, "error": str(e)})
json.dumps(__results__)
`;
}
// Simple function harness (default)
return ` return `
import json import json
@@ -109,7 +302,7 @@ for test in __tests__:
json.dumps(__results__) json.dumps(__results__)
`; `;
}, },
[code, functionName] [code, functionName, signature]
); );
const runTests = useCallback( const runTests = useCallback(
@@ -138,12 +331,38 @@ json.dumps(__results__)
[generateTestHarness, runPython] [generateTestHarness, runPython]
); );
const analyzeComplexity = useCallback(
async (userCode: string) => {
const analyzerCode = `
${COMPLEXITY_ANALYZER}
import json
__user_code = '''${userCode.replace(/'/g, "\\'")}'''
__result = analyze_complexity(__user_code)
json.dumps(__result)
`;
const { output, error } = await runPython(analyzerCode);
if (error) {
return { success: false, error } as ComplexityResult;
}
try {
return JSON.parse(output as string) as ComplexityResult;
} catch {
return { success: false, error: "Failed to parse analysis" } as ComplexityResult;
}
},
[runPython]
);
const handleRunTests = useCallback(async () => { const handleRunTests = useCallback(async () => {
if (!pyodide || isRunning) return; if (!pyodide || isRunning) return;
setIsRunning(true); setIsRunning(true);
setHiddenPassedCount(0); setHiddenPassedCount(0);
setHiddenTotalCount(0); setHiddenTotalCount(0);
setComplexityResult(null);
try { try {
const tests = visibleTestCases.map((tc) => ({ const tests = visibleTestCases.map((tc) => ({
@@ -155,8 +374,7 @@ json.dumps(__results__)
const testResults: TestResult[] = results.map((r, idx) => { const testResults: TestResult[] = results.map((r, idx) => {
const testCase = visibleTestCases[idx]; const testCase = visibleTestCases[idx];
const passed = const passed =
!r.error && !r.error && compareOutputs(r.output, testCase.expected);
JSON.stringify(r.output) === JSON.stringify(testCase.expected);
return { return {
test_id: r.test_id, test_id: r.test_id,
@@ -170,16 +388,20 @@ json.dumps(__results__)
setVisibleResults(testResults); setVisibleResults(testResults);
setHasRunTests(true); setHasRunTests(true);
setRightTab("results");
// Run complexity analysis in the background
const complexity = await analyzeComplexity(code);
setComplexityResult(complexity);
} finally { } finally {
setIsRunning(false); setIsRunning(false);
} }
}, [pyodide, isRunning, visibleTestCases, runTests]); }, [pyodide, isRunning, visibleTestCases, runTests, analyzeComplexity, code]);
const handleSubmit = useCallback(async () => { const handleSubmit = useCallback(async () => {
if (!pyodide || isSubmitting) return; if (!pyodide || isSubmitting) return;
setIsSubmitting(true); setIsSubmitting(true);
setComplexityResult(null);
try { try {
// Run visible tests first // Run visible tests first
@@ -193,8 +415,7 @@ json.dumps(__results__)
(r, idx) => { (r, idx) => {
const testCase = visibleTestCases[idx]; const testCase = visibleTestCases[idx];
const passed = const passed =
!r.error && !r.error && compareOutputs(r.output, testCase.expected);
JSON.stringify(r.output) === JSON.stringify(testCase.expected);
return { return {
test_id: r.test_id, test_id: r.test_id,
@@ -232,6 +453,20 @@ json.dumps(__results__)
setHiddenPassedCount(response.total_passed); setHiddenPassedCount(response.total_passed);
setHiddenTotalCount(response.total_tests); setHiddenTotalCount(response.total_tests);
setHasRunTests(true); setHasRunTests(true);
// Run complexity analysis in the background
const complexity = await analyzeComplexity(code);
setComplexityResult(complexity);
// Check if all tests passed
const allVisiblePassed = visibleTestResults.every((r) => r.passed);
const allHiddenPassed = response.total_passed === response.total_tests;
if (allVisiblePassed && allHiddenPassed) {
markQuestionCompleted(question.slug, code);
setIsCompleted(true);
}
// Auto-switch to Results tab after submit
setRightTab("results"); setRightTab("results");
} catch (error) { } catch (error) {
console.error("Submission error:", error); console.error("Submission error:", error);
@@ -246,6 +481,7 @@ json.dumps(__results__)
runTests, runTests,
question.slug, question.slug,
code, code,
analyzeComplexity,
]); ]);
const isLoading = isRunning || isSubmitting; const isLoading = isRunning || isSubmitting;
@@ -253,7 +489,12 @@ json.dumps(__results__)
return ( return (
<div className="fixed inset-0 top-[65px] flex bg-[var(--background)]"> <div className="fixed inset-0 top-[65px] flex bg-[var(--background)]">
{/* Left panel - Problem description / Explanation */} {/* Left panel - Problem description / Explanation */}
<div className="w-1/2 border-r border-[var(--border)] flex flex-col"> {viewMode !== "right" && (
<div
className={`${
viewMode === "left" ? "w-full" : "w-1/2"
} border-r border-[var(--border)] flex flex-col`}
>
{/* Header - compact */} {/* Header - compact */}
<div className="px-4 pt-3 pb-0"> <div className="px-4 pt-3 pb-0">
<div className="flex items-center justify-between gap-2 mb-2"> <div className="flex items-center justify-between gap-2 mb-2">
@@ -268,7 +509,14 @@ json.dumps(__results__)
<Badge variant={getDifficultyVariant(question.difficulty)} className="text-xs"> <Badge variant={getDifficultyVariant(question.difficulty)} className="text-xs">
{capitalize(question.difficulty)} {capitalize(question.difficulty)}
</Badge> </Badge>
{isCompleted && (
<span className="flex items-center gap-1 text-xs text-green-500">
<CheckCircle className="h-3.5 w-3.5" />
Completed
</span>
)}
</div> </div>
<div className="flex items-center gap-2">
{question.leetcode_url && ( {question.leetcode_url && (
<a <a
href={question.leetcode_url} href={question.leetcode_url}
@@ -279,6 +527,18 @@ json.dumps(__results__)
LeetCode LeetCode
</a> </a>
)} )}
<button
onClick={() => toggleViewMode("left")}
className="p-1.5 rounded hover:bg-[var(--accent)] text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
title={viewMode === "left" ? "Split view" : "Maximize problem"}
>
{viewMode === "left" ? (
<Columns2 className="h-4 w-4" />
) : (
<PanelRightClose className="h-4 w-4" />
)}
</button>
</div>
</div> </div>
<div className="flex flex-wrap gap-1.5 mb-2"> <div className="flex flex-wrap gap-1.5 mb-2">
@@ -320,12 +580,22 @@ json.dumps(__results__)
> >
Explanation Explanation
</button> </button>
<button
onClick={() => setLeftTab("solutions")}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
leftTab === "solutions"
? "border-[var(--primary)] text-[var(--foreground)]"
: "border-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
}`}
>
Solutions
</button>
</div> </div>
</div> </div>
{/* Tab content - scrollable */} {/* Tab content - scrollable */}
<div className="flex-1 overflow-auto px-4 py-3"> <div className="flex-1 overflow-auto px-4 py-3">
{leftTab === "problem" ? ( {leftTab === "problem" && (
<div className="space-y-6"> <div className="space-y-6">
{/* Problem */} {/* Problem */}
<div> <div>
@@ -381,7 +651,9 @@ json.dumps(__results__)
</div> </div>
)} )}
</div> </div>
) : ( )}
{leftTab === "explanation" && (
<div className="space-y-6"> <div className="space-y-6">
{/* Intuition */} {/* Intuition */}
{question.explanation?.intuition && ( {question.explanation?.intuition && (
@@ -474,14 +746,12 @@ json.dumps(__results__)
</div> </div>
</div> </div>
)} )}
</div>
)}
{/* Solutions */} {leftTab === "solutions" && (
{question.solutions && question.solutions.length > 0 && ( <div className="space-y-6">
<div> {question.solutions && question.solutions.length > 0 ? (
<h2 className="flex items-center gap-2 text-lg font-semibold mb-3">
<Code className="h-5 w-5" />
Solutions
</h2>
<div className="space-y-4"> <div className="space-y-4">
{question.solutions.map((solution, i) => { {question.solutions.map((solution, i) => {
const content = ( const content = (
@@ -515,17 +785,36 @@ json.dumps(__results__)
); );
})} })}
</div> </div>
</div> ) : (
<p className="text-[var(--muted-foreground)]">
No solutions available for this problem yet.
</p>
)} )}
</div> </div>
)} )}
</div> </div>
</div> </div>
)}
{/* Right panel - Editor and test results */} {/* Right panel - Editor and test results */}
<div className="w-1/2 flex flex-col"> {viewMode !== "left" && (
<div
className={`${viewMode === "right" ? "w-full" : "w-1/2"} flex flex-col`}
>
{/* Tabs header */} {/* Tabs header */}
<div className="px-4 pt-2 border-b border-[var(--border)] flex items-center justify-between"> <div className="px-4 pt-2 border-b border-[var(--border)] flex items-center justify-between">
<div className="flex items-center gap-2">
<button
onClick={() => toggleViewMode("right")}
className="p-1.5 rounded hover:bg-[var(--accent)] text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
title={viewMode === "right" ? "Split view" : "Maximize editor"}
>
{viewMode === "right" ? (
<Columns2 className="h-4 w-4" />
) : (
<PanelLeftClose className="h-4 w-4" />
)}
</button>
<div className="flex gap-1"> <div className="flex gap-1">
<button <button
onClick={() => setRightTab("code")} onClick={() => setRightTab("code")}
@@ -538,7 +827,14 @@ json.dumps(__results__)
<Code className="h-4 w-4" /> <Code className="h-4 w-4" />
Code Code
</button> </button>
{hasRunTests && ( {hasRunTests && (() => {
const visiblePassed = visibleResults.filter(r => r.passed).length;
const visibleTotal = visibleResults.length;
const allVisiblePassed = visibleTotal > 0 && visiblePassed === visibleTotal;
const allHiddenPassed = hiddenTotalCount === 0 || hiddenPassedCount === hiddenTotalCount;
const allPassed = allVisiblePassed && allHiddenPassed;
return (
<button <button
onClick={() => setRightTab("results")} onClick={() => setRightTab("results")}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
@@ -547,10 +843,16 @@ json.dumps(__results__)
: "border-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)]" : "border-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
}`} }`}
> >
<FileText className="h-4 w-4" /> {allPassed ? (
<CheckCircle className="h-4 w-4 text-green-500" />
) : (
<XCircle className="h-4 w-4 text-red-500" />
)}
Results Results
</button> </button>
)} );
})()}
</div>
</div> </div>
<button <button
onClick={handleReset} onClick={handleReset}
@@ -564,11 +866,130 @@ json.dumps(__results__)
<div className="flex-1 min-h-0 flex flex-col"> <div className="flex-1 min-h-0 flex flex-col">
{rightTab === "code" ? ( {rightTab === "code" ? (
<> <>
{/* Editor - full height */} {/* Editor */}
<div className="flex-1 min-h-0"> <div className="flex-1 min-h-0">
<CodeEditor value={code} onChange={setCode} /> <CodeEditor value={code} onChange={setCode} />
</div> </div>
{/* Complexity Analysis - shown after running tests */}
{complexityResult?.success && (
<div className="border-t border-[var(--border)] bg-[var(--secondary)]/50 px-4 py-2">
<div className="flex items-center gap-2 text-sm">
<Gauge className="h-4 w-4 text-[var(--primary)]" />
<span className="font-medium">
Estimated: {complexityResult.complexity}
</span>
{(complexityResult.details?.findings?.length ||
complexityResult.details?.recursive_calls?.length) && (
<button
onClick={() => setIsComplexityExpanded(!isComplexityExpanded)}
className="flex items-center gap-1 text-xs text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors ml-2"
>
<ChevronRight
className={`h-3 w-3 transition-transform ${
isComplexityExpanded ? "rotate-90" : ""
}`}
/>
Details
</button>
)}
</div>
{isComplexityExpanded && complexityResult.details && (
<div className="mt-2 space-y-2 text-xs">
{/* Loop findings */}
{complexityResult.details.findings
?.filter((f) => f.type === "loop")
.map((finding, i) => (
<div key={`loop-${i}`} className="space-y-0.5">
<div className="text-[var(--muted-foreground)]">
{finding.kind === "for" ? (
<>
Loop over {finding.iterates_over || "iterable"}
{finding.depth && finding.depth > 1
? ` (nested, depth ${finding.depth})`
: ""}{" "}
O(n)
</>
) : (
<>
While loop
{finding.depth && finding.depth > 1
? ` (nested, depth ${finding.depth})`
: ""}
</>
)}
</div>
<div className="ml-3 flex items-center gap-2">
<span className="text-[var(--muted-foreground)]/60 text-[10px]">
L{finding.line}
</span>
<code className="bg-[var(--secondary)] px-1 rounded text-[var(--foreground)]/80">
{finding.snippet}
</code>
</div>
</div>
))}
{/* Sort operations */}
{complexityResult.details.findings
?.filter((f) => f.kind === "sort")
.map((finding, i) => (
<div key={`sort-${i}`} className="space-y-0.5">
<div className="text-[var(--muted-foreground)]">
Sorting operation O(n log n)
</div>
<div className="ml-3 flex items-center gap-2">
<span className="text-[var(--muted-foreground)]/60 text-[10px]">
L{finding.line}
</span>
<code className="bg-[var(--secondary)] px-1 rounded text-[var(--foreground)]/80">
{finding.snippet}
</code>
</div>
</div>
))}
{/* Linear search warnings */}
{complexityResult.details.findings
?.filter((f) => f.kind === "linear_search")
.map((finding, i) => (
<div key={`search-${i}`} className="space-y-0.5">
<div className="text-yellow-500">
{finding.note || "Linear search in loop"}
</div>
<div className="ml-3 flex items-center gap-2">
<span className="text-[var(--muted-foreground)]/60 text-[10px]">
L{finding.line}
</span>
<code className="bg-[var(--secondary)] px-1 rounded text-[var(--foreground)]/80">
{finding.snippet}
</code>
</div>
</div>
))}
{/* Recursive calls */}
{complexityResult.details.recursive_calls?.map((call, i) => (
<div key={`rec-${i}`} className="space-y-0.5">
<div className="text-[var(--muted-foreground)]">
Recursive call
</div>
<div className="ml-3 flex items-center gap-2">
<span className="text-[var(--muted-foreground)]/60 text-[10px]">
L{call.line}
</span>
<code className="bg-[var(--secondary)] px-1 rounded text-[var(--foreground)]/80">
{call.snippet}
</code>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Action buttons */} {/* Action buttons */}
<div className="p-3 border-t border-[var(--border)] flex items-center justify-between bg-[var(--secondary)]"> <div className="p-3 border-t border-[var(--border)] flex items-center justify-between bg-[var(--secondary)]">
<div className="text-sm text-[var(--muted-foreground)]"> <div className="text-sm text-[var(--muted-foreground)]">
@@ -611,17 +1032,59 @@ json.dumps(__results__)
</> </>
) : ( ) : (
/* Results tab */ /* Results tab */
<div className="flex-1 overflow-auto p-4"> <div className="flex-1 overflow-auto p-4 flex flex-col">
<TestResults <TestResults
visibleResults={visibleResults} visibleResults={visibleResults}
hiddenPassedCount={hiddenPassedCount} hiddenPassedCount={hiddenPassedCount}
hiddenTotalCount={hiddenTotalCount} hiddenTotalCount={hiddenTotalCount}
visibleTestCases={visibleTestCases} visibleTestCases={visibleTestCases}
/> />
{/* Complexity Analysis in Results tab */}
{complexityResult?.success && (
<div className="mt-4 p-3 rounded bg-[var(--secondary)]/50 border border-[var(--border)]">
<div className="flex items-center gap-2 text-sm">
<Gauge className="h-4 w-4 text-[var(--primary)]" />
<span className="font-medium">
Estimated Complexity: {complexityResult.complexity}
</span>
</div>
</div>
)}
{/* Success state with navigation */}
{(() => {
const visiblePassed = visibleResults.filter((r) => r.passed).length;
const visibleTotal = visibleResults.length;
const allVisiblePassed = visibleTotal > 0 && visiblePassed === visibleTotal;
const allHiddenPassed =
hiddenTotalCount === 0 || hiddenPassedCount === hiddenTotalCount;
const allPassed = allVisiblePassed && allHiddenPassed && hiddenTotalCount > 0;
if (!allPassed) return null;
return (
<div className="mt-6 p-4 rounded-lg bg-green-500/10 border border-green-500/30">
<div className="flex items-center gap-2 text-green-500 font-semibold mb-3">
<CheckCircle className="h-5 w-5" />
All tests passed!
</div>
<div className="flex items-center gap-3">
<Link
href="/questions"
className="px-4 py-2 text-sm font-medium rounded bg-[var(--secondary)] border border-[var(--border)] hover:bg-[var(--accent)] transition-colors"
>
Back to Questions
</Link>
</div>
</div>
);
})()}
</div> </div>
)} )}
</div> </div>
</div> </div>
)}
</div> </div>
); );
} }