diff --git a/frontend/src/expr/JsonTree.test.tsx b/frontend/src/expr/JsonTree.test.tsx
index b0aec2c..3b69d7e 100644
--- a/frontend/src/expr/JsonTree.test.tsx
+++ b/frontend/src/expr/JsonTree.test.tsx
@@ -65,4 +65,19 @@ describe("JsonTree", () => {
render();
expect(screen.getByText(/no data/i)).toBeInTheDocument();
});
+
+ it("renders a bare string root as a visible quoted value", () => {
+ render();
+ expect(screen.getByText("hello")).toBeInTheDocument();
+ });
+
+ it("renders a bare number root", () => {
+ render();
+ expect(screen.getByText("42")).toBeInTheDocument();
+ });
+
+ it("renders a bare boolean root", () => {
+ render();
+ expect(screen.getByText("true")).toBeInTheDocument();
+ });
});
diff --git a/frontend/src/expr/JsonTree.tsx b/frontend/src/expr/JsonTree.tsx
index 9e0f43b..e4753e2 100644
--- a/frontend/src/expr/JsonTree.tsx
+++ b/frontend/src/expr/JsonTree.tsx
@@ -33,6 +33,27 @@ interface NodeProps {
function Node({ label, value, path, depth, pickable, onPick }: NodeProps) {
// Root — render children as top-level rows without a wrapping expando.
if (depth === 0) {
+ // Scalar root (string/number/bool): render as a single labelless leaf so
+ // e.g. `output = "hello"` shows the value. Without this branch, scalar
+ // roots would render as an empty
because renderChildren only handles
+ // arrays and objects.
+ if (!Array.isArray(value) && !(value !== null && typeof value === "object")) {
+ return (
+
+ -
+ {typeof value === "string" ? (
+ <>
+ "
+ {value}
+ "
+ >
+ ) : (
+ {leafPreview(value)}
+ )}
+
+
+ );
+ }
return (
{renderChildren(value, path, depth, pickable, onPick)}