Flow Studio
Subscribing in Tab Components
Tab components import from flow-observer-core and subscribe to slices of both stores using the standard Zustand selector pattern. Narrow selectors and shallow equality keep tab renders focused and performant.
Import Pattern
// Always import from flow-observer-core — never from flow-studio-services
import { useFlowObserverPanelStore } from '@flow-observer-core/stores/flowObserverPanelStore';
import { useExecutionStore } from '@flow-observer-core/stores/executionStore';
Subscribing to Panel UI State
// Read which tab is active
function FlowObserverPanel() {
const activeTabId = useFlowObserverPanelStore(state => state.activeTabId);
const tabDescriptors = useFlowObserverPanelStore(state => state.tabDescriptors);
const activeTab = tabDescriptors.find(t => t.id === activeTabId);
return (
<div>
<TabBar tabs={tabDescriptors} activeTabId={activeTabId} />
{activeTab && <activeTab.component />}
</div>
);
}
Subscribing to Execution Data
import { shallow } from 'zustand/shallow';
// ExecutionStatusTabContent — reads overall status and timing
function ExecutionStatusTabContent() {
const { status, startedAt, completedAt, durationMs, errorMessage } =
useExecutionStore(
state => ({
status : state.status,
startedAt : state.startedAt,
completedAt : state.completedAt,
durationMs : state.durationMs,
errorMessage: state.errorMessage
}),
shallow
);
// ...
}
// LogsTabContent — reads only the logs array
function LogsTabContent() {
const logs = useExecutionStore(state => state.logs);
// Re-renders whenever logs.length changes (appendLog creates new array ref)
return <VirtualLogList logs={logs} />;
}
Custom Tab — Full Example
// A custom tab that reads from both stores
function AuditTrailTabContent() {
const executionId = useExecutionStore(state => state.executionId);
const isDocked = useFlowObserverPanelStore(state => state.isDocked);
// Load audit data from custom API when executionId is known
const { data: auditTrail } = useAuditTrailQuery(executionId);
return (
<div className={isDocked ? 'audit-docked' : 'audit-floating'}>
{auditTrail?.map(entry => <AuditRow key={entry.id} entry={entry} />)}
</div>
);
}
Custom tab components receive no special props. They access all data they need directly from the two stores. The panel renders them as
<activeTab.component /> — no props passed. This makes custom tabs completely self-contained.