Skip to content

perf(settings): stop laying out every choice row when the filter drops rows - #2111

Merged
chhoumann merged 1 commit into
masterfrom
perf/choice-filter-layout
Oct 1, 2026
Merged

chhoumann merged 1 commit into
masterfrom
perf/choice-filter-layout

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Typing in Filter choices... no longer stalls when the filter drops rows. With 342 choices, a keystroke that removes rows took 390-550 ms and now takes about 40 ms.

Cause

Every row of the choice list was wrapped in <div animate:flip={{ duration: flipDurationMs }}>, and flipDurationMs is 0, which the drag needs (the comment on it is now in baseDndOptions). A 0 ms flip moves nothing. Svelte's animate runtime still runs for a keyed {#each} with animate:: it measures every row, and for each row that leaves it writes position: absolute and reads the row's rect again. Each of those reads forces a layout of the whole list, so the cost grows with rows removed times rows shown.

A CPU profile of ca -> cap -> ca -> cap -> ca -> cap on master spent 1.5 s of 4.1 s in getBoundingClientRect under Svelte's fix() and measure().

Change

  • ChoiceList.svelte keeps the row wrapper (the zone's direct child) without animate:flip.
  • baseDndOptions no longer takes flipDurationMs; it was only passed by the choice list and is always 0.
  • ChoiceList.crosszone.test.ts drops its Web Animations stub, which existed for animate:flip.

Measured

Obsidian 1.13.7, isolated vault with 20,151 notes and 342 choices (22 at the root, 10 folders of 27 with nested folders). Time from the input event to the first painted frame after the list settles, two runs each:

Query Rows master This branch
c 186 257, 235 ms 228, 173 ms
ca 180 164, 189 ms 37, 36 ms
cap 138 392-547 ms 43, 38 ms
capt 126 145, 180 ms 37, 37 ms
captu 12 32, 54 ms 35, 30 ms

The first letter still takes about 170-230 ms. That is mounting about 186 rows, each with its name rendered as Markdown, and is unchanged here.

Opening the tab with every folder expanded (331 rows) is unchanged (about 330-430 ms on both).

Drag still reorders: in the same vault, dragging the first root row down two places moved Group 0 from first to third, and the drop painted in 51 ms. Nothing animated before (0 ms flip) and nothing animates now, so the list looks the same.

Tests

  • ChoiceView.test.ts: narrowing the filter from 20 rows to 10 doesn't call getBoundingClientRect. With animate:flip back (and a Web Animations stub so jsdom can run it), it is called 40 times.
  • pnpm run build-with-lint, pnpm run test (6600 passed), .agents/run-e2e (395 passed, 24 Templater-only skipped).

Release / migration

None. Pre-existing (2.29.0 measures the same), not a 2.30.0 regression.

Fixes #2107

Note

Remove FLIP row animations from ChoiceList to skip layout measurement on filter

  • Removes the Svelte animate:flip usage and flip-duration state from ChoiceList, so filtered-out rows no longer trigger getBoundingClientRect measurements on every remaining row. Drag reordering now uses the shared zero-duration drag-zone configuration.
  • Drops flipDurationMs from the baseDndOptions option type in dndReorder.ts, so all drag zones use a zero flip duration.
  • Adds a regression test in ChoiceView.test.ts that narrows the filter from 20 to 10 entries and asserts no layout measurements occur. Also removes Web Animations API stubs from the cross-zone test setup.
  • Behavioral Change: drag reordering no longer animates row movement; row FLIP animation is intentionally absent from the keyed row wrapper in ChoiceList.svelte.

Macroscope summarized 61f311c.

Summary by CodeRabbit

  • User Experience
    • Choice list rows no longer use flip animations when reordered, providing unanimated row movement during drag and drop.
  • Performance
    • Filtering a choice list and dropping rows avoids unnecessary layout measurements.

…s rows

Each row of the settings choice list was wrapped in animate:flip with a
0 ms duration. The flip moved nothing, but Svelte's animate still measured
every row and forced a layout per row that left the list, so narrowing the
filter took up to ~500 ms a keystroke with a few hundred choices.

Fixes #2107
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: bd12cb99-2d39-4201-9dfe-937c06f01840

📥 Commits

Reviewing files that changed from the base of the PR and between fcb315d and 61f311c.

📒 Files selected for processing (4)
  • src/gui/choiceList/ChoiceList.crosszone.test.ts
  • src/gui/choiceList/ChoiceList.svelte
  • src/gui/choiceList/ChoiceView.test.ts
  • src/gui/shared/dndReorder.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

ChoiceList removes row flip animation and no longer passes a flip duration to the drag-and-drop zone. The shared drag-and-drop options always set the flip duration to zero. A regression test checks row measurement when filtering removes choices.

Changes

Choice List Reorder

Layer / File(s) Summary
Fix drag-and-drop flip duration
src/gui/shared/dndReorder.ts
baseDndOptions no longer accepts a per-zone flipDurationMs value and always sets the duration to zero.
Remove ChoiceList row flip animation
src/gui/choiceList/ChoiceList.svelte, src/gui/choiceList/ChoiceList.crosszone.test.ts, src/gui/choiceList/ChoiceView.test.ts
ChoiceList no longer applies animate:flip or passes a flip duration to the drag-and-drop zone. The tests remove Web Animations API stubs and check that filtering rows does not call getBoundingClientRect.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 61f31

The filtering change avoids row measurements when choices are removed, while drag-and-drop still commits same- and cross-zone order changes. No actionable merge-blocking risk is evident.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to 61f31

The change removes redundant row animation and centralizes a zero-duration setting already used by existing callers. Filtering safeguards, reorder callbacks, and data ownership remain unchanged. No introduced or worsened security exposure was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected changes do not expand the component's input surface or the authority of its reorder callback. Effective exposure remains the existing settings drag-and-drop flow, rather than a new externally reachable service or privileged operation.

Trust Boundaries and Controls

  • observed — Existing controls remain: rendered entries require unique nonempty string IDs; drag disabling is passed to the zone; and filtered-view guards prevent consider, finalize, drag initiation, and keyboard reorder from mutating or persisting the derived list.

Resilience and Maintainability Implications

  • observed — Source assertions cover same-zone ordering, cross-zone source removal, and placeholder recovery. They use synthetic events and do not establish actual runtime behavior for cancellation, interrupted drags, concurrent zones, or repeated independent sessions. The corresponding handlers and lifecycle options are unchanged by this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main performance change: avoiding layout work for choice rows removed by filtering.
Linked Issues check ✅ Passed Issue #2107 requires lower filtering cost in the Settings choice list. ChoiceList.svelte removes animate:flip from each choice-row wrapper and keeps flipDurationMs at 0 in baseDndOptions. This…
Out of Scope Changes check ✅ Passed The changed files support issue #2107. The cross-zone test removes Web Animations API stubs that are no longer required after the animation removal. The shared drag-and-drop change removes the unused …
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (1 skipped: 1 …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit watched the choices flow,
As rows departed, smooth and slow.
No measuring each row in sight,
The list now filters light.
The rabbit hops through rows anew.

Comment @coderabbitai help to get the list of available commands.

@chhoumann
chhoumann marked this pull request as ready for review October 1, 2026 10:29
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-01T10:30:59.480891Z 61f311c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chhoumann
chhoumann merged commit 97bce88 into master Oct 1, 2026
15 checks passed
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.

Filtering the choice list in settings takes up to 500 ms per keystroke with a few hundred choices

1 participant