Skip to content

feat(html): lay out only the lines of a text file near the viewport - #1247

Merged
andiwand merged 3 commits into
mainfrom
feat/text-chunks
Oct 24, 2026
Merged

andiwand merged 3 commits into
mainfrom
feat/text-chunks

Conversation

@andiwand

@andiwand andiwand commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Refs #764.

Summary

This is level 1 of docs/design/lazy-loading.md, for text files. With a text file of 1 M lines (71 MB) in headless Chrome:

first frame layout renderer RSS
before 154 s 91 s 6.3 GB
after 0.85 s 0.1 s 0.9 GB
  • Chunks. The view writes the lines in chunks of 256. A chunk has content-visibility: hidden until text.js marks it near the viewport with an IntersectionObserver (one screen of margin). So the browser lays out a few chunks at a time. content-visibility: auto alone takes 13.5 s here, because Chrome lays out every auto chunk once on the first frame. Without the script, the chunks fall back to auto (<noscript>), and beforeprint shows every chunk.
  • Line numbers. A line number is now a CSS counter in the line itself (::before, with empty alternative text for screen readers). So the gutter column and its ResizeObserver are gone. That observer measured every line and doubled the load. A wrapped line keeps its number beside it. A chunk has style containment, so the counter does not count on past it, and the first line of each chunk sets where it starts. After an edit that adds or removes lines, the editor sets the starts and the digits of the gutter again, and it removes a chunk that has no line left.
  • Search. odr.search shows the chunk of a hit before it scrolls to it, as it opens a folded <details>. The find of the browser does not reach a far chunk. The design doc lists hidden="until-found" as an open question.
  • The names are generic (odr-chunk, odr-chunk-near), so pdf and sheets can use them later.

The reference output changes for the five txt pages and text.css, text.js and search.js. The output repo main is at b0a3b91, and the pin is advanced. The gutter is now as wide as the digits of the last line, so the text moves by about a pixel.

Test

  • html.a_text_file_writes_its_lines_in_chunks: a 300-line file. The test checks the number of digits, where each chunk counts from, and the short last chunk. All 2,529 tests pass.
  • test/browser/plaintext: 54 checks, 0 failed. The fixture now has two chunks, so the checks cover an Enter, a join and a delete across the chunk boundary.
  • Screenshots at the top, at the chunk boundary (lines 251–267), and in the middle of the 1 M-line file: the numbers are right and the lines have no gaps.

Review fixes

  • An edit that went from 9 to 10 lines put the number 10 into a gutter of one digit. renumber now sets --odr-text-digits again. Two new checks in test/browser/plaintext cover it.
  • The comments that said "the containment of a chunk hides a counter" now give the reason in the words of the design: style containment keeps the counter inside the chunk.
  • This change touches only the edit path of text.js, so the rendered pages do not change. CI compares only output/, so the reference output stays at b0a3b91.

Base automatically changed from docs/lazy-loading to main October 24, 2026 18:31
andiwand and others added 3 commits October 24, 2026 20:31
A text file of one million lines took 151 s to load in Chrome, and its
renderer held 6.3 GB. Now it takes 0.85 s and 0.9 GB.

- The view writes the lines in chunks of 256. A chunk has
  `content-visibility: hidden` unless `text.js` marks it near the
  viewport, so the browser lays out a few chunks at a time. With
  `auto` instead, Chrome lays out every chunk once on the first frame.
  That costs 7.5 s for the same file.
- A line number is a CSS counter in the line itself. So the gutter
  needs no `ResizeObserver`, which doubled the load, and a wrapped line
  keeps its number beside it. The containment of a chunk hides a
  counter on the chunk, so its first line states where it counts from.
- `odr.search` shows the chunk of a hit before it scrolls to it.
- Without the script, the chunks fall back to `auto`.

The find of the browser does not reach a chunk far from the viewport.
`odr.search` does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QtawyvLP2SCnoBtnVfFed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QtawyvLP2SCnoBtnVfFed
The view states the digits of the largest line number once. An edit that
went from 9 to 10 lines put the number 10 into a gutter of one digit.
`renumber` now states the digits again, as it states the start of each
chunk.

Also state the reason for the start in each chunk in the same words as
the design: style containment keeps the counter inside the chunk.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
@andiwand
andiwand merged commit 40f1ce4 into main Oct 24, 2026
32 checks passed
@andiwand
andiwand deleted the feat/text-chunks branch October 24, 2026 18:43
andiwand added a commit that referenced this pull request Oct 24, 2026
The status said that nothing of the design has landed, but #1247 and
#1248 did. Also say how `spreadsheet.js` learns that a chunk became
near: `viewport.js` dispatches an event on it. A change of the class
alone tells no script.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
andiwand added a commit that referenced this pull request Oct 24, 2026
* docs: design the chunks of a sheet for lazy loading

`content-visibility` has no effect on a table row, so a chunk of a sheet
is a table of its own (decision 6):

- A `div.odr-sheet` takes the attributes of the sheet.
- The ruler gets a sticky table of its own.
- Every column gets a fixed width. A column that states none gets the
  width of its longest text.
- A chunk ends only where no merge is open.
- Each chunk states the estimated height of its rows.
- The first chunk stays near where shapes hang from A1.

For each script that reads the rows or the geometry, the design states
how it works with chunks. Level 2 of a text file drops out of the order
of work, because it gained little when measured.

* docs: state that level 1 of lazy loading has landed for text and pdf

The status said that nothing of the design has landed, but #1247 and
#1248 did. Also say how `spreadsheet.js` learns that a chunk became
near: `viewport.js` dispatches an event on it. A change of the class
alone tells no script.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
andiwand added a commit that referenced this pull request Oct 24, 2026
The views lay out only the parts near the viewport since #1247, #1248 and
#1250, and this PR adds `HtmlConfig::lazy_layout`. A consumer notices
both, but the changelog had no entry. Also check the round trip of the
jni test with `assertFalse`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
andiwand added a commit that referenced this pull request Oct 24, 2026
* feat(html): add a switch for lazy layout

`HtmlConfig::lazy_layout` is on by default. Off, no view writes a chunk
or the style that hides one, so the browser lays out everything. Its
own find and a screen reader then reach every line, row and page. A
host can take that where a file is small.

- The text and sheet views write all their lines or rows as one block.
  The pdf pages get no chunk class.
- `chunk_classes` gives the classes of a chunk, empty where the switch
  is off, and `write_chunk_style` writes nothing then.
- The text styles move from `.odr-chunk` to `.odr-text-lines`, so a
  text page without chunks keeps its line numbers.
- The apple, jni and python bindings carry the switch.

* test: advance the reference output to the lazy layout switch

The design names the switch, and the pins move to the reference output
of the renamed text class and the reordered pdf page classes.

* docs: add the lazy layout and its switch to the changelog

The views lay out only the parts near the viewport since #1247, #1248 and
#1250, and this PR adds `HtmlConfig::lazy_layout`. A consumer notices
both, but the changelog had no entry. Also check the round trip of the
jni test with `assertFalse`.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant