@@ -47,8 +47,14 @@ import { fetchCodeCompletions, type CompletionContextProvider } from "@/lib/code
4747import { isExecutableBlock , type CellAiAction } from "@/lib/cellModel" ;
4848import type { CellAiHelpState } from "@/lib/assistantTypes" ;
4949import { statusLabel } from "@/lib/displayFormat" ;
50+ import {
51+ blockInCycle ,
52+ cellDiagnosticChips ,
53+ formatCyclePaths ,
54+ type CellDiagnosticChip ,
55+ } from "@/lib/reactiveDiagnostics" ;
5056import { cn } from "@/lib/utils" ;
51- import type { BlockConfig , CodaroDocument , ExecutionResult } from "@/types" ;
57+ import type { BlockConfig , CodaroDocument , ExecutionResult , ReactiveDiagnostics } from "@/types" ;
5258
5359type ResultMap = Record < string , ExecutionResult > ;
5460
@@ -114,13 +120,15 @@ const codeCellEditorTheme = EditorView.theme({
114120export function NotebookPanel ( {
115121 canRun,
116122 cellHelpByBlockId,
123+ diagnostics,
117124 document,
118125 drafts,
119126 notebookRunning,
120127 pendingBlocks,
121128 results,
122129 runningBlockId,
123130 selectedBlockId,
131+ staleBlockIds,
124132 onAddCell,
125133 onAcceptPendingBlocks,
126134 onCellAsk,
@@ -134,13 +142,15 @@ export function NotebookPanel({
134142} : {
135143 canRun : boolean ;
136144 cellHelpByBlockId : Record < string , CellAiHelpState > ;
145+ diagnostics : ReactiveDiagnostics ;
137146 document : CodaroDocument ;
138147 drafts : Record < string , string > ;
139148 notebookRunning : boolean ;
140149 pendingBlocks : BlockConfig [ ] ;
141150 results : ResultMap ;
142151 runningBlockId : string | null ;
143152 selectedBlockId : string ;
153+ staleBlockIds : string [ ] ;
144154 onAddCell : ( type : "code" | "markdown" , referenceBlockId ?: string , placement ?: "before" | "after" ) => void ;
145155 onAcceptPendingBlocks : ( ) => void ;
146156 onCellAsk : ( action : CellAiAction , block : BlockConfig , question ?: string ) => void ;
@@ -152,6 +162,8 @@ export function NotebookPanel({
152162 onRunNotebook : ( ) => void ;
153163 onSelectBlock : ( blockId : string ) => void ;
154164} ) {
165+ const staleSet = new Set ( staleBlockIds ) ;
166+ const cyclePaths = formatCyclePaths ( diagnostics . cycles ) ;
155167 return (
156168 < section className = "grid h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] p-2 sm:p-3" >
157169 < div className = "mb-2 flex min-h-8 items-center gap-2 pl-9" >
@@ -180,6 +192,11 @@ export function NotebookPanel({
180192 onAccept = { onAcceptPendingBlocks }
181193 onReject = { onRejectPendingBlocks }
182194 />
195+ { cyclePaths . length ? (
196+ < div className = "rounded-md border border-destructive/40 bg-destructive/10 px-2.5 py-1.5 text-[11px] text-destructive" >
197+ < span className = "font-medium" > 순환 의존</ span > — 실행 순서가 정해지지 않습니다: { cyclePaths . join ( " · " ) }
198+ </ div >
199+ ) : null }
183200 </ div >
184201
185202 < ScrollArea className = "h-full min-h-0" >
@@ -194,6 +211,9 @@ export function NotebookPanel({
194211 result = { results [ block . id ] }
195212 cellHelp = { cellHelpByBlockId [ block . id ] }
196213 isRunning = { runningBlockId === block . id }
214+ isStale = { staleSet . has ( block . id ) }
215+ inCycle = { blockInCycle ( diagnostics , block . id ) }
216+ diagnosticChips = { cellDiagnosticChips ( diagnostics , block . id ) }
197217 onCellAsk = { ( action , question ) => onCellAsk ( action , block , question ) }
198218 onDelete = { ( ) => onDeleteCell ( block . id ) }
199219 onDraftChange = { ( value ) => onDraftChange ( block . id , value ) }
@@ -548,6 +568,9 @@ function DocumentBlock({
548568 draft,
549569 isSelected,
550570 isRunning,
571+ isStale = false ,
572+ inCycle = false ,
573+ diagnosticChips = [ ] ,
551574 result,
552575 cellHelp,
553576 onDraftChange,
@@ -562,6 +585,9 @@ function DocumentBlock({
562585 draft : string ;
563586 isSelected : boolean ;
564587 isRunning : boolean ;
588+ isStale ?: boolean ;
589+ inCycle ?: boolean ;
590+ diagnosticChips ?: CellDiagnosticChip [ ] ;
565591 result ?: ExecutionResult ;
566592 cellHelp ?: CellAiHelpState ;
567593 onCellAsk : ( action : CellAiAction , question ?: string ) => void ;
@@ -572,7 +598,8 @@ function DocumentBlock({
572598 onSelect : ( ) => void ;
573599} ) {
574600 const cellTitle = block . type === "markdown" ? "Markdown" : block . type === "automation" ? "Automation" : "Python" ;
575- const resultStatus = isRunning ? "running" : result ?. status ?? "idle" ;
601+ // 우선순위: 실행 중 → 순환(conflict, 빨강) → stale(오래됨) → 실행 결과 → 대기.
602+ const resultStatus = isRunning ? "running" : inCycle ? "conflict" : isStale ? "stale" : result ?. status ?? "idle" ;
576603
577604 if ( block . type === "markdown" ) {
578605 return (
@@ -583,6 +610,7 @@ function DocumentBlock({
583610 type = "markdown"
584611 selected = { isSelected }
585612 cellHelp = { cellHelp }
613+ diagnosticChips = { diagnosticChips }
586614 onCellAsk = { onCellAsk }
587615 onDelete = { onDelete }
588616 />
@@ -614,6 +642,7 @@ function DocumentBlock({
614642 type = "code"
615643 selected = { isSelected }
616644 cellHelp = { cellHelp }
645+ diagnosticChips = { diagnosticChips }
617646 onCellAsk = { onCellAsk }
618647 onDelete = { onDelete }
619648 onRun = { onRun }
@@ -664,6 +693,7 @@ function CellMetaBar({
664693 type,
665694 selected,
666695 cellHelp,
696+ diagnosticChips = [ ] ,
667697 onCellAsk,
668698 onDelete,
669699 onRun,
@@ -675,6 +705,7 @@ function CellMetaBar({
675705 type : "code" | "markdown" ;
676706 selected : boolean ;
677707 cellHelp ?: CellAiHelpState ;
708+ diagnosticChips ?: CellDiagnosticChip [ ] ;
678709 onCellAsk : ( action : CellAiAction , question ?: string ) => void ;
679710 onDelete : ( ) => void ;
680711 onRun ?: ( ) => void ;
@@ -693,11 +724,24 @@ function CellMetaBar({
693724 < span className = "truncate" > { title } </ span >
694725 </ div >
695726 < div className = "ml-auto flex shrink-0 items-center gap-1" >
727+ { diagnosticChips . map ( ( chip ) => (
728+ < span
729+ key = { chip . kind }
730+ className = "h-6 rounded-md border border-amber-500/40 bg-amber-500/10 px-1.5 py-0.5 text-[11px] font-medium leading-5 text-amber-700 dark:text-amber-400"
731+ title = "정합성 경고 — 실행은 진행되며, 마지막 정의가 적용됩니다."
732+ >
733+ { chip . label }
734+ </ span >
735+ ) ) }
696736 { status !== "idle" ? (
697737 < span
698738 className = { cn (
699739 "h-6 rounded-md px-1.5 py-0.5 text-[11px] font-medium leading-5" ,
700- status === "error" ? "bg-destructive text-destructive-foreground" : "border bg-background text-muted-foreground" ,
740+ status === "error" || status === "conflict"
741+ ? "bg-destructive text-destructive-foreground"
742+ : status === "stale"
743+ ? "border border-dashed bg-background text-muted-foreground"
744+ : "border bg-background text-muted-foreground" ,
701745 ) }
702746 >
703747 { statusLabel ( status ) }
0 commit comments