diff --git a/frontend/src/index.css b/frontend/src/index.css
index cc6b757..66ef541 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -544,6 +544,25 @@ button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: n
white-space: pre-wrap;
word-break: break-word;
}
+/* Recursive script-state value tree */
+.jv-string { color: var(--text); }
+.jv-number { color: var(--player); }
+.jv-bool { color: var(--accent-bright); }
+.jv-null { color: var(--text-dim); font-style: italic; }
+.jv-empty { color: var(--text-dim); }
+.jv-tree { display: inline-block; vertical-align: top; max-width: 100%; }
+.jv-toggle {
+ background: none; border: none; padding: 0; cursor: pointer;
+ font-family: inherit; font-size: 0.72rem; color: var(--text-dim);
+}
+.jv-toggle:hover { color: var(--accent-bright); }
+.jv-children {
+ list-style: none; margin: 2px 0 2px 0; padding-left: 12px;
+ border-left: 1px solid var(--border);
+}
+.jv-children li { padding: 2px 0; word-break: break-word; }
+.jv-key { color: var(--accent); margin-right: 6px; }
+.jv-key::after { content: ':'; color: var(--text-dim); }
@keyframes panel-in {
from { opacity: 0; transform: translateX(16px); }
to { opacity: 1; transform: none; }
diff --git a/frontend/src/pages/Play.jsx b/frontend/src/pages/Play.jsx
index f889f70..0f38946 100644
--- a/frontend/src/pages/Play.jsx
+++ b/frontend/src/pages/Play.jsx
@@ -392,6 +392,41 @@ function ScriptsPanel({ advId }) {
)
}
+// Renders one script-state value: primitives inline (typed/coloured), and
+// objects/arrays as a collapsible, indented tree — recursing into nesting so
+// deep state shows structure instead of a flat JSON blob.
+function StateValue({ value }) {
+ if (value === null || value === undefined) return null
+ if (typeof value === 'boolean') return {String(value)}
+ if (typeof value === 'number') return {value}
+ if (typeof value === 'string') return {value}
+ if (Array.isArray(value)) return [i, v])} empty="[ ]" />
+ if (typeof value === 'object') return
+ return {String(value)}
+}
+
+function StateTree({ entries, empty }) {
+ const [open, setOpen] = useState(true)
+ if (entries.length === 0) return {empty}
+ return (
+
+
+ {open && (
+
+ {entries.map(([k, v]) => (
+ -
+ {k}
+
+
+ ))}
+
+ )}
+
+ )
+}
+
// Collapsible left rail showing the scripting `state` object — every variable
// scripts read/write via state.x, refreshed after each turn.
function StatusDrawer({ advId, refreshKey }) {
@@ -433,9 +468,7 @@ function StatusDrawer({ advId, refreshKey }) {
{entries.map(([k, v]) => (
{k}
-
- {typeof v === 'string' ? v : JSON.stringify(v, null, 2)}
-
+
))}