React
EnergyProvider, hooks, and headless components from @kumbatio/energy-system/react
import { EnergyProvider, useEnergyLevel, EnergyGate } from '@kumbatio/energy-system/react'Requires the optional peer dependency react >= 19.2. All hooks must be used inside an EnergyProvider; they throw Error('Energy hooks must be used within an EnergyProvider') otherwise. State reads use useSyncExternalStore, so they are concurrent-rendering safe.
EnergyProvider
function EnergyProvider(props: EnergyProviderProps): React.ReactElementProvides an EnergyEngine to the tree. Either pass a pre-created engine, or let the provider create and own one (it disposes its internal engine on unmount, and recreates it correctly under React StrictMode).
import { EnergyProvider } from '@kumbatio/energy-system/react'
import { localStoragePersistence } from '@kumbatio/energy-system/persistence'
export function App({ children }: { children: React.ReactNode }) {
return (
<EnergyProvider defaultLevel={75} persistence={localStoragePersistence()}>
{children}
</EnergyProvider>
)
}EnergyProviderProps
Prop
Type
/>
Hooks
useEnergyState
function useEnergyState(): EnergyStateGet the full energy state (level + timestamp + source + revision + origin).
useEnergyLevel
function useEnergyLevel(): [EnergyLevel, (level: EnergyLevel, source?: EnergySource) => void]Read the current energy level and a setter (source defaults to 'manual').
const [level, setLevel] = useEnergyLevel()useEnergyLevelCycler
function useEnergyLevelCycler(): () => voidReturns a stable function that cycles to the next energy level (100 -> 75 -> 50 -> 25 -> 0 -> 100).
useStrategy
function useStrategy<T>(strategy: AdaptationStrategy<T>): TResolve a strategy against the current energy level. Memoized on [strategy, level].
import { uiVisibilityStrategy } from '@kumbatio/energy-system'
import { useStrategy } from '@kumbatio/energy-system/react'
const ui = useStrategy(uiVisibilityStrategy)
if (!ui.sidebar) return nulluseEnergyGate
function useEnergyGate(minLevel: EnergyLevel): booleanReturns true if the current energy level meets or exceeds the given minimum.
useEnergyPresence
function useEnergyPresence(presence: EnergyPresenceMap): EnergyPresenceResolve a presence map against the current energy level. Memoized on [presence, level] - keep the map referentially stable (module scope or useMemo).
EnergyGate
function EnergyGate(props: EnergyGateProps): React.ReactNodeDeclarative energy gating for a subtree. Headless: renders no wrapper element of its own. When the resolved presence is 'hidden', fallback takes over visually.
Hidden subtrees keep their state by default. Energy moves up and down, so a gate that destroyed its children would throw away a half-written message every time capacity dipped. Hiding goes through React 19.2's <Activity>: component state, DOM and scroll position survive, effects are torn down while hidden and re-run on reveal, and hidden content is not server-rendered. Set whenHidden="unmount" for subtrees whose cost is worth reclaiming instead - media, canvases, live connections.
// Hide the AI chat at 50 and below:
<EnergyGate min={75}>
<AiChatPanel />
</EnergyGate>
// Full presence map, muted state styled by the child:
<EnergyGate presence={aiChatPresence}>
{(presence) => <AiChatPanel muted={presence === 'muted'} />}
</EnergyGate>EnergyGateProps
Exactly one gating form must be used: a full presence map, min (optionally with max for band gating), or max alone. Passing none throws Error('EnergyGate requires a presence map or min/max level').
Prop
Type
/>
EnergyHiddenBehavior
type EnergyHiddenBehavior = 'preserve' | 'unmount'The whenHidden vocabulary, exported for props that pass the choice through.
EnergyIndicator
function EnergyIndicator(props: EnergyIndicatorProps): React.ReactNodeHeadless energy indicator - bring your own UI. Takes a single render-prop child.
<EnergyIndicator>
{({ level, label, cycle }) => (
<button onClick={cycle}>
{label} ({level})
</button>
)}
</EnergyIndicator>EnergyIndicatorProps
Prop
Type
/>
EnergyIndicatorRenderProps
Prop
Type
/>
