perf(app): rebuild the layout and remeasure widths only when their inputs moved (improvise-felo.13)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (felo13)
2026-10-02 18:47:30 -07:00
committed by Edward Langley
parent 2f05f6b62d
commit b42aae2e08
+66 -10
View File
@@ -434,6 +434,14 @@ impl Default for ModelState {
}
}
/// What a layout was built from other than the model.
#[derive(Debug, Default, PartialEq, Eq)]
struct LayoutBasis {
view: String,
/// The address of the frozen records, never dereferenced.
drill: Option<usize>,
}
/// What the keypress path measures of a built layout.
pub(crate) struct GridGeometry {
pub col_widths: Vec<u16>,
@@ -466,6 +474,14 @@ pub struct App {
layout_builds: usize,
/// How many times the grid's column widths were measured, for the same reason.
width_passes: std::cell::Cell<usize>,
/// The widths of the layout above, measured on first use and dropped with it.
geometry: std::cell::OnceCell<GridGeometry>,
/// What the layout above was built from besides the model: the view it drew
/// and the drill snapshot it froze. `layout_current` compares it.
layout_basis: LayoutBasis,
/// The model changed since the layout was built. Set by the one writer that
/// does not rebuild at once.
layout_stale: bool,
/// **Transient**: set during `apply_effects` to skip the rest of the batch,
/// reset at the start of every call. Reach it through `AbortChain`.
pub abort_effects: bool,
@@ -566,6 +582,9 @@ impl App {
layout,
layout_builds: 0,
width_passes: std::cell::Cell::new(0),
geometry: std::cell::OnceCell::new(),
layout_basis: LayoutBasis::default(),
layout_stale: true,
abort_effects: false,
batch_aborted: false,
batch_refused: false,
@@ -623,6 +642,7 @@ impl App {
self.view_state.view_back_stack.clear();
self.view_state.view_forward_stack.clear();
self.model_state.workbook = workbook;
self.layout_stale = true;
// Every client's mirror is scoped to the outgoing document too, and an
// effect cannot reach the session list. The generation is how it hears.
self.document_generation += 1;
@@ -914,17 +934,39 @@ impl App {
self.layout =
GridLayout::with_frozen_records(&self.model_state.workbook.model, view, frozen);
self.layout_builds += 1;
self.layout_stale = false;
self.layout_basis = self.current_layout_basis();
self.geometry = std::cell::OnceCell::new();
}
/// Rebuild the layout only if something it is a function of has moved: the
/// model (`layout_stale`), the view it draws or its drill snapshot. A cursor
/// key moves none of them (improvise-felo.13, `context/code/command.md`).
fn refresh_layout(&mut self) {
if self.layout_stale || self.layout_basis != self.current_layout_basis() {
self.rebuild_layout();
}
}
fn current_layout_basis(&self) -> LayoutBasis {
LayoutBasis {
view: self.view_name().to_string(),
drill: self.drill_state.as_ref().map(|d| Rc::as_ptr(&d.records) as usize),
}
}
/// The measurements a keypress needs of the grid: column widths and the row
/// header. Counted, so a test can see whether a keypress paid for them.
pub(crate) fn grid_geometry(&self) -> GridGeometry {
self.width_passes.set(self.width_passes.get() + 1);
let model = &self.model_state.workbook.model;
GridGeometry {
col_widths: compute_col_widths(model, &self.layout, &FormatPlan::of(model, self.view())),
row_header_width: compute_row_header_width(&self.layout),
}
pub(crate) fn grid_geometry(&self) -> &GridGeometry {
self.geometry.get_or_init(|| {
self.width_passes.set(self.width_passes.get() + 1);
let model = &self.model_state.workbook.model;
let plan = FormatPlan::of(model, self.view());
GridGeometry {
col_widths: compute_col_widths(model, &self.layout, &plan),
row_header_width: compute_row_header_width(&self.layout),
}
})
}
/// Editable text for the cell under the cursor: staged drill edit, else the
@@ -1139,7 +1181,8 @@ impl App {
// is read only behind the epoch gate — `context/code/command.md`.
let reshaped = self.last_reshaped_views.clone();
self.last_reframed_views.extend(reshaped);
self.rebuild_layout();
self.layout_stale |= touches_model;
self.refresh_layout();
// After the rebuild, or a batch that crosses into or out of records mode
// settles against the grid it started on — `context/code/command.md`.
self.settle_normal_mode();
@@ -1219,7 +1262,7 @@ impl App {
/// Resolve one keypress through the keymap and apply what it produces. The
/// projection half is factored out; `ui::reduce` is the named door (improvise-gxi).
pub fn dispatch_key(&mut self, key: KeyEvent) -> Dirty {
self.rebuild_layout();
self.refresh_layout();
// An armed prefix key takes priority. Resolve it back to its
// sub-keymap through the keymap set (improvise-jc8).
@@ -1943,7 +1986,7 @@ mod tests {
let mut app = two_col_model();
let down = || KeyEvent::new(KeyCode::Down, KeyModifiers::NONE);
app.dispatch_key(down());
let narrow = app.grid_geometry().col_widths;
let narrow = app.grid_geometry().col_widths.clone();
let (builds, widths) = (app.layout_builds, app.width_passes.get());
let key = CellKey::new(vec![("Row".into(), "A".into()), ("Col".into(), "X".into())]);
let long = CellValue::Text("a value far wider than a column".into());
@@ -1957,6 +2000,19 @@ mod tests {
assert_ne!(fresh, narrow, "the edit widened a column, or this proves nothing");
}
/// improvise-felo.13: a `ViewOnly` effect that changes which view is drawn
/// must still rebuild, so skipping the rebuild is not keyed on `EffectKind` alone.
#[test]
fn switching_view_rebuilds_though_the_effect_is_view_only() {
let mut app = two_col_model();
app.model_state.workbook.create_view("Second");
app.rebuild_layout();
let builds = app.layout_builds;
app.apply_effects(vec![Box::new(crate::ui::effect::SwitchView("Second".into()))]);
assert_eq!(app.view_state.active_view.as_str(), "Second");
assert!(app.layout_builds > builds, "the layout still draws Default");
}
/// improvise-htl6. Evidence: `context/test-evidence/command.md`.
#[test]
fn the_view_history_walks_both_ways_from_the_keyboard() {