Files
dev/docs/history_render_cache_bridge.md
T

77 lines
3.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# History Render Cache Bridge
This document captures the current bridge between the new `HistoryState` data
model and the legacy per-cell layout caches that still live inside a few
history cells (`ExecCell`, streaming/assistant cells, and diff cells). The
bridge allows `ChatWidget` to render the unified history vector without
re-computing expensive layouts every frame while we complete the Step 6 renderer
work.
## Rendering pipeline overview
1. The draw loop in `ChatWidget::render_history` assembles a list of
`RenderRequest` values. Each request contains the `HistoryId` of the record
being rendered, the existing `HistoryCell` cache if one exists, and a set of
"fallback" lines derived on demand from the semantic `HistoryRecord`.
2. `HistoryRenderState::visible_cells()` receives those requests along with the
current `RenderSettings` (width, theme epoch, and reasoning visibility
toggle). The render state stores cached layouts in a `HashMap<CacheKey,
CachedLayout>`, where `CacheKey` is `(HistoryId, width, theme_epoch,
reasoning_visible)`.
3. When a cache entry exists, `visible_cells()` returns the layout immediately.
Otherwise it invokes the builder closure supplied in the request. The
closure uses the `HistoryCell` if one exists, and falls back to the semantic
lines so the renderer can still make forward progress even when we no longer
materialize a trait object.
4. The resulting `VisibleCell` bundles the resolved layout (plus the assistant
streaming plan, if applicable). `ChatWidget` uses these `VisibleCell`
snapshots to copy pre-rendered buffer rows into the terminal buffer.
## Cache invalidation
`HistoryRenderState` exposes several targeted invalidation helpers:
- `invalidate_history_id(HistoryId)` removes cached layouts for a single
record. We call this whenever a record’s semantic state changes (for example
when streaming assistant output arrives).
- `handle_width_change(width)` prunes cache entries whose width no longer
matches the viewport. Width changes also clear prefix sums so we rebuild the
height map.
- `invalidate_all()` clears the layout cache and prefix sums. We use this when
restoring snapshots or performing operations that massively reshuffle
history.
The prefix sum cache (`prefix_sums`, `last_prefix_*` fields) is separate from
the layout cache. It stores cumulative heights so that scrolling computations
are O(1) per frame.
## Fallback behaviors
- `RenderRequest::fallback_lines` are derived from the semantic
`HistoryRecord`. This ensures that even if a legacy `HistoryCell` cache is
missing (for example, after deserializing a snapshot from disk) we still have
content to render.
- `ChatWidget::draw_history` keeps the old `HistoryCell` objects around only as
an optimization; any cache miss rebuilds the layout from semantic state and
stores the result in the shared cache.
- Cells that still own per-width caches (`ExecCell`, diff/assistant streaming)
remain responsible for invalidating their internal caches. The history render
cache treats those cells as opaque; once Step 6 lands we can remove the
per-cell caches and rely exclusively on `HistoryRenderState`.
## Removal roadmap
The interim bridge exists solely to prevent regressions while we finish Step 6
of the plan. Once all cells render from structured state and the shared cache
proves stable, we can:
1. Delete the per-cell layout caches (`ExecCell::cached_layout`, assistant
streaming wrappers, diff caches).
2. Convert all render paths to supply semantic state only, removing
`HistoryCell` trait objects from the steady-state draw loop.
3. Simplify `ChatWidget::draw_history` so it only iterates over
`HistoryState.records`.
Tracking this work in the plan keeps the team aligned on why the bridge exists
and what remains before it can be removed.