From 3c9b29b055ec94e2ec04b93684f9274486e8df08 Mon Sep 17 00:00:00 2001 From: ccchow Date: Thu, 26 Feb 2026 19:30:22 -0800 Subject: [PATCH 1/2] feat: add 2-line description preview with Show more/less toggle in MacroNodeCard Replace single-line truncation with line-clamp-2 preview and inline expand/collapse toggle in both collapsed and expanded card views. Uses useRef + useEffect overflow detection to conditionally show toggle. Expanded content renders full markdown via MarkdownContent. Also adds AgentBadge support, disabled button tooltips, and accessibility improvements. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/MacroNodeCard.tsx | 93 ++++++++++++++++++++--- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/MacroNodeCard.tsx b/frontend/src/components/MacroNodeCard.tsx index 0707885..dbd85aa 100644 --- a/frontend/src/components/MacroNodeCard.tsx +++ b/frontend/src/components/MacroNodeCard.tsx @@ -2,12 +2,13 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; -import { type MacroNode, type PendingTask, runNode, updateMacroNode, deleteMacroNode, enrichNode, reevaluateNode, resumeNodeSession, unqueueNode } from "@/lib/api"; +import { type MacroNode, type PendingTask, type AgentType, runNode, updateMacroNode, deleteMacroNode, enrichNode, reevaluateNode, resumeNodeSession, unqueueNode } from "@/lib/api"; import { StatusIndicator } from "./StatusIndicator"; import { MarkdownContent } from "./MarkdownContent"; import { MarkdownEditor } from "./MarkdownEditor"; import { AISparkle } from "./AISparkle"; import { type DepRowLayout, DepGutter } from "./DependencyGraph"; +import { AgentBadge } from "./AgentSelector"; /** Strip markdown syntax for plain-text preview */ function stripMarkdown(text: string): string { @@ -31,6 +32,7 @@ export function MacroNodeCard({ index, total, blueprintId, + blueprintAgentType, onRefresh, onNodeUpdated, onNodeDeleted, @@ -43,6 +45,7 @@ export function MacroNodeCard({ index: number; total: number; blueprintId?: string; + blueprintAgentType?: AgentType; onRefresh?: () => void; onNodeUpdated?: () => void; onNodeDeleted?: () => void; @@ -96,6 +99,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 +132,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 +289,7 @@ export function MacroNodeCard({ {/* Card */}
!isEditing && setExpanded(!expanded)} + onClick={() => { if (!isEditing) { setExpanded(!expanded); setDescExpanded(false); } }} > {/* Collapsed header */}
@@ -313,11 +328,39 @@ export function MacroNodeCard({ }`}> {running ? "running" : reevaluateQueued ? "re-eval" : node.status === "queued" ? (queuePosition > 0 ? `queued #${queuePosition}` : "queued") : node.status} + {/* Show agent badge if node has a different agent type than the blueprint default */} + {node.agentType && node.agentType !== (blueprintAgentType ?? "claude") && ( + + )}
{!expanded && !isEditing && node.description && ( -

- {stripMarkdown(node.description)} -

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

+ {stripMarkdown(node.description)} +

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

+ {stripMarkdown(node.description)} +

+ {descOverflows && ( + + )} + + )} + )} {node.prompt && ( @@ -646,7 +719,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 ? ( From 345bf9331b9a6d466f13ef48e84ad037554f290c Mon Sep 17 00:00:00 2001 From: ccchow Date: Thu, 26 Feb 2026 19:37:12 -0800 Subject: [PATCH 2/2] fix: remove uncommitted agent-type references from MacroNodeCard to fix CI typecheck AgentType, AgentBadge, and blueprintAgentType were referenced in the committed MacroNodeCard.tsx but their source files (AgentSelector.tsx, api.ts AgentType export) were not included in this feature branch, causing TypeScript compilation failures in CI. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/MacroNodeCard.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/frontend/src/components/MacroNodeCard.tsx b/frontend/src/components/MacroNodeCard.tsx index dbd85aa..5e198b2 100644 --- a/frontend/src/components/MacroNodeCard.tsx +++ b/frontend/src/components/MacroNodeCard.tsx @@ -2,13 +2,12 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; -import { type MacroNode, type PendingTask, type AgentType, runNode, updateMacroNode, deleteMacroNode, enrichNode, reevaluateNode, resumeNodeSession, unqueueNode } from "@/lib/api"; +import { type MacroNode, type PendingTask, runNode, updateMacroNode, deleteMacroNode, enrichNode, reevaluateNode, resumeNodeSession, unqueueNode } from "@/lib/api"; import { StatusIndicator } from "./StatusIndicator"; import { MarkdownContent } from "./MarkdownContent"; import { MarkdownEditor } from "./MarkdownEditor"; import { AISparkle } from "./AISparkle"; import { type DepRowLayout, DepGutter } from "./DependencyGraph"; -import { AgentBadge } from "./AgentSelector"; /** Strip markdown syntax for plain-text preview */ function stripMarkdown(text: string): string { @@ -32,7 +31,6 @@ export function MacroNodeCard({ index, total, blueprintId, - blueprintAgentType, onRefresh, onNodeUpdated, onNodeDeleted, @@ -45,7 +43,6 @@ export function MacroNodeCard({ index: number; total: number; blueprintId?: string; - blueprintAgentType?: AgentType; onRefresh?: () => void; onNodeUpdated?: () => void; onNodeDeleted?: () => void; @@ -328,10 +325,6 @@ export function MacroNodeCard({ }`}> {running ? "running" : reevaluateQueued ? "re-eval" : node.status === "queued" ? (queuePosition > 0 ? `queued #${queuePosition}` : "queued") : node.status} - {/* Show agent badge if node has a different agent type than the blueprint default */} - {node.agentType && node.agentType !== (blueprintAgentType ?? "claude") && ( - - )} {!expanded && !isEditing && node.description && (