Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions webview/src/components/MetaschemaStackTrace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useState } from "react";
import type { MetaschemaError,Position } from "../../../protocol/cli";
import { goToPosition } from "../message";

interface Props {
errors: MetaschemaError[];
}

function ErrorBox({
error,
size,
indent
}: {
error: MetaschemaError;
size?: 'primary' | 'stack';
indent: number;
}) {
const clickable = Boolean(error.instancePosition);

return (
<div
className={`
bg-(--vscode-selection)
border-l-[3px]
rounded
p-3
transition-colors
w-full
max-w-full
${clickable ? "cursor-pointer hover:bg-(--vscode-hover)" : ""}
`}
style={{
marginLeft: indent,
borderLeftColor: "var(--error)"
}}
onClick={() =>
clickable && goToPosition(error.instancePosition as Position)
}
>
<div className="flex items-start gap-2">
{size === 'stack' && (
<span className="text-(--vscode-muted) font-mono select-none">↳</span>
)}
<div className="flex-1">
<div className={`text-(--vscode-fg) ${size === 'primary' ? 'text-[14px] font-semibold' : 'text-[12px]'}`}>
{error.error}
</div>
{error.instanceLocation && (
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The (root) fallback is unreachable because the block is only rendered when instanceLocation is truthy. If instanceLocation is empty/undefined, no location is shown. Render the block unconditionally (or explicitly check for null) so the fallback is displayed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webview/src/components/MetaschemaStackTrace.tsx, line 48:

<comment>The `(root)` fallback is unreachable because the block is only rendered when `instanceLocation` is truthy. If `instanceLocation` is empty/undefined, no location is shown. Render the block unconditionally (or explicitly check for null) so the fallback is displayed.</comment>

<file context>
@@ -0,0 +1,124 @@
+          <div className={`text-(--vscode-fg) ${size === 'primary' ? 'text-[14px] font-semibold' : 'text-[12px]'}`}>
+            {error.error}
+          </div>
+          {error.instanceLocation && (
+            <div className="mt-1 text-[11px] text-(--vscode-muted) break-all">
+              {error.instanceLocation || "(root)"}
</file context>
Fix with Cubic

<div className="mt-1 text-[11px] text-(--vscode-muted) break-all">
{error.instanceLocation || "(root)"}
</div>
)}
</div>
</div>
</div>
);
}


export function MetaschemaStackTrace({errors}:Props){
const [expanded,setExpanded]= useState(false);

if(errors.length===0) return null;

const [primary, ...stack] = errors;

if (!primary) return null;

return (
<div className="flex flex-col gap-3">
{/* Top level error */}
<ErrorBox
error={primary}
size="primary"
indent={0}
/>

{stack.length > 0 && (
<button
className="flex items-center gap-1.5 cursor-pointer py-2 select-none hover:opacity-80"
onClick={() => setExpanded(!expanded)}
>
<span
className="text-(--vscode-muted) text-[10px] transition-transform duration-200"
style={{ transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)' }}
>
</span>
{expanded ? 'Hide stack trace' : 'Show stack trace'}
</button>
)}

{/* Stack trace */}
<div className="w-full">
<div className={`overflow-hidden transition-all duration-300 ease-in-out ${expanded ? "max-h-[2000px] opacity-100" : "max-h-0 opacity-0"}`}>
<div className="flex flex-col gap-2 mt-2">
{
stack.map((err,ind)=>{
const STACK_INDENT = 16;
return (
<div
key={ind}
className="flex items-start gap-2"
style={{ paddingLeft: STACK_INDENT }}
>
<span className="text-(--vscode-muted) font-mono select-none mt-1">↳</span>
<ErrorBox
error={err}
indent={0}
/>
</div>
)
})
}
</div>
</div>
</div>
</div>
)
}


57 changes: 5 additions & 52 deletions webview/src/components/MetaschemaTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MetaschemaResult, MetaschemaError, Position } from '../../../proto
import { goToPosition } from '../message';
import { RawOutput } from './RawOutput';
import { CheckCircle, AlertTriangle, FileQuestion } from 'lucide-react';
import { MetaschemaStackTrace } from './MetaschemaStackTrace';

export interface MetaschemaTabProps {
metaschemaResult: MetaschemaResult;
Expand Down Expand Up @@ -55,55 +56,7 @@ export function MetaschemaTab({ metaschemaResult, noFileSelected }: MetaschemaTa
return (
<div>
<div className="flex flex-col gap-3 mb-5">
{metaschemaErrors.map((error, index) => (
<div
key={index}
className="bg-(--vscode-selection) border-l-[3px] rounded p-3 cursor-pointer transition-colors hover:bg-(--vscode-hover)"
style={{
cursor: error.instancePosition ? 'pointer' : 'default',
borderLeftColor: 'var(--error)'
}}
onClick={() => error.instancePosition && handleGoToPosition(error.instancePosition)}
>
<div className="mb-2">
<div className="text-(--vscode-fg) text-[13px] font-semibold">
{error.error}
</div>
</div>
<div className="flex flex-col gap-1 text-[11px]">
{error.instancePosition && (
<div className="flex gap-1.5">
<span className="text-(--vscode-muted) font-semibold min-w-20">
Location:
</span>
<span className="text-(--vscode-fg) font-(--vscode-editor-font)">
Line {error.instancePosition[0]}, Col {error.instancePosition[1]}
</span>
</div>
)}
{error.instanceLocation && (
<div className="flex gap-1.5">
<span className="text-(--vscode-muted) font-semibold min-w-20">
Path:
</span>
<span className="text-(--vscode-fg) font-(--vscode-editor-font) break-all">
{error.instanceLocation || '(root)'}
</span>
</div>
)}
{error.keywordLocation && (
<div className="flex gap-1.5">
<span className="text-(--vscode-muted) font-semibold min-w-20">
Schema:
</span>
<span className="text-(--vscode-fg) font-(--vscode-editor-font) break-all">
{error.keywordLocation}
</span>
</div>
)}
</div>
</div>
))}
<MetaschemaStackTrace errors={metaschemaErrors}/>
</div>
<RawOutput output={metaschemaResult.output} />
</div>
Expand All @@ -128,12 +81,12 @@ export function MetaschemaTab({ metaschemaResult, noFileSelected }: MetaschemaTa
error && 'instancePosition' in error && error.instancePosition
? error.instancePosition
: null;

return (
<>
<div
<div
className="bg-(--vscode-selection) border-l-[3px] rounded p-4 mb-5 transition-colors"
style={{
style={{
borderLeftColor: 'var(--fatal)',
cursor: errorPosition ? 'pointer' : 'default'
}}
Expand Down
18 changes: 18 additions & 0 deletions webview/src/utils/splitIntoRows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function splitIntoRows<T>(
items: T[],
containerWidth: number,
itemWidth: number,
gap: number
): T[][] {
const itemsPerRow = Math.max(
1,
Math.floor((containerWidth + gap) / (itemWidth + gap))
);

const rows: T[][] = [];
for (let i = 0; i < items.length; i += itemsPerRow) {
rows.push(items.slice(i, i + itemsPerRow));
}
return rows;
}