Skip to content

feat: session analysis report with assessment badges#60

Merged
matt1398 merged 18 commits intomatt1398:mainfrom
holstein13:feat/session-analysis-report
Feb 23, 2026
Merged

feat: session analysis report with assessment badges#60
matt1398 merged 18 commits intomatt1398:mainfrom
holstein13:feat/session-analysis-report

Conversation

@holstein13
Copy link
Contributor

@holstein13 holstein13 commented Feb 21, 2026

Summary

  • Adds a Session Analysis Report — a new tab that provides a comprehensive post-session breakdown of any Claude Code session, covering cost, tokens, tools, timing, quality signals, friction points, git activity, subagents, and errors
  • All analysis runs client-side via analyzeSession() — no API calls, no AI inference, instant results
  • Every metric section includes color-coded severity badges (green/amber/red/gray) computed from threshold-based rules, turning raw numbers into instant actionable signal
  • Includes model mismatch detection (flags Opus on mechanical/read-only tasks), switch pattern recognition (detects Opus plan mode), and centralized assessment utilities

Report sections

Section What it shows
Overview Duration, total cost, token count, model, context health
Cost Total spend, cost/commit, cost/line, subagent cost share
Tokens Input/output/cache breakdown, cache efficiency, R/W ratio
Tools Per-tool call counts, success rates, health badges
Timeline Active vs idle time, model switches, pacing
Quality Prompt quality, startup overhead, file read redundancy, test progression
Friction Permission denials, retries, thrashing signals
Git Commits, lines changed, files touched
Subagents Count, tokens, duration, cost, model mismatch warnings
Errors Error breakdown by type
Insights Key takeaways and notable patterns

Assessment badges

Threshold-based assessments with severity coloring:

  • Cost: Efficient / Normal / Expensive / Red Flag
  • Cache: Good / Concerning
  • Tool Health: Healthy / Degraded / Unreliable (per tool + overall)
  • Idle: Efficient / Moderate / High Idle
  • Thrashing: None / Mild / Severe
  • Startup Overhead: Normal / Heavy
  • File Redundancy: Normal / Wasteful
  • Model Mismatch: Flags Opus on mechanical (rename, lint, format) or read-only (search, explore) tasks
  • Switch Pattern: Detects Sonnet→Opus→Sonnet as "Opus Plan Mode"

Files changed

  • Created: reportAssessments.ts (centralized severity/color/threshold utility), reportAssessments.test.ts (44 tests)
  • Modified: sessionReport.ts (assessment type fields), sessionAnalyzer.ts (compute assessments), all 8 report section components (badge rendering), SessionReportTab.tsx (prop wiring)
  • Tests: 60 session analyzer tests (19 new), 44 assessment utility tests

Test plan

  • pnpm typecheck — no errors
  • pnpm test — 712 tests passing (40 files)
  • Open a session report tab and verify badges render with correct colors
  • Verify null assessments (no commits, no cache data) don't show misleading badges

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • "Analyze" button opens a dedicated Session Report tab with Key Takeaways, Overview, Token & Cost breakdowns, per-model cost drilldowns, Tool/Subagent metrics, Errors, Git activity, Friction/Thrashing, Timeline, Conversation Tree, Quality signals, Insights, and jump-to-section navigation.
    • Collapsible, titled report sections and assessment badges with hover explanations and interactive takeaways.
  • Tests
    • Extensive unit tests validating the analysis engine and assessment logic.
  • Documentation
    • Design/plan docs for the session analysis report.
  • Style
    • New theme variables for assessment severity colors.

holstein13 and others added 10 commits February 21, 2026 16:20
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 tasks with acceptance criteria covering types, analyzer engine,
tests, tab integration, UI components, routing, and polish.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
….py)

Port all analysis logic from the Python script to TypeScript, running
entirely in the renderer process. Produces a typed SessionReport from
a SessionDetail with: token/cost analysis, friction signals, idle gaps,
conversation tree metrics, test progression, and thrashing detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 7 missing sections and fix 4 incomplete ones that the Python
analyzer generates but the TypeScript port was missing:

New sections: skillsInvoked, bashCommands, lifecycleTasks,
userQuestions, outOfScopeFindings, agentTree, subagentsList

Fixed: compaction (was just count, now has summaryCount + note),
serviceTiers removed (not available in parsed types),
compactionCount replaced with compaction object

Adds InsightsSection UI component and 8 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tions

Add interpretive assessment layer to session reports. Every metric section
now shows color-coded severity badges (green/amber/red) computed from
configurable thresholds, replacing raw numbers with instant signal.

Includes centralized reportAssessments utility, model mismatch detection,
switch pattern recognition, and 44 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lint rule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@matt1398
Copy link
Owner

Hey, great work — instant client-side analysis is genuinely useful.

A few things:

  1. Subagent cost looks off. Worth checking how costs are attributed between subagents and parent session. If
    those numbers are wrong the cost section loses credibility.

  2. Too much, too little context. Friction Signals, Timeline, Quality Signals, Insights — even knowing Claude
    Code well, it's hard to tell what actually matters. The thresholds behind badges aren't explained anywhere, so
    "Concerning" vs "Normal" feels arbitrary without the why. More sections ≠ more useful.

  3. Quality > quantity. The feature is solid but could use a UX pas. Surface the top few actionable takeaways
    prominently, collapse or trim the rest. A first-time user should open this and immediately get it, not parse 11
    dense sections to find signal.

… progressive disclosure

- Add AssessmentBadge component with hover tooltips explaining thresholds
- Add Key Takeaways summary section surfacing top actionable findings
- Add cost attribution stacked bar and per-token calculation breakdowns
- Add clickable navigation from takeaways and tool errors to detail sections
- Add theme-aware assessment colors via CSS variables for light/dark mode
- Collapse lower-priority sections by default for progressive disclosure
- Replace all hardcoded color hex values with CSS variable references
- Fix missing Fragment key in CostSection model table
- Add defensive division-by-zero guard in stacked bar calculation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@holstein13
Copy link
Contributor Author

Thanks so much for the thorough review, @matt1398 — really appreciate you taking the time. These are great comments and they've made the report significantly more useful. Here's what we've done to address each point:


1. Subagent cost accuracy

We double-checked the cost attribution logic and believe the numbers are accurate — parent and subagent costs are computed from separate API response streams with no double-counting. That said, we totally understand why it looked off: the previous UI didn't make the split obvious.

To address the clarity concern, we've added:

  • A stacked bar visualization showing the parent vs subagent cost proportion at a glance (blue/purple segments with labeled legend)
  • Denominator annotations under "Per Commit" and "Per Line Changed" that explicitly say "total cost ÷ N commits" so it's clear these metrics include subagent costs
  • Expandable per-model cost breakdowns — click any model row to see the exact token counts × per-million rates = line-item cost calculation, so users can verify the math themselves

2. Thresholds not explained — badges feel arbitrary

Great call. We've created an AssessmentBadge component that replaces all the inline badge markup across every section. When you hover any badge (e.g., "Concerning", "Efficient", "Healthy"), a tooltip explains the threshold behind it — for example, hovering a "Concerning" cost-per-commit badge shows "Over $2.00/commit". Every metric/assessment combination has a human-readable explanation derived from the actual threshold constants.

We've also introduced theme-aware assessment colors via CSS variables (--assess-good, --assess-warning, --assess-danger, --assess-neutral) with distinct values for dark and light mode, so badges maintain good contrast in both themes.


3. Too many sections, no hierarchy — quality > quantity

Completely agree. We've redesigned the report UX around progressive disclosure:

  • Key Takeaways section at the top — a new always-visible summary card that surfaces the top 4 actionable findings, sorted by severity (danger first). Each takeaway has a severity icon, title, detail text, and a colored left border. If everything looks healthy, it shows a single green "Session looks healthy" item. First-time users immediately see what matters.

  • Clickable drill-down — each takeaway row is a button that navigates directly to the referenced detail section, auto-expanding it and scrolling it into view. Same pattern for error counts in the Tool Usage table — clicking a non-zero error count jumps you to the Errors section.

  • Progressive collapse — Overview, Cost, Token, and Tool sections stay expanded by default (the high-signal sections). The remaining 7 sections (Subagent, Error, Git, Friction, Timeline, Quality, Insights) are collapsed by default and expandable on click. This cuts the initial visual noise dramatically while keeping everything accessible.

  • Error context — expanded errors now show the tool input that triggered the error (the inputPreview field) above the error message, so users understand what caused each failure without needing to cross-reference the session.

@coderabbitai coderabbitai bot added the feature request New feature or request label Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a renderer-only Session Analysis Report: new TypeScript SessionReport types, a single-pass analyzeSession engine, assessment utilities with takeaway generation, SessionReportTab and many report section components, store action and routing for 'report' tabs, CSS variables, and comprehensive tests for analyzer and assessments.

Changes

Cohort / File(s) Summary
Planning Docs
docs/plans/2026-02-21-session-analysis-report-design.md, docs/plans/2026-02-21-session-analysis-report.md
Design and implementation plans and rollout notes for the Session Analysis Report feature.
Types
src/renderer/types/sessionReport.ts, src/renderer/types/tabs.ts
Adds comprehensive report-related interfaces (SessionReport and many section types) and extends Tab.type to include 'report'.
Analyzer & Assessments
src/renderer/utils/sessionAnalyzer.ts, src/renderer/utils/reportAssessments.ts
Implements analyzeSession(detail: SessionDetail): SessionReport, pricing/cost logic, many detection/assessment helpers, and computeTakeaways.
Report Container & Primitives
src/renderer/components/report/SessionReportTab.tsx, src/renderer/components/report/ReportSection.tsx, src/renderer/components/report/AssessmentBadge.tsx
Adds SessionReportTab and shared UI primitives (collapsible ReportSection, AssessmentBadge with tooltip).
Report Sections
src/renderer/components/report/sections/*
Adds numerous report section components: Overview, Cost, Token, Tool, Subagent, Error, Git, Friction, Timeline, Quality, Insights, KeyTakeaways, and related subcomponents.
Store, Routing & Tab UI
src/renderer/store/slices/tabSlice.ts, src/renderer/components/layout/PaneContent.tsx, src/renderer/components/layout/SortableTab.tsx, src/renderer/components/layout/TabBar.tsx
Adds openSessionReport(sourceTabId) action, routes tab.type === 'report' to SessionReportTab, maps Activity icon for report tabs, and exposes an Analyze Session button wired to open reports.
Styling
src/renderer/index.css
Adds CSS custom properties for assessment severity colors (--assess-good, --assess-warning, --assess-danger, --assess-neutral) for dark and light themes.
Tests
test/renderer/utils/sessionAnalyzer.test.ts, test/renderer/utils/reportAssessments.test.ts
Extensive unit tests covering analyzer behavior, detection logic, assessment computations, and takeaway generation.
Minor Formatting
src/renderer/components/chat/SessionContextPanel/components/FlatInjectionList.tsx, src/renderer/components/chat/items/linkedTool/renderHelpers.tsx, src/renderer/utils/displayItemBuilder.ts
Small formatting and ternary cleanups with no behavior changes.

Possibly related issues

  • Session Analysis Report #61: Implements the same Session Analysis Report feature (analyzeSession, SessionReport types, reportAssessments, report UI, and tab integration), matching the issue objectives.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/components/layout/PaneContent.tsx (1)

6-15: ⚠️ Potential issue | 🟡 Minor

Fix import ordering to keep path aliases before relatives.

@renderer imports should come before relative imports.
As per coding guidelines, organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🔧 Suggested reorder
 import { TabUIProvider } from '@renderer/contexts/TabUIContext';
+import type { Pane } from '@renderer/types/panes';
 
 import { DashboardView } from '../dashboard/DashboardView';
 import { NotificationsView } from '../notifications/NotificationsView';
 import { SessionReportTab } from '../report/SessionReportTab';
 import { SettingsView } from '../settings/SettingsView';
 
 import { SessionTabContent } from './SessionTabContent';
-
-import type { Pane } from '@renderer/types/panes';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/layout/PaneContent.tsx` around lines 6 - 15, The
imports in PaneContent.tsx are out of the project's import-ordering rules: move
all path-alias imports (those starting with `@renderer`) before any relative
imports and ensure the ordering is: external packages, path aliases (`@main`,
`@renderer`, `@shared`, `@preload`), then relative imports; specifically ensure
TabUIProvider, DashboardView, NotificationsView, SessionReportTab, SettingsView,
and the Pane type (the `@renderer` imports) appear above the relative import
SessionTabContent and any other relative paths and adjust their grouping
accordingly.
src/renderer/components/layout/SortableTab.tsx (1)

6-12: ⚠️ Potential issue | 🟡 Minor

Reorder imports: keep all externals before @renderer alias.

The new lucide-react import should remain with other externals above the store import.
As per coding guidelines, organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🔧 Suggested reorder
 import { useCallback, useState } from 'react';
 
 import { useSortable } from '@dnd-kit/sortable';
 import { CSS } from '@dnd-kit/utilities';
-import { useStore } from '@renderer/store';
 import { Activity, Bell, FileText, LayoutDashboard, Pin, Search, Settings, X } from 'lucide-react';
 import { useShallow } from 'zustand/react/shallow';
 
+import { useStore } from '@renderer/store';
 import type { Tab } from '@renderer/types/tabs';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/layout/SortableTab.tsx` around lines 6 - 12, Move the
external package imports (including the lucide-react icons import that currently
reads "Activity, Bell, FileText, LayoutDashboard, Pin, Search, Settings, X from
'lucide-react'") above any alias imports such as the "@renderer" import
(useStore); specifically reorder the import block in SortableTab.tsx so all
externals (react, `@dnd-kit/`*, `@lucide-react` icons, zustand/react/shallow) appear
before the "@renderer/store" import to match the external → path-alias →
relative import ordering convention.
🧹 Nitpick comments (23)
docs/plans/2026-02-21-session-analysis-report.md (1)

13-13: Minor markdown structure: Heading levels skip from h1 to h3.

The document jumps from # Session Analysis Report Implementation Plan (h1) directly to ### Task 1 (h3), skipping h2. While functional, this may cause navigation issues in some markdown viewers.

Consider using h2 for tasks
-### Task 1: Add report types
+## Task 1: Add report types
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-02-21-session-analysis-report.md` at line 13, The markdown
skips an h2 level: change the task headings (e.g., "### Task 1: Add report
types") to h2 (use "## Task 1: Add report types") so they follow the main title
"Session Analysis Report Implementation Plan"; update any other task headings in
the document that use "### Task X" to "## Task X" to keep heading hierarchy
consistent and avoid navigation issues.
src/renderer/components/report/sections/KeyTakeawaysSection.tsx (2)

1-6: Import order: external packages should precede path aliases.

Per coding guidelines, imports should be organized: external packages first, then path aliases, then relative imports.

Suggested import order
-import { severityColor } from '@renderer/utils/reportAssessments';
 import { AlertTriangle, CheckCircle, ChevronRight, Info, XCircle } from 'lucide-react';
+
+import { severityColor } from '@renderer/utils/reportAssessments';

 import { sectionId } from '../ReportSection';

As per coding guidelines: "Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/KeyTakeawaysSection.tsx` around lines
1 - 6, The imports in KeyTakeawaysSection are out of order; reorder them so
external packages come first (e.g., import { AlertTriangle, CheckCircle,
ChevronRight, Info, XCircle } from 'lucide-react'), then path-alias imports
(e.g., import { severityColor } and type { Severity, Takeaway } from
'@renderer/utils/reportAssessments'), and finally relative imports (e.g., import
{ sectionId } from '../ReportSection'); update the import block accordingly to
follow the external → path-alias → relative rule.

34-35: Consider using a stable key if takeaways can be reordered.

Using array index as a React key is acceptable here since takeaways are derived from report data and don't change during the component's lifecycle. However, if takeaways could be filtered or reordered dynamically in the future, consider using a composite key like ${t.sectionTitle}-${t.title}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/KeyTakeawaysSection.tsx` around lines
34 - 35, The button elements in KeyTakeawaysSection currently use the array
index (idx) as the React key which can break identity if takeaways are ever
reordered; change the key to a stable composite derived from the takeaway object
(e.g., use `${t.sectionTitle}-${t.title}` or another unique field on the
takeaway `t`) for each mapped `takeaways` item to ensure consistent identity
across reorders; update the `key={idx}` on the button to use that composite or
unique identifier instead.
src/renderer/utils/sessionAnalyzer.ts (2)

60-97: Model pricing is hardcoded and will require manual updates.

The MODEL_PRICING table contains static prices that may drift from actual API pricing over time. Consider:

  1. Adding a comment noting when prices were last verified
  2. Creating a mechanism to update prices or fetch them dynamically in the future
+// Last verified: February 2026
+// Source: Anthropic API pricing page
+// TODO: Consider fetching pricing dynamically or adding a version check
 const MODEL_PRICING: Record<string, ModelPricing> = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/sessionAnalyzer.ts` around lines 60 - 97, MODEL_PRICING
currently hardcodes per-model costs which can drift; add a short inline comment
above MODEL_PRICING with the "last verified" date and a TODO; refactor so the
static map is a fallback and allow loading pricing via a new fetchModelPricing()
util or from configuration (e.g., loadModelPricingFromConfig or
loadModelPricingFromRemote) and use that in places that reference MODEL_PRICING
so prices can be updated dynamically or overridden by environment/config in
future; keep the existing MODEL_PRICING as default/fallback and document
ModelPricing shape for clarity.

493-503: Type assertion for untyped agent metadata fields.

The code uses a type assertion to access agentType, teamName, and parentToolUseId which aren't part of the ParsedMessage type. This works but is fragile if the raw message structure changes.

Consider extending the ParsedMessage type to include these optional fields, or add a dedicated type guard to validate the presence of these fields before accessing them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/sessionAnalyzer.ts` around lines 493 - 503, The code
currently casts m to Record<string, unknown> to read
agentType/teamName/parentToolUseId in sessionAnalyzer (where agentTreeNodes is
built), which is fragile; fix by either adding optional properties agentType?:
string, teamName?: string, parentToolUseId?: string to the ParsedMessage type
definition used by this module and remove the ad-hoc cast, or add a small type
guard (e.g. isAgentMetadata(obj): obj is {agentType?: string; teamName?: string;
parentToolUseId?: string}) and use it to safely read those fields before pushing
into agentTreeNodes (update references to m and the push to use typed properties
if guard passes).
src/renderer/components/report/AssessmentBadge.tsx (2)

17-17: Add explicit return type for the component.

The static analysis tool flagged a missing return type. Adding an explicit return type improves type safety and documentation.

Add return type
-export const AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps) => {
+export const AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps): JSX.Element => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/AssessmentBadge.tsx` at line 17, The
AssessmentBadge component is missing an explicit return type; update its
signature to include one (e.g. change "export const AssessmentBadge = ({
assessment, metricKey }: AssessmentBadgeProps) => {" to "export const
AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps): JSX.Element
=> {" or use React.ReactElement) so the component has an explicit return type;
ensure any necessary React types are imported or already available in the
project types.

52-60: Accessibility: Add keyboard support for tooltip trigger.

The badge acts as an interactive element but only responds to mouse events. For accessibility, consider adding keyboard support so screen reader users can access the tooltip explanation.

Add keyboard accessibility
       <span
         ref={badgeRef}
         className="rounded px-2 py-0.5 text-xs font-medium"
         style={{ backgroundColor: `${color}20`, color }}
         onMouseEnter={handleMouseEnter}
         onMouseLeave={handleMouseLeave}
+        onFocus={handleMouseEnter}
+        onBlur={handleMouseLeave}
+        tabIndex={explanation ? 0 : undefined}
+        role={explanation ? 'button' : undefined}
+        aria-describedby={showTooltip ? 'assessment-tooltip' : undefined}
       >
         {assessmentLabel(assessment)}
       </span>

And add an id to the tooltip div:

           <div
+            id="assessment-tooltip"
             className="pointer-events-none fixed z-50 ..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/AssessmentBadge.tsx` around lines 52 - 60, The
badge span currently only uses mouse handlers
(handleMouseEnter/handleMouseLeave) and isn't keyboard-focusable; make the span
focusable by adding tabIndex={0}, wire up onFocus/onBlur to reuse
handleMouseEnter/handleMouseLeave, add an onKeyDown handler that opens the
tooltip on Enter or Space (and closes on Escape) using the same handlers, and
add an id on the tooltip div and an aria-describedby attribute on the span so
screen readers associate the badge with the tooltip; keep using
assessmentLabel(assessment) as the visible content and badgeRef for positioning.
src/renderer/utils/reportAssessments.ts (1)

380-417: Consider exporting TakeawayReport interface for type safety in tests.

The TakeawayReport interface is currently private, but tests may need to construct mock report objects. Exporting it would enable type-safe test fixtures.

Export the interface
-interface TakeawayReport {
+export interface TakeawayReport {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/reportAssessments.ts` around lines 380 - 417, The
TakeawayReport interface should be exported so tests can import and construct
type-safe mock reports: add an export modifier to the TakeawayReport declaration
(export interface TakeawayReport) and update any internal usages if needed; then
update test fixtures to import the type (e.g., import type { TakeawayReport }
from the module containing reportAssessments) and use it for mock objects to
ensure compile-time safety.
test/renderer/utils/reportAssessments.test.ts (1)

309-328: Rename healthyReport to UPPER_SNAKE_CASE constant.

This is a constant test fixture and should follow the repo’s constant naming rule.
As per coding guidelines, use UPPER_SNAKE_CASE for constant values.

♻️ Proposed rename (update references below)
-    const healthyReport = {
+    const HEALTHY_REPORT = {
       costAnalysis: {
         costPerCommitAssessment: 'efficient',
         costPerLineAssessment: 'efficient',
         totalSessionCostUsd: 0.5,
       },
       cacheEconomics: { cacheEfficiencyAssessment: 'good', cacheEfficiencyPct: 97 },
       toolUsage: { overallToolHealth: 'healthy' },
       thrashingSignals: {
         thrashingAssessment: 'none',
         bashNearDuplicates: [],
         editReworkFiles: [],
       },
       idleAnalysis: { idleAssessment: 'efficient', idlePct: 10 },
       promptQuality: { assessment: 'well_specified', frictionRate: 0.05 },
       overview: { contextAssessment: 'healthy', compactionCount: 0 },
       fileReadRedundancy: { redundancyAssessment: 'normal', readsPerUniqueFile: 1.5 },
       testProgression: { trajectory: 'improving' },
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/renderer/utils/reportAssessments.test.ts` around lines 309 - 328, Rename
the test fixture healthyReport to an UPPER_SNAKE_CASE constant (e.g.,
HEALTHY_REPORT) in the describe('computeTakeaways') block and update all
references to that identifier in this test file so imports/usages of
healthyReport (used inside computeTakeaways tests) point to the new constant
name; ensure the declaration uses const and follows the same object structure,
and update any other uses within this test file (assertions, helper calls) to
the new HEALTHY_REPORT identifier.
test/renderer/utils/sessionAnalyzer.test.ts (1)

12-62: Rename builder helpers to follow buildXxx convention.

These helpers construct objects and should use the buildXxx naming pattern to match the repo standard.
As per coding guidelines, use buildXxx naming convention for builder functions.

♻️ Proposed rename (update call sites accordingly)
-function createMockMessage(overrides: Partial<ParsedMessage> = {}): ParsedMessage {
+function buildMockMessage(overrides: Partial<ParsedMessage> = {}): ParsedMessage {
   msgCounter++;
   return {
     uuid: `uuid-${msgCounter}`,
     parentUuid: `uuid-${msgCounter - 1}`,
     type: 'assistant',
     timestamp: new Date('2024-01-01T10:00:00Z'),
     content: '',
     isSidechain: false,
     isMeta: false,
     toolCalls: [],
     toolResults: [],
     ...overrides,
   };
 }
 
-function createMockSession(overrides: Partial<Session> = {}): Session {
+function buildMockSession(overrides: Partial<Session> = {}): Session {
   return {
     id: 'test-session',
     projectId: 'test-project',
     projectPath: '/test/path',
     createdAt: Date.now(),
     hasSubagents: false,
     messageCount: 0,
     ...overrides,
   };
 }
 
-function createMockMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
+function buildMockMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
   return {
     durationMs: 0,
     totalTokens: 0,
     inputTokens: 0,
     outputTokens: 0,
     cacheReadTokens: 0,
     cacheCreationTokens: 0,
     messageCount: 0,
     ...overrides,
   };
 }
 
-function createMockDetail(overrides: Partial<SessionDetail> = {}): SessionDetail {
+function buildMockDetail(overrides: Partial<SessionDetail> = {}): SessionDetail {
   return {
-    session: createMockSession(),
+    session: buildMockSession(),
     messages: [],
     chunks: [],
     processes: [],
-    metrics: createMockMetrics(),
+    metrics: buildMockMetrics(),
     ...overrides,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/renderer/utils/sessionAnalyzer.test.ts` around lines 12 - 62, Rename the
builder helpers to follow the buildXxx convention: change createMockMessage →
buildMessage, createMockSession → buildSession, createMockMetrics →
buildMetrics, and createMockDetail → buildDetail; update all usages/call sites
to the new names and preserve existing parameter types and default behavior
(including the overrides Partial<T> logic) so only the identifiers change;
ensure any imports/exports or tests referencing createMock* are updated to the
new build* names.
src/renderer/components/layout/PaneContent.tsx (1)

46-51: Consider wrapping report tabs with TabUIProvider for per‑tab UI state isolation.

If the report view will track UI state (e.g., collapsed sections, expanded items), it should use the same per‑tab isolation pattern as session tabs.
Based on learnings, Use TabUIContext for managing per-tab UI state instead of local component state when applicable.

💡 Suggested wrapper
-            {tab.type === 'report' && <SessionReportTab tab={tab} />}
+            {tab.type === 'report' && (
+              <TabUIProvider tabId={tab.id}>
+                <SessionReportTab tab={tab} />
+              </TabUIProvider>
+            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/layout/PaneContent.tsx` around lines 46 - 51, The
report view should be wrapped with the same TabUIProvider used for session tabs
so its UI state is isolated per tab; update the render branch where tab.type ===
'report' to wrap <SessionReportTab tab={tab} /> with <TabUIProvider
tabId={tab.id}>...</TabUIProvider> (same pattern used for SessionTabContent) so
SessionReportTab can consume TabUIContext instead of relying on local state.
src/renderer/components/report/sections/FrictionSection.tsx (1)

1-7: Reorder imports to match the required grouping.

External → alias → relative:

Proposed diff
-import { severityColor } from '@renderer/utils/reportAssessments';
-import { MessageSquareWarning } from 'lucide-react';
-
-import { AssessmentBadge } from '../AssessmentBadge';
-import { ReportSection } from '../ReportSection';
-
-import type { ReportFrictionSignals, ReportThrashingSignals } from '@renderer/types/sessionReport';
+import { MessageSquareWarning } from 'lucide-react';
+
+import { severityColor } from '@renderer/utils/reportAssessments';
+import type { ReportFrictionSignals, ReportThrashingSignals } from '@renderer/types/sessionReport';
+
+import { AssessmentBadge } from '../AssessmentBadge';
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/FrictionSection.tsx` around lines 1 -
7, Reorder the imports in FrictionSection.tsx to follow the external → alias →
relative grouping: place third-party imports like MessageSquareWarning from
'lucide-react' first, then path-alias imports such as severityColor from
'@renderer/utils/reportAssessments' and the types
ReportFrictionSignals/ReportThrashingSignals from
'@renderer/types/sessionReport', and finally local relative imports
AssessmentBadge and ReportSection; adjust import order so severityColor and the
types come after external packages and before the relative component imports.
src/renderer/components/report/sections/CostSection.tsx (1)

1-11: Reorder imports to match the agreed grouping.

External packages should be listed first, then path aliases, then relative imports:

Proposed diff
-import { Fragment, useState } from 'react';
-
-import { getPricing } from '@renderer/utils/sessionAnalyzer';
-import { DollarSign } from 'lucide-react';
-
-import { AssessmentBadge } from '../AssessmentBadge';
-import { ReportSection } from '../ReportSection';
-
-import type { ModelTokenStats, ReportCostAnalysis } from '@renderer/types/sessionReport';
-import type { ModelPricing } from '@renderer/types/sessionReport';
+import { Fragment, useState } from 'react';
+import { DollarSign } from 'lucide-react';
+
+import { getPricing } from '@renderer/utils/sessionAnalyzer';
+import type { ModelPricing, ModelTokenStats, ReportCostAnalysis } from '@renderer/types/sessionReport';
+
+import { AssessmentBadge } from '../AssessmentBadge';
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/CostSection.tsx` around lines 1 - 11,
Reorder the import statements in CostSection.tsx to follow the project's import
grouping: place external packages first (e.g., import React items like Fragment
and useState from 'react' and DollarSign from 'lucide-react'), then path-alias
imports (e.g., getPricing from '@renderer/utils/sessionAnalyzer' and the types
ModelTokenStats, ReportCostAnalysis, ModelPricing from
'@renderer/types/sessionReport'), and finally relative imports (e.g.,
AssessmentBadge and ReportSection from '../AssessmentBadge' and
'../ReportSection'); keep the exact imported symbols unchanged but move their
lines to match the external → alias → relative order.
src/renderer/components/report/sections/SubagentSection.tsx (1)

1-6: Reorder imports to match project conventions.

External packages should appear before path aliases, followed by relative imports. Consider this order:

Proposed diff
-import { severityColor } from '@renderer/utils/reportAssessments';
-import { Users } from 'lucide-react';
-
-import { ReportSection } from '../ReportSection';
-
-import type { ReportSubagentMetrics } from '@renderer/types/sessionReport';
+import { Users } from 'lucide-react';
+
+import { severityColor } from '@renderer/utils/reportAssessments';
+import type { ReportSubagentMetrics } from '@renderer/types/sessionReport';
+
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/SubagentSection.tsx` around lines 1 -
6, Reorder the import statements in SubagentSection.tsx so external packages
come first (e.g., lucide-react import for Users), then path-alias imports (e.g.,
`@renderer/utils/reportAssessments` for severityColor and
`@renderer/types/sessionReport` for ReportSubagentMetrics), and finally relative
imports (e.g., ../ReportSection); update the import order accordingly to match
project conventions.
src/renderer/components/report/sections/OverviewSection.tsx (1)

1-6: Reorder imports to match the project convention.

External packages should come first, then path aliases, then relative imports:

Proposed diff
-import { assessmentColor } from '@renderer/utils/reportAssessments';
-import { Activity } from 'lucide-react';
-
-import { ReportSection } from '../ReportSection';
-
-import type { ReportOverview } from '@renderer/types/sessionReport';
+import { Activity } from 'lucide-react';
+
+import { assessmentColor } from '@renderer/utils/reportAssessments';
+import type { ReportOverview } from '@renderer/types/sessionReport';
+
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/OverviewSection.tsx` around lines 1 -
6, Reorder the imports in OverviewSection.tsx to follow project convention:
place external packages first (e.g., the Activity import from 'lucide-react'),
then path-alias imports (e.g., assessmentColor from
'@renderer/utils/reportAssessments' and ReportOverview from
'@renderer/types/sessionReport'), and finally relative imports (e.g.,
ReportSection from '../ReportSection'); update the import block accordingly so
import order is external → aliases → relative.
src/renderer/components/report/sections/InsightsSection.tsx (1)

1-12: Reorder imports to follow the repo’s grouping.

Path alias imports should appear before relative imports:

Proposed diff
 import { Lightbulb } from 'lucide-react';
-
-import { ReportSection } from '../ReportSection';
-
-import type {
+import type {
   OutOfScopeFindings,
   ReportAgentTree,
   ReportBashCommands,
   SkillInvocation,
   SubagentBasicEntry,
   UserQuestion,
 } from '@renderer/types/sessionReport';
+
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/InsightsSection.tsx` around lines 1 -
12, Reorder the import block so external packages come first (e.g., import {
Lightbulb } from 'lucide-react'), then path-alias imports (move all imports from
'@renderer' or other '@...' namespaces next, e.g., the types OutOfScopeFindings,
ReportAgentTree, ReportBashCommands, SkillInvocation, SubagentBasicEntry,
UserQuestion), and finally the relative imports (e.g., import { ReportSection }
from '../ReportSection'); update the import order in InsightsSection.tsx to
follow external → path-alias → relative grouping while preserving named imports
and formatting.
src/renderer/components/report/sections/ToolSection.tsx (1)

1-7: Reorder imports to follow the repo’s import order rule.

External packages should precede path aliases, followed by relative imports:

Proposed diff
-import { assessmentColor } from '@renderer/utils/reportAssessments';
-import { Wrench } from 'lucide-react';
-
-import { AssessmentBadge } from '../AssessmentBadge';
-import { ReportSection, sectionId } from '../ReportSection';
-
-import type { ReportToolUsage } from '@renderer/types/sessionReport';
+import { Wrench } from 'lucide-react';
+
+import { assessmentColor } from '@renderer/utils/reportAssessments';
+import type { ReportToolUsage } from '@renderer/types/sessionReport';
+
+import { AssessmentBadge } from '../AssessmentBadge';
+import { ReportSection, sectionId } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/ToolSection.tsx` around lines 1 - 7,
The imports in ToolSection.tsx are not ordered per the repo rule; reorder them
so external packages (lucide-react) come first, then path-alias imports (from
`@renderer` and other @... aliases like assessmentColor and ReportToolUsage), and
finally relative imports (AssessmentBadge, ReportSection, sectionId if they are
relative) — specifically ensure the Wrench import from 'lucide-react' appears
before imports from '@renderer' and that assessmentColor, ReportToolUsage,
AssessmentBadge, ReportSection, and sectionId follow the alias-to-relative
ordering to match the project's import ordering guideline.
src/renderer/components/report/ReportSection.tsx (1)

5-6: Consider moving sectionId to a separate module to preserve Fast Refresh.

Exporting helpers alongside components can trigger the Fast Refresh warning. Extracting sectionId to a small utility module (and re-exporting it) avoids the warning while keeping the API intact.

Also applies to: 58-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/ReportSection.tsx` around lines 5 - 6, The
helper function sectionId should be extracted into a small utility module to
avoid Fast Refresh warnings: create a new module that exports the
sectionId(title: string) function (preserving its implementation and name),
update ReportSection to import sectionId from that module, and optionally
re-export sectionId from the component module if other callers expect it; ensure
the function signature and behavior remain unchanged so the API is preserved.
src/renderer/components/report/sections/QualitySection.tsx (1)

1-12: Reorder imports to follow the repo’s import grouping.

External packages should come first, then path aliases, then relative imports:

Proposed diff
-import { severityColor } from '@renderer/utils/reportAssessments';
-import { BarChart3 } from 'lucide-react';
-
-import { AssessmentBadge } from '../AssessmentBadge';
-import { ReportSection } from '../ReportSection';
-
-import type {
+import { BarChart3 } from 'lucide-react';
+
+import { severityColor } from '@renderer/utils/reportAssessments';
+import type {
   ReportFileReadRedundancy,
   ReportPromptQuality,
   ReportStartupOverhead,
   ReportTestProgression,
 } from '@renderer/types/sessionReport';
+
+import { AssessmentBadge } from '../AssessmentBadge';
+import { ReportSection } from '../ReportSection';

As per coding guidelines: Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/QualitySection.tsx` around lines 1 -
12, Reorder the import block so external packages come first (e.g., lucide-react
import for BarChart3), then path-alias imports from `@renderer` and other aliases
(e.g., severityColor from `@renderer/utils/reportAssessments` and the Report*
types from `@renderer/types/sessionReport`), and finally relative imports
(AssessmentBadge and ReportSection); update the import order accordingly while
keeping the same imported symbols (severityColor, BarChart3, AssessmentBadge,
ReportSection, ReportFileReadRedundancy, ReportPromptQuality,
ReportStartupOverhead, ReportTestProgression).
src/renderer/components/report/sections/TokenSection.tsx (2)

1-6: Fix import ordering to match coding guidelines.

Similar to TimelineSection, imports should follow: external packages → path aliases → relative imports.

🔧 Suggested import reordering
 import { Coins } from 'lucide-react';

+import type { ReportCacheEconomics, ReportTokenUsage } from '@renderer/types/sessionReport';
+
 import { AssessmentBadge } from '../AssessmentBadge';
 import { ReportSection } from '../ReportSection';
-
-import type { ReportCacheEconomics, ReportTokenUsage } from '@renderer/types/sessionReport';

As per coding guidelines: "Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/TokenSection.tsx` around lines 1 - 6,
Reorder the imports in TokenSection.tsx so they follow the project guideline:
put external package imports first (e.g., Coins from 'lucide-react'), then
path-alias imports (e.g., ReportCacheEconomics, ReportTokenUsage from
'@renderer/types/sessionReport'), and finally relative imports (e.g.,
AssessmentBadge and ReportSection from '../AssessmentBadge' and
'../ReportSection'); update the import block accordingly without changing the
imported symbols or their names.

17-18: Consider memoizing sorted model entries.

modelEntries is recomputed on every render. While the dataset is typically small, memoizing with useMemo would be consistent with React best practices.

♻️ Proposed memoization
+import { useMemo } from 'react';
+
 export const TokenSection = ({ data, cacheEconomics, defaultCollapsed }: TokenSectionProps) => {
-  const modelEntries = Object.entries(data.byModel).sort((a, b) => b[1].costUsd - a[1].costUsd);
+  const modelEntries = useMemo(
+    () => Object.entries(data.byModel).sort((a, b) => b[1].costUsd - a[1].costUsd),
+    [data.byModel]
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/TokenSection.tsx` around lines 17 -
18, The sorted modelEntries array in TokenSection is being recomputed on every
render; wrap the Object.entries(...).sort(...) computation in React's useMemo
inside the TokenSection function so modelEntries is only recalculated when
data.byModel changes (reference the TokenSection component and the modelEntries
variable), i.e. import useMemo and replace the direct assignment with a useMemo
that has [data.byModel] as its dependency.
src/renderer/components/report/sections/TimelineSection.tsx (2)

66-75: Consider reusing AssessmentBadge for the switch pattern badge.

This inline badge duplicates the styling logic from AssessmentBadge. While the pattern badge doesn't need a tooltip, extracting a shared styling utility or using AssessmentBadge would improve consistency.

♻️ Proposed refactor to reuse AssessmentBadge
             {modelSwitches.switchPattern && (
-              <span
-                className="rounded px-2 py-0.5 text-xs font-medium"
-                style={{
-                  backgroundColor: `${assessmentColor(modelSwitches.switchPattern)}20`,
-                  color: assessmentColor(modelSwitches.switchPattern),
-                }}
-              >
-                {assessmentLabel(modelSwitches.switchPattern)}
-              </span>
+              <AssessmentBadge assessment={modelSwitches.switchPattern} />
             )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/TimelineSection.tsx` around lines 66
- 75, The inline badge in TimelineSection (the span using
assessmentColor(modelSwitches.switchPattern) and
assessmentLabel(modelSwitches.switchPattern)) duplicates styling from
AssessmentBadge; replace it by reusing AssessmentBadge or extract shared styling
logic into a small helper used by both components so the switchPattern badge
gets consistent color, background and label rendering; locate the badge in
TimelineSection around modelSwitches.switchPattern and either render
<AssessmentBadge level={modelSwitches.switchPattern} /> (suppress tooltip via a
prop) or move the color/label/background computation into a utility function
(e.g., getAssessmentStyles or assessmentColor/assessmentLabel wrappers) and call
that from TimelineSection to apply the same styles.

1-11: Fix import ordering to match coding guidelines.

Per coding guidelines, imports should be organized as: external packages first, then path aliases (@renderer), then relative imports. Currently, relative imports (../AssessmentBadge, ../ReportSection) appear before the @renderer/types import.

🔧 Suggested import reordering
-import { assessmentColor, assessmentLabel } from '@renderer/utils/reportAssessments';
-import { Clock } from 'lucide-react';
-
-import { AssessmentBadge } from '../AssessmentBadge';
-import { ReportSection } from '../ReportSection';
-
-import type {
-  KeyEvent,
-  ReportIdleAnalysis,
-  ReportModelSwitches,
-} from '@renderer/types/sessionReport';
+import { Clock } from 'lucide-react';
+
+import { assessmentColor, assessmentLabel } from '@renderer/utils/reportAssessments';
+import type {
+  KeyEvent,
+  ReportIdleAnalysis,
+  ReportModelSwitches,
+} from '@renderer/types/sessionReport';
+
+import { AssessmentBadge } from '../AssessmentBadge';
+import { ReportSection } from '../ReportSection';

As per coding guidelines: "Organize imports in order: external packages, path aliases (@main, @renderer, @shared, @preload), then relative imports"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/report/sections/TimelineSection.tsx` around lines 1 -
11, Reorder the imports in TimelineSection.tsx so they follow the project's
convention: place external package imports first (e.g., Clock from
"lucide-react"), then path-alias imports from "@renderer" (e.g.,
assessmentColor, assessmentLabel from "@renderer/utils/reportAssessments" and
the types KeyEvent, ReportIdleAnalysis, ReportModelSwitches from
"@renderer/types/sessionReport"), and finally the relative component imports
(AssessmentBadge and ReportSection from "../AssessmentBadge" and
"../ReportSection"); adjust the import block to preserve the same identifiers
(assessmentColor, assessmentLabel, Clock, AssessmentBadge, ReportSection,
KeyEvent, ReportIdleAnalysis, ReportModelSwitches) but in the required order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plans/2026-02-21-session-analysis-report-design.md`:
- Around line 20-27: Update the fenced code block that currently starts with
"TabBar.tsx (Activity button click) → store action:
openSessionReport(sourceTabId) ..." by adding a language specifier (use "text")
to the opening fence (i.e., change ``` to ```text) so syntax
highlighting/accessibility tools recognize it; locate the block in the
docs/plans/2026-02-21-session-analysis-report-design.md file near the lines
describing TabBar.tsx, openSessionReport, SessionReportTab, analyzeSession and
sessionAnalyzer.ts and apply the change.

In `@src/renderer/components/report/sections/CostSection.tsx`:
- Around line 183-214: The row currently toggles expansion even when token stats
are missing; change the click/expand behavior in the modelEntries.map block so
expansion only occurs if stats exists: compute isExpandable = Boolean(stats) and
use that to guard the onClick (call setExpandedModel only when isExpandable), to
compute isExpanded use expandedModel === model && isExpandable, and update the
row className to remove cursor-pointer and hover styles when !isExpandable so
rows without tokens are non-interactive and the caret does not toggle open;
refer to modelEntries.map, tokensByModel, stats, isExpanded, expandedModel and
setExpandedModel to implement these checks.

In `@src/renderer/components/report/sections/ErrorSection.tsx`:
- Around line 1-8: Imports are out of the required order: move the path-alias
type import "ReportErrors, ToolError" (from '@renderer/types/sessionReport') so
it sits with other path-alias imports (after external packages like 'react' and
'lucide-react' and before the relative import 'ReportSection'); ensure ordering
is: external packages (useState, lucide-react), path aliases (`@renderer` types),
then relative imports (ReportSection), keeping symbols ReportErrors and
ToolError grouped with other path-alias imports.

In `@src/renderer/components/report/sections/GitSection.tsx`:
- Around line 1-6: Reorder the imports so path-alias imports come before
relative imports: keep external package import (GitBranch from 'lucide-react')
first, then import ReportGitActivity from '@renderer/types/sessionReport', and
finally the relative import ReportSection from '../ReportSection'; ensure the
symbols GitBranch, ReportGitActivity, and ReportSection are used in that order
to follow the project's import ordering rules.

In `@src/renderer/store/slices/tabSlice.ts`:
- Around line 384-402: In openSessionReport, guard against missing
sourceTab.projectId or sourceTab.sessionId before calling state.openTab: verify
that sourceTab exists and both sourceTab.projectId and sourceTab.sessionId are
defined (and non-empty if applicable), and return early (or log) if either is
missing to avoid creating a report tab with no context; keep the existing label
logic but only call state.openTab({ type: 'report', label, projectId:
sourceTab.projectId, sessionId: sourceTab.sessionId }) when those IDs are
present.

In `@src/renderer/utils/sessionAnalyzer.ts`:
- Around line 106-112: The getPricing function’s substring check fails for
models where token order differs (e.g., "claude-3-opus-20240229" vs key
"opus-3"); update getPricing (referencing getPricing, MODEL_PRICING,
DEFAULT_PRICING and the local variable name) to do tokenized, order-insensitive
matching: normalize modelName to lowercase, split both the incoming modelName
and each MODEL_PRICING key into alphanumeric tokens, and consider a match when
all tokens from a pricing key are present in the modelName tokens (or vice
versa) rather than using includes(); return the matching pricing or
DEFAULT_PRICING as before.
- Around line 470-474: The loop that accumulates per-message costs (updating
stats.cacheRead, calling costUsd(...) and adding to parentCost) should
explicitly skip sidechain/subagent messages to avoid double-counting; update the
loop that iterates detail.messages to check m.isSidechain (or the message
object's isSidechain flag) and continue when true before computing callCost and
adding to parentCost, leaving stats updates for non-sidechain messages only;
this complements the existing processSubagentCost logic and ensures parentCost
only includes non-sidechain messages.

---

Outside diff comments:
In `@src/renderer/components/layout/PaneContent.tsx`:
- Around line 6-15: The imports in PaneContent.tsx are out of the project's
import-ordering rules: move all path-alias imports (those starting with
`@renderer`) before any relative imports and ensure the ordering is: external
packages, path aliases (`@main`, `@renderer`, `@shared`, `@preload`), then relative
imports; specifically ensure TabUIProvider, DashboardView, NotificationsView,
SessionReportTab, SettingsView, and the Pane type (the `@renderer` imports) appear
above the relative import SessionTabContent and any other relative paths and
adjust their grouping accordingly.

In `@src/renderer/components/layout/SortableTab.tsx`:
- Around line 6-12: Move the external package imports (including the
lucide-react icons import that currently reads "Activity, Bell, FileText,
LayoutDashboard, Pin, Search, Settings, X from 'lucide-react'") above any alias
imports such as the "@renderer" import (useStore); specifically reorder the
import block in SortableTab.tsx so all externals (react, `@dnd-kit/`*,
`@lucide-react` icons, zustand/react/shallow) appear before the "@renderer/store"
import to match the external → path-alias → relative import ordering convention.

---

Nitpick comments:
In `@docs/plans/2026-02-21-session-analysis-report.md`:
- Line 13: The markdown skips an h2 level: change the task headings (e.g., "###
Task 1: Add report types") to h2 (use "## Task 1: Add report types") so they
follow the main title "Session Analysis Report Implementation Plan"; update any
other task headings in the document that use "### Task X" to "## Task X" to keep
heading hierarchy consistent and avoid navigation issues.

In `@src/renderer/components/layout/PaneContent.tsx`:
- Around line 46-51: The report view should be wrapped with the same
TabUIProvider used for session tabs so its UI state is isolated per tab; update
the render branch where tab.type === 'report' to wrap <SessionReportTab
tab={tab} /> with <TabUIProvider tabId={tab.id}>...</TabUIProvider> (same
pattern used for SessionTabContent) so SessionReportTab can consume TabUIContext
instead of relying on local state.

In `@src/renderer/components/report/AssessmentBadge.tsx`:
- Line 17: The AssessmentBadge component is missing an explicit return type;
update its signature to include one (e.g. change "export const AssessmentBadge =
({ assessment, metricKey }: AssessmentBadgeProps) => {" to "export const
AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps): JSX.Element
=> {" or use React.ReactElement) so the component has an explicit return type;
ensure any necessary React types are imported or already available in the
project types.
- Around line 52-60: The badge span currently only uses mouse handlers
(handleMouseEnter/handleMouseLeave) and isn't keyboard-focusable; make the span
focusable by adding tabIndex={0}, wire up onFocus/onBlur to reuse
handleMouseEnter/handleMouseLeave, add an onKeyDown handler that opens the
tooltip on Enter or Space (and closes on Escape) using the same handlers, and
add an id on the tooltip div and an aria-describedby attribute on the span so
screen readers associate the badge with the tooltip; keep using
assessmentLabel(assessment) as the visible content and badgeRef for positioning.

In `@src/renderer/components/report/ReportSection.tsx`:
- Around line 5-6: The helper function sectionId should be extracted into a
small utility module to avoid Fast Refresh warnings: create a new module that
exports the sectionId(title: string) function (preserving its implementation and
name), update ReportSection to import sectionId from that module, and optionally
re-export sectionId from the component module if other callers expect it; ensure
the function signature and behavior remain unchanged so the API is preserved.

In `@src/renderer/components/report/sections/CostSection.tsx`:
- Around line 1-11: Reorder the import statements in CostSection.tsx to follow
the project's import grouping: place external packages first (e.g., import React
items like Fragment and useState from 'react' and DollarSign from
'lucide-react'), then path-alias imports (e.g., getPricing from
'@renderer/utils/sessionAnalyzer' and the types ModelTokenStats,
ReportCostAnalysis, ModelPricing from '@renderer/types/sessionReport'), and
finally relative imports (e.g., AssessmentBadge and ReportSection from
'../AssessmentBadge' and '../ReportSection'); keep the exact imported symbols
unchanged but move their lines to match the external → alias → relative order.

In `@src/renderer/components/report/sections/FrictionSection.tsx`:
- Around line 1-7: Reorder the imports in FrictionSection.tsx to follow the
external → alias → relative grouping: place third-party imports like
MessageSquareWarning from 'lucide-react' first, then path-alias imports such as
severityColor from '@renderer/utils/reportAssessments' and the types
ReportFrictionSignals/ReportThrashingSignals from
'@renderer/types/sessionReport', and finally local relative imports
AssessmentBadge and ReportSection; adjust import order so severityColor and the
types come after external packages and before the relative component imports.

In `@src/renderer/components/report/sections/InsightsSection.tsx`:
- Around line 1-12: Reorder the import block so external packages come first
(e.g., import { Lightbulb } from 'lucide-react'), then path-alias imports (move
all imports from '@renderer' or other '@...' namespaces next, e.g., the types
OutOfScopeFindings, ReportAgentTree, ReportBashCommands, SkillInvocation,
SubagentBasicEntry, UserQuestion), and finally the relative imports (e.g.,
import { ReportSection } from '../ReportSection'); update the import order in
InsightsSection.tsx to follow external → path-alias → relative grouping while
preserving named imports and formatting.

In `@src/renderer/components/report/sections/KeyTakeawaysSection.tsx`:
- Around line 1-6: The imports in KeyTakeawaysSection are out of order; reorder
them so external packages come first (e.g., import { AlertTriangle, CheckCircle,
ChevronRight, Info, XCircle } from 'lucide-react'), then path-alias imports
(e.g., import { severityColor } and type { Severity, Takeaway } from
'@renderer/utils/reportAssessments'), and finally relative imports (e.g., import
{ sectionId } from '../ReportSection'); update the import block accordingly to
follow the external → path-alias → relative rule.
- Around line 34-35: The button elements in KeyTakeawaysSection currently use
the array index (idx) as the React key which can break identity if takeaways are
ever reordered; change the key to a stable composite derived from the takeaway
object (e.g., use `${t.sectionTitle}-${t.title}` or another unique field on the
takeaway `t`) for each mapped `takeaways` item to ensure consistent identity
across reorders; update the `key={idx}` on the button to use that composite or
unique identifier instead.

In `@src/renderer/components/report/sections/OverviewSection.tsx`:
- Around line 1-6: Reorder the imports in OverviewSection.tsx to follow project
convention: place external packages first (e.g., the Activity import from
'lucide-react'), then path-alias imports (e.g., assessmentColor from
'@renderer/utils/reportAssessments' and ReportOverview from
'@renderer/types/sessionReport'), and finally relative imports (e.g.,
ReportSection from '../ReportSection'); update the import block accordingly so
import order is external → aliases → relative.

In `@src/renderer/components/report/sections/QualitySection.tsx`:
- Around line 1-12: Reorder the import block so external packages come first
(e.g., lucide-react import for BarChart3), then path-alias imports from
`@renderer` and other aliases (e.g., severityColor from
`@renderer/utils/reportAssessments` and the Report* types from
`@renderer/types/sessionReport`), and finally relative imports (AssessmentBadge
and ReportSection); update the import order accordingly while keeping the same
imported symbols (severityColor, BarChart3, AssessmentBadge, ReportSection,
ReportFileReadRedundancy, ReportPromptQuality, ReportStartupOverhead,
ReportTestProgression).

In `@src/renderer/components/report/sections/SubagentSection.tsx`:
- Around line 1-6: Reorder the import statements in SubagentSection.tsx so
external packages come first (e.g., lucide-react import for Users), then
path-alias imports (e.g., `@renderer/utils/reportAssessments` for severityColor
and `@renderer/types/sessionReport` for ReportSubagentMetrics), and finally
relative imports (e.g., ../ReportSection); update the import order accordingly
to match project conventions.

In `@src/renderer/components/report/sections/TimelineSection.tsx`:
- Around line 66-75: The inline badge in TimelineSection (the span using
assessmentColor(modelSwitches.switchPattern) and
assessmentLabel(modelSwitches.switchPattern)) duplicates styling from
AssessmentBadge; replace it by reusing AssessmentBadge or extract shared styling
logic into a small helper used by both components so the switchPattern badge
gets consistent color, background and label rendering; locate the badge in
TimelineSection around modelSwitches.switchPattern and either render
<AssessmentBadge level={modelSwitches.switchPattern} /> (suppress tooltip via a
prop) or move the color/label/background computation into a utility function
(e.g., getAssessmentStyles or assessmentColor/assessmentLabel wrappers) and call
that from TimelineSection to apply the same styles.
- Around line 1-11: Reorder the imports in TimelineSection.tsx so they follow
the project's convention: place external package imports first (e.g., Clock from
"lucide-react"), then path-alias imports from "@renderer" (e.g.,
assessmentColor, assessmentLabel from "@renderer/utils/reportAssessments" and
the types KeyEvent, ReportIdleAnalysis, ReportModelSwitches from
"@renderer/types/sessionReport"), and finally the relative component imports
(AssessmentBadge and ReportSection from "../AssessmentBadge" and
"../ReportSection"); adjust the import block to preserve the same identifiers
(assessmentColor, assessmentLabel, Clock, AssessmentBadge, ReportSection,
KeyEvent, ReportIdleAnalysis, ReportModelSwitches) but in the required order.

In `@src/renderer/components/report/sections/TokenSection.tsx`:
- Around line 1-6: Reorder the imports in TokenSection.tsx so they follow the
project guideline: put external package imports first (e.g., Coins from
'lucide-react'), then path-alias imports (e.g., ReportCacheEconomics,
ReportTokenUsage from '@renderer/types/sessionReport'), and finally relative
imports (e.g., AssessmentBadge and ReportSection from '../AssessmentBadge' and
'../ReportSection'); update the import block accordingly without changing the
imported symbols or their names.
- Around line 17-18: The sorted modelEntries array in TokenSection is being
recomputed on every render; wrap the Object.entries(...).sort(...) computation
in React's useMemo inside the TokenSection function so modelEntries is only
recalculated when data.byModel changes (reference the TokenSection component and
the modelEntries variable), i.e. import useMemo and replace the direct
assignment with a useMemo that has [data.byModel] as its dependency.

In `@src/renderer/components/report/sections/ToolSection.tsx`:
- Around line 1-7: The imports in ToolSection.tsx are not ordered per the repo
rule; reorder them so external packages (lucide-react) come first, then
path-alias imports (from `@renderer` and other @... aliases like assessmentColor
and ReportToolUsage), and finally relative imports (AssessmentBadge,
ReportSection, sectionId if they are relative) — specifically ensure the Wrench
import from 'lucide-react' appears before imports from '@renderer' and that
assessmentColor, ReportToolUsage, AssessmentBadge, ReportSection, and sectionId
follow the alias-to-relative ordering to match the project's import ordering
guideline.

In `@src/renderer/utils/reportAssessments.ts`:
- Around line 380-417: The TakeawayReport interface should be exported so tests
can import and construct type-safe mock reports: add an export modifier to the
TakeawayReport declaration (export interface TakeawayReport) and update any
internal usages if needed; then update test fixtures to import the type (e.g.,
import type { TakeawayReport } from the module containing reportAssessments) and
use it for mock objects to ensure compile-time safety.

In `@src/renderer/utils/sessionAnalyzer.ts`:
- Around line 60-97: MODEL_PRICING currently hardcodes per-model costs which can
drift; add a short inline comment above MODEL_PRICING with the "last verified"
date and a TODO; refactor so the static map is a fallback and allow loading
pricing via a new fetchModelPricing() util or from configuration (e.g.,
loadModelPricingFromConfig or loadModelPricingFromRemote) and use that in places
that reference MODEL_PRICING so prices can be updated dynamically or overridden
by environment/config in future; keep the existing MODEL_PRICING as
default/fallback and document ModelPricing shape for clarity.
- Around line 493-503: The code currently casts m to Record<string, unknown> to
read agentType/teamName/parentToolUseId in sessionAnalyzer (where agentTreeNodes
is built), which is fragile; fix by either adding optional properties
agentType?: string, teamName?: string, parentToolUseId?: string to the
ParsedMessage type definition used by this module and remove the ad-hoc cast, or
add a small type guard (e.g. isAgentMetadata(obj): obj is {agentType?: string;
teamName?: string; parentToolUseId?: string}) and use it to safely read those
fields before pushing into agentTreeNodes (update references to m and the push
to use typed properties if guard passes).

In `@test/renderer/utils/reportAssessments.test.ts`:
- Around line 309-328: Rename the test fixture healthyReport to an
UPPER_SNAKE_CASE constant (e.g., HEALTHY_REPORT) in the
describe('computeTakeaways') block and update all references to that identifier
in this test file so imports/usages of healthyReport (used inside
computeTakeaways tests) point to the new constant name; ensure the declaration
uses const and follows the same object structure, and update any other uses
within this test file (assertions, helper calls) to the new HEALTHY_REPORT
identifier.

In `@test/renderer/utils/sessionAnalyzer.test.ts`:
- Around line 12-62: Rename the builder helpers to follow the buildXxx
convention: change createMockMessage → buildMessage, createMockSession →
buildSession, createMockMetrics → buildMetrics, and createMockDetail →
buildDetail; update all usages/call sites to the new names and preserve existing
parameter types and default behavior (including the overrides Partial<T> logic)
so only the identifiers change; ensure any imports/exports or tests referencing
createMock* are updated to the new build* names.

…ter, UX guards

- getPricing: use tokenized order-insensitive matching so Claude 3 era
  models (e.g. "claude-3-opus-20240229") correctly match pricing key "opus-3"
- sessionAnalyzer: skip isSidechain messages in parentCost loop to prevent
  double-counting with processSubagentCost
- CostSection: disable row expansion when token stats are missing
- tabSlice: guard against undefined sessionId/projectId in openSessionReport

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@holstein13
Copy link
Contributor Author

CodeRabbit Review — Addressed in 586cb00

Fixed (4 findings):

Finding File Fix
getPricing substring matching fails for Claude 3 era models (e.g. "claude-3-opus-20240229" doesn't match key "opus-3") sessionAnalyzer.ts:106-112 Switched to tokenized, order-insensitive matching — split both model name and pricing key into alphanumeric tokens and check all key tokens are present
parentCost loop doesn't filter sidechain messages, double-counting subagent costs alongside processSubagentCost sessionAnalyzer.ts:458 Added !m.isSidechain guard to the usage/cost accumulation block
CostSection rows expand even when token stats are missing (caret toggles to empty content) CostSection.tsx:183-195 Disabled click handler, hover styles, and caret when stats is falsy
openSessionReport doesn't guard against undefined sessionId/projectId tabSlice.ts:389 Added early return if either ID is missing

Skipped (3 findings):

Finding Reason
Plan doc missing markdown fence language Untracked file, not committed to the repo
ErrorSection import ordering False positive — project's auto-formatter reverts the suggested change; configured lint rules enforce a different order than CodeRabbit expects
GitSection import ordering Same as above — linter disagrees with the suggestion

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: 2

🤖 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/renderer/utils/sessionAnalyzer.ts`:
- Around line 261-265: The current duration calculation using timestamps[0] and
timestamps[timestamps.length - 1] can be wrong if messages are out of order;
replace that logic in sessionAnalyzer.ts by computing the minimum and maximum
timestamp values from the timestamps array (use Math.min/Math.max or an explicit
reduce) instead of firstTs/lastTs, then compute durationMs = maxTs.getTime() -
minTs.getTime() and durationSeconds accordingly, updating the variables
timestamps, firstTs, lastTs, durationMs, and durationSeconds references.
- Around line 569-573: The code currently uses an empty string fallback for tool
call IDs which can collide; update the loop over m.toolCalls to skip adding
entries to toolCallIndex when tc.id is missing (e.g., only call
toolCallIndex.set(tc.id, [i, tc]) if tc.id is non-null/defined) and remove the
use of '' as a key fallback; apply the same change to the other occurrence that
indexes tool calls (the block handling toolCalls around the other referenced
lines) so that lookups and mappings are only stored when a real tc.id exists.

Skip indexing and lookup when tool call/result IDs are missing to
prevent collisions on the empty-string key in toolCallIndex.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

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

Duplicate comments:
In `@src/renderer/utils/sessionAnalyzer.ts`:
- Around line 261-265: The duration calculation assumes messages are
chronologically ordered; instead compute the min and max of the collected
timestamps array before deriving duration: from the existing symbols (messages,
timestamps, firstTs, lastTs, durationMs, durationSeconds) replace the
index-based firstTs/lastTs with firstTs = min(timestamps) and lastTs =
max(timestamps) (or equivalent reduce) so durationMs = lastTs.getTime() -
firstTs.getTime() (falling back to 0 if timestamps is empty), ensuring
non-negative, correct duration even when messages are out of order.

@holstein13
Copy link
Contributor Author

Code review

Found 2 issues:

  1. getPricing("Subagents (combined)") silently falls back to default Sonnet pricing. The new "Subagents (combined)" row added to byModel is expandable in CostSection — when clicked, CostBreakdownCard calls getPricing(model) with the synthetic label, which matches no key in MODEL_PRICING and returns DEFAULT_PRICING (Sonnet rates). The breakdown card then recalculates cost at wrong rates, producing a total that disagrees with the row's costUsd (which was correctly computed per-subagent model). Fix: either skip expansion for the synthetic row, or pass the pre-computed cost through without recalculating.

{modelEntries.map(([model, cost]) => {
const stats = tokensByModel[model];
const isExpanded = expandedModel === model && !!stats;
const pricing = getPricing(model);
return (
<Fragment key={model}>
<tr
className={`border-border/50 border-b ${stats ? 'hover:bg-surface-raised/50 cursor-pointer' : ''}`}

export function getPricing(modelName: string): ModelPricing {
const nameTokens: string[] = modelName.toLowerCase().match(/[a-z0-9]+/g) ?? [];
for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
const keyTokens: string[] = key.match(/[a-z0-9]+/g) ?? [];
if (keyTokens.every((t) => nameTokens.includes(t))) return pricing;
}
return DEFAULT_PRICING;
}

  1. parseTestSummary requires both "passed" and "failed" counts to be present simultaneously — if a test runner outputs only "42 passed" (common when all tests pass, since runners often omit "0 failed"), the function returns null. This drops the last test snapshot for clean-passing sessions, which can corrupt the test trajectory assessment (e.g., a session that fixed all failures appears as "stable" instead of "improving").

function parseTestSummary(text: string): [number, number] | null {
// Try "passed"/"failed" keywords
const passed = extractNumberBefore(text, ' passed');
const failed = extractNumberBefore(text, ' failed');
if (passed != null && failed != null) return [passed, failed];
// Try "passing"/"failing" keywords (mocha-style)
const passing = extractNumberBefore(text, ' passing');
const failing = extractNumberBefore(text, ' failing');
if (passing != null && failing != null) return [passing, failing];
return null;
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

… parsing

- Compute subagent cost from token breakdown instead of relying on
  unpopulated proc.metrics.costUsd; extract actual model from subagent
  messages for accurate pricing and mismatch detection
- Add aggregated "Subagents (combined)" row to cost table with arrow
  navigation to the Subagents report section (no misleading breakdown)
- Filter <synthetic> model from token/cost tracking to eliminate zero rows
- Fix parseTestSummary to treat missing pass/fail count as 0 so clean
  all-passing test runs are not dropped from trajectory analysis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@holstein13
Copy link
Contributor Author

Pushed bbd6441 addressing the code review findings plus related fixes:

Subagent cost accuracy

  • Subagent costs were always $0 — proc.metrics.costUsd is never populated upstream. Now computed client-side from the subagent's token breakdown using costUsd().
  • Model was hardcoded to "default (inherits parent)". Now extracted from the first assistant message in the subagent's session, which also fixes detectModelMismatch().

Cost table: "Subagents (combined)" row

  • Added an aggregated row showing combined subagent tokens and cost in the per-model cost table.
  • Row shows a arrow (not expand/collapse) — clicking it smooth-scrolls to the Subagents section and expands it. This avoids the getPricing fallback bug where the synthetic label would return wrong default rates in the breakdown card.

<synthetic> zero row

  • Filtered <synthetic> model messages from token/cost tracking so they no longer produce a $0.0000 row.

parseTestSummary fix

  • Changed from requiring both "passed" and "failed" counts to accepting either. Missing count defaults to 0. Fixes clean all-passing test runs being dropped from trajectory analysis.

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: 2

🤖 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/renderer/components/report/sections/CostSection.tsx`:
- Around line 1-11: Reorder the import block so external packages come first
(React imports: Fragment, useState and lucide-react's DollarSign), then
path-alias imports (getPricing from '@renderer/utils/sessionAnalyzer' and the
types ModelTokenStats, ReportCostAnalysis, ModelPricing from
'@renderer/types/sessionReport'), and finally the relative component imports
(AssessmentBadge and ReportSection/sectionId); update the top of CostSection.tsx
to reflect that ordering so imports follow the project's external → alias →
relative grouping.

In `@src/renderer/utils/sessionAnalyzer.ts`:
- Around line 1129-1153: Totals drift because tokenUsage.totals and grandTotal
are computed before injecting the "Subagents (combined)" entry into
byModel/modelStats; fix by either recomputing token totals after you add the
aggregate row or by not inserting the aggregate into tokenUsage.byModel and
instead return it separately to the cost table. Concretely, after creating
subagentTokenStats and assigning byModel[subagentLabel] and
modelStats.set(subagentLabel, subagentTokenStats), recompute
totalInputTokens/totalOutputTokens/cache totals and grandTotal (the same values
currently computed earlier as total*Tokens/grandTotal) from the updated
byModel/modelStats, or alternatively omit adding subagentLabel to byModel and
supply subagentTokenStats alongside tokenUsage so table rendering can include it
without affecting tokenUsage.totals.

---

Duplicate comments:
In `@src/renderer/utils/sessionAnalyzer.ts`:
- Around line 261-266: The duration calculation currently assumes messages are
chronological by taking firstTs/lastTs; instead compute the minimum and maximum
from the timestamps array (e.g., compute minTs = Math.min(...) and maxTs =
Math.max(...) or iterate to find min/max) and use those as the bounds when
computing durationMs and durationSeconds so negative or understated durations
are avoided; update the logic around timestamps, firstTs, lastTs, durationMs and
durationSeconds to use min/max timestamps.

- Recompute totalInputTokens, totalOutputTokens, totalCacheCreation,
  totalCacheRead, and grandTotal after injecting the "Subagents (combined)"
  row so tokenUsage.totals stays consistent with byModel rows
- Fix CostSection import order: external packages before path aliases,
  merge duplicate import type statements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 1

🤖 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/renderer/components/report/sections/CostSection.tsx`:
- Around line 139-169: In CostSection.tsx update the explanatory text that
currently renders "total cost ÷ {commitCount} commit(s)" and "total cost ÷
{linesChanged} line(s)" to avoid showing a ÷ 0 expression: detect when
commitCount === 0 or linesChanged === 0 and render an alternative label (e.g.
"total cost — no commits" / "total cost — no lines changed" or simply "N/A" /
"no data") instead of the division phrase; modify the JSX around the commitCount
and linesChanged usages and keep existing pluralization logic and
AssessmentBadge rendering unchanged (references: commitCount, linesChanged,
CostSection.tsx, costPerCommit, costPerLineChanged).

---

Duplicate comments:
In `@src/renderer/components/report/sections/CostSection.tsx`:
- Around line 1-13: Reorder the imports to follow the project's grouping: put
external package imports first (e.g., Fragment, useState from 'react' and
DollarSign from 'lucide-react'), then all `@renderer` alias imports (e.g.,
getPricing from '@renderer/utils/sessionAnalyzer' and the types ModelPricing,
ModelTokenStats, ReportCostAnalysis from '@renderer/types/sessionReport'), and
finally the relative/component imports (e.g., AssessmentBadge, ReportSection,
sectionId). Ensure a single blank line separates each group and keep the import
specifiers unchanged.

holstein13 and others added 2 commits February 22, 2026 15:47
Show "no commits" / "no lines changed" instead of "total cost ÷ 0"
when commitCount or linesChanged is zero.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ormatShortcut import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@matt1398 matt1398 merged commit 6264ec5 into matt1398:main Feb 23, 2026
4 checks passed
@holstein13 holstein13 deleted the feat/session-analysis-report branch February 23, 2026 14:50
matt1398 added a commit that referenced this pull request Feb 26, 2026
…report"

This reverts commit 6264ec5, reversing
changes made to 58c979f.
matt1398 added a commit that referenced this pull request Feb 26, 2026
The MoreMenu component (from PR #71) referenced openSessionReport
which was introduced by PR #60. Remove the dangling reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
matt1398 added a commit that referenced this pull request Feb 26, 2026
Revert PRs #60, #65, #73 and clarify project scope
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants