Skip to content

fix(logs): split compact logs layout changes from PR 788#878

Closed
NieiR wants to merge 1 commit intoding113:devfrom
NieiR:split/pr788-logs-ui
Closed

fix(logs): split compact logs layout changes from PR 788#878
NieiR wants to merge 1 commit intoding113:devfrom
NieiR:split/pr788-logs-ui

Conversation

@NieiR
Copy link
Contributor

@NieiR NieiR commented Mar 7, 2026

Summary

Split from PR #788 to isolate the logs page UI changes into a standalone, independently reviewable PR.

Problem

Issue #766 requested optimizing the logs page to reduce above-fold space, specifically:

  • Collapsing the filter criteria panel by default
  • Making the stats panel more compact
  • Compressing empty states

Related Issues

Changes

Core Changes

This PR extracts only the logs dashboard layout changes from PR #788:

  • usage-logs-sections.tsx - Added compactEmpty prop to ActiveSessionsList
  • usage-logs-stats-panel.tsx - Redesigned from 4-card grid to compact inline bar
  • usage-logs-view-virtualized.tsx - Collapsible filter panel with active filter count badge
  • active-sessions-list.tsx - Added compactEmpty prop for compact empty state

Excluded from this PR

  • Chart typing or build fixes
  • Pricing i18n fixes
  • Pricing semantics changes
  • Lease budget / rate-limit behavior changes

Why Split This Way

  • The logs page UI changes are independently reviewable user-facing work
  • Keeping the branch limited to the 4 logs-related files preserves a clean review boundary
  • Unrelated regressions can be reviewed and merged separately instead of being mixed into one PR

How to Verify

  • Run git diff --name-only upstream/dev...split/pr788-logs-ui and confirm only the 4 intended files differ
  • Run bun run typecheck
  • Open the logs dashboard and verify the compact layout / filter presentation changes visually

Description enhanced by Claude AI

Greptile Summary

This PR refactors the logs dashboard UI into a more compact layout by collapsing the filter panel into a togglable trigger row (with an active-filter count badge), replacing the glassmorphism 4-column stats card with an inline stats bar, and showing a compact single-row empty state for the active sessions list.

Key changes:

  • usage-logs-view-virtualized.tsx: Filter criteria section is now a Collapsible with a chevron trigger; active filter count badge appears when collapsed; statsFilters wrapped in useMemo; auto-expand logic via a filterAutoExpandRef fires only on initial mount if URL filters are present.
  • usage-logs-stats-panel.tsx: Entire glassmorphism card replaced with a slim inline flex row using StatItem sub-components and a local pipe Separator. cn import correctly removed.
  • active-sessions-list.tsx: New compactEmpty boolean prop renders a compact single-line empty state instead of the full card; all hooks are still called unconditionally (valid).
  • usage-logs-sections.tsx: Passes compactEmpty to the ActiveSessionsList used on the logs page.

Issues found:

  • A brief layout shift occurs in ActiveSessionsList when compactEmpty=true because the compact path is gated on !isLoading, so the component renders at full card height during the initial fetch before collapsing.
  • activeFilterCount only increments for a date range when both startTime and endTime are set, but hasStatsFilters checks each independently — a partial date range will show the stats panel without incrementing the filter badge.
  • The local Separator component in usage-logs-stats-panel.tsx shadows shadcn's Separator in name only; no runtime impact but may confuse future maintainers.

Confidence Score: 4/5

  • Safe to merge — changes are purely UI/layout with no data-layer or business-logic impact; issues found are cosmetic or minor.
  • All four files contain well-scoped UI refactors. The logic issues are low-severity: the layout shift is a brief visual flicker on initial load, and the filter-count inconsistency only matters in a partial date-range edge case. No data mutations, no auth changes, and no new async paths are introduced.
  • src/components/customs/active-sessions-list.tsx (layout shift with compactEmpty) and src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx (date range counting in activeFilterCount).

Important Files Changed

Filename Overview
src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx Replaces the always-visible filter card with a collapsible trigger row, adds an active-filter count badge, wraps statsFilters in useMemo, and auto-expands the panel on initial load when URL-based filters are detected. Minor inconsistency found between activeFilterCount and hasStatsFilters for partial date ranges.
src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx Replaces the glassmorphism 4-column card grid with a compact inline stats bar. Code is cleaner; cn import correctly removed; local Separator component has a naming collision risk with shadcn's Separator (cosmetic only).
src/components/customs/active-sessions-list.tsx Adds compactEmpty prop for a compact single-row empty state. Logic is placed after all hooks (valid). A layout shift occurs during initial load because the compact path is gated on !isLoading, causing the component to briefly render at full card height before collapsing.
src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx One-line change that passes the new compactEmpty prop to ActiveSessionsList. Straightforward and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Logs Page Mount] --> B{URL has filters?}
    B -- Yes --> C[filterAutoExpandRef fires\nsetIsFiltersOpen true]
    B -- No --> D[isFiltersOpen = false\nFilter panel collapsed]
    C --> E[Filter panel expanded]

    D --> F{User clicks trigger row}
    F --> G[isFiltersOpen toggles]
    G --> H{activeFilterCount > 0\nand panel collapsed?}
    H -- Yes --> I[Show active filter Badge]
    H -- No --> J[No badge]

    E --> K{hasStatsFilters?}
    D --> K
    K -- Yes --> L[UsageLogsStatsPanel\nInline stats bar]
    K -- No --> M[Stats panel hidden]

    N[ActiveSessionsList compactEmpty=true] --> O{isLoading?}
    O -- true --> P[Full card layout\nLoading spinner]
    O -- false --> Q{totalCount === 0?}
    Q -- Yes --> R[Compact single-row\nempty state]
    Q -- No --> S[Full card with sessions]
Loading

Last reviewed commit: eed4b90

Greptile also left 3 inline comments on this PR.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a focused set of UI improvements for the logs dashboard, aiming to enhance the user experience through a more compact and organized layout. By making the statistics panel inline and the filter section collapsible, the changes contribute to a cleaner and more efficient interface for viewing and managing logs. These targeted updates are intentionally isolated to ensure a streamlined review process.

Highlights

  • Logs Dashboard Layout Refinement: This pull request introduces significant layout changes to the logs dashboard, focusing on a more compact and user-friendly presentation for statistics and filters.
  • Collapsible Filter Section: The filter criteria section has been converted into a collapsible component, which now displays a badge indicating the number of active filters when collapsed. It also auto-expands if URL-based filters are present on initial load.
  • Compact Statistics Panel: The usage statistics panel has been redesigned to a compact, inline format, replacing the previous multi-column card layout for better space utilization.
  • Active Sessions List Enhancement: The ActiveSessionsList component now supports a compactEmpty prop, allowing it to render a condensed, single-line display when there are no active sessions, further optimizing UI space.
Changelog
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx
    • Added the compactEmpty prop to the ActiveSessionsList component.
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
    • Refactored the usage statistics panel to a compact, inline layout.
    • Introduced StatItem and Separator helper components for the new layout.
    • Removed unused cn import and various comments/JSDoc.
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
    • Implemented a collapsible filter section using Collapsible components.
    • Added a badge to display the count of active filters when the filter section is collapsed.
    • Introduced auto-expansion for the filter panel when URL-based filters are detected on initial load.
    • Memoized statsFilters and activeFilterCount for performance optimization.
    • Imported new UI components: ChevronDown, Badge, Collapsible related components.
  • src/components/customs/active-sessions-list.tsx
    • Added a compactEmpty prop to the ActiveSessionsListProps interface.
    • Implemented logic to render a compact, single-line display when compactEmpty is true and the list of sessions is empty.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

总体概述

该拉取请求包含四个文件的更改:添加新的 compactEmpty 属性以支持紧凑型空状态,将统计面板的玻璃态UI重构为简洁的标题样式布局,为日志查看组件引入可折叠式筛选面板及自动展开逻辑。

变更内容

组件/文件 变更摘要
UI布局重构
src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
将玻璃态容器结构替换为紧凑的标题布局。移除了外层面板和网格容器,改用基于flex的单行标题设计。引入StatItem和Separator原语渲染单个指标,替代原有的四列网格结构。骨架屏和错误/内容状态现已内联渲染。
交互式筛选面板
src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
新增可折叠式筛选面板,包含CollapsibleTrigger和CollapsibleContent组件。添加isFiltersOpen状态和活跃筛选计数Badge。实现URL驱动的自动展开逻辑:初始加载时,若存在URL筛选条件,面板将通过一次性effect自动展开。statsFilters和activeFilterCount现已通过useMemo优化。
组件属性增强
src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx, src/components/customs/active-sessions-list.tsx
为ActiveSessionsList组件添加新的公共属性compactEmpty(布尔值,默认false)。启用早期返回路径:当compactEmpty为true、isLoading为false且totalCount为0时,渲染紧凑型单行空状态。

代码审查工作量

🎯 3 (中等) | ⏱️ ~25 分钟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰明确地反映了主要变化:从PR 788中分离日志页面的紧凑布局改动。
Description check ✅ Passed PR描述清晰详细,准确说明了拆分的目的、包含的四个文件变更、排除的内容,以及验证方法。与代码变更内容完全对应。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added bug Something isn't working area:UI labels Mar 7, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively refactors the logs dashboard UI to be more compact, as described. The changes are well-executed, introducing a collapsible filter section and a more condensed statistics panel. The code is cleaner and more componentized as a result. I have a couple of suggestions for minor improvements to enhance maintainability and conciseness.

Comment on lines +284 to +296
const activeFilterCount = useMemo(() => {
let count = 0;
if (filters.userId !== undefined) count++;
if (filters.keyId !== undefined) count++;
if (filters.providerId !== undefined) count++;
if (filters.sessionId) count++;
if (filters.startTime && filters.endTime) count++;
if (filters.statusCode !== undefined || filters.excludeStatusCode200) count++;
if (filters.model) count++;
if (filters.endpoint) count++;
if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) count++;
return count;
}, [filters]);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation for calculating activeFilterCount is correct, but it could be refactored to be more concise and easier to maintain, especially if more filters are added in the future. Using a declarative array-based approach can improve readability.

Suggested change
const activeFilterCount = useMemo(() => {
let count = 0;
if (filters.userId !== undefined) count++;
if (filters.keyId !== undefined) count++;
if (filters.providerId !== undefined) count++;
if (filters.sessionId) count++;
if (filters.startTime && filters.endTime) count++;
if (filters.statusCode !== undefined || filters.excludeStatusCode200) count++;
if (filters.model) count++;
if (filters.endpoint) count++;
if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) count++;
return count;
}, [filters]);
const activeFilterCount = useMemo(() => {
const conditions = [
filters.userId !== undefined,
filters.keyId !== undefined,
filters.providerId !== undefined,
!!filters.sessionId,
!!(filters.startTime && filters.endTime),
filters.statusCode !== undefined || filters.excludeStatusCode200,
!!filters.model,
!!filters.endpoint,
(filters.minRetryCount ?? 0) > 0,
];
return conditions.filter(Boolean).length;
}, [filters]);

if (compactEmpty && !isLoading && totalCount === 0) {
return (
<div
className={`flex items-center gap-3 px-4 py-2.5 rounded-lg border border-border/50 bg-card/30 text-sm ${className}`}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For improved maintainability and consistency with other components in the project, consider using the cn utility function for concatenating class names instead of template literals. This is especially useful for handling conditional classes and keeps the code cleaner.

To apply this, you would first need to import the utility:

import { cn } from "@/lib/utils";

Then, you could update this line to:

className={cn("flex items-center gap-3 px-4 py-2.5 rounded-lg border border-border/50 bg-card/30 text-sm", className)}

@github-actions github-actions bot added the size/S Small PR (< 200 lines) label Mar 7, 2026
Comment on lines +69 to +79
if (compactEmpty && !isLoading && totalCount === 0) {
return (
<div
className={`flex items-center gap-3 px-4 py-2.5 rounded-lg border border-border/50 bg-card/30 text-sm ${className}`}
>
<Activity className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="text-muted-foreground">{tc("activeSessions.title")}</span>
<span className="text-xs text-muted-foreground">{tc("activeSessions.empty")}</span>
</div>
);
}
Copy link

Choose a reason for hiding this comment

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

Layout shift during initial load with compactEmpty

When compactEmpty is true the condition is only checked after loading completes (!isLoading). Until then the component falls through to the full card layout (with maxHeight="200px" and a loading spinner), so on initial mount there is a visible height jump from ~200 px down to ~40 px once the empty state is confirmed.

To avoid the shift you can show the compact row during loading as well:

Suggested change
if (compactEmpty && !isLoading && totalCount === 0) {
return (
<div
className={`flex items-center gap-3 px-4 py-2.5 rounded-lg border border-border/50 bg-card/30 text-sm ${className}`}
>
<Activity className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="text-muted-foreground">{tc("activeSessions.title")}</span>
<span className="text-xs text-muted-foreground">{tc("activeSessions.empty")}</span>
</div>
);
}
if (compactEmpty && (isLoading || totalCount === 0) && !(isLoading && totalCount > 0)) {

Or more simply, treat compactEmpty as "always render compact when there are no sessions to display (including while loading)":

Suggested change
if (compactEmpty && !isLoading && totalCount === 0) {
return (
<div
className={`flex items-center gap-3 px-4 py-2.5 rounded-lg border border-border/50 bg-card/30 text-sm ${className}`}
>
<Activity className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="text-muted-foreground">{tc("activeSessions.title")}</span>
<span className="text-xs text-muted-foreground">{tc("activeSessions.empty")}</span>
</div>
);
}
if (compactEmpty && totalCount === 0) {

This ensures the element keeps its compact height throughout the loading phase, preventing the layout jump. (The loading spinner inside the compact row is omitted in the current implementation, which is fine — the compact row is a low-profile element and a brief skeleton would be a heavier change.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/customs/active-sessions-list.tsx
Line: 69-79

Comment:
**Layout shift during initial load with `compactEmpty`**

When `compactEmpty` is `true` the condition is only checked after loading completes (`!isLoading`). Until then the component falls through to the full card layout (with `maxHeight="200px"` and a loading spinner), so on initial mount there is a visible height jump from ~200 px down to ~40 px once the empty state is confirmed.

To avoid the shift you can show the compact row during loading as well:

```suggestion
  if (compactEmpty && (isLoading || totalCount === 0) && !(isLoading && totalCount > 0)) {
```

Or more simply, treat `compactEmpty` as "always render compact when there are no sessions to display (including while loading)":

```suggestion
  if (compactEmpty && totalCount === 0) {
```

This ensures the element keeps its compact height throughout the loading phase, preventing the layout jump. (The loading spinner inside the compact row is omitted in the current implementation, which is fine — the compact row is a low-profile element and a brief skeleton would be a heavier change.)

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +284 to +296
const activeFilterCount = useMemo(() => {
let count = 0;
if (filters.userId !== undefined) count++;
if (filters.keyId !== undefined) count++;
if (filters.providerId !== undefined) count++;
if (filters.sessionId) count++;
if (filters.startTime && filters.endTime) count++;
if (filters.statusCode !== undefined || filters.excludeStatusCode200) count++;
if (filters.model) count++;
if (filters.endpoint) count++;
if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) count++;
return count;
}, [filters]);
Copy link

Choose a reason for hiding this comment

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

Inconsistent date-range counting vs hasStatsFilters

The activeFilterCount memo only increments by 1 when both startTime and endTime are set:

if (filters.startTime && filters.endTime) count++;

However, hasStatsFilters (which controls whether the stats panel is rendered) uses Object.values(statsFilters).some(v => v !== undefined && v !== false), so it will be true if either boundary is set alone.

This means it is possible for the stats bar to appear while the filter badge still reads 0 — for example when only a startTime is provided. If the filter form prevents that state in practice this is harmless, but if partial date ranges are a valid intermediate state (e.g. during typing) the badge will mislead the user.

Consider aligning the two checks:

Suggested change
const activeFilterCount = useMemo(() => {
let count = 0;
if (filters.userId !== undefined) count++;
if (filters.keyId !== undefined) count++;
if (filters.providerId !== undefined) count++;
if (filters.sessionId) count++;
if (filters.startTime && filters.endTime) count++;
if (filters.statusCode !== undefined || filters.excludeStatusCode200) count++;
if (filters.model) count++;
if (filters.endpoint) count++;
if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) count++;
return count;
}, [filters]);
if (filters.startTime !== undefined || filters.endTime !== undefined) count++;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
Line: 284-296

Comment:
**Inconsistent date-range counting vs `hasStatsFilters`**

The `activeFilterCount` memo only increments by 1 when _both_ `startTime` and `endTime` are set:

```ts
if (filters.startTime && filters.endTime) count++;
```

However, `hasStatsFilters` (which controls whether the stats panel is rendered) uses `Object.values(statsFilters).some(v => v !== undefined && v !== false)`, so it will be `true` if _either_ boundary is set alone.

This means it is possible for the stats bar to appear while the filter badge still reads 0 — for example when only a `startTime` is provided. If the filter form prevents that state in practice this is harmless, but if partial date ranges are a valid intermediate state (e.g. during typing) the badge will mislead the user.

Consider aligning the two checks:

```suggestion
    if (filters.startTime !== undefined || filters.endTime !== undefined) count++;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +133 to +135
function Separator() {
return <span className="text-border hidden sm:inline">|</span>;
}
Copy link

Choose a reason for hiding this comment

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

Local Separator shadows shadcn's Separator component

The file defines a private Separator function that renders a styled pipe character. The name collides with the commonly-imported shadcn/ui Separator component. While this is scoped to the file and causes no runtime issue today, future developers seeing <Separator /> here may assume it is the radix-based Separator and be confused by the apparent lack of an import.

Consider a more descriptive name such as StatsDivider or PipeDivider to make the local nature obvious.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
Line: 133-135

Comment:
**Local `Separator` shadows shadcn's `Separator` component**

The file defines a private `Separator` function that renders a styled pipe character. The name collides with the commonly-imported shadcn/ui `Separator` component. While this is scoped to the file and causes no runtime issue today, future developers seeing `<Separator />` here may assume it is the radix-based `Separator` and be confused by the apparent lack of an import.

Consider a more descriptive name such as `StatsDivider` or `PipeDivider` to make the local nature obvious.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR implements a clean, focused UI refactoring for the logs dashboard with no significant issues identified.

PR Size: S

  • Lines changed: 322 (158 additions, 164 deletions)
  • Files changed: 4

Review Assessment

Architecture & Design:

  • Clean transition from card-based to inline compact layout
  • Proper use of for to prevent unnecessary re-renders
  • Well-implemented auto-expand logic for filter panel with proper ref-based guard

Code Quality:

  • Follows CLAUDE.md guidelines
  • No emoji in code
  • Proper i18n usage
  • Unused import () correctly removed

Logic & Correctness:

  • Filter count logic correctly handles date range and status code filters
  • Error states properly handled in all components
  • TypeScript types properly maintained

UX Improvements:

  • Compact empty state reduces visual clutter
  • Collapsible filter panel with active count badge improves information density
  • Auto-expand on URL filters improves discoverability

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate for UI refactoring
  • Code clarity - Good

Automated review by Claude AI

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx:
- Around line 133-135: The visual pipe in the Separator component is being
exposed to assistive tech; update the Separator function (Separator) to mark the
element as purely decorative by adding aria-hidden="true" (and optionally
role="presentation") to the <span> so screen readers ignore it while preserving
the visual separator for sighted users.
- Around line 106-118: Replace the hardcoded concatenation passed into the
StatItem detail props with single i18n keys so word order can vary per locale:
create new translation keys (e.g. "logs.stats.totalTokensDetail" and
"logs.stats.cacheTokensDetail") and pass the already formatted numbers as
interpolation parameters (formatted totalInputTokens/totalOutputTokens and
formatted totalCacheCreationTokens/totalCacheReadTokens) to those keys; update
all 5 locale files (zh-CN, zh-TW, en, ja, ru) with the corresponding
translations for those keys. Ensure the change touches the two StatItem usages
in usage-logs-stats-panel.tsx and uses the t(...) call with parameter objects
rather than building strings in JSX.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx:
- Around line 284-305: activeFilterCount currently only increments when both
filters.startTime and filters.endTime exist, so single-sided time filters are
ignored; update the useMemo that computes activeFilterCount (referencing
activeFilterCount, filters.startTime, filters.endTime) to increment when either
startTime or endTime is present (i.e., count += 1 if filters.startTime ||
filters.endTime), leaving the existing behavior for the both-present case
intact; this will ensure the UI badge and the filterAutoExpandRef /
setIsFiltersOpen logic trigger correctly on URL-only start or end time filters.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 42d5df2b-af2b-4bbe-99ba-9e1a21c38423

📥 Commits

Reviewing files that changed from the base of the PR and between 2b60af9 and eed4b90.

📒 Files selected for processing (4)
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/components/customs/active-sessions-list.tsx

Comment on lines +106 to +118
<StatItem
label={t("logs.stats.totalTokens")}
value={formatTokenAmount(stats.totalTokens)}
detail={`${t("logs.stats.input")} ${formatTokenAmount(stats.totalInputTokens)} / ${t("logs.stats.output")} ${formatTokenAmount(stats.totalOutputTokens)}`}
/>

<Separator />

<StatItem
label={t("logs.stats.cacheTokens")}
value={formatTokenAmount(stats.totalCacheCreationTokens + stats.totalCacheReadTokens)}
detail={`${t("logs.stats.write")} ${formatTokenAmount(stats.totalCacheCreationTokens)} / ${t("logs.stats.read")} ${formatTokenAmount(stats.totalCacheReadTokens)}`}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

把 token 明细收敛成单个翻译消息。

这里把 input/outputwrite/read 片段直接在组件里拼成整句,词序和空格就被写死了;一旦某个语言需要不同语序,这两处就没法只靠 locale 文件调整。建议把整句改成单个 i18n key,只把已经格式化好的数字作为参数传进去。

建议修改
       <StatItem
         label={t("logs.stats.totalTokens")}
         value={formatTokenAmount(stats.totalTokens)}
-        detail={`${t("logs.stats.input")} ${formatTokenAmount(stats.totalInputTokens)} / ${t("logs.stats.output")} ${formatTokenAmount(stats.totalOutputTokens)}`}
+        detail={t("logs.stats.totalTokensDetail", {
+          input: formatTokenAmount(stats.totalInputTokens),
+          output: formatTokenAmount(stats.totalOutputTokens),
+        })}
       />
@@
       <StatItem
         label={t("logs.stats.cacheTokens")}
         value={formatTokenAmount(stats.totalCacheCreationTokens + stats.totalCacheReadTokens)}
-        detail={`${t("logs.stats.write")} ${formatTokenAmount(stats.totalCacheCreationTokens)} / ${t("logs.stats.read")} ${formatTokenAmount(stats.totalCacheReadTokens)}`}
+        detail={t("logs.stats.cacheTokensDetail", {
+          write: formatTokenAmount(stats.totalCacheCreationTokens),
+          read: formatTokenAmount(stats.totalCacheReadTokens),
+        })}
       />

还需要在 5 个 locale 文件里补上对应翻译键。

As per coding guidelines, `All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
around lines 106 - 118, Replace the hardcoded concatenation passed into the
StatItem detail props with single i18n keys so word order can vary per locale:
create new translation keys (e.g. "logs.stats.totalTokensDetail" and
"logs.stats.cacheTokensDetail") and pass the already formatted numbers as
interpolation parameters (formatted totalInputTokens/totalOutputTokens and
formatted totalCacheCreationTokens/totalCacheReadTokens) to those keys; update
all 5 locale files (zh-CN, zh-TW, en, ja, ru) with the corresponding
translations for those keys. Ensure the change touches the two StatItem usages
in usage-logs-stats-panel.tsx and uses the t(...) call with parameter objects
rather than building strings in JSX.

Comment on lines +133 to +135
function Separator() {
return <span className="text-border hidden sm:inline">|</span>;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

把分隔符标记为纯装饰。

| 在这里是纯视觉分隔符,但当前桌面端会进入辅助技术树,屏幕阅读器会把它读出来,打断每组统计的朗读。给它加上 aria-hidden="true" 就够了。

建议修改
 function Separator() {
-  return <span className="text-border hidden sm:inline">|</span>;
+  return (
+    <span aria-hidden="true" className="text-border hidden sm:inline">
+      |
+    </span>
+  );
 }
Based on learnings, universal visual markers should stay purely visual rather than becoming part of translatable content.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Separator() {
return <span className="text-border hidden sm:inline">|</span>;
}
function Separator() {
return (
<span aria-hidden="true" className="text-border hidden sm:inline">
|
</span>
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
around lines 133 - 135, The visual pipe in the Separator component is being
exposed to assistive tech; update the Separator function (Separator) to mark the
element as purely decorative by adding aria-hidden="true" (and optionally
role="presentation") to the <span> so screen readers ignore it while preserving
the visual separator for sighted users.

Comment on lines +284 to +305
const activeFilterCount = useMemo(() => {
let count = 0;
if (filters.userId !== undefined) count++;
if (filters.keyId !== undefined) count++;
if (filters.providerId !== undefined) count++;
if (filters.sessionId) count++;
if (filters.startTime && filters.endTime) count++;
if (filters.statusCode !== undefined || filters.excludeStatusCode200) count++;
if (filters.model) count++;
if (filters.endpoint) count++;
if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) count++;
return count;
}, [filters]);

// Auto-expand filter panel when URL-based filters are present on initial load
const filterAutoExpandRef = useRef(true);
useEffect(() => {
if (filterAutoExpandRef.current && activeFilterCount > 0) {
setIsFiltersOpen(true);
}
filterAutoExpandRef.current = false;
}, [activeFilterCount]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

单边时间过滤会被计成 0 个筛选条件。

Line 290 只在 startTimeendTime 同时存在时才加 1,但这里的过滤器类型把这两个字段定义成独立可选项。URL 里只带开始时间或结束时间时,统计面板会认为有过滤条件,折叠按钮却不显示数量,下面基于 activeFilterCount 的首次自动展开也不会触发。

建议修改
-    if (filters.startTime && filters.endTime) count++;
+    if (filters.startTime !== undefined || filters.endTime !== undefined) count++;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
around lines 284 - 305, activeFilterCount currently only increments when both
filters.startTime and filters.endTime exist, so single-sided time filters are
ignored; update the useMemo that computes activeFilterCount (referencing
activeFilterCount, filters.startTime, filters.endTime) to increment when either
startTime or endTime is present (i.e., count += 1 if filters.startTime ||
filters.endTime), leaving the existing behavior for the both-present case
intact; this will ensure the UI badge and the filterAutoExpandRef /
setIsFiltersOpen logic trigger correctly on URL-only start or end time filters.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR implements a clean, focused UI refactoring for the logs dashboard with no significant issues identified.

PR Size: S

  • Lines changed: 322 (158 additions, 164 deletions)
  • Files changed: 4

Review Assessment

Architecture & Design:

  • Clean transition from card-based to inline compact layout
  • Proper use of useMemo for statsFilters to prevent unnecessary re-renders
  • Well-implemented auto-expand logic for filter panel with proper ref-based guard

Code Quality:

  • Follows CLAUDE.md guidelines
  • No emoji in code
  • Proper i18n usage
  • Unused import (cn) correctly removed

Logic & Correctness:

  • Filter count logic correctly handles date range and status code filters
  • Error states properly handled in all components
  • TypeScript types properly maintained

UX Improvements:

  • Compact empty state reduces visual clutter
  • Collapsible filter panel with active count badge improves information density
  • Auto-expand on URL filters improves discoverability

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate for UI refactoring
  • Code clarity - Good

Automated review by Claude AI

@NieiR
Copy link
Contributor Author

NieiR commented Mar 7, 2026

Closing this PR because the current CI failures need to be recovered in one baseline-fix PR first.

I have resubmitted the blocking build/test fixes as a new standalone PR so the CI baseline can be restored before smaller follow-up changes are reviewed separately.

@NieiR NieiR closed this Mar 7, 2026
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UI bug Something isn't working size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant