Skip to content

Desktop: draw only the result rows on screen, and throttle progress events - #537

Merged
fstubner merged 2 commits into
mainfrom
perf/gui-large-results
Oct 6, 2026
Merged

fstubner merged 2 commits into
mainfrom
perf/gui-large-results

Conversation

@fstubner

@fstubner fstubner commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Gap 3 from the audit.

Measured

Production build, headless Chrome, a scan result of N rows, two runs each:

Rows Arrow key (median) Scroll step Switching back to the tab
1,000 34 ms → 33 ms 34 → 33 ms 0.21 s → 34 ms
4,096 107–117 ms → 33 ms 59–65 → 33 ms 0.9 s → 33 ms
20,000 620–660 ms → 50–66 ms 313–321 → 33 ms 4.7–5.5 s → 100 ms

33 ms is the measurement floor: two animation frames. 4,096 is a full port scan; 20,000 rows is a long packet capture.

What changed

  • Windowed rows (rowWindow.ts). Above 200 rows the table renders the rows in view plus 20 either side, between two spacer rows that keep the scrollbar the full size. Below 200 everything renders as before.
  • Keyboard focus scrolls the focused row into view itself, since a row that is not rendered cannot scrollIntoView. aria-rowcount and aria-rowindex keep the true row count for assistive tech.
  • No re-render on every scroll. The scroll handler set the overflow state each time, even with no edge changed, which re-rendered every row.
  • Progress events throttled in the Tauri backend: at most one per operation per 100 ms, plus the first, the last and each phase change. Before, a full port scan sent 4,096 IPC events, each a React update.

Checked

  • On 4,096 rows: the middle of the result renders with no gaps, and End lands on the last row, on screen, with aria-activedescendant pointing at it (screenshots in the session).
  • Unit tests for the window and scroll maths, and for the throttle (a 4,096-port scan inside one interval sends 2 events; phases and operations are throttled separately).
  • Desktop: typecheck, eslint, 233 unit tests, vite build, cargo clippy -p netscli-gui -D warnings, its 25 Rust tests.

The spacer CSS is in its own file because results.css is at its size cap.

…vents

The results table rendered every row and re-rendered all of them on every
selection change and scroll (the scroll handler set overflow state even
when it had not changed). Production build in headless Chrome, 2 runs:

  rows    arrow key (median)   scroll step   tab switch
  4,096   107-117 ms -> 33 ms  59-65 -> 33   0.9 s -> 33 ms
  20,000  620-660 ms -> 50-66  313-321 -> 33 4.7-5.5 s -> 100 ms

33 ms is the measurement floor (two frames). Above 200 rows the table now
renders the rows in view plus 20 either side between two spacer rows
(rowWindow.ts), scrolls a focused row into view itself since an
unrendered row cannot scrollIntoView, and sets aria-rowcount and
aria-rowindex so assistive tech still counts every row. Overflow state is
only set when an edge actually changes. Checked on 4,096 rows: the middle
of the result renders without gaps, and End lands on the last row, on
screen, with aria-activedescendant pointing at it.

Desktop progress events: one per port or host was one IPC event and one
React update each, 4,096 for a full port scan. The backend now sends at
most one per operation per 100 ms, plus the first, the last and each
phase change.
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Site preview: https://pr-537.netscli-site-preview.pages.dev

Built from 0aa7615 with NETSCLI_PREVIEW=1 — noindex, and analytics disabled so it does not report into netscli.com's numbers.

Production is unaffected: netscli.com is served from GitHub Pages via pages.yml, which is manual-only.

@fstubner
fstubner merged commit 0688a64 into main Oct 6, 2026
16 checks passed
@fstubner
fstubner deleted the perf/gui-large-results branch October 6, 2026 01:23
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