({
filePath,
workspacePath,
fileName,
jumpToLine: _jumpToLine,
jumpToColumn: _jumpToColumn,
})
| 52 | } |
| 53 | |
| 54 | const PlanViewer: React.FC<PlanViewerProps> = ({ |
| 55 | filePath, |
| 56 | workspacePath, |
| 57 | fileName, |
| 58 | jumpToLine: _jumpToLine, |
| 59 | jumpToColumn: _jumpToColumn, |
| 60 | }) => { |
| 61 | const { t } = useI18n('tools'); |
| 62 | const { isLight } = useTheme(); |
| 63 | const mEditorTheme = isLight ? 'light' : 'dark'; |
| 64 | const [loading, setLoading] = useState(true); |
| 65 | const [error, setError] = useState<string | null>(null); |
| 66 | const [planData, setPlanData] = useState<PlanData | null>(null); |
| 67 | const [planContent, setPlanContent] = useState<string>(''); |
| 68 | // Initialize build state from the shared service to survive unmounts. |
| 69 | const [isBuildStarted, setIsBuildStarted] = useState(() => { |
| 70 | return filePath ? planBuildStateService.isBuildActive(filePath) : false; |
| 71 | }); |
| 72 | const [isContentDirty, setIsContentDirty] = useState(false); |
| 73 | // Edit mode: display raw yaml frontmatter |
| 74 | const [yamlEditorPlacement, setYamlEditorPlacement] = useState<YamlEditorPlacement>('none'); |
| 75 | const [yamlContent, setYamlContent] = useState<string>(''); |
| 76 | const [originalYamlContent, setOriginalYamlContent] = useState<string>(''); |
| 77 | // Todos list expand/collapse state (collapsed by default) |
| 78 | const [isTodosExpanded, setIsTodosExpanded] = useState(false); |
| 79 | const [isInlineTodoEditing, setIsInlineTodoEditing] = useState(false); |
| 80 | const [inlineTodoDrafts, setInlineTodoDrafts] = useState<Record<string, string>>({}); |
| 81 | const [inlineDeletedTodoKeys, setInlineDeletedTodoKeys] = useState<string[]>([]); |
| 82 | const [inlineAddedTodos, setInlineAddedTodos] = useState<PlanTodo[]>([]); |
| 83 | const [isTrailingTodoEditing, setIsTrailingTodoEditing] = useState(false); |
| 84 | const [trailingTodoDrafts, setTrailingTodoDrafts] = useState<Record<string, string>>({}); |
| 85 | const [trailingDeletedTodoKeys, setTrailingDeletedTodoKeys] = useState<string[]>([]); |
| 86 | const [trailingAddedTodos, setTrailingAddedTodos] = useState<PlanTodo[]>([]); |
| 87 | |
| 88 | const isEditingYaml = yamlEditorPlacement !== 'none'; |
| 89 | |
| 90 | const editorRef = useRef<EditorInstance>(null); |
| 91 | const yamlEditorRef = useRef<EditorInstance>(null); |
| 92 | const isUnmountedRef = useRef(false); |
| 93 | |
| 94 | const basePath = useMemo(() => { |
| 95 | if (!filePath) return undefined; |
| 96 | const normalizedPath = filePath.replace(/\\/g, '/'); |
| 97 | const lastSlashIndex = normalizedPath.lastIndexOf('/'); |
| 98 | if (lastSlashIndex >= 0) { |
| 99 | return normalizedPath.substring(0, lastSlashIndex); |
| 100 | } |
| 101 | return undefined; |
| 102 | }, [filePath]); |
| 103 | |
| 104 | const displayFileName = useMemo(() => { |
| 105 | if (fileName) return fileName; |
| 106 | return basenamePath(filePath); |
| 107 | }, [filePath, fileName]); |
| 108 | |
| 109 | const hasTodos = !!(planData?.todos && planData.todos.length > 0); |
| 110 | |
| 111 | useEffect(() => { |
nothing calls this directly
no test coverage detected