Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## 2024-05-18 - [Recharts Optimization]
**Learning:** In Next.js components utilizing Recharts, transforming props data into chart-ready data points directly in the component body can cause significant performance bottlenecks due to expensive unnecessary recalculations on every React re-render. Some components have `useMemo` optimizations, but others are missing them.
**Action:** Always wrap data transformation logic (e.g., mapping arrays or reducing data arrays) within `useMemo` hooks before passing the result to Recharts components.
## 2024-05-18 - [EventList Rendering Optimization]
**Learning:** In a virtualized list component (e.g., `react-window` List), if a function executed on every row calculates a value based on a parent's constant prop (e.g., parsing a `Date` string), it creates a severe performance bottleneck during scrolling due to redundant string parsing on every render cycle.
**Action:** Always parse constant date strings or pre-calculate expensive parent-level constants using `useMemo` at the top level of the component and pass the computed primitive value (e.g., timestamp in milliseconds) down to the virtualized rows to eliminate repeated processing.
16 changes: 9 additions & 7 deletions packages/web/src/components/dashboard/event-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ type FlatRow =

const ROW_HEIGHT = 36;

function formatElapsed(timestamp: string, sessionStartedAt: string): string {
function formatElapsed(timestamp: string, sessionStartMs: number): string {
const t = new Date(timestamp).getTime();
const start = new Date(sessionStartedAt).getTime();
const start = sessionStartMs;
if (Number.isNaN(t) || Number.isNaN(start)) return "";
const diffSec = Math.max(0, Math.floor((t - start) / 1000));
const h = Math.floor(diffSec / 3600);
Expand Down Expand Up @@ -207,7 +207,7 @@ function RowView({
type RowProps = {
rows: FlatRow[];
selectedIdx: number;
sessionStartedAt: string;
sessionStartMs: number;
onSelect: (idx: number) => void;
onToggleGroup: (firstIdx: number) => void;
};
Expand All @@ -217,7 +217,7 @@ function Row({
style,
rows,
selectedIdx,
sessionStartedAt,
sessionStartMs,
onSelect,
onToggleGroup,
}: RowComponentProps<RowProps>) {
Expand All @@ -230,7 +230,7 @@ function Row({
<RowView
label="Tool"
preview={`${row.toolName} x${row.count}`}
time={formatElapsed(row.firstEvent.timestamp, sessionStartedAt)}
time={formatElapsed(row.firstEvent.timestamp, sessionStartMs)}
icon={getIcon(row.firstEvent)}
isSelected={false}
onClick={() => onToggleGroup(row.groupFirstIdx)}
Expand All @@ -252,7 +252,7 @@ function Row({
<RowView
label={label}
preview={preview}
time={formatElapsed(row.event.timestamp, sessionStartedAt)}
time={formatElapsed(row.event.timestamp, sessionStartMs)}
icon={getIcon(row.event)}
isSelected={row.idx === selectedIdx}
onClick={() => onSelect(row.idx)}
Expand All @@ -271,6 +271,8 @@ export function EventList({
expandedGroups,
onToggleGroup,
}: EventListProps) {
const sessionStartMs = useMemo(() => new Date(sessionStartedAt).getTime(), [sessionStartedAt]);

const rows = useMemo(
() => buildFlatRows(groups, expandedGroups, selectedIdx),
[groups, expandedGroups, selectedIdx],
Expand All @@ -292,7 +294,7 @@ export function EventList({
rowProps={{
rows,
selectedIdx,
sessionStartedAt,
sessionStartMs,
onSelect,
onToggleGroup,
}}
Expand Down
Loading