feat: new topic messages page behind enableNewTopicMessagesPage flag - #2588
feat: new topic messages page behind enableNewTopicMessagesPage flag#2588jvorcak wants to merge 33 commits into
Conversation
Redesigned messages UX (from the Console Messages UX prototype): - Read scope popover (newest/oldest/offset/timestamp) with live tail in the menu, continuous pagination for newest/oldest only, and an illustrated "how reading starts" doc sheet - Filter bar with typed tokens (partition:1, offset>5, key:abc), autocomplete with value suggestions and ghost completion, keyboard chip editing (ArrowLeft unwraps a badge into editable text, Enter recommits in place), and JS predicate filters with live preview - Field filters, partition, quick search and selected message persist in the URL; JS filters stay in sessionStorage - Message detail as docked resizable panel or expanded sheet, with persisted view state (mode, widths, section expansion) and a metadata table, headers grid and troubleshoot reports - View settings sidebar: row density, drag-reorderable columns with per-column config (timestamp format, deserializers, preview fields) - Quick info stats rebuilt on the registry Stat component Fixes surfaced while building it: - assignDeep now replaces arrays wholesale; the index-wise merge mutated shared elements through the uiSettings sync and corrupted reordered arrays (duplicate/lost message columns after drag'n'drop); getMessageColumns heals already-corrupted persisted entries - SidebarInset gets min-w-0 so wide content (messages table) scrolls inside its container instead of widening the page past the viewport
Standalone dev no longer seeds initialFlags from constants.ts defaults overlaid with debug-dialog overrides; back to only E2E globals.
Replace arbitrary pixel font sizes (text-[10px], text-[11px], text-[12.5px], text-[13px]) with the semantic text-caption/text-body-sm/text-label utilities from the redpanda-ui typography scale.
|
| Count | |
|---|---|
| 0 | |
| 🛠 Locally-modified components | 0 |
| ❓ Unknown to registry | 3 |
| 🎨 Off-token palette colours | 7 |
| 🔢 Ad-hoc utility classes | 10 |
Components needing attention
| Status | Component | Uses | Detail |
|---|---|---|---|
| ❓ unknown-to-registry | chip |
1× | not in registry manifest |
| ❓ unknown-to-registry | highlighted-input |
1× | not in registry manifest |
| ❓ unknown-to-registry | listbox |
1× | not in registry manifest |
🎨 Off-token colours (palette literals)
Use semantic tokens (primary, muted-foreground, border, …) instead of raw palette names.
| Class | Uses | Files |
|---|---|---|
green-600 |
8 | 4 |
green-100 |
4 | 3 |
green-950 |
4 | 3 |
green-400 |
2 | 1 |
green-700 |
2 | 1 |
green-200 |
1 | 1 |
green-900 |
1 | 1 |
🔢 Ad-hoc utility classes (arbitrary values)
These bypass the design tokens. Prefer a named scale entry or add a new token.
| Class | Category | Uses | Files |
|---|---|---|---|
text-[10px] |
🔢 text-size | 7 | 4 |
text-[11px] |
🔢 text-size | 5 | 4 |
text-[10.5px] |
🔢 text-size | 4 | 2 |
text-[13px] |
🔢 text-size | 4 | 2 |
rounded-[2px] |
🔢 radius | 3 | 1 |
text-[12.5px] |
🔢 text-size | 2 | 2 |
rounded-[3px] |
🔢 radius | 1 | 1 |
text-[13.5px] |
🔢 text-size | 1 | 1 |
text-[15px] |
🔢 text-size | 1 | 1 |
text-[9px] |
🔢 text-size | 1 | 1 |
Generated by lookout audit-changes.
StatGroup now uses CSS subgrid so stat values align on a shared baseline across a row even when labels wrap.
Drop the client-side predicate preview sandbox (hit list, error highlighting, apply-gating) from the JS filter dialog. The backend already validates and applies the predicate on save.
|
The filter bar is still under developments, other things can be reviewed |
…th pill highlights Replaces the chip-based filter bar with a single real text input whose caret behaves natively (click/arrow/select/backspace anywhere) while recognized field:value words are painted with a subtle pill highlight overlay — no discrete chip elements, no synthetic keyboard "browse" state. - New utils/filter-line.ts continuously parses the whole line into partitionId/fieldTokens/quickSearch, replacing the old commit-on-space model - Quote-aware value parsing (`field:"multi word"`) in filter-token.ts - Suggestions/ghost-completion now scope to the word under the caret - JS predicates keep a small separate chip (can't live inline in free text); js:<label> now seeds the new filter's name instead of its code when the typed text doesn't look like an actual JS expression - Fixes a duplicate-filter bug where a stray Enter after committing a token could reinsert it
…ogic Follow-up review pass on the filter bar rewrite: - Match the JS filter chip's border-only look on the highlighted pills (outline instead of a flat bg-muted fill) and extract a shared FILTER_BADGE_TEXT_CLASS so the chip, the pills, and the suggestion dropdown's labels can't drift out of sync again (they'd already diverged: 13px chip text next to 14px pill text) - Extract sameFieldTokens into filter-token.ts, shared by the filter bar's own resync check and use-messages-url-state's fieldTokensParser.eq (previously two independent copies of the same array-equality check) - parseFilterLine now returns tokenRanges directly instead of the filter bar re-tokenizing/re-parsing the same text a second way for the highlight overlay - Swap arbitrary text-[10.5px]/text-[11px] for the registry's text-caption utility in the suggestion dropdown's headers and sub-labels - Use registry Button (secondary-ghost, icon-xs) for the clear-all control instead of a hand-rolled icon button that happened to reinvent the same preset
…he registry Chip, HighlightedInput, and Listbox are now generic, domain-agnostic registry components (src/components/redpanda-ui/components/) — filter-bar.tsx keeps only the filter-parsing/suggestion logic, with rendering served from the registry.
…pe popover Renders the calendar/time panel as normal flowed content instead of a click-to-open popup nested inside the already-open read-scope popover, with a Unix timestamp label, a registry ToggleGroup for Local/UTC, and matching indigo-600 styling between the selected day and the Now button. Also excludes .playwright-mcp/ from rsbuild's dev-server watcher — it was triggering rebuilds (and occasional rspack panics) on every screenshot/log written into the project root while driving the dev server with Playwright.
malinskibeniamin
left a comment
There was a problem hiding this comment.
Inline findings: P1/P2. No P0/P3 identified.
…d-scope popover Blocker: partition:<non-numeric> produced NaN that desynced the filter bar's resync guard and wiped whatever the user had typed. Majors: - Removed stat.tsx, which shadowed the newer subgrid-aligned stat/index.tsx via file-before-directory module resolution — it was never actually loaded. - Chip's remove button didn't stop propagation, so removing a JS-filter chip bubbled into the filter bar's container and reopened the suggestion dropdown. - Listbox had no ARIA roles; wired role="combobox"/aria-expanded/aria-controls/ aria-activedescendant on the filter input to match. - The filter bar's resync effect couldn't tell "still catching up to our own emission" from "genuinely external," so a parent whose three pieces of state echo back across separate renders (rather than atomically) could destroy in-progress typing. - tokenQueryText wasn't actually lossless — values with whitespace or embedded quotes changed meaning after a URL round-trip. Added backslash-escaping. - An unbalanced/stray quote could swallow the rest of the typed line into one token, silently discarding every filter after it. - offset:N matched via substring containment (offset:12 matched 1123); it now means equality, and the existing-but-unreachable offset comparison-suggestion UI is wired up to the current word-under-caret architecture. - neq returned false (excluding the message) when the field was absent, when "does not equal" should keep it. - The 'c' row-copy shortcut fired on Cmd/Ctrl+C too, clobbering the user's own clipboard selection with the row's JSON. - Keyboard nav stayed armed while the view-settings panel, docs sheet, or read-scope popover was open; ReadScopePopover now reports its own open state via a new onOpenChange prop so all of these can gate on it. - Toggling continuous pagination didn't reset pageIndex like every sibling read-scope handler, so staying on a later page could show a permanently blank table with no page controls to recover with. Every fix is paired with a regression test that fails on the prior behavior and passes with the fix (verified via temporary revert for each).
- Disable partition/max-results/deserializer controls while live tail is streaming, since edits there update state the running stream never picks up. - Stop showing JS filter chips as active when continuous mode has actually dropped them from the request; reset pageIndex when field tokens or quick search change. - Fix a shared-closure bug where hovering any filter suggestion row highlighted the last row instead of the hovered one. - Restrict the partition filter to exact match — reject partition>/</!= instead of silently collapsing them to equality.
…lter - Keyboard nav (visiblePageKeys) now skips re-sorting in continuous mode, matching the table's own sortingDisabled — previously it kept applying urlState.sorting even when the table fell back to server order, so j/arrow-key selection landed on a different row than what was displayed. Extracted messageKey into its own leaf module so the new pure-logic test doesn't pull in sonner/React transitively. - parseFilterInput/parseFilterLine take an optional partitionCount and reject an out-of-range partition:N (e.g. partition:9999 on a 3-partition topic) instead of sending it through as a real request. Threaded through FilterBar from topic.partitionCount.
…e gate Swap the enableNewTopicMessagesPage check from the custom feature-flag provider hook to the shared isFeatureFlagEnabled helper.
malinskibeniamin
left a comment
There was a problem hiding this comment.
Automated /review: 3 finding(s).
offset<=N and offset>=N previously fell through to plain text since parseOperator matched the bare </> prefix first, stranding a leading = in the value. Adds gte/lte to FilterOp, wires them through parsing and client-side matching, and centralizes the operator<->symbol mapping in one table shared by parsing and display formatting.
- Fix "Download Record" silently doing nothing: append the anchor to the DOM before click() (required in Firefox) and revoke the object URL on a later tick instead of racing the download. - Fix keyboard nav (j/k, arrow keys) selecting a different row than what's rendered: derive which column is sortable from a single isSortableColumnId predicate shared by the table's enableSorting and visiblePageKeys, instead of independently guessing and defaulting unrecognized sort ids (stale key/value sorts from the legacy page) to an offset tiebreak the table itself never applies. - Fix column-visibility checkboxes having no accessible name by wiring each one to its label via aria-labelledby. Also drops .playwright-mcp/ from .gitignore per review feedback to use the Playwright CLI instead of MCP.
…-check on quick search - messages-footer.tsx: read onLoadMore/isLoadingMore through refs instead of effect deps, so a fresh inline onLoadMore from the parent no longer tears down and rebuilds the IntersectionObserver every render (which re-fired against the sentinel's current intersection state and auto-loaded the entire topic with no scrolling). - use-client-filters.ts: thread partitionCount through to parseFilterInput so quick search rejects out-of-range partition tokens the same way the filter bar already does, instead of silently blanking the table.
malinskibeniamin
left a comment
There was a problem hiding this comment.
Automated /review: 2 finding(s).
| } | ||
| flush(); | ||
| setIsLoadingMore(false); | ||
| setPhase('done'); |
There was a problem hiding this comment.
Priority: P1
A superseded stream finalizes the state of the stream that replaced it.
runStream starts by calling stop(), which aborts the previous controller, then installs its own. But the previous run's finally block still executes unconditionally — after the new run has already set phase='connecting', cleared bufferRef, and reset stats. The identity check just above guards only abortControllerRef; every state write below it is ungated:
flush()publishes whatever is in the now-sharedbufferRefsetIsLoadingMore(false)setPhase('done')← this linesetBackendPhase(null)
The abort rejection surfaces on a later microtask than the synchronous setup of the new run, so the stale finally reliably lands after it. Reachable on ordinary flows in topic-messages-view.tsx: toggling live tail (the live effect's start aborts the paged stream, whose finally then reports done while live tail is still connecting), changing partition/deserializer/read scope within the 100ms debounce window, and clicking "Load more" while an auto-search restart is in flight.
Consequence: the toolbar and table stop showing progress for a request that is still running — isSearching goes false, the backend phase label disappears, and a continuous-mode load can lose its loading state. With flush() publishing a buffer the new run just emptied, the table can also briefly render as "done, no messages" for a live search.
Correction: capture a generation counter (or compare abortControllerRef.current === abortController) and gate all of the finally writes plus every setPhase/setStats/setBackendPhase inside the for await loop on it, so only the current run may write state. Add a regression test that starts search A, starts search B before A settles, and asserts phase never becomes 'done' from A.
Verify: cd frontend && bun vitest run src/components/pages/topics/messages/hooks/use-message-search.test.tsx
Automated /review.
There was a problem hiding this comment.
It should be fixed now
| // header's top-right — keep the last column's title clear of them. | ||
| className={cn(canSort && 'cursor-pointer select-none', 'last:pr-20')} | ||
| key={header.id} | ||
| onClick={canSort ? header.column.getToggleSortingHandler() : undefined} |
There was a problem hiding this comment.
Priority: P1
Column sorting is mouse-only — no keyboard path exists.
The sort affordance is an onClick on TableHead (a plain <th>) with cursor-pointer select-none styling. There is no role="button"/<button> wrapper, no tabIndex, and no onKeyDown, so the header is not focusable and Enter/Space do nothing. aria-sort correctly announces the current state, which makes the omission worse: a screen-reader or keyboard-only user is told the table is sortable but has no way to sort it. The page's own useKeyboardNav covers row movement and copy, not sorting, so no alternative route exists.
This also conflicts with the repo standard for click handlers on non-interactive elements (role + tabIndex + keyboard handler).
Consequence: WCAG 2.1.1 (keyboard) failure on a primary table interaction of a new customer-facing page.
Correction: render the sortable header label as a real <button type="button"> inside the <th> (keeping aria-sort on the th), move onClick onto it, and give it focus-visible:ring-*; keep the plain <th> for non-sortable columns. Add a test that tabs to the Timestamp header and presses Enter, asserting onSortingChange fired.
Verify: cd frontend && bun vitest run src/components/pages/topics/messages/table/messages-table.test.tsx
Automated /review.
There was a problem hiding this comment.
I think this is more like a component library issue?
The Registry V3 merge bumped @tanstack/react-table to v9 and renamed several component APIs; update this branch's files to match: - messages-table: migrate to useDataTable/DataTableColumnDef and table.FlexRender (react-table v9) - Button: secondary-ghost -> ghost, secondary -> default/primary - Badge: tone neutral -> default - ToggleGroupItem: drop removed buttonProps, keep flex-1 on className
Brokers on CreateTopics response versions < 5 report -1 for partition count and replication factor even when the request named explicit values, so the success dialog rendered em dashes. Fall back to the values the form computed for the request when the broker reports -1.
…search Enter on the 'Search "..."' suggestion runs a fill-text action, and applySuggestionAt only closed the popup for open-js actions — so confirming a plain-text search left the suggestions open. Mark the full-text fill as committing and close the popup when applying it; prefix fills like value: still keep it open for continued typing.
| const { rows: windowedMessages, trimmed } = useMemo( | ||
| () => | ||
| continuousActive || urlState.liveTail | ||
| ? applyDisplayWindow(orderedMessages, DISPLAY_WINDOW_CAP, { newestFirst: continuousNewest }) |
There was a problem hiding this comment.
[P1 Major] Continuous “Newest” cannot advance past 150 rows
What: After three default 50-row pages, this window keeps the first/newest 150 rows. loadMore appends the next older page, but ordering descending and then taking slice(0, 150) discards every row just fetched. A four-page probe leaves offsets 200–51 visible both before and after offsets 50–1 arrive.
Why: Loading beyond the cap changes only the footer counters; users cannot inspect records older than the initial window, so continuous pagination is functionally stuck.
Suggested fix: Make the cap a sliding/virtualized viewport: show the newly loaded older page, preserve the scroll anchor, and keep the trimmed side reachable when scrolling back. Alternatively, stop offering loadMore once fetched rows cannot be displayed.
Verify: Load four 50-row pages in continuous + Newest; assert page-four rows become visible and the newest rows remain reachable.
| if (isLongLived) { | ||
| appendWithSlackCap(bufferRef.current, message, LIVE_BUFFER_MAX, LIVE_BUFFER_SLACK); | ||
| } else { | ||
| bufferRef.current.push(message); |
There was a problem hiding this comment.
[P1 Major] Continuous pagination retains an unbounded message buffer
What: Paged Newest/Oldest requests are not isLongLived, so every loadMore appends here without a cap. The 150-row limit is only applied later while rendering; each flush still clones the entire buffer, and client filters/path hints scan it.
Why: Scrolling a normal large topic retains all decoded and raw payloads and performs increasingly large copies/scans. The page can become progressively slower or exhaust browser memory despite claiming a bounded window.
Suggested fix: Bound the continuous-mode backing buffer/page cache along with the rendered window, while keeping cumulative counts and the next-page token separately; or use a virtualized, explicitly bounded cache.
Verify: Append many continuous pages and assert retained hook state stays bounded while forward/back navigation still works; include a heap/profile check with representative payloads.
| messages={tableData} | ||
| newKeys={search.newKeys} | ||
| onPaginationChange={handlePaginationChange} | ||
| onRetry={handleRefresh} |
There was a problem hiding this comment.
[P1 Major] Live-tail Retry is a no-op after retries exhaust
What: After the loop gives up on the sixth failed start, liveTail remains true and search.error renders the Retry action. That action calls handleRefresh, which immediately returns in live mode; neither active nor start changes, so the loop cannot restart. The toolbar also keeps spinning and says “Streaming live…”.
Why: The visible recovery path does nothing after a transient/persistent outage, while the page falsely reports an inactive consumer as live.
Suggested fix: Expose exhausted/restart state from the live loop. Make Retry reset/restart the loop (or disable live and re-enable it), and derive the toolbar status from the actual loop phase rather than the URL flag.
Verify: Reject through maxRetries, advance timers, click Retry, and assert a new request starts and the status reflects the stopped/retrying state.
There was a problem hiding this comment.
This seems like a potential registry hook
| /* Topic messages live tail: freshly arrived rows flash green then fade. | ||
| Respects prefers-reduced-motion via the global override above. */ | ||
| @keyframes message-flash { | ||
| 0% { | ||
| background-color: var(--color-green-100, #d1fadf); | ||
| } | ||
| 100% { | ||
| background-color: transparent; | ||
| } | ||
| } | ||
| .animate-message-flash { | ||
| animation: message-flash 2.6s ease-out; | ||
| } |
There was a problem hiding this comment.
is there any way we can just use tailwind animations in that particular instance and avoid updating the globals.css file by hand
| // triggered by files that never touch the actual module graph. | ||
| config.watchOptions = { | ||
| ignored: ['**/routeTree.gen.ts'], | ||
| ignored: ['**/routeTree.gen.ts', '**/.playwright-mcp/**'], |
There was a problem hiding this comment.
nit: strongly recommend using playwright CLI over MCP, it's official guidance to save money on tokens
| // Actions for the messages-table view settings (new messages UX) | ||
| setRowDensity: (topicName: string, density: RowDensity) => void; | ||
| getRowDensity: (topicName: string) => RowDensity; | ||
| setMessageColumns: (topicName: string, columns: MessageColumnConfig[]) => void; | ||
| getMessageColumns: (topicName: string) => MessageColumnConfig[]; | ||
| resetViewSettings: (topicName: string) => void; |
There was a problem hiding this comment.
how many of these actions actually could have been handled inside the tanstack table internal store vs needing global zustand setup
| @@ -0,0 +1,79 @@ | |||
| import { XIcon } from 'lucide-react'; | |||
There was a problem hiding this comment.
How does a Chip differ from our existing Badge components.
https://redpanda-ui-registry.netlify.app/docs/badge
Also general comment on adding components, I think we should reserve the redpanda-ui folder for registry components, or components will plan to upstream in the near future.
SpicyPete
left a comment
There was a problem hiding this comment.
Handful of comments, mostly related to using the updated registry theme.
I think with a once-over to update to the new colours, use base components a bit more, and use the new set of text-utility components will resolve most/all of these.
| {seg.text} | ||
| </span> | ||
| ))} | ||
| {ghostText ? <span className="text-muted-foreground/60">{ghostText}</span> : null} |
There was a problem hiding this comment.
With the updated theme colours, we want to avoid color/opacity as much as possible.
Still a valuable pattern, but with the updates we should see if we still need them added in places like this.
| <Button disabled={disabled} onClick={() => onChange(Date.now())} size="sm" type="button" variant="primary"> | ||
| Now | ||
| </Button> | ||
| {/* Scoped override: the registry's `--color-selected` token (indigo-800) reads as a |
There was a problem hiding this comment.
Many of these comments are outdated now with the updated theme.
Suggest we run a cleanup agent to remove comments which aren't too valuable after this branch merges, and be updated to be up to date.
| repaint the selected day with the same shade instead of touching the shared Calendar | ||
| component. `p-0` strips Calendar's own built-in padding, which otherwise stacks on top | ||
| of this panel's own spacing and insets the grid from its siblings. */} | ||
| <div className="[--color-selected-foreground:var(--color-primary-foreground)] [--color-selected:var(--color-primary)]"> |
There was a problem hiding this comment.
Let's double check if we need this still after the registry updates.
| return ( | ||
| <div className="flex flex-col gap-3.5"> | ||
| <div className="flex flex-col gap-1.5"> | ||
| <Label className="text-[10px] uppercase tracking-wide">Deserializer</Label> |
There was a problem hiding this comment.
There are now text utilities to cover this font size and styling, perhaps lets do a pass on aiming to remove inline utility classes.
| <div className="flex h-full min-h-0 flex-col bg-card" data-testid="view-settings-panel"> | ||
| <div className="flex shrink-0 items-center gap-2 border-b px-4 py-3"> | ||
| <div className="min-w-0 flex-1"> | ||
| <div className="font-semibold text-[15px]">View settings</div> |
There was a problem hiding this comment.
Same here for example, lets try aim to rely on our theme sizes as much as possible
| <div className="font-semibold text-[15px]">View settings</div> | ||
| <div className="text-muted-foreground text-xs">Changes apply to the table instantly</div> | ||
| </div> | ||
| <Button className="size-7" onClick={onClose} size="icon" testId="view-settings-close" variant="ghost"> |
There was a problem hiding this comment.
Do we need a larger button size? if so we should upstream that to the Button itself as a fast follow
| return ( | ||
| <div className="flex flex-col gap-3"> | ||
| <div> | ||
| <div className="mb-1 font-semibold text-[10px] text-muted-foreground uppercase tracking-wide"> |
There was a problem hiding this comment.
Another spot we can likely use one of our text utilities for consistency.
Won't comment on more in this PR, but a pass should catch most/all of these.
| <button | ||
| className={cn( | ||
| 'flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-left hover:bg-accent', | ||
| selected && 'bg-accent/60' |
There was a problem hiding this comment.
With the new set of wash colours, we should avoid opacity mixins unless really needed
| <ReadScopePopover {...scopeProps} /> | ||
| <div className="min-w-0 flex-1">{filterSlot}</div> | ||
| {actionsSlot} | ||
| <Button |
There was a problem hiding this comment.
https://ui-playground.netlify.app/button
Does the Lg button work well? we want to avoid custom base level components when possible for consistency
| /** Collapsible section with an uppercase label, optional right-side meta and copy action. | ||
| * Open state is controlled — it lives in the persisted detail view state. */ |
| const HeaderGrid = ({ headers }: { headers: TopicMessage['headers'] }) => ( | ||
| <div className="overflow-hidden rounded-md border bg-card"> | ||
| <div className="grid grid-cols-[minmax(110px,42%)_1fr] border-b"> | ||
| <div className="px-2.5 py-1 font-semibold text-caption text-muted-foreground uppercase tracking-wide">Key</div> |
There was a problem hiding this comment.
can we use semantic html elements for text (like label)
| const TroubleshootNote = ({ payload }: { payload: Payload }) => { | ||
| const report = payload.troubleshootReport; | ||
| if (!report || report.length === 0) { | ||
| return null; | ||
| } | ||
| return ( | ||
| <div className="mt-1.5 rounded-md border border-destructive/40 bg-destructive/5 px-2.5 py-2"> | ||
| <div className="mb-1 flex items-center gap-1.5 font-semibold text-body-sm text-destructive"> | ||
| <AlertTriangleIcon className="size-3.5 shrink-0" /> | ||
| Errors were encountered when deserializing this payload | ||
| </div> | ||
| <div className="flex flex-col gap-1"> | ||
| {report.map((entry) => ( | ||
| <div className="break-words font-mono text-body-sm leading-relaxed" key={entry.serdeName}> | ||
| <span className="font-semibold capitalize">{entry.serdeName}:</span> {entry.message} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); |
| > | ||
| <div className="min-w-0 break-all px-2.5 py-1.5 text-primary">{header.key}</div> | ||
| <div className="min-w-0 break-all border-l px-2.5 py-1.5"> | ||
| {text === null ? <span className="text-muted-foreground italic">null</span> : text} |
There was a problem hiding this comment.
I'd rather render nothing than null
| const startSheetResize = (e: React.PointerEvent) => { | ||
| e.preventDefault(); | ||
| const startX = e.clientX; | ||
| const startWidth = sheetWidth; | ||
| let latestWidth = startWidth; | ||
| const onMove = (event: PointerEvent) => { | ||
| latestWidth = Math.min(Math.max(480, startWidth + (startX - event.clientX)), window.innerWidth - 80); | ||
| setSheetWidth(latestWidth); | ||
| }; | ||
| const onUp = () => { | ||
| window.removeEventListener('pointermove', onMove); | ||
| window.removeEventListener('pointerup', onUp); | ||
| document.body.style.userSelect = ''; | ||
| document.body.style.cursor = ''; | ||
| patchDetailViewState({ sheetWidth: latestWidth }); | ||
| }; | ||
| window.addEventListener('pointermove', onMove); | ||
| window.addEventListener('pointerup', onUp); | ||
| document.body.style.userSelect = 'none'; | ||
| document.body.style.cursor = 'col-resize'; | ||
| }; |
There was a problem hiding this comment.
this seems overengineered, would resizable suffice?
| useEffect(() => { | ||
| const onKeyDown = (e: KeyboardEvent) => { | ||
| const target = e.target as HTMLElement; | ||
| if (e.key === 'Escape' && !expanded && !/^(input|textarea|select)$/i.test(target.tagName)) { | ||
| onClose(); | ||
| } | ||
| }; | ||
| window.addEventListener('keydown', onKeyDown); | ||
| return () => window.removeEventListener('keydown', onKeyDown); | ||
| }, [expanded, onClose]); |
There was a problem hiding this comment.
iirc there's already a use-keyboard-shortcut (or something similar) in console
| if (e.key === 'c' && selectedKey && !(e.metaKey || e.ctrlKey || e.altKey)) { | ||
| const text = getCopyText(selectedKey); | ||
| if (text !== undefined) { | ||
| navigator.clipboard | ||
| .writeText(text) | ||
| .then(() => toast.success('Value copied to clipboard')) | ||
| .catch(() => toast.error('Could not copy to clipboard')); | ||
| } |
There was a problem hiding this comment.
| import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, DownloadIcon, Trash2Icon } from 'lucide-react'; | ||
| import { useEffect, useRef } from 'react'; | ||
|
|
||
| import { prettyBytes } from '../../../../../utils/utils'; |
There was a problem hiding this comment.
| import { prettyBytes } from '../../../../../utils/utils'; | |
| import { prettyBytes } from 'utils/utils'; |
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akra type seams
Five app-owned wrappers keep their exports and get Registry internals, so
their importers do not change:
- tsx-utils: DefaultSkeleton -> SkeletonText; InfoText -> one Tooltip /
TooltipTrigger / TooltipContent tree (placement narrows to top|bottom|
left|right, no caller passed one); OptionGroup -> RadioGroup +
RadioGroupItem nested in the Registry Label. The file no longer imports
@redpanda-data/ui.
- misc/section: Chakra Section -> Card size="full" kept in block flow with
Section's px-6; props are children, id, className and a grid-area-only
style. The 17 call sites that passed py / my / minWidth / gridArea /
overflowY / borderColor move them to a class, a wrapper or gridArea;
flexDirection on a block Box was a no-op. Overview's <hr> keeps the
margins the Chakra Section injected. LogsTab loses a `variant` no caller
passed.
- misc/tabs: Chakra Tabs items= -> Registry Tabs, uncontrolled with
defaultValue, underline variant, activateOnFocus so arrow keys still
select; panels keep Chakra's 1rem padding; isFitted -> layout="full".
Its six tests pass unchanged.
- misc/statistic: Chakra Stat -> Registry Stat label/value (title, value,
className; hint and SpaceProps had no callers). Spacing moves to the
rows that lay stats out (Flex gap={8} / flex gap-8).
- misc/small-stat: Flex + Text -> a flex div.
Type seams: state/ui.ts takes SortingState from @tanstack/react-table (same
shape as the v8 one Chakra re-exported) and defines ConnectTabKeys itself
instead of importing it from pages/connect/overview. TopicTabId stays an
`import type` from topic-details until the #2588 stream lands.
utils/legacy-data-table.ts is untouched on purpose: it derives its types
from the legacy DataTable because the v9 ColumnDef is the wrong shape for
it, and deletes with the last legacy table.
Importers of @redpanda-data/ui: 95 -> 89.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Redesigned messages UX (from the Console Messages UX prototype):
field:valuewords pill-highlighted (parsed continuously as you type, no commit step), word-scoped autocomplete/ghost-completion, and JS predicate filters as a separate chipFixes surfaced while building it:
Before:

After:

