Problem
The Learn page's right-side Progress card pulls from graphNodes, which is fetched once at page bootstrap (frontend/src/components/screens/Learn.tsx:128,147). During a tutor session, the backend mutates mastery via services/graph_service.py::apply_graph_update, but the client never sees the new state until the page is reloaded — so the card feels stale through a session.
Proposal — cheapest "live" option
Have the tutor SSE stream (backend/routes/learn.py) emit a graph_update event whenever apply_graph_update runs, carrying just the changed node fields (id, mastery_tier, mastery_score, optionally name/course_id for new nodes). On the client, handle the event by patching the affected entries in graphNodes / graphEdges state in place.
This avoids any extra HTTP requests — the data piggybacks on the stream we are already opening for the assistant turn — and the progress card re-derives via its existing useMemo as soon as state updates.
Scope
Backend
- Extend the SSE event schema in
backend/routes/learn.py with a graph_update event type.
- Capture deltas from
apply_graph_update (return changed nodes/edges from the service or wrap the call) and emit them on the stream.
Frontend
- Extend the SSE consumer used by the Learn chat to recognize
graph_update events.
- Apply a reducer that merges incoming node/edge deltas into
graphNodes / graphEdges (upsert by id).
- No change needed to
progressItems — it will recompute via useMemo.
Non-goals
- Reworking how mastery is computed.
- Refetching the full graph per turn (rejected as too heavy).
- Backfilling history for past sessions.
Acceptance
- During a live tutor session, when the model triggers a graph update, the Progress card on the right reflects the new mastery state without a page reload.
- No additional REST round-trips per turn.
Problem
The Learn page's right-side Progress card pulls from
graphNodes, which is fetched once at page bootstrap (frontend/src/components/screens/Learn.tsx:128,147). During a tutor session, the backend mutates mastery viaservices/graph_service.py::apply_graph_update, but the client never sees the new state until the page is reloaded — so the card feels stale through a session.Proposal — cheapest "live" option
Have the tutor SSE stream (
backend/routes/learn.py) emit agraph_updateevent wheneverapply_graph_updateruns, carrying just the changed node fields (id, mastery_tier, mastery_score, optionally name/course_id for new nodes). On the client, handle the event by patching the affected entries ingraphNodes/graphEdgesstate in place.This avoids any extra HTTP requests — the data piggybacks on the stream we are already opening for the assistant turn — and the progress card re-derives via its existing
useMemoas soon as state updates.Scope
Backend
backend/routes/learn.pywith agraph_updateevent type.apply_graph_update(return changed nodes/edges from the service or wrap the call) and emit them on the stream.Frontend
graph_updateevents.graphNodes/graphEdges(upsert by id).progressItems— it will recompute viauseMemo.Non-goals
Acceptance