Skip to content
Draft
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
118 changes: 67 additions & 51 deletions apps/app/src/components/LogsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Logs view component β€” logs viewer with filtering.
*/

import { useEffect, useMemo, useState } from "react";
import { memo, useEffect, useMemo, useState } from "react";
import { useApp } from "../AppContext";
import type { LogEntry } from "../api-client";
import { formatTime } from "./shared/format";
Expand All @@ -18,6 +18,69 @@ const TAG_COLORS: Record<string, { bg: string; fg: string }> = {
websocket: { bg: "rgba(20, 184, 166, 0.15)", fg: "rgb(20, 184, 166)" },
};

// ⚑ Bolt Performance Optimization
// What: Extract and memoize individual log entries.
// Why: When typing in the search filter, the entire list of logs re-rendered on every keystroke.
// Impact: Prevents O(N) re-renders of DOM nodes for unaffected log lines, significantly improving text input latency.
const LogEntryItem = memo(function LogEntryItem({
entry,
}: {
entry: LogEntry;
}) {
Comment on lines +25 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better readability and maintainability, consider defining the props for LogEntryItem in a separate interface instead of defining them inline. This makes the component's public contract clearer and is a common practice in React with TypeScript.

For example:

interface LogEntryItemProps {
  entry: LogEntry;
}

const LogEntryItem = memo(function LogEntryItem({ entry }: LogEntryItemProps) {
  // ... component implementation
});

return (
<div
className="font-mono text-xs px-2 py-1 border-b border-border flex gap-2 items-baseline"
data-testid="log-entry"
>
{/* Timestamp */}
<span className="text-muted whitespace-nowrap">
{formatTime(entry.timestamp, { fallback: "β€”" })}
</span>

{/* Level */}
<span
className={`font-semibold w-[44px] uppercase text-[11px] ${
entry.level === "error"
? "text-danger"
: entry.level === "warn"
? "text-warn"
: "text-muted"
}`}
>
{entry.level}
</span>

{/* Source */}
<span className="text-muted w-16 overflow-hidden text-ellipsis whitespace-nowrap text-[11px]">
[{entry.source}]
</span>

{/* Tag badges */}
<span className="inline-flex gap-0.5 shrink-0">
{(entry.tags ?? []).map((t: string) => {
const c = TAG_COLORS[t];
return (
<span
key={t}
className="inline-block text-[10px] px-1.5 py-px rounded-lg mr-0.5"
style={{
background: c ? c.bg : "var(--bg-muted)",
color: c ? c.fg : "var(--muted)",
fontFamily: "var(--font-body, sans-serif)",
}}
>
{t}
</span>
);
})}
</span>

{/* Message */}
<span className="flex-1 break-all">{entry.message}</span>
</div>
);
});

export function LogsView() {
const [searchQuery, setSearchQuery] = useState("");

Expand Down Expand Up @@ -163,57 +226,10 @@ export function LogsView() {
</div>
) : (
filteredLogs.map((entry: LogEntry) => (
<div
<LogEntryItem
key={`${entry.timestamp}-${entry.source}-${entry.level}-${entry.message}`}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The key being constructed here is not guaranteed to be unique. If two log entries have the same timestamp, source, level, and message, React will see a duplicate key. This can happen with logs generated in rapid succession (e.g., in a tight loop) if the timestamp resolution is not high enough. Duplicate keys can lead to rendering bugs and should be avoided.

The ideal solution is for each log entry to have a unique ID provided by the backend. If modifying the LogEntry type is not feasible, please be aware of this risk. Additionally, using the full message in the key can be inefficient for very long log messages, which could slightly undermine the performance gains from memoization.

className="font-mono text-xs px-2 py-1 border-b border-border flex gap-2 items-baseline"
data-testid="log-entry"
>
{/* Timestamp */}
<span className="text-muted whitespace-nowrap">
{formatTime(entry.timestamp, { fallback: "β€”" })}
</span>

{/* Level */}
<span
className={`font-semibold w-[44px] uppercase text-[11px] ${
entry.level === "error"
? "text-danger"
: entry.level === "warn"
? "text-warn"
: "text-muted"
}`}
>
{entry.level}
</span>

{/* Source */}
<span className="text-muted w-16 overflow-hidden text-ellipsis whitespace-nowrap text-[11px]">
[{entry.source}]
</span>

{/* Tag badges */}
<span className="inline-flex gap-0.5 shrink-0">
{(entry.tags ?? []).map((t: string) => {
const c = TAG_COLORS[t];
return (
<span
key={t}
className="inline-block text-[10px] px-1.5 py-px rounded-lg mr-0.5"
style={{
background: c ? c.bg : "var(--bg-muted)",
color: c ? c.fg : "var(--muted)",
fontFamily: "var(--font-body, sans-serif)",
}}
>
{t}
</span>
);
})}
</span>

{/* Message */}
<span className="flex-1 break-all">{entry.message}</span>
</div>
entry={entry}
Comment on lines +229 to +231

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The React key for each log entry is constructed as:

key={`${entry.timestamp}-${entry.source}-${entry.level}-${entry.message}`}

If two log entries share the same timestamp, source, level, and message, this could result in duplicate keys, which may cause React to misidentify elements and lead to rendering bugs. If the log data provides a unique identifier (e.g., entry.id), it is preferable to use that as the key. If not, ensure that the combination used is guaranteed to be unique, or consider including an additional unique property if available.

/>
))
)}
</div>
Expand Down
Loading