The Four Graphs¶
HBIA's frontend architecture decomposes the UI into four interconnected reactive graphs. This page covers each graph type in detail.
UI Graph¶
The UI Graph represents the component hierarchy as a tree of pure render nodes.
UINode¶
from honey_badgeria.front.graph import UINode
node = UINode(
name="Dashboard",
view="dashboard_view",
reads=["FilterState.dateRange", "ChartState.series"],
events={"refresh": "refresh_chart_event"},
children=["ChartHeader", "ChartCanvas", "ChartLegend"],
props={"variant": "full"},
)
| Field | Type | Description |
|---|---|---|
name |
str |
Component name (PascalCase) |
view |
str |
View function/file name |
reads |
list[str] |
State fields this component subscribes to |
events |
dict[str, str] |
Events this component can emit: {action: event_name} |
children |
list[str] |
Child component names |
props |
dict[str, str] |
Static props |
The Pure Component Rule¶
UI nodes are pure render functions. This is a strict rule:
- No
useState— State lives in the State Graph. - No
useEffect— Effects live in the Effect Graph. - No
useContext— Data comes viareads(state subscriptions). - No side effects — Everything is declared externally.
A component receives data through reads and emits user interactions through events. It does nothing else.
UIGraph¶
from honey_badgeria.front.graph import UIGraph
ui = UIGraph()
ui.add_node(node)
root = ui.root # Root component name
subtree = ui.get_subtree("Dashboard") # BFS descendants
all_reads = ui.all_reads() # All state references across all nodes
all_events = ui.all_events() # All events emitted across all nodes
State Graph¶
The State Graph represents state ownership — what data exists, what types it has, how it can change, and what effects it triggers.
StateNode¶
from honey_badgeria.front.graph import StateNode
node = StateNode(
name="FilterState",
interface="FilterStateData",
fields={"dateRange": "DateRange", "region": "string", "category": "string"},
mutations=["set_date_range", "set_region", "set_category", "reset_filters"],
triggers=["fetch_chart_data", "update_url_params"],
initial={"region": '"all"', "category": '"all"'},
)
| Field | Type | Description |
|---|---|---|
name |
str |
State node name (PascalCase + "State") |
interface |
str |
TypeScript interface name for the generated code |
fields |
dict[str, str] |
Field names and TypeScript types |
mutations |
list[str] |
Named mutation operations |
triggers |
list[str] |
Effect names triggered on state change |
initial |
dict[str, str] |
Default field values |
Key Concepts¶
Fields are the typed data the state holds. Each field has a name and a TypeScript type:
fields:
dateRange: DateRange # Custom type
region: string # Primitive
loading: boolean
items: ChartItem[] # Array type
Mutations are the only way to modify state. They are named operations that the Event Graph and Effect Graph dispatch:
Triggers connect state changes to effects. When any field in this state changes, the listed effects are invoked:
StateGraph¶
from honey_badgeria.front.graph import StateGraph
state = StateGraph()
state.add_node(node)
all_fields = state.all_fields() # {state_name: [field_names]}
all_mutations = state.all_mutations() # {state_name: [mutation_names]}
all_triggers = state.all_triggers() # {state_name: [effect_names]}
# Resolve a field reference
state_name, field = state.resolve_field("FilterState.dateRange")
# ("FilterState", "dateRange")
Effect Graph¶
The Effect Graph represents side effects — operations triggered by state changes or events that interact with the outside world.
EffectNode¶
from honey_badgeria.front.graph import EffectNode
node = EffectNode(
name="fetch_chart_data",
watch=["FilterState.dateRange", "FilterState.region"],
on_event=["chart_refresh_clicked"],
handler="handlers/fetch_chart_data",
effect_type="side_effect",
mutates=["ChartState", "NotificationState"],
debounce_ms=300,
)
| Field | Type | Description |
|---|---|---|
name |
str |
Effect identifier |
watch |
list[str] |
State fields that trigger this effect on change |
on_event |
list[str] |
Events that trigger this effect |
handler |
str |
Path to handler implementation |
effect_type |
str |
"side_effect" or "derived" |
mutates |
list[str] |
State nodes this effect can write to |
debounce_ms |
int \| None |
Optional debounce in milliseconds |
Effect Types¶
side_effect — Interacts with the outside world: API calls, analytics, localStorage, WebSocket messages.
derived — Computes new state from existing state. Like a computed property, but explicitly declared.
Trigger Mechanisms¶
Effects fire in two ways:
- Watch — State field changes trigger the effect. When
FilterState.dateRangechanges,fetch_chart_datafires. - On Event — User events trigger the effect. When
chart_refresh_clickedoccurs, the effect fires.
Mutation Targets¶
The mutates field declares which state nodes an effect can write to. This creates explicit, traceable data flow:
effect: fetch_chart_data
mutates:
- ChartState # This effect can modify ChartState
- NotificationState # ...and NotificationState
Debounce¶
Optional debounce prevents rapid-fire effects. Useful for search-as-you-type, resize handlers, and filter changes:
EffectGraph¶
from honey_badgeria.front.graph import EffectGraph
effects = EffectGraph()
effects.add_node(node)
# Find effects watching a state
watching = effects.effects_for_state("FilterState")
# Find effects watching a specific field
watching = effects.effects_for_field("FilterState.region")
# Find effects triggered by an event
triggered = effects.effects_for_event("chart_refresh")
# Get mutation targets
targets = effects.mutation_targets() # {effect_name: [states]}
# Full propagation chain from a state
chain = effects.trigger_chain("FilterState")
Event Graph¶
The Event Graph maps user interactions to ordered sequences of state mutations.
EventNode¶
from honey_badgeria.front.graph import EventNode
node = EventNode(
name="refresh_chart_clicked",
flow=["FilterState.reset_filters", "ChartState.set_loading"],
source="ChartHeader",
guards=["is_authenticated", "has_permission"],
)
| Field | Type | Description |
|---|---|---|
name |
str |
Event identifier |
flow |
list[str] |
Ordered list of mutations or effect triggers |
source |
str \| None |
Component that emits this event |
guards |
list[str] |
Preconditions that must be true |
Event Flow¶
The flow field defines an ordered mutation sequence. When the event fires:
- Check all guards.
- Execute
FilterState.reset_filters(first mutation). - Execute
ChartState.set_loading(second mutation). - Any triggered effects run after mutations complete.
Guards¶
Guards are preconditions. If any guard fails, the event doesn't execute:
guards:
- is_not_loading # Don't refresh while already loading
- is_authenticated # Must be logged in
EventGraph¶
from honey_badgeria.front.graph import EventGraph
events = EventGraph()
events.add_node(node)
# Find events affecting a state
affecting = events.events_for_state("ChartState")
# Find events from a component
from_source = events.events_from_source("ChartHeader")
FrontendDomain¶
The FrontendDomain bundles all four graphs into a single domain:
from honey_badgeria.front.graph import FrontendDomain
domain = FrontendDomain(
name="dashboard",
ui_graph=ui,
state_graph=state,
effect_graph=effects,
event_graph=events,
)
A domain represents a logical section of your application (e.g., "dashboard", "auth", "settings"). Each domain has its own set of four graphs.
The Complete Data Flow¶
1. User clicks a button in a UINode
→ UINode.events["refresh"] = "refresh_chart_event"
2. EventGraph dispatches "refresh_chart_event"
→ flow: [ChartState.set_loading, FilterState.reset_filters]
3. StateGraph mutations execute
→ ChartState.loading = true
→ FilterState fields reset
4. State changes trigger EffectGraph
→ FilterState triggers: [fetch_chart_data]
→ fetch_chart_data watches FilterState.dateRange, FilterState.region
5. Effect executes (API call)
→ fetch_chart_data handler runs
→ mutates ChartState (new series data)
6. UIGraph re-renders
→ Dashboard reads ChartState.series → updated chart
→ Dashboard reads ChartState.loading → spinner hidden
Every step is declared in YAML. An AI agent can trace this entire flow from user action to UI update by reading four files.