Render table rows incrementally and start the data fetch earlier#663
Merged
Conversation
After #661 removed the 30 MB download, the main page still took ~2.3s from navigation to a rendered table. Profiling showed that was not our JS (a CPU profile over the whole load found 1151 ms idle, 799 ms "(program)", and only 98 ms of JS) but two structural problems. 1. The data request was blocked behind the CDN stylesheets. A parser-inserted <script> must wait for all pending stylesheets before it executes. custom.js sits at the end of <body> and runs inside jQuery(document).ready(), so the fetch could not start until the CDN CSS and jQuery had both resolved. Measured: registry-data.js finished downloading at 107 ms, but the fetch did not start until 3917 ms, behind bootstrap.min.css. The fetch needs neither jQuery nor CSS, so it now lives in a dependency-free assets/js/registry-data.js loaded from the TOP of <head>, above the stylesheet links, where nothing blocks it. The request now goes out at ~76-105 ms, in parallel with the CDN assets. custom.js awaits the in-flight promise. Retry still issues a fresh request rather than replaying the settled one. 2. Building 1012 rows of DOM cost ~600 ms, and was repaid on every filter. Isolating the phases: building the row HTML string for 1012 rows takes 16 ms, but inserting it (~1977 KB of HTML, 8000+ cells) takes 597 ms; the first 50 rows cost 47 ms. So the cost is DOM construction, and the fix is to not build nodes until they are needed. (content-visibility: auto was tested first and only gave 1.2x, since it skips layout but not construction.) The table now renders 60 rows and appends more as an IntersectionObserver sentinel scrolls into view. Because only part of the table is in the DOM, sorting can no longer reorder rows the way sorttable.js did -- it now sorts the dataset and re-renders from the top, preserving the aria-sort and chevron behavior. sorttable.js has no remaining references and is removed; nothing else used it (no CSS, no other tables). Also removed as part of this: the 50-row setTimeout chunking (it split a 16 ms string concat across ~21 macrotasks and inserted the DOM only at the end, so it bought nothing), the MutationObserver that re-ran initSortableTables on every mutation, and the dead $tableMain/#table-main no-ops. renderTable no longer shows a spinner on re-render, which fixes the flicker on every keystroke noted in #662. Measured on a local Jekyll build, navigation -> first row (median of 3): localhost 5 Mbps/40ms before #661 2863 ms 49416 ms after #661 2340 ms 3303 ms after this change 556 ms 2150 ms A filter change from 1012 -> 153 rows drops from ~263 ms to ~125 ms. Verified in Chromium against the built site: 17 checks covering the fetch ordering, batch rendering, full-count badges, scrolling to all 1012 rows, whole-dataset sorting in both directions (descending correctly yields "zwd", which DOM-only sorting could not), license sorting through the nested object, aria-sort state, filters, search, the empty-result path, and absence of spinner flicker. Known tradeoff: in-page Ctrl-F only finds rows that have been scrolled into existence. The search box searches the whole dataset and is unaffected. Refs #662 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #662.
After #661 removed the 30 MB download, the main page still took ~2.3 s from navigation to a rendered table. A CPU profile over the whole load showed 1151 ms idle, 799 ms "(program)", and only 98 ms of actual JS — so this was never our JS being slow. It was two structural problems.
1. The data request was blocked behind the CDN stylesheets
A parser-inserted
<script>must wait for all pending stylesheets before it executes.custom.jssits at the end of<body>and runs insidejQuery(document).ready(), so the fetch couldn't start until the CDN CSS and jQuery had both resolved. The measurement was stark:The fetch needs neither jQuery nor CSS, so it now lives in a dependency-free
assets/js/registry-data.jsloaded from the top of<head>, above the stylesheet links, where nothing blocks it. The request now goes out at ~76–105 ms, in parallel with the CDN assets, andcustom.jsawaits the in-flight promise.It's guarded to the home page (
page.url == "/") so other pages don't fetch data they never use, and it derives its URLs from its own<script>src so the site'sbaseurlis picked up without Liquid.2. Building 1012 rows of DOM cost ~600 ms, repaid on every filter
The cost is DOM construction, so the fix is to not build the nodes until needed. (
content-visibility: autowas tried first and only gave 1.2× — it skips layout but not construction.)The table now renders 60 rows and appends more as an
IntersectionObserversentinel scrolls into view. Because only part of the table is in the DOM, sorting can no longer reorder rows the waysorttable.jsdid — it now sorts the dataset and re-renders from the top, preserving thearia-sortand chevron behavior.sorttable.jshas no remaining references and is removed; nothing else used it (no CSS, no other tables).Also removed: the 50-row
setTimeoutchunking (it split a 16 ms string concat across ~21 macrotasks and only inserted the DOM at the very end, so it bought nothing), theMutationObserverthat re-raninitSortableTables()on every mutation, and the dead$tableMain/#table-mainno-ops.renderTableno longer shows a spinner on re-render, fixing the keystroke flicker noted in #662.Results
Measured on a local Jekyll build in Chromium, navigation → first row (median of 3):
A filter change from 1012 → 153 rows drops from ~263 ms to ~125 ms. End to end, the main page went from ~49 s to ~2.2 s on a 5 Mbps connection.
Verification
17 checks in Chromium against the built site, all passing:
1012 resources)zwd, which DOM-only sorting of 60 loaded rows could not producearia-sortstate is correctKnown tradeoff
In-page Ctrl-F only finds rows that have been scrolled into existence. The search box searches the whole dataset and is unaffected. This is inherent to virtualization and was the accepted cost when choosing this approach over pagination.
Not addressed here
The code review of #661 (now merged) surfaced that its
.catch(showLoadError)also wraps the render path, so an exception while rendering good data is reported to the user as a network failure. This PR touches that code but deliberately leaves the behaviour alone to keep the diff scoped — worth fixing in #661 or a follow-up.🤖 Generated with Claude Code