diff --git a/frontend/src/components/MacroNodeCard.tsx b/frontend/src/components/MacroNodeCard.tsx index 0707885..5e198b2 100644 --- a/frontend/src/components/MacroNodeCard.tsx +++ b/frontend/src/components/MacroNodeCard.tsx @@ -96,6 +96,11 @@ export function MacroNodeCard({ // Warning state (e.g. dependency not met) const [warning, setWarning] = useState(null); + // Inline description expand/collapse (independent of card expand) + const [descExpanded, setDescExpanded] = useState(false); + const [descOverflows, setDescOverflows] = useState(false); + const descRef = useRef(null); + // Mobile overflow menu state const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const mobileMenuRef = useRef(null); @@ -124,6 +129,13 @@ export function MacroNodeCard({ return () => document.removeEventListener("mousedown", handler); }, [mobileMenuOpen]); + // Detect whether description text overflows 2 lines + useEffect(() => { + const el = descRef.current; + if (!el) { setDescOverflows(false); return; } + setDescOverflows(el.scrollHeight > el.clientHeight); + }, [node.description, expanded, descExpanded]); + const handleRun = async (e: React.MouseEvent) => { e.stopPropagation(); if (!blueprintId || running) return; @@ -274,7 +286,7 @@ export function MacroNodeCard({ {/* Card */}
!isEditing && setExpanded(!expanded)} + onClick={() => { if (!isEditing) { setExpanded(!expanded); setDescExpanded(false); } }} > {/* Collapsed header */}
@@ -315,9 +327,33 @@ export function MacroNodeCard({
{!expanded && !isEditing && node.description && ( -

- {stripMarkdown(node.description)} -

+
+ {descExpanded ? ( +
e.stopPropagation()}> + + +
+ ) : ( + <> +

+ {stripMarkdown(node.description)} +

+ {descOverflows && ( + + )} + + )} +
)}
@@ -338,7 +374,7 @@ export function MacroNodeCard({ +
+ ) : ( + <> +

+ {stripMarkdown(node.description)} +

+ {descOverflows && ( + + )} + + )} + )} {node.prompt && ( @@ -646,7 +712,7 @@ export function MacroNodeCard({ }} disabled={resumingExecId === exec.id || running} className="inline-flex items-center gap-1 px-2 py-1 rounded-lg bg-green-600/15 text-green-500 hover:bg-green-600/25 transition-colors text-xs font-medium disabled:opacity-50 disabled:cursor-not-allowed" - title={resumingExecId === exec.id ? "AI is resuming the failed session..." : "Resume this failed session — AI continues with full context from the previous attempt"} + title={resumingExecId === exec.id ? "AI is resuming the failed session..." : running ? "Cannot resume while node is running" : "Resume this failed session — AI continues with full context from the previous attempt"} aria-label={resumingExecId === exec.id ? "Resuming session" : "Resume failed session"} > {resumingExecId === exec.id ? (