feat: introduce dither-kit component library and integrate generative…#33
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (14)
🚧 Files skipped from review as they are similar to previous changes (14)
📝 WalkthroughWalkthroughThe PR adds a dither-kit charting system with Cartesian and polar charts, telemetry-driven dashboard widgets, pause/reconnect controls, styling/tooling configuration, and new chart primitives. It also updates Raft proposal flow and snapshot installation behavior. ChangesDither dashboard and telemetry visualization
Raft proposal and snapshot handling
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Dashboard
participant useClusterTelemetry
participant TelemetryProvider
participant ChartRoot
participant CanvasRenderer
Dashboard->>useClusterTelemetry: connect with data and error handlers
useClusterTelemetry->>TelemetryProvider: receive telemetry frames
TelemetryProvider-->>Dashboard: normalized metrics and histories
Dashboard->>ChartRoot: provide chart data and configuration
ChartRoot->>CanvasRenderer: provide chart context and render targets
CanvasRenderer-->>Dashboard: draw animated dither chart layers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (2)
drmq-dashboard/tsconfig.app.json (1)
7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid leaking Node.js types into the client environment.
Including
"node"in thecompilerOptions.typesfor the browser environment (tsconfig.app.json) makes Node.js globals likeprocess,Buffer, and__dirnameavailable to client-side code during type checking. This can lead to runtime errors if these globals are accidentally used, as they do not exist in the browser.Node types should typically be isolated to
tsconfig.node.json(which is used to type-check files likevite.config.ts).♻️ Proposed refactor
- "types": ["vite/client", "node"], + "types": ["vite/client"],🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@drmq-dashboard/tsconfig.app.json` at line 7, Remove "node" from the compilerOptions.types array in the browser-focused tsconfig.app.json, leaving only the Vite client types. Keep Node.js typings isolated to tsconfig.node.json for configuration files such as vite.config.ts.drmq-dashboard/src/App.tsx (1)
98-105: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winKeep provider side effects outside the state updater.
Updater replay under React Strict Mode can call
reconnect()twice, creating duplicate intervals or sockets. Derive the action frompaused, perform it once, then update state.Proposed fix
const handleTogglePause = useCallback(() => { - setPaused(prev => { - const next = !prev; - if (next) provider.disconnect(); - else provider.reconnect(); - return next; - }); - }, [provider]); + if (paused) provider.reconnect(); + else provider.disconnect(); + setPaused(!paused); + }, [paused, provider]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@drmq-dashboard/src/App.tsx` around lines 98 - 105, Update handleTogglePause so it derives the next pause state from the current paused value, performs provider.disconnect or provider.reconnect outside the setPaused updater exactly once, then updates state with the derived value. Keep provider in the callback dependencies and avoid side effects inside the updater passed to setPaused.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@drmq-dashboard/src/components/dither-kit/bar-canvas.tsx`:
- Around line 142-150: The bloom canvases are copied every animation frame even
when the source renderer is unchanged. In bar-canvas.tsx lines 142-150 and
radar-canvas.tsx lines 158-164, gate the bloom clear/draw operation on the
renderer becoming dirty through paint() or bloom transitioning to active, while
preserving the existing bloom enable and hover conditions.
- Around line 103-117: The bar rendering logic around paintColumn must support
reversed orientation for negative bars. Determine negativity from the bar’s
value, pass a reversed-orientation option for negative bars, and update
paintColumn so the outline is drawn at bottom and dithering density progresses
in reverse while positive bars retain the current behavior.
- Around line 156-157: Normalize non-positive animation durations across
bar-canvas.tsx lines 156-157, radar-canvas.tsx lines 170-171, and avatar.tsx
lines 145-152. Update the animation progress logic in the bar and radar canvas
loops to treat duration values at or below zero as instant completion, and
update the avatar animation flow to avoid scheduling its RAF loop for those
durations; preserve normal animation behavior for positive durations.
In `@drmq-dashboard/src/components/dither-kit/bar.tsx`:
- Around line 49-50: Update the dimmed calculation in the bar-series rendering
logic to also apply focusDataKey-based legend spotlight behavior, matching the
existing handling for areas and common tooltip items. Preserve the current
selectedDataKey dimming while dimming non-focused bar children when focusDataKey
is set.
In `@drmq-dashboard/src/components/dither-kit/button.tsx`:
- Around line 20-27: Update DitherButton’s button ref handling so the consumer
ref from DitherButtonProps is composed with the internal buttonRef rather than
overwriting it through prop spreading. Ensure both refs receive the rendered
button element, preserving buttonRef.current for the paint/resize effect;
alternatively, omit ref from the public props if external refs are not
supported.
In `@drmq-dashboard/src/components/dither-kit/cartesian-canvas.tsx`:
- Around line 60-64: Update the long-lived RAF loop around the canvas paint
logic to read the latest animation state on each revision rather than capturing
animate and duration at creation. Extend paintSig to include seedOf(key), or use
a single context paint revision that covers animation settings and palette
seeds, so color-only configuration changes invalidate cached fills and repaint
with current values.
In `@drmq-dashboard/src/components/dither-kit/cartesian-root.tsx`:
- Around line 121-129: Flatten nested React fragments before layer
classification in both the Cartesian root child-routing logic and the Polar root
equivalent. Update the child traversal used by both roots so fragment-wrapped
back and dom children are classified by layer and routed to their respective
arrays instead of defaulting to the SVG layer; apply this at cartesian-root.tsx
lines 121-129 and polar-root.tsx lines 99-107.
In `@drmq-dashboard/src/components/dither-kit/chart-context.tsx`:
- Around line 283-287: Update the entranceDone calculation near the entrance
state so it returns true immediately whenever animate is false, regardless of
the stored entrance.done value or revision. Preserve the existing revision-based
completion behavior while animation remains enabled, ensuring markers are
released if animation is disabled mid-reveal.
In `@drmq-dashboard/src/components/dither-kit/legend.tsx`:
- Around line 24-54: Update the legend entries in the chart.names.map flow so
temporary focus via onPointerEnter, onPointerLeave, onFocus, and onBlur remains
available when isClickable is false. Keep persistent selection disabled in that
mode, but use non-button focusable entries (or equivalent) and restore pointer
events for focus/hover interaction despite the parent pointer-events-none;
retain button behavior and click selection when isClickable is true.
In `@drmq-dashboard/src/components/dither-kit/palette.ts`:
- Around line 39-40: Update isDitherColor to validate only PALETTE’s own
properties instead of using the inherited-property-inclusive in operator, while
preserving the existing string type check.
In `@drmq-dashboard/src/components/dither-kit/pie-canvas.tsx`:
- Around line 131-141: Update the animation loop in draw and the bloom
transition handling near paint so bloomCtx is cleared and redrawn only when
paint() changes the main canvas or bloom transitions between enabled and
disabled states. Remove the per-frame clearRect/drawImage calls while preserving
the existing bloom visibility conditions.
- Around line 57-59: Update the requestAnimationFrame loop around the animation
state handling to read and validate state.current.animate and
state.current.animationDuration on every frame rather than only during resize.
Ensure zero or otherwise invalid durations are handled safely before progress
calculations so the first frame does not produce NaN or leave needsFill active
indefinitely, while preserving reduced-motion behavior.
In `@drmq-dashboard/src/components/dither-kit/polar-context.tsx`:
- Around line 184-190: Clamp and sanitize innerRadiusRatio to the documented 0–1
range before the innerRadius calculation in the geometry setup, treating NaN as
a safe valid fallback. Use the clamped ratio in the chartType === "pie"
computation so donut geometry always remains valid.
In `@drmq-dashboard/src/components/dither-kit/polar.ts`:
- Around line 58-70: Update axisAtAngle to return null when the axes array is
empty, while preserving the existing nearest-axis calculation and index return
for non-empty data. Ensure the return type reflects the nullable result so
PolarRoot.setHoverIndex can receive no hover target.
In `@drmq-dashboard/src/components/dither-kit/scales.ts`:
- Around line 44-62: The bounds scan in the series loop of the scales builder
must inspect both endpoints of every stacked band. Update the logic around bands
and buildYScale’s returned bounds so each point’s point[0] and point[1] can
update both min and max, preserving the existing flat-range fallback when all
values are zero.
In `@drmq-dashboard/src/components/dither-kit/tooltip.tsx`:
- Around line 45-69: Update the tooltip motion configuration in the
AnimatePresence/motion.div block to use the dither kit’s existing reduced-motion
pattern. When reduced motion is enabled, apply immediate positioning and opacity
without spring or fade transitions; preserve the current animated behavior
otherwise.
In `@drmq-dashboard/src/pages/Dashboard.tsx`:
- Around line 115-124: Update the throughputData mapping in Dashboard so the
produce series uses the corresponding total throughput sample minus
consumeHist[i], rather than treating throughputHistory directly as produce
throughput. Preserve the existing scaling, rounding, fallback behavior, and
consume series mapping.
- Around line 138-158: Move the stateful hooks in Dashboard, including
prevCommit, latencyHistory, and their useEffect calls, above the error/loading
early returns, or isolate the loaded view in a child component so hook order
remains consistent. Update the throughput chart to avoid presenting
metrics.throughputHistory as the produce series; use the correct
produce-specific data and label pairing.
In `@drmq-dashboard/src/services/telemetry/MockTelemetryProvider.ts`:
- Around line 67-70: Align the seeded consumeHistory values with the scale
produced for live consume samples by reusing the same conversion helper or
seeding with realistic consumeMB inputs. Update both the initial consumeHistory
generation and the corresponding live-sample path so values do not collapse to
the minimum during the first 30 ticks.
---
Nitpick comments:
In `@drmq-dashboard/src/App.tsx`:
- Around line 98-105: Update handleTogglePause so it derives the next pause
state from the current paused value, performs provider.disconnect or
provider.reconnect outside the setPaused updater exactly once, then updates
state with the derived value. Keep provider in the callback dependencies and
avoid side effects inside the updater passed to setPaused.
In `@drmq-dashboard/tsconfig.app.json`:
- Line 7: Remove "node" from the compilerOptions.types array in the
browser-focused tsconfig.app.json, leaving only the Vite client types. Keep
Node.js typings isolated to tsconfig.node.json for configuration files such as
vite.config.ts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fe78ac80-e1c1-4f4c-af66-804edafc1646
⛔ Files ignored due to path filters (1)
drmq-dashboard/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (54)
drmq-broker/src/main/java/com/drmq/broker/raft/RaftNode.javadrmq-dashboard/components.jsondrmq-dashboard/dither-kit.jsondrmq-dashboard/package.jsondrmq-dashboard/src/App.tsxdrmq-dashboard/src/components/ClusterTopology.tsxdrmq-dashboard/src/components/DashboardWidgets.tsxdrmq-dashboard/src/components/dither-kit/area-chart.tsxdrmq-dashboard/src/components/dither-kit/area.tsxdrmq-dashboard/src/components/dither-kit/avatar.tsxdrmq-dashboard/src/components/dither-kit/bar-canvas.tsxdrmq-dashboard/src/components/dither-kit/bar-chart.tsxdrmq-dashboard/src/components/dither-kit/bar.tsxdrmq-dashboard/src/components/dither-kit/block-legend.tsxdrmq-dashboard/src/components/dither-kit/button.tsxdrmq-dashboard/src/components/dither-kit/cartesian-canvas.tsxdrmq-dashboard/src/components/dither-kit/cartesian-root.tsxdrmq-dashboard/src/components/dither-kit/chart-context.tsxdrmq-dashboard/src/components/dither-kit/common-context.tsxdrmq-dashboard/src/components/dither-kit/dither-paint.tsdrmq-dashboard/src/components/dither-kit/dot.tsxdrmq-dashboard/src/components/dither-kit/gradient.tsxdrmq-dashboard/src/components/dither-kit/grid.tsxdrmq-dashboard/src/components/dither-kit/index.tsdrmq-dashboard/src/components/dither-kit/legend.tsxdrmq-dashboard/src/components/dither-kit/lib.tsdrmq-dashboard/src/components/dither-kit/palette.tsdrmq-dashboard/src/components/dither-kit/pie-canvas.tsxdrmq-dashboard/src/components/dither-kit/pie-chart.tsxdrmq-dashboard/src/components/dither-kit/pie.tsxdrmq-dashboard/src/components/dither-kit/pixel.tsdrmq-dashboard/src/components/dither-kit/polar-context.tsxdrmq-dashboard/src/components/dither-kit/polar-root.tsxdrmq-dashboard/src/components/dither-kit/polar.tsdrmq-dashboard/src/components/dither-kit/radar-canvas.tsxdrmq-dashboard/src/components/dither-kit/radar-chart.tsxdrmq-dashboard/src/components/dither-kit/radar-frame.tsxdrmq-dashboard/src/components/dither-kit/radar.tsxdrmq-dashboard/src/components/dither-kit/reference-line.tsxdrmq-dashboard/src/components/dither-kit/scales.tsdrmq-dashboard/src/components/dither-kit/series-context.tsxdrmq-dashboard/src/components/dither-kit/sparkline.tsxdrmq-dashboard/src/components/dither-kit/tooltip.tsxdrmq-dashboard/src/components/dither-kit/use-chart-dimensions.tsdrmq-dashboard/src/components/dither-kit/x-axis.tsxdrmq-dashboard/src/components/dither-kit/y-axis.tsxdrmq-dashboard/src/index.cssdrmq-dashboard/src/pages/Dashboard.tsxdrmq-dashboard/src/services/telemetry/MockTelemetryProvider.tsdrmq-dashboard/src/services/telemetry/WebSocketTelemetryProvider.tsdrmq-dashboard/src/types/telemetry.tsdrmq-dashboard/src/useClusterTelemetry.tsdrmq-dashboard/tsconfig.app.jsondrmq-dashboard/vite.config.ts
💤 Files with no reviewable changes (1)
- drmq-broker/src/main/java/com/drmq/broker/raft/RaftNode.java
| const base = t.base[i] ?? rows - 1 | ||
| const grown = base + ((t.top[i] ?? base) - base) * bp | ||
| // Bars grow from the zero baseline toward the value. Positive values | ||
| // sit above the baseline (smaller pixel), negative ones below it — | ||
| // paintColumn wants the higher edge first, so order the pair. | ||
| const top = Math.min(grown, base) | ||
| const bottom = Math.max(grown, base) | ||
| const active = s.hoverIndex === i | ||
| const hoverDim = | ||
| s.hoverIndex != null && !active && s.isMouseInChart ? 0.5 : 1 | ||
| const slot = s.barSlot(i, si, keys.length) | ||
| const c0 = Math.round(slot.x * fx) | ||
| const c1 = Math.round((slot.x + slot.width) * fx) | ||
| for (let x = c0; x < c1; x++) { | ||
| paintColumn(c, x, top, bottom, seed, { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reverse the dither edge for negative bars.
For negative values, top is the zero baseline, but paintColumn always draws its outline at top and fades toward it. The visible value edge is therefore placed at zero instead of the negative endpoint. Add a reversed-orientation option that puts the outline at bottom and reverses density for negative bars.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@drmq-dashboard/src/components/dither-kit/bar-canvas.tsx` around lines 103 -
117, The bar rendering logic around paintColumn must support reversed
orientation for negative bars. Determine negativity from the bar’s value, pass a
reversed-orientation option for negative bars, and update paintColumn so the
outline is drawn at bottom and dithering density progresses in reverse while
positive bars retain the current behavior.
| if (bloomCtx) { | ||
| const on = | ||
| s.bloom !== "off" && | ||
| (!s.bloomOnHover || s.isMouseInChart || s.hovered) | ||
| if (on) { | ||
| bloomCtx.clearRect(0, 0, cols, rows) | ||
| bloomCtx.drawImage(canvas, 0, 0) | ||
| } | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Gate bloom copies on dirty or activation changes. Both renderers copy their complete canvas on every RAF before checking whether a repaint is required.
drmq-dashboard/src/components/dither-kit/bar-canvas.tsx#L142-L150: copy to bloom only afterpaint()or when bloom becomes active.drmq-dashboard/src/components/dither-kit/radar-canvas.tsx#L158-L164: apply the same dirty-state gating.
📍 Affects 2 files
drmq-dashboard/src/components/dither-kit/bar-canvas.tsx#L142-L150(this comment)drmq-dashboard/src/components/dither-kit/radar-canvas.tsx#L158-L164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@drmq-dashboard/src/components/dither-kit/bar-canvas.tsx` around lines 142 -
150, The bloom canvases are copied every animation frame even when the source
renderer is unchanged. In bar-canvas.tsx lines 142-150 and radar-canvas.tsx
lines 158-164, gate the bloom clear/draw operation on the renderer becoming
dirty through paint() or bloom transitioning to active, while preserving the
existing bloom enable and hover conditions.
| if (!animStart) animStart = now | ||
| const prog = animate ? Math.min(1, (now - animStart) / duration) : 1 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Normalize animation durations consistently. Negative public duration values prevent all three animations from completing and leave their RAF loops running indefinitely.
drmq-dashboard/src/components/dither-kit/bar-canvas.tsx#L156-L157: treat non-positive duration as instant or clamp it.drmq-dashboard/src/components/dither-kit/radar-canvas.tsx#L170-L171: apply the same normalization.drmq-dashboard/src/components/dither-kit/avatar.tsx#L145-L152: avoid scheduling the loop when duration is non-positive.
📍 Affects 3 files
drmq-dashboard/src/components/dither-kit/bar-canvas.tsx#L156-L157(this comment)drmq-dashboard/src/components/dither-kit/radar-canvas.tsx#L170-L171drmq-dashboard/src/components/dither-kit/avatar.tsx#L145-L152
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@drmq-dashboard/src/components/dither-kit/bar-canvas.tsx` around lines 156 -
157, Normalize non-positive animation durations across bar-canvas.tsx lines
156-157, radar-canvas.tsx lines 170-171, and avatar.tsx lines 145-152. Update
the animation progress logic in the bar and radar canvas loops to treat duration
values at or below zero as instant completion, and update the avatar animation
flow to avoid scheduling its RAF loop for those durations; preserve normal
animation behavior for positive durations.
| <AnimatePresence> | ||
| {show && items.length > 0 && ( | ||
| <motion.div | ||
| key="dither-tooltip" | ||
| initial={{ | ||
| opacity: 0, | ||
| x: "-50%", | ||
| y: "-115%", | ||
| top: chart.tooltipTop, | ||
| left: chart.tooltipLeft, | ||
| }} | ||
| animate={{ | ||
| opacity: 1, | ||
| x: "-50%", | ||
| y: "-115%", | ||
| top: chart.tooltipTop, | ||
| left: chart.tooltipLeft, | ||
| }} | ||
| exit={{ opacity: 0 }} | ||
| transition={{ | ||
| type: "spring", | ||
| stiffness: 520, | ||
| damping: 38, | ||
| mass: 0.6, | ||
| }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== outline tooltip.tsx =="
ast-grep outline drmq-dashboard/src/components/dither-kit/tooltip.tsx --view expanded || true
echo "== relevant lines tooltip.tsx =="
cat -n drmq-dashboard/src/components/dither-kit/tooltip.tsx | sed -n '1,220p'
echo "== search reduced motion in dither-kit =="
rg -n "useReducedMotion|reduced motion|prefers-reduced-motion|reducedMotion" drmq-dashboard/src/components/dither-kit drmq-dashboard/src/components -g '!**/dist/**' || trueRepository: samuel025/DRMQ
Length of output: 5460
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== dither-paint reduced motion block =="
cat -n drmq-dashboard/src/components/dither-kit/dither-paint.ts | sed -n '155,190p'
echo "== pixel reduced motion block =="
cat -n drmq-dashboard/src/components/dither-kit/pixel.ts | sed -n '90,120p'Repository: samuel025/DRMQ
Length of output: 1992
Honor reduced-motion preferences for tooltip transitions. The tooltip still springs and fades even when OS motion reduction is enabled; follow the same reduced-motion pattern used elsewhere in the dither kit and switch to immediate positioning/opacity in that mode.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@drmq-dashboard/src/components/dither-kit/tooltip.tsx` around lines 45 - 69,
Update the tooltip motion configuration in the AnimatePresence/motion.div block
to use the dither kit’s existing reduced-motion pattern. When reduced motion is
enabled, apply immediate positioning and opacity without spring or fade
transitions; preserve the current animated behavior otherwise.
| consumeHistory: Array.from({ length: 30 }, (_, i) => | ||
| Math.max(3, 30 + Math.sin(i / 4 + 1) * 20 + Math.random() * 8) | ||
| ), | ||
| errorHistory: Array.from({ length: 30 }, () => 0), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use one scale for seeded and live consume history.
The seed is centered near 30, while typical live samples evaluate below 3 and are clamped. The graph therefore collapses to a flat floor over the first 30 ticks. Generate both paths through the same conversion helper or seed from realistic consumeMB values.
Also applies to: 158-159
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@drmq-dashboard/src/services/telemetry/MockTelemetryProvider.ts` around lines
67 - 70, Align the seeded consumeHistory values with the scale produced for live
consume samples by reusing the same conversion helper or seeding with realistic
consumeMB inputs. Update both the initial consumeHistory generation and the
corresponding live-sample path so values do not collapse to the minimum during
the first 30 ticks.
…andling, and data normalization.
… avatar system
Summary by CodeRabbit