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}
-
)