From c1ee0b9acdb8ae5411687d4e2c7c35bab705f0b6 Mon Sep 17 00:00:00 2001 From: matei jordache Date: Tue, 31 Mar 2026 01:03:09 -0700 Subject: [PATCH] add journal --- backend/nodes/text_note.py | 2 +- frontend/src/App.jsx | 10 +++++++++- frontend/src/HelpPanelManager.jsx | 30 +++++++++++++++++++++++------- frontend/src/styles.css | 19 +++++++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/backend/nodes/text_note.py b/backend/nodes/text_note.py index 306e297..fa8501d 100644 --- a/backend/nodes/text_note.py +++ b/backend/nodes/text_note.py @@ -3,7 +3,7 @@ from __future__ import annotations from backend.node_registry import register_node -@register_node(display_name="Text Note") +@register_node(display_name="Journal") class TextNote: """A floating text card for annotating workflows. Supports Markdown.""" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 89f6580..0a96220 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -873,6 +873,7 @@ function Flow() { const canvasRightZoomRef = useRef(null); const suppressPaneContextMenuUntilRef = useRef(0); const loadNodeOutputRequestVersionsRef = useRef(new Map()); + const journalContentRef = useRef(''); const reactFlow = useReactFlow(); // ── WebSocket ─────────────────────────────────────────────────────── @@ -1959,12 +1960,13 @@ function Flow() { const openJournalTab = useCallback(() => { setHelpTabs((prev) => { if (prev.find((t) => t.label === 'Journal')) return prev; - return [...prev, { label: 'Journal', type: 'journal', content: '' }]; + return [...prev, { label: 'Journal', type: 'journal', content: journalContentRef.current }]; }); setActiveHelpTab('Journal'); }, []); const updateTabContent = useCallback((label, content) => { + if (label === 'Journal') journalContentRef.current = content; setHelpTabs((prev) => prev.map((t) => t.label === label ? { ...t, content } : t)); }, []); @@ -1993,6 +1995,10 @@ function Flow() { setNodes(sortNodesForParentOrder(hydrated.nodes)); setEdges(hydrated.edges); nextIdRef.current = hydrated.nextNodeId; + journalContentRef.current = data.journalContent || ''; + setHelpTabs((prev) => prev.map((t) => + t.label === 'Journal' ? { ...t, content: journalContentRef.current } : t, + )); initializeDynamicNodes(hydrated.nodes); }, [initializeDynamicNodes, setNodes, setEdges]); @@ -2095,6 +2101,7 @@ function Flow() { const stampedBlob = await stampLogoOnBlob(blob); const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges()); + if (journalContentRef.current) workflow.journalContent = journalContentRef.current; return embedWorkflow(stampedBlob, workflow); }, [reactFlow]); @@ -3003,6 +3010,7 @@ function Flow() { onTabSelect={setActiveHelpTab} onTabClose={closeHelpTab} onTabContentChange={updateTabContent} + onOpenJournal={openJournalTab} /> ); diff --git a/frontend/src/HelpPanelManager.jsx b/frontend/src/HelpPanelManager.jsx index 6f71930..07380cc 100644 --- a/frontend/src/HelpPanelManager.jsx +++ b/frontend/src/HelpPanelManager.jsx @@ -4,7 +4,11 @@ import { marked } from 'marked'; function JournalTab({ content, onChange }) { const [isEditing, setIsEditing] = useState(false); - const renderedHtml = content?.trim() ? marked.parse(content) : ''; + + let renderedHtml = ''; + if (!isEditing && content?.trim()) { + try { renderedHtml = marked.parse(content); } catch { /* fallback to raw */ renderedHtml = content; } + } return (
@@ -34,23 +38,26 @@ function JournalTab({ content, onChange }) { // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus /> - ) : ( + ) : renderedHtml ? (
setIsEditing(true)} // eslint-disable-next-line react/no-danger - dangerouslySetInnerHTML={renderedHtml ? { __html: renderedHtml } : undefined} + dangerouslySetInnerHTML={{ __html: renderedHtml }} + /> + ) : ( +
setIsEditing(true)} > - {!renderedHtml && ( - Double-click to write… - )} + Double-click to write…
)}
); } -function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange }) { +function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange, onOpenJournal }) { const [collapsed, setCollapsed] = useState(false); useEffect(() => { @@ -92,6 +99,15 @@ function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabConte
))} + {!tabs.some((t) => t.type === 'journal') && ( + + )} {/* Content */} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index a8f8941..acf36c1 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -543,6 +543,25 @@ html, body, #root { } .node-help-tab-close:hover { opacity: 1; } +.node-help-tab-add { + background: none; + border: 1px dashed #334155; + color: #475569; + font-size: 13px; + line-height: 1; + width: 22px; + height: 22px; + border-radius: 4px; + cursor: pointer; + align-self: center; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: color 0.12s, border-color 0.12s; +} +.node-help-tab-add:hover { color: #f1f5f9; border-color: #64748b; } + .node-help-panel { position: fixed; top: 60px;