[Chakra exit · Phase 2 · PR 12/14] frontend: the reassign-partitions wizard on Registry components, plus a smoke spec - #2639
Conversation
✅ Clean — no registry drift, off-token colours, or ad-hoc classesApp:
Generated by lookout audit-changes. |
02b498b to
c4cdf03
Compare
1c8e81c to
043c502
Compare
45 distinct Chakra symbols, three steps of state, and no Playwright coverage until now. The spec walks step 1 -> step 2 and back, because the step state is the thing a re-skin can break invisibly: the wizard owns `currentStep` and every guard reads it. It stops before "Start Reassignment" — this suite runs against a live cluster. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seven files. The shell and the three steps share `currentStep` and the
selection maps, so they ship together. No API and no behaviour change: every
guard (`isEnabled`, `computeWarning`, `maximumSelectedReplicationFactor`) reads
the same state it did before.
The hunks that carry judgement:
- `components/wizard-steps.tsx` replaces Chakra's `<Stepper index={…}>`. The
Registry ships `defineStepper`, but that owns navigation through its own
`methods` — this wizard keeps `currentStep` in its own state, so the indicator
is a pure function of that index and nothing in it can move the wizard.
- `bandwidth-slider` — the Registry Slider renders its own track and thumb and
has no mark or thumb-tooltip slot, so the five marks and the value bubble are
positioned against the same value scale (`percentOf`). The bubble appears on
hover, as Chakra's `isOpen={isDragging}` tooltip did.
- Three tables gain the expander column Chakra's DataTable injected for
`subComponent` (step 1's topics, step 3's review, and the nested tables get
explicit `pagination={false} sorting={false}` — false in Chakra, true in the
Registry).
- step 1's table passed a no-op `onRowSelectionChange` and a placeholder
`rowSelection={{ _internal_connectors_configs: true }}`; selection is done by
the `check` column, so both simply go.
- Every checkbox gains an accessible name. They had none: Chakra's took its
label from children, and these render bare in table cells. That is also what
makes the spec able to find them.
- `CancelReassignmentButton` — a Chakra Popover with header/body/footer slots
becomes a Registry Popover with plain markup; `closeOnBlur={false}` and
`returnFocusOnClose={false}` have no Base UI equivalent and were fighting the
default focus behaviour rather than relying on it.
- `Progress colorScheme` becomes an indicator class: the Registry indicator
paints `bg-primary`, so only the success state needs an override.
- The active-reassignments table is row-clickable; it now declares
`getRowAriaLabel` so the row's purpose is announced.
`misc/kowl-table`'s SearchTitle rides along — it is the column-header search
step 1's topic table uses, and nothing else imports it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by a review pass over the branch: - **The bandwidth slider rendered a phantom second thumb that could write NaN.** The Registry Slider counts thumbs from an array; a scalar `value` fell through to `[min, max]`, so a second thumb rendered with no value — invisible but focusable, and one arrow key from `maxReplicationTraffic = NaN` and a `setReplicationThrottleRate(brokers, NaN)` call. It takes `[sliderValue]` now. - **The value bubble was positioned at `left: -Infinity%`** in the default state: `maxReplicationTraffic` defaults to 0, so `log10` is `-Infinity`. The slider value is clamped once and used for the thumb, the bubble and the label. - **Four net-new lint errors were shipping.** `bun run lint` is `ultracite fix`, which exits 0 with errors still present — `lint:check` is the real gate. The two `biome-ignore` comments named a rule that does not fire on those nodes, so they suppressed nothing: the search overlay's handlers move onto the `Input` (the actual interactive element), the slider wrapper uses pointer events, and the select-all checkbox uses `checked` + `indeterminate` instead of a nested ternary. Added to the gate recipe. - **The throttle dialog's footer collapsed to the right.** `className="justify-between"` loses to the variant's own `sm:justify-end` (tailwind-merge drops only the unprefixed class), so "Remove throttle" no longer sat opposite Close/Apply. It uses `justify="between"` now. - **Dialog widths had jumped.** `minW="5xl"` (64rem) had become `full` (90vw, ~1728px on the Playwright viewport) and `minW="3xl"` (48rem) had become `xl` (56rem); they are `xl` and `lg` respectively. - **step 1 lost click-to-sort on four columns.** Chakra painted the affordance itself; restored with `DataTableColumnHeader`. The topic-name column is explicitly `enableSorting: false` because its header *is* the search control, so the model no longer claims sortability there is no UI for. - **Two columns shared `accessorKey: 'partitions'`**, so both resolved to the same TanStack column id — duplicate React keys and column lookups hitting the wrong def. Pre-existing, but the array is rewritten here. - The "no partitions" state was unframed text: the `Empty` root sets only `border-dashed` and Preflight leaves the width at 0. It has a border, an icon and a title now. - Popover content is `w-auto max-w-[500px]` — the Registry's fixed `w-72` was halving the width Chakra's `size="auto"` gave partition errors; `aria-expanded` on both expanders; `size-4` on the chevrons was overridden by the button variant's own `[&_svg]` rule; icons from `components/icons`; the active-reassignments `tableOptions` restated the DataTable's own default. Not fixed, pre-existing and unrelated to the migration: "Remove throttle" reads `newThrottleValue` from the same render it just set, so it re-applies the current throttle instead of clearing it. Identical on the base commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… row Step 1's SelectionInfoBar repeats "Leader Partitions" and "Total Partitions", so the page-wide exact match in the new smoke spec tripped Playwright strict mode on every run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sorting fix replaced the Topic column's SearchTitle header with DataTableColumnHeader; that overlay was already unreachable (nothing ever set filterOpen), and kowl-table.tsx had no other importer. Delete it and the filterOpen flag it wrote to. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The wizard tables paginate like the legacy DataTable again: 50 a page with the pager only past that (10 for the active-reassignments list, which had defaultPageSize 10), instead of 10 a page with a permanent footer or, on the review step, no paging at all. - Column size hints the Registry never reads are gone from all four tables. - Statistic wrapper replaced by the Registry Stat on the header row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- 'Remove throttle' re-applied the existing throttle: the handler read the stale value from the render closure. It now takes the value explicitly, and the topic-throttle dialog closes after a successful reset. - Partition-error popovers open on hover again, are titled (the dialog was unnamed), and take the width their text needs; the cancel-confirm popover is titled and its buttons are a spaced pair, not an attached group. - The Size column sorted on a field that does not exist; it reads logDirSummary.totalSizeBytes. The Progress column takes the slack again. - Progress bars paint brand for active state, not the Registry default; the slider bubble was background-on-background; the slider's dead 'Unlimited' end is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The remove-throttle dialog's hover note is a Registry Tooltip; the hand-rolled bubble was clipped by the Dialog's overflow. - The dead topic-search path (Highlighter and the quickSearch read) goes with the deleted SearchTitle. - Comments trimmed to the constraints; the wizard-steps note no longer misstates the Registry Stepper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
enableHiding: false moves from every column into each step's shared table options, so a column added later cannot bring the header menu's "Hide" item back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- `react-highlight-words` and its types leave `package.json` and both lockfiles: `SearchTitle` was the last importer. - Wizard icons come from `components/icons` (adds `InboxIcon`). - The throttle-dialog tooltip gets a `TooltipProvider` (150 ms, not Base UI's 600) and a focusable trigger. - Nav buttons use `min-w-56`/`ml-auto` classes and the link buttons `size="xs"` instead of inline styles; the loading skeleton's margin moves onto a wrapper. - `uiSettings.reassignment.quickSearch`/`pageSizeSelect` are dropped: step 1 no longer reads them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
043c502 to
a17f8cf
Compare
`accessorKey: 'Brokers After'` reads no field, so table-level `sorting` marked the column sortable while its header — the accessor key, rendered by TanStack's default header — offers nothing to trigger it. Name it and opt out, as the other derived columns do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
eblairmckee
left a comment
There was a problem hiding this comment.
Tested this branch against a 3-broker Redpanda cluster with 61 topics (50/page pager exercised, sizes spanning 351 B to 246 kiB). Step 1's table renders correctly and the four sortable columns are the right four — but sorting is effectively unusable because the page's polling refresh tears down the header menu. Detail inline. Sorting logic is fine; it's the menu lifetime.
| header: () => ( | ||
| <SearchTitle observableFilterOpen={this} observableSettings={uiSettings.reassignment} title="Topic" /> | ||
| ), | ||
| header: ({ column }) => <DataTableColumnHeader column={column} title="Topic" />, |
There was a problem hiding this comment.
The column sort menu is destroyed by the page's auto-refresh about a second after it opens, and the trigger's state desyncs.
Master used Chakra's DataTable, whose headers sorted on a single click. DataTableColumnHeader is a dropdown-menu-trigger (aria-haspopup="menu"), so sorting is now two steps: open the menu, then pick Asc/Desc. On /reassign-partitions that second step usually never happens.
Measured on the Size column:
| t | [role=menu] count |
items | aria-expanded |
|---|---|---|---|
| before click | 0 | — | false |
| +150 ms | 1 | Asc, Desc |
true |
| +1650 ms | 0 | — | true |
| +4650 ms | 0 | — | true |
Two consequences:
- You get roughly a one-second window to hit
Asc/Descbefore the refresh unmounts the menu. - After it's torn down,
aria-expandedstaystruewhile nothing is rendered — the trigger thinks it's open. The next click toggles it to closed, so it reads as a dead button. That's what a user hits: click header, nothing; click again, still nothing.
I first assumed sorting was broken outright — clicking each of the four headers twice, by coordinate and via button.click(), never reordered a row. It isn't broken. Driving it fast enough to beat the refresh sorts correctly:
Desc → rp-seed-00=246 kiB, rp-seed-50=149 kiB, rp-seed-01=101 kiB, rp-seed-02=26.8 kiB
That's genuinely numeric — a string compare would put 26.8 kiB above 246 kiB — so the "Size sorts by real bytes" requirement passes. Worth keeping the seeded spread when you verify a fix, since equal sizes would hide a lexical regression.
The refresh is pre-existing on this page (the "Fetching data…" indicator by the title), so the collision is new only because the sort UI moved into a portal. Worth checking whether the other 12 migrated tables in #2636 sit on polling pages too — same pattern would bite them, and it would be invisible on a static page.
Unrelated but adjacent: no header sets aria-sort — I checked all four before and after sorting, always absent. Screen readers can't announce which column is sorted or in which direction. If DataTableColumnHeader is shared, that's one fix for every migrated table.
There was a problem hiding this comment.
Fixed in 03e7b12 — thanks, this was the right diagnosis and the measurement made it easy to confirm.
One correction to the mechanism, because it makes the blast radius bigger than one page: there is no MobX observer here — nothing in this feature is one. PageComponent.componentDidMount subscribes to all five Zustand api stores and calls forceUpdate() on any write, so every page built on PageComponent re-renders on every poll. The inline columns={[…]} arrays then hand v9 fresh header/cell identities, and flexRender does createElement on the column's own function — a new function is a new component type, so React unmounts and remounts the header cell rather than updating it. That is what took the portal with it. The same remount was also killing the partition-error popovers in cells.
All five tables in the wizard now build their columns once: an instance field in the class steps (arrow bodies read this.props at call time, so nothing goes stale) and useMemo keyed on props.assignments in the nested review table. topic-sorting.test.tsx gains a guard — open the Size menu, re-render the page, assert the menu is still mounted; it fails on the pre-fix code.
On aria-expanded staying true: that was the detached node. A remounted trigger renders false, which is what the guard test sees in the DOM dump. The user-visible symptom you described is the same either way.
Your last point stands and I have recorded it: of the merged Phase 2 tables, connect/overview.tsx is the one that combines an inline columns array with DataTableColumnHeader and PageComponent, so it has the same exposure at auto-refresh cadence (10 s once a user turns it on); the four security tabs and topics/…/message-headers.tsx have popovers in cells. Those are other PRs' surface, so they are on the tracker rather than in this diff.
aria-sort is a fair hit and one fix upstream covers every migrated table, but data-table-column-header.tsx is vendored from ui-registry — this repo does not edit that tree — so it needs a registry PR plus a version bump here. Also tracked.
|
📺 Visually reviewed |
eblairmckee
left a comment
There was a problem hiding this comment.
Two more from testing against a 3-broker cluster, both pre-existing rather than regressions from this PR. This one is the bandwidth slider's input lag; the submit failure is in a separate conversation comment since its code sits well outside the diff.
| tableOptions={TABLE_OPTIONS} | ||
| /> | ||
|
|
||
| {this.reassignmentOptions()} |
There was a problem hiding this comment.
The bandwidth slider and the throttle summary both lag user input by up to a poll interval — not a race, a missing store notification.
Reported while testing: dragging the slider updates the thumb and the "Traffic throttle" summary line noticeably late, and they lag together.
Root cause is on L184 just below (outside this PR's hunks, so I can't anchor there directly):
onSettingsChange={(x) => {
settings.maxReplicationTraffic = x; // settings === uiSettings.reassignment
}}uiSettings is a Proxy over the Zustand settings store, and its set trap only fires for top-level assignment:
set(_target, prop, value) {
useUISettingsStore.getState().updateSettings({ [prop]: value });
return true;
}uiSettings.reassignment goes through get, which returns the raw nested object out of getState() — not another proxy. So settings.maxReplicationTraffic = x mutates Zustand's state in place and never calls updateSettings(). No set(), no subscriber notification, ever.
The value genuinely changes, so reads are correct — which is why it eventually appears. The tree only re-renders when something unrelated triggers it, and on this page that's the periodic refresh. Hence the delay, and hence both the thumb and the summary lagging in lockstep: they read the same non-reactive value.
Two halves to a real fix, and it needs both:
Notify on write — go through the top-level trap so the store actually publishes:
onSettingsChange={(x) => {
uiSettings.reassignment = { ...uiSettings.reassignment, maxReplicationTraffic: x };
}}Subscribe on read — StepReview is a plain Component reading through the proxy, so it won't re-render even once the store publishes. It needs useUISettingsStore(s => s.reassignment.maxReplicationTraffic), which means a function component or a small subscribing wrapper around the slider + summary.
Worth noting the trap: giving BandwidthSlider local useState for its own value would make the thumb feel instant and look fixed, while the summary kept lagging — so it'd hide half the bug. The store write is the thing to fix.
Not introduced here. Master has the identical assignment on its L146, master's Chakra slider is equally controlled (value={sliderValue}), and neither file uses observer. I did not measure whether Chakra's slider masked it better with internal optimistic state, so the perceived severity may have changed even though the wiring didn't. Non-blocking for this PR — flagging because this is the file that owns the line, and because the same nested-mutation pattern likely affects other uiSettings.<section>.<field> = … writes across the app.
There was a problem hiding this comment.
Fixed in 03e7b12, both halves as you specified — and you were right that a local useState in BandwidthSlider would have hidden half the bug, so it stayed out.
- Notify on write: the handler now assigns the whole section, so it goes through the proxy's top-level trap:
uiSettings.reassignment = { ...uiSettings.reassignment, maxReplicationTraffic: x }. - Subscribe on read:
StepReviewis now a small function wrapper that readsuseUISettingsStore((s) => s.reassignment.maxReplicationTraffic)and passes it to the class (renamedStepReviewContent), so the slider and the summary both come off the same subscribed value.step3-review.test.tsxasserts the summary flips fromdisabledto1 kiB/son a store write with no forced re-render; it fails on the pre-fix code.
I kept it in this PR rather than the flagged pile, on your own last point: the wiring is pre-existing, but master's Chakra slider masked half of it with internal drag state while the Registry Slider is fully controlled — so the migration is what froze the thumb, not just the summary. That makes it a perceived regression from this PR even though the store write never changed.
Your "same pattern likely affects other uiSettings.<section>.<field> = … writes" is correct — seven remain in four files. Three are benign (topic-list-new, schema-list, connect/cluster-details drive their UI from URL or local state and read the setting only at mount). rp-connect/pipelines-list.tsx:185/187 is live: it reads uiSettings.pipelinesList.quickSearch in render and writes it nested, inside a PageComponent that only re-renders on api-store writes — and Chakra's SearchField is fully controlled, so typing in that box does not appear until the next pipelines refresh. That file is still Chakra and belongs to the queued rp-connect PR, so it is on the tracker to fix there rather than in this diff.
The submit failure from your other comment is split out into #2644, since none of those lines is a migration hunk.
"Start Reassignment" fails silently whenever the bandwidth throttle is above the minimumAlso pre-existing — Symptom. Click Start Reassignment with any throttle above the slider minimum: a toast flashes for a fraction of a second, then nothing. No error, no alert, nothing in the console, and the wizard stays on step 3. Re-clicking behaves the same. The only way to submit is to drag the slider to its minimum. Why the throttle step fails. Redpanda doesn't support per-broker property patches — it uses cluster config. So the throttle can never be applied on Redpanda, for every broker, every time. Note the HTTP 200: the failure is per-resource in the body, so nothing rejects at the fetch layer. Why it's silent. } catch (_err) {
closeToast(toastRef); // ← the flash: loading toast opened, then closed
return false; // ← error object dropped
}
const success = await this.setTrafficLimit(request);
if (!success) {
return false;
}and Suggested fixes, smallest first:
} catch (err) {
closeToast(toastRef);
showToast({
status: 'error',
title: 'Could not set bandwidth throttle',
description: err instanceof Error ? err.message : String(err),
duration: 6000,
});
console.error('setTrafficLimit failed', err);
return false;
}The
Happy to split this into its own issue if you'd rather keep the PR scoped; it's a functional gap on Redpanda rather than anything about the Registry migration. |
…across the poll `PageComponent` force-updates the whole page on every store write, and the reassignments poll fires every three seconds. The column arrays were built inline in render, so each poll handed TanStack fresh `header`/`cell` function identities — v9's `flexRender` calls `createElement` on those, so React saw a new component type and remounted the header cells, taking any open sort menu with them. Sorting worked, but only if you beat the poll to the menu item. The five wizard tables now build their columns once. The bandwidth slider and the throttle summary also lagged input by up to a poll: `settings.maxReplicationTraffic = x` mutates the nested object the `uiSettings` proxy's `get` returns, so `updateSettings()` never ran and no subscriber was notified. Master's Chakra slider hid half of it behind its own internal drag state; the Registry Slider is fully controlled, so the thumb froze too. The write now goes through the top-level trap and `StepReview` subscribes to the value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were on `DEFAULT_TABLE_PAGE_SIZE` (50), the legacy-parity size the Chakra DataTable used. Ten is the Registry pager's own default and the first of its "Rows per page" options, and both pages keep the choice in the URL, so a wider page is one select away and survives a reload. The quotas route's `pageSize` fallback moves with it: `.catch(50)` only fires for a URL value outside the schema's range, and landing on 50 when the page now defaults to 10 would be its own surprise. `quota-pagination.spec.ts` hardcoded 50 as the app default for its "no pager below one page" check, so it moves too. That assertion is vacuous today for an unrelated reason — it locates `[aria-label="pagination"]` while the Registry pager labels its group `"Pagination"` — and the pager renders unconditionally on that page, so the spec cannot see either state. Left as found; noted so it can be fixed as a spec change rather than inside this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Split into #2644 — Fix 3 I have deliberately left alone: whether the throttle should be offered on Redpanda at all is a product call, not a bug fix. It cannot work there for any broker, and the feature already branches on Your other two findings are fixed in this PR (03e7b12) with replies on each thread. Thank you for the throughput on this one — the response-body paste is what made the silent path obvious. |
Chakra exit · Phase 2 (route by route) · PR 12 of 14
Part of the Chakra exit plan and live tracker. Branch
chakra-exit/12-reassign-partitions, based onmaster(rebased onto it 2026-09-09, 0 commits behind).Sixteen files: ten source (including the new
wizard-steps;misc/kowl-tabledeleted), two unit tests, one Playwright spec, pluspackage.jsonand both lockfiles for thereact-highlight-wordsdrop. The shell and the three steps sharecurrentStepand the selection maps, so they ship together. No API change: every guard (isEnabled,computeWarning,maximumSelectedReplicationFactor) reads the same state it did before. The route had no Playwright coverage, so a smoke spec lands first, in its own commit — it walks step 1 → step 2 and back, because the step state is what a re-skin can break invisibly. It stops before "Start Reassignment": this suite runs against a live cluster.The hunks that carry judgement
components/wizard-steps.tsxreplaces Chakra's<Stepper index={…}>. The Registry shipsdefineStepper, but that owns navigation through its ownmethods— this wizard keepscurrentStepin its own state, so the indicator is a pure function of that index and nothing in it can move the wizard.bandwidth-slider— the Registry Slider renders its own track and thumb with no mark or thumb-tooltip slot, so the five marks and the value bubble are positioned against the same value scale. It also counts thumbs from an array: a scalarvaluefell through to[min, max]and rendered a second, valueless but focusable thumb — one arrow key from writingNaNtomaxReplicationTrafficand intosetReplicationThrottleRate. Caught in review; it takes[sliderValue], clamped (the defaultmaxReplicationTrafficis 0, solog10is-Infinity).subComponent. Every table paginates like the legacy DataTable — 50 a page (10 for the active-reassignments list), pager only past that; Chakra defaulted pagination on, the Registry to 10 with a permanent pager. Step 1 also lost click-to-sort on four columns; restored withDataTableColumnHeader, except the topic-name column, whose header is the search control.onRowSelectionChangeand a placeholderrowSelection; selection is done by thecheckcolumn, so both simply go. Two columns also sharedaccessorKey: 'partitions', so both resolved to the same TanStack column id.CancelReassignmentButton— a Chakra Popover with header/body/footer slots becomes a Registry Popover with plain markup;closeOnBlur={false}andreturnFocusOnClose={false}have no Base UI equivalent and were fighting the default focus behaviour.minW="5xl"(64rem) →xl(56rem),minW="3xl"(48rem) →lg(42rem). The first cut had usedfull, which is 90vw.misc/kowl-table(SearchTitle) is deleted: the sort fix putDataTableColumnHeaderon the Topic column, the search overlay it carried was unreachable (filterOpenwas never set), and nothing else imported it.Fixed while here (pre-existing)
"Remove throttle" read
newThrottleValuefrom the render that had just cleared it and re-applied the current throttle; the handler takes the value explicitly. The topic-throttle dialog now closes after a successful reset.Fifth review round (2026-09-09) — the sort menu, and the slider's input lag
The sort menu was destroyed by the page's own poll about a second after it opened (finding). Not a MobX subtlety — nothing on this page is an
observer.PageComponent.componentDidMountsubscribes to all five Zustand api stores and callsforceUpdate()on any write, so the 3 s reassignments poll re-renders the whole page; the inlinecolumns={[…]}arrays then handed TanStack v9 freshheader/cellfunction identities,flexRenderdoescreateElementon those, and React treats a new function as a new component type — so it unmounted and remounted the header cells, taking the open portal with them. Sorting itself was correct; you had roughly a one-second window to reachAsc/Desc.All five tables in the wizard now build their columns once — an instance field in the class steps (the arrow bodies read
this.propsat call time, so nothing goes stale),useMemokeyed onprops.assignmentsin the nested review table. The same remount was also killing the partition-error popovers in cells.topic-sorting.test.tsxgains the guard: open the Size menu, re-render the page, assert the menu is still mounted — it fails on the pre-fix code.The bandwidth slider and the throttle summary lagged input by up to a poll, and fixing it needed both halves.
uiSettings.reassignment.maxReplicationTraffic = xmutates the nested object the proxy'sgetreturns, soupdateSettings()never ran and no subscriber was notified; the write now assigns the whole section, which goes through the top-level trap. AndStepReviewis now a small function wrapper that subscribes withuseUISettingsStore((s) => s.reassignment.maxReplicationTraffic)around the class (StepReviewContent), because a plainComponentreading through the proxy would not re-render even once the store published.step3-review.test.tsxcovers it. (finding)The wiring is pre-existing — master has the identical assignment and no
observer— but it belongs here rather than in §04's flagged pile: master's Chakra slider masked half of it with its own internal drag state, while the Registry Slider is fully controlled, so the thumb froze too. The third finding from the same round, a silent submit failure on Redpanda, is genuinely outside every migration hunk and is split out into #2644.Also in this PR, unrelated to the migration
The quotas and consumer-group lists now default to ten rows a page, not the legacy-parity 50 (requested directly). Ten is the Registry pager's own default and the first of its "Rows per page" options, and both pages keep the choice in the URL, so a wider page is one select away and survives a reload. The quotas route's
pageSizefallback moves with it —.catch(50)only fires for an out-of-range URL value, and landing on 50 when the page defaults to 10 would be its own surprise — andquota-pagination.spec.ts's hardcoded 50 follows. That spec's "no pager below one page" assertion is vacuous for an unrelated, pre-existing reason: it locates[aria-label="pagination"]while the Registry pager labels its group"Pagination", and the pager renders unconditionally on that page. Left as found, so it can be fixed as a spec change.Effect
Files importing
@redpanda-data/ui: 50 → 43 — the sixreassign-partitions/files plusmisc/kowl-table.tsx, which this PR deletes. OSS-only surface. (Earlier revisions of this body quoted 81 → 74, against the pre-Phase-2 master.)Gates
Re-run after the fifth round (2026-09-09):
type:checkclean · unit 882 tests · integration 1321 tests (the two new specs included), all passing ·bun run lintleaves the tree clean, so CI's dirty-check passes ·lint:checkper changed file reports only pre-existing errors, each verified against an in-place probe of master's copy — see the lint note below.Second review round (2026-09-08)
logDirSummary.totalSizeBytes. Progress bars paint brand for the active state; the slider bubble was background-on-background; the slider's dead "Unlimited" end is gone.Statistic→ RegistryStaton the header row; columnsizehints the Registry never reads are gone from all four tables.Final review (2026-09-08)
Highlighter, thequickSearchread) went withSearchTitle. Comments trimmed.noBarrelFile+ a deadsuppressions/unusedincomponents/icons/index.tsx,noUselessConstructorinactive-reassignments.tsxandreassign-partitions.tsx, andnoExcessiveCognitiveComplexityinreassign-partitions.tsxandstate/ui.ts. The branch also clears master's twonoRestrictedImportserrors on these files.a11y/noNoninteractiveTabindexatreassign-partitions.tsx:351— the fourth round gave the throttle-dialog tooltip a focusable<span tabIndex={0}>trigger so it is reachable by keyboard, which is what Biome objects to. It does not fail CI (bun run lintis fix-mode and this rule's fix is unsafe), but it wants either a<button type="button">trigger or an explicit suppression before merge.Table polish (2026-09-08)
TABLE_OPTIONS.enableHiding: false) instead of per column, so a column added later cannot bring the header menu's "Hide" item back.Fourth review round (2026-09-08)
react-highlight-wordsand its types leavepackage.jsonand both lockfiles —SearchTitlewas the last importer. Lockfiles regenerated with the pinned bun (frontend/.bun-version), so CI's dirty-check stays clean.components/icons(which gainsInboxIcon) instead of being imported ad hoc.TooltipProvider(150 ms, not Base UI's 600) and a focusable trigger. See the lint note above.min-w-56/ml-autoclasses and the link buttonssize="xs", replacing inline styles; the loading skeleton's margin moves onto a wrapper.uiSettings.reassignment.quickSearchandpageSizeSelectare dropped — step 1 no longer reads either.🤖 Generated with Claude Code