Evening of 10-14: the autosave's print, B against C again, views in the worker

2026-10-14, Claude, while Matthew was away (nothing pushed). ^ticket(84), ^ticket(29), ^ticket(18).

Pushed, 2026-10-15 morning, on Matthew's word. #2635, now at 653ebc4adf: the autosave's print (ec9f125acd, was cbc585933b), the tests' startup (c053aec689, was 0dd613dbba) and the parser's count (653ebc4adf, was 3663ef22e6). #2597, now at f8ae6f2b9c: the same three (a8426b1893, 0322b09ca2, 7f62da773a), the undo compaction (d41c7a0298, was bc66ae5616) and the newest undo and redo entries kept whole (f8ae6f2b9c, was 3041fd0483). Each is the local commit named below, cherry-picked; the affected test groups pass on both branches. The views-in-worker prototype (^ticket(18)) is not pushed. Later that morning, #2635 too keeps its newest undo and redo entries whole (032fe3aca6): its undo and redo had taken about three edits' time, and now take under 0.3 s (^ticket(89)).

How it was measured

B against C, today (^ticket(29))

BuildC, whole programB, UX mode
#2635 head1.33, 1.44 s2.05, 2.04 s
with ^ticket(84)'s first change (linear slicing)1.76, 1.30, 1.23 s1.26, 1.43, 1.51 s
with both changes1.41, 1.30 s1.54, 1.56 s
with both, and the view runner moved to language1.34 s1.40 s

So on today's head B is slower than C again, by 0.6–0.7 s; ^report(8)'s fixed B (1.26 against 1.23 s) no longer holds. With ^ticket(84)'s fix the two are within run-to-run spread, B perhaps 0.1–0.2 s slower: livelit views run on the main thread in UX mode (live_html, about 380 ms of samples per change, profiled).

The autosave's print (^ticket(84))

Views in the worker (^ticket(18))

Update, later the same evening: views in the worker, with reuse

Two more experiments on experiment/2635-views-in-worker-all, after the slow one above. All are measured against the same build with views on the page (perf/2635-views-in-worker: the autosave fix and the module move).

MeasureViews on the pageViews in the worker (2 and 3)
Full Body, B (UX mode), release to redraw1.40 s1.52 s
Full Body, B, the page's update (profiled, four changes)2,989 samples (live_html 1,345, mk_view 1,376)831 (live_html none, mk_view 24)
Full Body, B, the worker (four changes)2,0385,070 (the view runs about 280 ms a change; hashing and marshaling the bigger Html about as much)
Full Body, C (whole program), release to redraw1.30–1.41 s1.46 s
Full Body, C, the page's view work (three changes, 2.5 s tail)memo_view 664, user_view 728, mk_view 999memo_view none, user_view 68, mk_view 323
Kids' Choice, eye size, release to redraw (scripts/release_timing.py, medians of 12)144 ms (125–158)187 and 195.5 ms (164–253)

So: on a big picture, the main thread does about 540 ms (B) or 220 ms (C) less per change, and the redraw arrives no later. On a small slide, the redraw comes about 45 ms later, since the worker's run and the Html's trip to the page cost more than the page's own view did. Not checked: whether the page's optimistic entries still converge with the worker's Html (LivelitProj.user_view compares them with fast_equal); ordinary probes whose values happen to be view commands are converted too; the per-projector cache is never cleared. Questions for Matthew: run views in the worker for every livelit, or only for those whose view is costly (measured per view)? And the ids of the livelits on screen in the request, so the worker runs only those?

The recovering parser, and every test run's start (^ticket(46), ^ticket(85))

Memory per edit: #2635 against #2597 (^ticket(65), for ^ticket(58))

^report(14) found that a #2597 tab keeps about 92 MB more with every edit of Full Body's shirt-width slider, and asked whether that growth is #2597's own. The same driver (014/drivers/heap_per_edit.py: headless Chromium, two forced GCs after each edit, then the live JS heap) ran on #2635's exact head, 9fb2bdf067, rebuilt for the run.

0 20 40 60 80 100 120 0 5 10 15 20 25 30 edits of the shirt-width slider (Full Body) MB kept since loading #2635, edit 1: +9.2 MB #2635, edit 2: +17.5 MB #2635, edit 3: +25.7 MB #2635, edit 4: +33.7 MB #2635, edit 5: +43.5 MB #2635, edit 6: +51.4 MB #2635, edit 7: +59.3 MB #2635, edit 8: +67.5 MB #2635, edit 9: +75.4 MB #2635, edit 10: +93.6 MB #2635, edit 11: +46.0 MB #2635, edit 12: +51.9 MB #2635, edit 13: +57.8 MB #2635, edit 14: +63.7 MB #2635, edit 15: +69.7 MB #2635, edit 16: +77.2 MB #2635, edit 17: +83.1 MB #2635, edit 18: +88.9 MB #2635, edit 19: +95.4 MB #2635, edit 20: +101.9 MB #2635, edit 21: +108.3 MB #2635, edit 22: +53.1 MB #2635, edit 23: +59.0 MB #2635, edit 24: +64.8 MB #2635, edit 25: +71.2 MB #2635, edit 26: +77.5 MB #2635, edit 27: +85.6 MB #2635, edit 28: +91.9 MB #2635, edit 29: +98.3 MB #2635, edit 30: +104.6 MB #2597, edit 1: +84.2 MB #2597: +1388 MB after 15 edits (report 14), off the chart #2635
The live heap after each of 30 edits, less the heap after loading. #2635 (dots): about 8 MB an edit, falling back twice. #2597 (dashed, ^report(14)'s as-built run): 84 MB after the first edit, 1,388 MB after fifteen.

Update, near midnight: what #2597 keeps per edit is its undo stack (^ticket(58))

The answer. #2597's page-level undo stack (History.Model.undo_stack in src/web/app/History.re) keeps a whole page model for every edit, with that version's syntax, statics and layout. Its cap only applies when the setting cap_undo_stack is on, and that setting is off by default (Settings.re: cap_undo_stack: false). So ^report(14)'s undo test, which lowered the cap from 250 to 2, changed nothing: the cap never ran. dev has the same code. #2635 does not, because Andrew fixed it on the modular editors in 6010128b7d (2026-08-26, "memory: kill the exponential co_ctx leak + compact undo snapshots"): each snapshot drops its derived caches, they are rebuilt on undo, and the cap always applies.

0 200 400 600 800 1000 0 2 4 6 8 10 12 edits of the shirt-width slider (Full Body) MB kept since loading #2597 as built, edit 1: +84.0 MB #2597 as built, edit 2: +171.9 MB #2597 as built, edit 3: +265.9 MB #2597 as built, edit 4: +345.7 MB #2597 as built, edit 5: +428.6 MB #2597 as built, edit 6: +508.3 MB #2597 as built, edit 7: +609.6 MB #2597 as built, edit 8: +690.3 MB #2597 as built, edit 9: +769.3 MB #2597 as built, edit 10: +886.5 MB #2597 as built, edit 11: +966.8 MB #2597 as built, edit 12: +1048.1 MB #2597 as built: +1048 MB #2597, undo stack capped at 2, edit 1: +83.8 MB #2597, undo stack capped at 2, edit 2: +163.9 MB #2597, undo stack capped at 2, edit 3: +249.7 MB #2597, undo stack capped at 2, edit 4: +259.2 MB #2597, undo stack capped at 2, edit 5: +259.0 MB #2597, undo stack capped at 2, edit 6: +264.1 MB #2597, undo stack capped at 2, edit 7: +234.3 MB #2597, undo stack capped at 2, edit 8: +244.3 MB #2597, undo stack capped at 2, edit 9: +253.1 MB #2597, undo stack capped at 2, edit 10: +287.4 MB #2597, undo stack capped at 2, edit 11: +292.5 MB #2597, undo stack capped at 2, edit 12: +284.1 MB #2597, undo stack capped at 2: +284 MB #2597 with the compaction port, edit 1: +76.2 MB #2597 with the compaction port, edit 2: +142.0 MB #2597 with the compaction port, edit 3: +165.9 MB #2597 with the compaction port, edit 4: +156.7 MB #2597 with the compaction port, edit 5: +150.8 MB #2597 with the compaction port, edit 6: +162.5 MB #2597 with the compaction port, edit 7: +167.2 MB #2597 with the compaction port, edit 8: +178.1 MB #2597 with the compaction port, edit 9: +187.7 MB #2597 with the compaction port, edit 10: +140.7 MB #2597 with the compaction port, edit 11: +147.5 MB #2597 with the compaction port, edit 12: +158.6 MB #2597 with the compaction port: +159 MB #2635 (has Andrew's fix), edit 1: +9.2 MB #2635 (has Andrew's fix), edit 2: +17.5 MB #2635 (has Andrew's fix), edit 3: +25.7 MB #2635 (has Andrew's fix), edit 4: +33.7 MB #2635 (has Andrew's fix), edit 5: +43.5 MB #2635 (has Andrew's fix), edit 6: +51.4 MB #2635 (has Andrew's fix), edit 7: +59.3 MB #2635 (has Andrew's fix), edit 8: +67.5 MB #2635 (has Andrew's fix), edit 9: +75.4 MB #2635 (has Andrew's fix), edit 10: +93.6 MB #2635 (has Andrew's fix), edit 11: +46.0 MB #2635 (has Andrew's fix), edit 12: +51.9 MB #2635 (has Andrew's fix): +52 MB
The live heap after each of 12 edits, less the heap after loading, in headless Chromium (^report(14)'s driver). #2597 as built, and two local builds of it: the undo stack always capped at 2, and Andrew's compaction ported. #2635's first 12 edits (above) for reference.

Where things are

Next steps

  1. Push ^ticket(84)'s fix to #2635 and cherry-pick it to #2597, on Matthew's word.
  2. Revise ^report(8) with these numbers and graphs (^ticket(29)); the draft waits for review, and its answer depends on ^ticket(84)'s fix landing.
  3. Push ^ticket(85)'s test fix and the parser's count fix to #2635 (and #2597), on Matthew's word.
  4. ^ticket(18): decide the protocol (the ids of the livelits on screen in the request, a content-keyed cache) before more code.
  5. Done (above): ^ticket(58)'s holder is #2597's undo stack.
  6. Push bc66ae5616 (the undo compaction), with or without 3041fd0483 (the newest entries kept whole), to #2597, on Matthew's word; and decide whether dev should get Andrew's fix before #2469 merges, since dev keeps whole page models per edit too.
  7. Bring ^report(14) up to date with the answer: drafted, a dated update with its data (local commits 0ddcef7, 6aabc40 and 8ba0119 in the series' working copy), waiting for review.