ui: Paginate GpuCompute summary table

Limit the summary table to 100 rows per page with prev/next
pagination controls. Previously the entire table (potentially
thousands of rows with percent-bar elements) was rendered into
the DOM, causing expensive browser paint and hit-test operations
on every frame even when the vdom itself was skipped.
diff --git a/ui/src/plugins/com.meta.GpuCompute/summary.ts b/ui/src/plugins/com.meta.GpuCompute/summary.ts
index d3e10ce..f7f4666 100644
--- a/ui/src/plugins/com.meta.GpuCompute/summary.ts
+++ b/ui/src/plugins/com.meta.GpuCompute/summary.ts
@@ -27,6 +27,7 @@
   COMPUTE_RENDER_STAGE_CATEGORY,
 } from './details';
 import {Icons} from '../../base/semantic_icons';
+import {Button} from '../../widgets/button';
 import {Icon} from '../../widgets/icon';
 import type {GpuComputeContext} from './index';
 import {adjustSeconds} from './humanize';
@@ -42,6 +43,8 @@
   gridSize: number | string | null;
 };
 
+const PAGE_SIZE = 100;
+
 // Component state holding the fetched rows and per-column max values.
 type SummaryState = {
   rows?: SummaryRow[];
@@ -54,6 +57,7 @@
   sortKey: SortKey | null;
   sortDescending: boolean;
   dirty: boolean;
+  pageOffset: number;
 };
 
 // Renders a bar whose width is proportional to `val / max`.
@@ -272,6 +276,7 @@
     state.launchIndexBySliceId = new Map();
     state.sortKey = 'id';
     state.sortDescending = false;
+    state.pageOffset = 0;
 
     const rows = await fetchKernelSummaryRows(attrs.ctx, attrs.engine);
 
@@ -359,6 +364,7 @@
         state.sortKey = key;
         state.sortDescending = true;
       }
+      state.pageOffset = 0;
       state.dirty = true;
       m.redraw();
     };
@@ -426,67 +432,97 @@
 
         m(
           'tbody',
-          sortedRows.map((r) =>
-            m(
-              'tr.pf-gpu-compute__summary-row',
-              {
-                ondblclick: () => attrs.openSliceInDetail?.(r.id),
-              },
-              [
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  String(launchIndexBySliceId.get(r.id) ?? r.id),
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td.pf-gpu-compute__summary-td--name',
-                  {title: r.demangledName},
-                  r.demangledName,
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  renderRelPercentBar(
-                    Number(r.durationNSecNum),
-                    state.maxDurationNSec,
-                    label(Number(r.durationNSecNum), 'nsecond'),
+          sortedRows
+            .slice(state.pageOffset, state.pageOffset + PAGE_SIZE)
+            .map((r) =>
+              m(
+                'tr.pf-gpu-compute__summary-row',
+                {
+                  ondblclick: () => attrs.openSliceInDetail?.(r.id),
+                },
+                [
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    String(launchIndexBySliceId.get(r.id) ?? r.id),
                   ),
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  renderRelPercentBar(
-                    Number(r.computePct),
-                    state.maxComputePct,
-                    label(r.computePct),
+                  m(
+                    'td.pf-gpu-compute__summary-td.pf-gpu-compute__summary-td--name',
+                    {title: r.demangledName},
+                    r.demangledName,
                   ),
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  renderRelPercentBar(
-                    Number(r.memoryPct),
-                    state.maxMemoryPct,
-                    label(r.memoryPct),
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    renderRelPercentBar(
+                      Number(r.durationNSecNum),
+                      state.maxDurationNSec,
+                      label(Number(r.durationNSecNum), 'nsecond'),
+                    ),
                   ),
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  renderRelPercentBar(
-                    Number(r.registersPerThread),
-                    state.maxRegisters,
-                    label(r.registersPerThread),
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    renderRelPercentBar(
+                      Number(r.computePct),
+                      state.maxComputePct,
+                      label(r.computePct),
+                    ),
                   ),
-                ),
-                m(
-                  'td.pf-gpu-compute__summary-td',
-                  renderRelPercentBar(
-                    Number(r.gridSize),
-                    state.maxGridSize,
-                    label(r.gridSize),
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    renderRelPercentBar(
+                      Number(r.memoryPct),
+                      state.maxMemoryPct,
+                      label(r.memoryPct),
+                    ),
                   ),
-                ),
-              ],
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    renderRelPercentBar(
+                      Number(r.registersPerThread),
+                      state.maxRegisters,
+                      label(r.registersPerThread),
+                    ),
+                  ),
+                  m(
+                    'td.pf-gpu-compute__summary-td',
+                    renderRelPercentBar(
+                      Number(r.gridSize),
+                      state.maxGridSize,
+                      label(r.gridSize),
+                    ),
+                  ),
+                ],
+              ),
             ),
-          ),
         ),
       ]),
+      sortedRows.length > PAGE_SIZE &&
+        m('.pf-gpu-compute__summary-pagination', [
+          m(Button, {
+            icon: Icons.PrevPage,
+            disabled: state.pageOffset === 0,
+            onclick: () => {
+              state.pageOffset = Math.max(0, state.pageOffset - PAGE_SIZE);
+              state.dirty = true;
+              m.redraw();
+            },
+          }),
+          m(
+            'span',
+            `${state.pageOffset + 1}–${Math.min(state.pageOffset + PAGE_SIZE, sortedRows.length)} of ${sortedRows.length}`,
+          ),
+          m(Button, {
+            icon: Icons.NextPage,
+            disabled: state.pageOffset + PAGE_SIZE >= sortedRows.length,
+            onclick: () => {
+              state.pageOffset = Math.min(
+                state.pageOffset + PAGE_SIZE,
+                sortedRows.length - PAGE_SIZE,
+              );
+              state.dirty = true;
+              m.redraw();
+            },
+          }),
+        ]),
     );
   },
 };