Skip to content

[Chakra exit · Phase 3 · PR 13] frontend: remove the @redpanda-data/ui dependency - #2650

Open
SpicyPete wants to merge 6 commits into
masterfrom
chakra-exit/13-remove-dependency
Open

[Chakra exit · Phase 3 · PR 13] frontend: remove the @redpanda-data/ui dependency#2650
SpicyPete wants to merge 6 commits into
masterfrom
chakra-exit/13-remove-dependency

Conversation

@SpicyPete

@SpicyPete SpicyPete commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What

Phase 3, PR 13 of the Chakra exit: remove @redpanda-data/ui from Console. Phase 2 closed on 2026-09-11 with #2645, #2647 and #2649, leaving five importers — all of them the files that mount ChakraProvider rather than use it. This unmounts them and takes the dependency out.

master goes from 5 importers to 0, and the bare git grep from 13 to 0.

Removed

The provider, from the three app roots (app.tsx, embedded-app.tsx, federation/federated-providers.tsx) and both test-utils.tsx render helpers.

Dependencies@redpanda-data/ui, the five @chakra-ui/* peers, @emotion/css; both lockfiles regenerated with the pinned bun 1.4.0.

Filesutils/redpanda-theme.ts (the Table.baseStyle kebab-case patch), all three build shims (react-onclickoutside, date-fns-tz, and its zonedTimeToUtc deep path), types/react-19-global-jsx.d.ts, tests/mocks/redpanda-ui.ts. src/shims/ and src/utils/vendor/ are now empty and gone.

All four resolver aliases, which lived in more places than the tracker recorded — rsbuild.config.ts, rsbuild.config.federation-test.ts, test.shared.ts and rstest.config.unit.ts. The src+tests metric could not see the last three because they sit at the frontend/ root.

Two rstest console suppressions that existed only for this package's Popover (isInPortal) and DataTable (debugTable: true). The nested-<button> suppression stays — consumers/group-details.tsx now renders the Registry Accordion, which has the same shape, so only its attribution changed.

Three judgment calls worth a reviewer's eye

  1. react-router-dom$ went too. It was aliased only so @module-federation/bridge-react's v6 shim could not shadow the real package for @redpanda-data/ui's NavLink/Link. Console declares no react-router-dom dependency and imports it nowhere, so the alias now points at a path that need not exist. test:federation passes.

  2. react-19-global-jsx.d.ts is safe to delete. Its stated reason was the react-markdown@8 vendored by this package, but Console's own code also uses the global JSX namespace — so I checked rather than assumed: all 22 files that write a bare JSX. already import type { JSX } from 'react', so zero rely on the global shim. type:check is clean without it.

  3. defaultTheme="light" stays. Chakra pinned light mode and the Registry ThemeProvider now carries that alone. Flipping it to system is a user-visible change onto a page with known dark-mode defects (the hard-light rp-connect YAML editor, the near-invisible Builder.io headlines), so it belongs to the dark-theme pass, not here.

Deliberately not in this PR

  • The Chakra interop CSS — the three globals.css hacks, body.chakra-ui-dark, the two nav.chakra-* selectors, the dead .ant-* rules. That is PR 14, which is a paired cross-repo change with cloud-ui. A built CSS chunk still carries them, as expected at this step.
  • The biome.jsonc noRestrictedImports entries for @redpanda-data/ui and @chakra-ui/react are kept, but not as the regression fence I first called them — review caught that. The rule's level is "warn", and CI's lint gate is ultracite fix failing on a dirty tree, so a warn leaves nothing dirty and gates nothing. The real fence is the one this PR creates for free: type:check now fails with TS2307 on any re-added import, because the packages are simply not installed. The entries stay as a developer-facing hint in lint:check output. (For the record, all five paths in that rule block — including mobx, mobx-react-lite and yup — now have zero live importers.)

Dependency result

Nine libraries collapse to a single install each — @tanstack/react-table 9 (was 8+9), react-markdown 10 (8+10), remark-gfm 4 (3+4), react-syntax-highlighter 16 (15+16), react-day-picker 9 (8+9), @hookform/resolvers 5 (3+5), remark-emoji 5 (3+5), react-hook-form 7.78 (was also 7.66), framer-motion 12 (was also 10).

react-datepicker, react-icons, yup, date-fns-tz, @textea/json-viewer and chakra-react-select leave the tree entirely, along with 78 MB of node_modules (54 @redpanda-data + 24 @chakra-ui).

Emotion does not leave, and CI is what proved it. shadowlink-create-page.tsx imports DevTool from @hookform/devtools — a package never declared in package.json, which resolved only as a transitive of @redpanda-data/ui. Removing Chakra took it with it and the clean-install build failed on Module not found. My local gates had passed because bun install leaves removed packages' directories behind in node_modules, so every one of them resolved a stale copy; I now re-ran them against a reinstall from the lockfile. It is declared at ^4.4.0 (the version Chakra was resolving, so nothing changes about what Console runs against) in dependencies rather than devDependencies, because that DevTool renders unconditionally in the production bundle — a form-debugging overlay shipping to users, older than this branch. Since @hookform/devtools depends on @emotion/react and @emotion/styled, emotion stays at 13 packages.

So the "emotion absent from the dependency tree" line in the definition of done is now blocked on one line of unrelated code, not on Chakra. Deleting that <DevTool> render is a small follow-up that fixes a real bug and collects the last of the payoff; I have flagged it rather than folding it in here.

I audited the rest of @redpanda-data/ui's dependency set the same way — @hookform/devtools was the only phantom. The other eight packages Console imports from it are all declared directly.

A correction to my own earlier claim here, caught in review: I had written that @emotion/is-prop-valid leaves with Chakra and that motion therefore falls back to its own prop filter. That is wrong. master carried two copies — Chakra's 0.8.8 and framer-motion's own 1.4.0 — and 1.4.0 is still hoisted top-level, so motion resolves it before and after and no fallback ever happens. The real latent risk runs the other way: motion's prop filtering will change silently on the day @hookform/devtools is dropped, which is worth knowing before doing it.

Gates

  • type:check clean
  • bun run build green; the bundle contains no @redpanda-data/ui, chakra-ui JS or framer-motion 10
  • 997 unit + 1438 integration + 1 federation passing
  • Per-file lint:check (ANSI-stripped, one file per invocation): zero net-new diagnostics, and three noRestrictedImports eliminated. Compared by swapping master's content in at the same path — a differently-named probe file silently gets a different rule set, because biome.jsonc keys overrides on **/*.test.tsx and rstest.setup*.ts.
  • Both lockfiles byte-stable across repeat bun install runs, so CI's dirty-tree check will not trip

Review

Reviewed at high effort; twelve findings, eight applied on the branch (see fix(review): commits). The three that mattered most were all in my own additions: the .snyk note I had already "fixed" once was still self-contradictory, dropping the Creating Table Instance pattern left the whole console.info interceptor as a pass-through no-op, and the two body claims corrected above.

Not applied, deliberately:

  • The <DevTool> render itself. Gating it and moving the dependency to devDependencies is the highest-value follow-up — it retires the Snyk ignore and finally takes emotion out — but it is a behaviour change plus a lockfile regeneration, well outside "remove the dependency".
  • Raising the biome rule to error. It would not gate CI either, since ultracite fix exits 0 with errors present.

Second review, on the full stack

Reviewed again with both PRs as one diff against master, plus a repo-wide audit for anything still naming the removed packages. Four more pieces of residue in this PR, now fixed:

  • A dead legacyUi splitChunks cacheGroup in rsbuild.config.ts, still testing node_modules/@redpanda-data/ui/ at priority: 40, enforce: true — outranking the monaco group, so it reads as a live constraint.
  • 35 lines of global monkey-patching in rstest.setup.ts: userEvent.setup was wrapped app-wide so HTMLElement.prototype.focus could be re-opened for assignment, purely because Chakra's @zag-js/focus-visible assigned to it. @zag-js is gone. (The matchMedia mock stays — the Registry theme provider needs it — but no longer credits Chakra for it.)
  • defaultSelectChakraStyles / inlineSelectChakraStyles in topics/Tab.Messages/constants.tschakra-react-select style objects, zero consumers.
  • no-legacy.md still told agents "Chakra components: throughout codebase (migrate on touch)".

It also caught that an earlier round's "simplification" of rsbuild.config.federation-test.ts introduced a real aliasing bug — passing the module-level sharedAliases object straight to rspack, which normalises resolve.alias in place, while the rstest projects share that same object. The defensive copy is back with a comment saying why.

One thing the review corrected about my reasoning rather than my code: judgment call 2 above is right, but my grep was lucky — test-utils.tsx does write bare JSX.Element, and type-checks only because it imports type JSX inside a multi-line react import that a single-line pattern misses. The conclusion holds (@types/react@19 declares no global JSX, confirmed with a probe that errors TS2503), but the audit was weaker than I described.

Still wants a human

An embedded / Cloud UI pass: federated-providers.tsx is the host path and nothing in this repo exercises it end to end.

🤖 Generated with Claude Code

…i dependency

Unmounts ChakraProvider from the three app roots and both test-harness
render helpers, then takes the package and its five @chakra-ui/* peers and
@emotion/css out of package.json and both lockfiles.

Deleted: utils/redpanda-theme.ts (the Table.baseStyle kebab-case patch), the
three build shims (react-onclickoutside, date-fns-tz and its zonedTimeToUtc
deep path) with all four resolver aliases across rsbuild.config.ts and
rsbuild.config.federation-test.ts, the @redpanda-data/ui aliases in
test.shared.ts and rstest.config.unit.ts with tests/mocks/redpanda-ui.ts, and
types/react-19-global-jsx.d.ts — no Console file relies on the global JSX
namespace, every bare `JSX.` use already imports the type from react.

react-router-dom$ goes with them: it was aliased only so
@module-federation/bridge-react's v6 shim could not shadow the real package
for @redpanda-data/ui's NavLink/Link. Console declares no react-router-dom
dependency and imports it nowhere.

Also drops the two rstest console suppressions that existed for
@redpanda-data/ui's Popover and DataTable, a dead rs.mock in
kowl-json-view.test.tsx, and route-utils' createVisibleSidebarItems (the
legacy sidebar helper, zero references) — which lets NavLinkProps.icon take
the same union SidebarItem declares instead of `any`.

Nine libraries collapse to one install each: @tanstack/react-table 9,
react-markdown 10, remark-gfm 4, react-syntax-highlighter 16,
react-day-picker 9, @hookform/resolvers 5, remark-emoji 5, react-hook-form
7.78, framer-motion 12. react-datepicker, react-icons, yup, date-fns-tz,
@textea/json-viewer, chakra-react-select and emotion leave the tree entirely.

The Chakra interop CSS stays for PR 14, which is paired with cloud-ui.

Gates: type:check clean, build green, 997 unit + 1438 integration passing,
both lockfiles stable across repeat installs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Clean — no registry drift, off-token colours, or ad-hoc classes

App: frontend · Scope: diff vs origin/master · Files: 18

Count
⚠️ Outdated registry components 0
🛠 Locally-modified components 0
❓ Unknown to registry 0
🎨 Off-token palette colours 0
🔢 Ad-hoc utility classes 0

Generated by lookout audit-changes.

SpicyPete and others added 3 commits September 11, 2026 13:02
…viding

CI caught what the local gates could not: `shadowlink-create-page.tsx`
imports `DevTool` from `@hookform/devtools`, which was never declared in
package.json — it resolved only as a transitive of `@redpanda-data/ui@4.2.0`.
Removing that package took it with it, so the clean-install build failed on
`Module not found: Can't resolve '@hookform/devtools'` and type-check on
TS2307.

The local run passed because `bun install` leaves the removed packages'
directories behind in node_modules; every gate resolved the stale copy.
Verified by reinstalling from the lockfile before re-running them.

Declared at ^4.4.0 — the version @redpanda-data/ui was resolving, so this
changes nothing about what Console has been running against. It goes in
`dependencies`, not `devDependencies`, because the DevTool renders
unconditionally in the production bundle.

Audited the rest of @redpanda-data/ui's dependency set the same way:
@hookform/devtools was the only phantom. The other eight packages Console
imports from it (@hookform/resolvers, @tanstack/react-table,
react-hook-form, react-markdown, remark-gfm, remark-emoji,
react-syntax-highlighter, react-day-picker) are all declared directly.

Consequence to be explicit about: @hookform/devtools depends on
@emotion/react and @emotion/styled, so emotion stays in the dependency tree
(13 packages) even though Chakra is gone. Clearing that means not shipping a
form-debugging overlay to users — a real bug, older than this branch, filed
rather than fixed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Self-review caught a factual error I introduced. The earlier commit's note
claimed the @hookform/devtools path "left with @redpanda-data/ui" — but the
follow-up commit declared @hookform/devtools directly, so that path did not
leave, it became direct. uuid@8.3.2 is still in the tree and
@hookform/devtools is its only dependant.

Wrong statements are worse in a security-policy file than anywhere else, so
the note now says where uuid actually comes from, and records that dropping
the production <DevTool> render would retire this ignore outright.

The reachability conclusion is unchanged: no direct uuid imports, no
caller-provided buffers, not reachable from user input.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eight findings from a review of this branch, all verified against HEAD before
applying.

.snyk — my own previous commit's note was still wrong, and self-contradictory:
it listed "Docker test tooling" as a second uuid path and then said
@hookform/devtools is uuid's only dependant. The lockfile settles it —
uuid@8.3.2 has exactly one dependant and testcontainers declares no uuid at
all. The retirement condition was wrong too: Snyk resolves the path from the
dependency graph, so deleting the <DevTool> render alone would not retire the
ignore; removing the dependency is what does.

rstest.setup.ts — `/Creating Table Instance/` was the only info-channel
pattern, so dropping it in the previous commit left the whole console.info
interceptor and its originalInfo capture as an unconditional pass-through that
still shadowed the global for 1438 tests. Removed both. Verified no test spies
on console.info and no surviving pattern targets that channel.

route-utils.tsx — NavLinkProps.group was dead (processSidebarItem never sets
it; the sidebar groups off SidebarGroupedItems.group), processSidebarItem's
doc comment still named the legacy consumer this branch deleted, and the new
SidebarItemIcon alias reached for the ambient UMD `React` namespace in a file
that already imports its types explicitly — SVGProps now comes from react.

Also: field-description.test.tsx asserted `textContent === ''` to work around
a hidden node the Chakra render wrapper used to add, which passes for any
markup that renders no text — now toBeEmptyDOMElement(). schema-create.tsx's
SVG-override comment and the ui-development skill doc both still attributed
the unlayered emotion reset to Console's own Chakra; both now name the cloud-ui
host, matching how PR 14 re-scoped the globals.css comments. And
rsbuild.config.federation-test.ts spreads a single source, so it just assigns
it.

Gates: type:check clean, build green, 997 unit + 1438 integration + 1
federation passing, no lint drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SpicyPete SpicyPete changed the title [Chakra exit · Phase 3 · PR 13/16] frontend: remove the @redpanda-data/ui dependency [Chakra exit · Phase 3 · PR 13] frontend: remove the @redpanda-data/ui dependency Sep 11, 2026
@SpicyPete
SpicyPete marked this pull request as ready for review September 11, 2026 21:24
@SpicyPete SpicyPete self-assigned this Sep 11, 2026
@SpicyPete
SpicyPete requested review from a team, Mateoc, eblairmckee, r-vasquez and yougotashovel and removed request for a team September 11, 2026 21:24
SpicyPete and others added 2 commits September 11, 2026 14:48
Review of the full Phase 3 stack against master, plus a repo-wide audit for
anything still naming the removed packages.

rsbuild.config.ts — a dead `legacyUi` splitChunks cacheGroup still tested for
`node_modules/@redpanda-data/ui/` at `priority: 40, enforce: true`. It can
never match now, and it outranks the monaco group, so it reads as a live
constraint to anyone tuning chunk priorities. A full build emits no
`lib-redpanda-ui` chunk either way.

rstest.setup.ts — 35 lines of global monkey-patching existed only for Chakra:
`userEvent.setup` was wrapped app-wide so `makeFocusPatchWritable()` could
re-open `HTMLElement.prototype.focus` for assignment, because Chakra's
@zag-js/focus-visible assigned to it. @zag-js is no longer in the tree and
user-event's own patchFocus is the only other toucher, so the wrapper, the
helper and the `_rawUserEvent` import are all gone. The matchMedia mock stays —
the Registry theme provider and the responsive hooks read media queries — but
its comment no longer credits Chakra's colorMode polling for it.

topics/Tab.Messages/constants.ts — `defaultSelectChakraStyles` and
`inlineSelectChakraStyles` were chakra-react-select style objects (they use
Chakra's `_hover` pseudo-prop syntax) with zero consumers anywhere in src or
tests. chakra-react-select left the tree with the package.

rsbuild.config.federation-test.ts — restored the defensive copy of
`sharedAliases`. An earlier review round simplified it to pass the object
directly, but that module-level object is shared with the rstest projects and
rspack normalises `resolve.alias` in place; the comment now says so.

no-legacy.md — the skill doc still told agents "Chakra components: throughout
codebase (migrate on touch)" and framed the packages as deprecated rather than
uninstalled, so it now names the real state and the real fence (TS2307, not
lint — biome's noRestrictedImports is level "warn").

Gates: type:check clean, build green, 997 unit + 1438 integration + 1
federation passing, no lint drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pre-existing race, unrelated to Chakra, but it failed `test-integration (1/2)`
on this branch and it was worth being sure it was not mine before dismissing
it: I had just removed a global `userEvent.setup` monkey-patch in
rstest.setup.ts, and the symptom — `expected 'c' to be 'custom:0'`, only the
first keystroke landing — looks exactly like broken typing.

It is not related. That patch only made user-event's getter-only
`HTMLElement.prototype.focus` descriptor accept assignment, for the benefit of
Chakra's @zag-js/focus-visible; with @zag-js out of the tree nothing assigns,
its `override` stays null and the getter passes straight through, so the patch
was a no-op before it was deleted. Evidence: the same commit passes on re-run,
the test passes 12/12 and its whole file 8/8 locally, and PR 14 — a superset of
this branch — went green on the same shard.

The real cause is in the test. `NodeConfigForm` reports through
`onConfigChange` from a `useEffect` keyed on react-hook-form's watched values,
so the final keystroke's propagation can land after `user.type` resolves, and
the assertion read `onConfigChange.mock.calls.at(-1)` synchronously. Wrapped it
in `waitFor`; it asserts the same thing. No other test in the file uses
`waitFor`, so the same latent race sits across the rest of it — this fixes the
one that actually trips.

Gates: type:check clean, 1438 integration passing, the file 10/10 on repeat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SpicyPete
SpicyPete marked this pull request as draft September 11, 2026 22:21
@SpicyPete
SpicyPete marked this pull request as ready for review September 11, 2026 23:01
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