logseq / logseq/db-test

Table view: every scroll start/stop re-renders all visible rows (2.0.1 beta) — #141 is not fixed

Open
#1,033 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
28
Forks
2
PR merge metrics
No merged PRs in 30d

Description

Summary

Scrolling a tag/class page rendered as Table View saturates the main thread. This is the same problem as #141 ("Scrolling horrifically slow with a table in view"), which was closed on 2025-05-19 as "addressed with the latest version". It is not fixed in 2.0.1. A user already reported on 2025-06-05 in that thread that it still reproduced; that comment received no reply.

The cost scales with rows × columns, matching the original report ("a few properties greater than 4 is choppy, at about 10 properties it's unusable").

Environment

  • Logseq Desktop 2.0.1 beta (2026-07-13), AppImage, Electron 42 / Chromium 148
  • Arch Linux, Hyprland / native Wayland (xwayland: 0), Intel Iris Plus (Ice Lake)
  • Hardware acceleration confirmed: ANGLE (Intel, Mesa Intel(R) Iris(R) Plus Graphics (ICL GT2), OpenGL ES 3.2) — not a software-rendering fallback
  • devicePixelRatio 1.25, honoured natively
  • DB graph: 76 pages, 22 tags, 37 properties — tiny graph, console clean, no exceptions

Reproduction

  1. Create a class with ~12 typed properties and ~60 objects (I imported via @logseq/cli import-edn)
  2. Open the class page — it renders as Table View, 12 columns
  3. Scroll

Measurements

Taken with CDP Performance.getMetrics (deltas over each window) and Profiler, on the 60 rows × 12 columns page.

Scrolling (programmatic wheel events, 3.2 s):

busy 92.8 % | script 46 % | style 20 % | layout 2.8 %
90 layouts, 165 style recalcs in 3.2 s

165 style recalcs in 3.2 s is roughly a full recalc every frame. Longest single blocking task observed: 153 ms. In a separate run, 40 wheel ticks over ~2 s produced 780–880 ms of total blocking time.

Mounting the view (navigate away and back), first 6 s:

busy 18.2 % | script 9.8 % | style 3.7 % | layout 0.8 % | 9 layouts, 18 recalcs

Steady state (view settled, no interaction) is clean:

busy 0.1 % | 0 layouts, 0 recalcs

So there is no permanent background loop — the problem is the scroll path (and, to a much lesser degree, the initial mount).

Scaling with cell count, sampled CPU during load/scroll windows:

view share of samples in (idle)
ordinary page 91–92 %
tag page, no objects 93–95 %
tag Task, 9 rows × 4 columns 56 %
class, 60 rows × 12 columns 24 % (30 % in (program) = layout/paint)

Suspected cause

src/main/frontend/components/views.cljs at tag 2.0.1, table-body (defined line 1951), lines 1966–1970:

:context {:scrolling scrolling?}
:is-scrolling set-scrolling!
:item-content (fn [idx _user ^js context]
                (let [option (assoc option
                                    :scrolling? (when context (.-scrolling context))

is-scrolling flips React state on every scroll start and stop. That produces a new Virtuoso context object, which invalidates item-content for every mounted row, and each row then re-renders all of its property-value cells. With 12 columns × ~26 rendered rows that is ~312 cell components re-rendered twice per scroll gesture.

is-scrolling occurs exactly once in the whole file — list-view and gallery-view do not pass it, and they are noticeably cheaper in practice.

Two other contributing factors in the same file:

  • Table overscan is :increase-viewport-by {:top 300 :bottom 300} (line 1957) vs {:top 64 :bottom 64} for list view (line 2018) — ~5× more rows kept mounted.
  • Grouped views disable virtualization entirely: ;; disabled virtualization for nested view:disable-virtualized? true. With grouping on, every row of an expanded group is mounted.

The only consumers of the flag appear to be the placeholder branch in the row component and the fetch guard in lazy-item. Both look like they could use a ref / useSyncExternalStore, or the row could be memoised on [row-id, visible-columns], so that a context change does not cascade into every cell.

Not caused by the Rum → HSX migration

I checked views.cljs at d185ea58~1 (the commit before #12748): :context {:scrolling ...} and :is-scrolling are already present there, and the HSX diff for these components is a mechanical rum/defc … < rum/statichsx/defc swap. HSX memoises by default (USE_MEMO), so memoisation was not dropped.

Ruled out

  • Software rendering — real GPU confirmed
  • Electron/Wayland launch flags — --in-process-gpu and --enable-features=WaylandLinuxDrmSyncobj were active during all measurements above and changed nothing
  • Fractional display scaling — DPR 1.25 is honoured natively; layout is only 2.8 % of scroll time
  • Graph size or corruption — tiny graph, logseq validate clean apart from two built-in properties, no console errors
  • CSS content-visibility: auto on rows — measured, made it slightly worse (92.8 % → 94.8 % busy); the bottleneck is JS re-render, not paint skipping

Workarounds for users hitting this today

  • Switch the view to List View (no is-scrolling, smaller overscan, cheaper row fetch)
  • Hide unused columns via Columns visibility
  • Turn Group by off, so virtualization stays enabled

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/main/frontend/components/views.cljs at tag 2.0.1, especially table-body around lines 1951-1970, and trace :context/:is-scrolling into the row component and lazy-item. Confirm which consumers require the scrolling flag, then measure the 60-row × 12-column view with the reported CDP metrics. Done means scroll start/stop no longer re-renders every visible row and scrolling blocking time improves.

Written by the indexing model from the issue text.

Assessment

Tech stack
clojure, electron
Domain
frontend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.