From 37de42d463a1c81b06f852b036e7c9d1e40a2d72 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sat, 25 Apr 2026 12:36:58 -0400 Subject: [PATCH] fix(ui): address PR-Agent review feedback on JsonPane and SectionRail JsonPane: - Reset active tab when the tabs prop changes. Previously activeTab was initialized once and never re-synced; a parent swapping the tab set (e.g. selecting a different entity) could leave us pointing at a stale id, leaving the pane empty and Copy returning '{}'. - Force textarea remount on data change in editable mode via key={text}. Uncontrolled defaultValue retained the prior value when the underlying config changed, so onBlur could save stale text. SectionRail: - Default scroll-spy root to the nearest scrollable ancestor instead of the page viewport. FormPane wraps its body in a shadcn ScrollArea, so IntersectionObserver with root:null was watching the wrong scroller and the active section never updated as the form scrolled. - observeRoot prop still wins when supplied for explicit overrides. --- ui/src/components/config-layout/json-pane.tsx | 15 ++++++++-- .../components/config-layout/section-rail.tsx | 28 ++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/ui/src/components/config-layout/json-pane.tsx b/ui/src/components/config-layout/json-pane.tsx index 35fa0414..1c48fac7 100644 --- a/ui/src/components/config-layout/json-pane.tsx +++ b/ui/src/components/config-layout/json-pane.tsx @@ -39,7 +39,14 @@ export function JsonPane({ className, }: JsonPaneProps) { const hasTabs = tabs && tabs.length > 0; - const [activeTab, setActiveTab] = useState(tabs?.[0]?.id ?? 'data'); + const [selectedTabId, setSelectedTabId] = useState(tabs?.[0]?.id ?? 'data'); + + // Derive the *effective* active tab during render rather than mutating state + // in an effect. If the parent swaps the `tabs` array (e.g. selects a different + // entity), the previous selectedTabId may no longer exist — fall back to the + // first available tab so the pane never shows empty content. + const activeTab = + hasTabs && tabs.some((t) => t.id === selectedTabId) ? selectedTabId : (tabs?.[0]?.id ?? 'data'); return (
@@ -61,7 +68,7 @@ export function JsonPane({ {hasTabs ? ( @@ -94,8 +101,12 @@ function JsonView({ data, editable, onChange }: JsonViewProps) { const text = useMemo(() => JSON.stringify(data ?? {}, null, 2), [data]); if (editable) { + // `key={text}` forces React to remount the textarea when the underlying data + // changes (e.g. parent swaps the selected entity). Without this, an + // uncontrolled textarea retains the prior value and onBlur saves stale text. return (