From 2bc9f684e8ffeb88ba556fda8f28be735bcc6352 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Sat, 15 Aug 2026 21:39:59 +0200 Subject: [PATCH] Say what went wrong on a node in one line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bubble opened with the loader's own frame — `exec(compile(...))` inside `_load_function` — before anything about the user's code, and then said all of it twice, because a load failure arrives both as the node's status and as a node_error issue carrying the same text. It is now `Type: message (line N)`, deduplicated, with the line taken from the node's own file. Full tracebacks still go to the server log. Long messages grow the bubble instead of stretching it, and scroll only past 240px. The tooltip arrow is gone: it read as a rotated square notch under the bubble, and no better anywhere else it appeared. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KkmeRiyeYmVZqJVwuyHq9o --- backend/app/flow/controller.py | 24 ++++++++++++++++++----- frontend/src/components/Flow/FlowNode.tsx | 8 ++++++-- frontend/src/components/ui/tooltip.tsx | 3 +-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/backend/app/flow/controller.py b/backend/app/flow/controller.py index 74410d9..aba7b51 100644 --- a/backend/app/flow/controller.py +++ b/backend/app/flow/controller.py @@ -351,7 +351,7 @@ class FlowController: except Exception as exc: logger.warning("Node '%s' failed to load: %s", node_id, exc) entry.status = NodeStatus.ERROR - entry.error = _short_traceback(exc) + entry.error = _short_error(exc) return entry # ------------------------------------------------------------------------- @@ -405,7 +405,21 @@ def _bound(specs: list[MessageSpec]) -> list[MessageSpec]: return [spec for spec in specs if spec.name] -def _short_traceback(exc: Exception) -> str: - """The last frames of a failure, which is what a node author needs.""" - lines = traceback.format_exception(type(exc), exc, exc.__traceback__) - return "".join(lines[-3:]).strip() +def _short_error(exc: Exception) -> str: + """One line a node author can act on: what broke, and where in their code. + + The frames in between belong to the loader rather than to the node, so only + the line in the node's own source is kept. The full traceback stays in the + server log. + """ + if isinstance(exc, SyntaxError): + # Its own message already names the compiled file, which is noise here. + return f"{type(exc).__name__}: {exc.msg} (line {exc.lineno})" + + frames = [ + frame + for frame in traceback.extract_tb(exc.__traceback__) + if frame.filename.startswith(" - + {problem || style.label} diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx index 715bf76..9f8762e 100644 --- a/frontend/src/components/ui/tooltip.tsx +++ b/frontend/src/components/ui/tooltip.tsx @@ -34,7 +34,7 @@ function TooltipTrigger({ function TooltipContent({ className, - sideOffset = 0, + sideOffset = 4, children, ...props }: React.ComponentProps) { @@ -50,7 +50,6 @@ function TooltipContent({ {...props} > {children} - )