Portal Community

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.