Skip to content

Advanced Analytics Dashboard - #27

Merged
OBenner merged 16 commits into
developfrom
auto-claude/029-advanced-analytics-dashboard
Feb 10, 2026
Merged

Advanced Analytics Dashboard#27
OBenner merged 16 commits into
developfrom
auto-claude/029-advanced-analytics-dashboard

Conversation

@OBenner

@OBenner OBenner commented Feb 9, 2026

Copy link
Copy Markdown
Owner

Create a dashboard showing build statistics, success rates, time savings, and productivity metrics. Help users understand the value Auto Claude provides.

OBenner and others added 12 commits February 7, 2026 21:04
- Implement SpecMetrics and ProductivitySummary data models
- Add aggregate_productivity_metrics() to collect cross-spec metrics
- Include time saved estimation based on complexity
- Support productivity trends over time (daily/weekly/monthly)
- Add export functionality for JSON and CSV formats
- Follow patterns from metrics_tracker.py and analytics_models.py
- Fix AttributeError when qa_signoff is None
- Use 'or {}' pattern to ensure dict type
- Update implementation plan status to completed
- Update build-progress.txt with session details
- Created productivity-analytics.ts types file matching Python backend models
- Added IPC channel constants for productivity analytics operations
- Implemented analytics-handlers.ts with handlers for:
  * getProductivityAnalytics (summary)
  * getTrends (time-series data)
  * export (JSON/CSV export)
- Registered handlers in IPC handlers index
- Follows pattern from merge-analytics-handlers.ts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added Activity icon import to Sidebar.tsx
- Added 'analytics' to SidebarView type
- Added analytics nav item with keyboard shortcut 'T'
- Added analytics translation keys (EN and FR)
- Imported ProductivityDashboard in App.tsx
- Added analytics route rendering ProductivityDashboard
- Added productivity analytics types to IPC interface
- Added mock implementations in browser-mock.ts
- Fixed ProductivityDashboard API calls to match IPC signatures
- Added CLI interface to productivity_analytics.py with --export flag
- Supports JSON and CSV export formats with date filtering
- Created ProductivityAnalyticsAPI preload module
- Integrated into AgentAPI for frontend access
- Fixed TypeScript export handler to properly call Python backend
- Export generates timestamped files in .auto-claude/analytics/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added window_days and granularity to ProductivityAnalyticsFilter
- Fixed type casting in productivity-analytics-api.ts
- All TypeScript compilation errors resolved
- Backend Python imports verified
- Created comprehensive testing documentation
@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

8 similar comments
@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

PR Summary:

Adds a full Advanced Analytics feature: backend aggregator + CLI, main ↔ renderer IPC plumbing, preload API, TypeScript types, mock data, and new dashboard UI (metrics, trends, breakdown, export/refresh).

Key changes:

  • Backend

    • New apps/backend/analysis/productivity_analytics.py: aggregates spec-level metrics from .auto-claude/specs, estimates time-saved, computes trends, exports JSON/CSV, and exposes a CLI entry (get-summary, get-trends, --export).
    • New apps/backend/cli/analytics_commands.py: CLI helper commands to display analytics, trends and export with textual UI and insights/recommendations.
    • apps/backend/cli/main.py: CLI flags and wiring to handle --analytics, --analytics-trends, --analytics-export-path, granularity/days/options; integrates handler into main CLI flow.
  • Frontend / Main process

    • New IPC handlers: apps/frontend/src/main/ipc-handlers/analytics-handlers.ts
      • Calls Python backend by spawning a process, parses JSON output.
      • Caches summary to .auto-claude/analytics/productivity_summary.json (5-minute TTL) to avoid frequent Python runs.
      • Exposes handlers: PRODUCTIVITY_ANALYTICS_GET_SUMMARY, _GET_TRENDS, _EXPORT.
    • apps/frontend/src/main/ipc-handlers/index.ts: registers new analytics handlers.
  • Preload / Renderer API

    • New preload API module apps/frontend/src/preload/api/modules/productivity-analytics-api.ts exposing:
      • getProductivitySummary, getProductivityTrends, exportProductivityAnalytics (uses invokeIpc).
    • Agent API aggregated exports updated to include ProductivityAnalyticsAPI; barrel export updated.
    • Window electronAPI type extended (IPC typings) to include analytics methods.
  • Renderer UI

    • New Analytics components:
      • ProductivityDashboard.tsx: main dashboard view (time-range filtering, refresh, export buttons), loads summary + trends via electronAPI.
      • MetricsSummaryCard.tsx: summary metrics card (4 metric cards).
      • TrendsChart.tsx: SVG-based time series chart for multiple metrics.
      • SpecBreakdownTable.tsx: searchable/filterable list of spec metrics.
    • App.tsx: mounts ProductivityDashboard for new 'analytics' view.
    • Sidebar.tsx + i18n updates: adds "Analytics" nav item and keyboard shortcut (T).
    • Browser mock updated to include productivity analytics mocks for preview.
  • Types & constants

    • New TypeScript definitions: apps/frontend/src/shared/types/productivity-analytics.ts (SpecMetrics, ProductivitySummary, Trends, filters, export options).
    • apps/frontend/src/shared/constants/ipc.ts: new IPC channel constants for productivity analytics.
    • Shared type exports updated to include productivity-analytics.
  • Docs & tests

    • New SUBTASK_4_1_COMPLETION.md and references to TESTING_RESULTS.md; integration testing completed notes included.

Behavioral/operational notes:

  • Frontend -> Python bridge executes the backend Python script via child_process spawn; on non-Windows uses 'python3', on Windows 'python'. Output must be valid JSON.
  • Handlers cache summary file for 5 minutes to reduce churn; export triggers Python export which returns output path.
  • No deletions or breaking API removals detected; most changes are additive. Small risk areas: Python process spawning environment assumptions and stdout JSON parsing; review platform/python availability and error handling for CI/packaged apps.

Next steps / considerations:

  • Verify Python runtime availability in end-user environments (packaged app).
  • Add unit/e2e tests for IPC error cases and export path behavior.
  • Replace placeholder chart/table areas with final components (some placeholders remain noted in dashboard).

Reviewed by Panto AI

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

Auto review disabled due to large PR. If you still want me to review this PR? Please comment /review

2 similar comments
@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

Auto review disabled due to large PR. If you still want me to review this PR? Please comment /review

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

Auto review disabled due to large PR. If you still want me to review this PR? Please comment /review

@OBenner OBenner closed this Feb 9, 2026
@OBenner OBenner reopened this Feb 9, 2026
@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

Do you want me to review this PR? Please comment /review .

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

OBenner commented Feb 9, 2026

Copy link
Copy Markdown
Owner Author

/review

Comment on lines +210 to +424
def _parse_timestamp(ts: str | None) -> datetime | None:
"""Parse ISO timestamp string to datetime object."""
if not ts:
return None
try:
# Handle both with and without timezone info
if ts.endswith("Z"):
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
return datetime.fromisoformat(ts)
except (ValueError, AttributeError):
return None


def _count_unique_sessions(plan: dict[str, Any]) -> int:
"""
Count unique session IDs across all subtasks.

Args:
plan: Implementation plan dict

Returns:
Number of unique sessions
"""
session_ids = set()

for phase in plan.get("phases", []):
for subtask in phase.get("subtasks", []):
session_id = subtask.get("session_id")
if session_id:
session_ids.add(session_id)

return len(session_ids)


def _estimate_time_saved(spec_metrics: SpecMetrics) -> float:
"""
Estimate time saved by AI automation (in hours).

Uses industry benchmarks:
- Simple spec: ~2-4 hours manual work
- Standard spec: ~8-16 hours manual work
- Complex spec: ~24-40 hours manual work

Returns conservative estimate based on completed subtasks.

Args:
spec_metrics: Spec metrics

Returns:
Estimated time saved in hours
"""
# Time estimates per subtask (hours) based on complexity
time_per_subtask = {
"simple": 0.5, # 30 minutes per subtask
"standard": 1.5, # 1.5 hours per subtask
"complex": 3.0, # 3 hours per subtask
}

subtask_time = time_per_subtask.get(spec_metrics.complexity, 1.5)

# Conservative estimate: only count completed subtasks
estimated_manual_hours = spec_metrics.completed_subtasks * subtask_time

# AI build time
ai_build_hours = spec_metrics.duration_seconds / 3600

# Time saved = manual time - AI time (but never negative)
return max(0.0, estimated_manual_hours - ai_build_hours)


def _extract_spec_metrics(spec_dir: Path) -> SpecMetrics | None:
"""
Extract metrics from a single spec directory.

Args:
spec_dir: Path to spec directory

Returns:
SpecMetrics object or None if invalid
"""
plan_file = spec_dir / "implementation_plan.json"
if not plan_file.exists():
return None

try:
with open(plan_file, encoding="utf-8") as f:
plan = json.load(f)

# Extract basic info
spec_id = spec_dir.name
spec_name = plan.get("feature", spec_id)
workflow_type = plan.get("workflow_type", "feature")

# Determine complexity from plan or spec metadata
complexity = "standard" # default
# Try to infer from subtask count
total_subtasks = sum(
len(phase.get("subtasks", [])) for phase in plan.get("phases", [])
)
if total_subtasks <= 3:
complexity = "simple"
elif total_subtasks >= 10:
complexity = "complex"

# Count subtasks by status
completed_subtasks = 0
failed_subtasks = 0
for phase in plan.get("phases", []):
for subtask in phase.get("subtasks", []):
status = subtask.get("status", "pending")
if status == "completed":
completed_subtasks += 1
elif status == "failed":
failed_subtasks += 1

# Determine overall status
plan_status = plan.get("status", "pending")
if plan_status == "completed" or (
total_subtasks > 0 and completed_subtasks == total_subtasks
):
status = "completed"
elif completed_subtasks > 0 or any(
subtask.get("status") == "in_progress"
for phase in plan.get("phases", [])
for subtask in phase.get("subtasks", [])
):
status = "in_progress"
else:
status = "pending"

# Time tracking
created_at = _parse_timestamp(plan.get("created_at"))
updated_at = _parse_timestamp(plan.get("updated_at"))

# Calculate duration
completed_at = None
duration_seconds = 0.0
if created_at:
if status == "completed" and updated_at:
completed_at = updated_at
duration_seconds = (completed_at - created_at).total_seconds()
elif status == "in_progress":
# In-progress: duration so far
duration_seconds = (datetime.now(UTC) - created_at).total_seconds()

# QA metrics
qa_signoff = plan.get("qa_signoff") or {}
qa_iterations = qa_signoff.get("qa_session", 0)
qa_status = qa_signoff.get("status", "pending")

# Session count
unique_sessions = _count_unique_sessions(plan)

return SpecMetrics(
spec_id=spec_id,
spec_name=spec_name,
workflow_type=workflow_type,
complexity=complexity,
status=status,
created_at=created_at,
completed_at=completed_at,
duration_seconds=duration_seconds,
total_subtasks=total_subtasks,
completed_subtasks=completed_subtasks,
failed_subtasks=failed_subtasks,
qa_iterations=qa_iterations,
qa_status=qa_status,
unique_sessions=unique_sessions,
)

except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
return None


# =============================================================================
# PUBLIC API
# =============================================================================


def aggregate_productivity_metrics(
project_dir: Path,
start_date: datetime | None = None,
end_date: datetime | None = None,
) -> ProductivitySummary:
"""
Aggregate productivity metrics across all specs.

Args:
project_dir: Path to project root
start_date: Optional start date filter (inclusive)
end_date: Optional end date filter (inclusive)

Returns:
ProductivitySummary with aggregated metrics
"""
# Locate specs directory
specs_dir = project_dir / ".auto-claude" / "specs"
if not specs_dir.exists():
# Return empty summary
return ProductivitySummary(
period_start=start_date or datetime.now(UTC),
period_end=end_date or datetime.now(UTC),
)

# Collect all spec metrics
all_specs: list[SpecMetrics] = []
for spec_dir in specs_dir.iterdir():
if not spec_dir.is_dir():
continue

spec_metrics = _extract_spec_metrics(spec_dir)
if not spec_metrics:
continue

# Apply date filters

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] Datetime handling mixes naive and timezone-aware datetimes which will raise TypeError when comparing or subtracting (e.g. _parse_timestamp returns naive when input has no tz, then code does datetime.now(UTC) - created_at and compares created_at to start_date). Normalize all parsed timestamps to timezone-aware UTC (e.g. in _parse_timestamp: if parsed dt.tzinfo is None, set tzinfo=UTC or dt = dt.replace(tzinfo=UTC); or always call dt.astimezone(UTC)). Also ensure any start_date/end_date passed into aggregate_productivity_metrics are normalized/validated (interpret naive datetimes as UTC or reject). Add explicit unit-tests for comparisons and duration calculations to cover naive vs aware inputs.

def _parse_timestamp(ts: str | None) -> datetime | None:
    """Parse ISO timestamp string to timezone-aware UTC datetime.

    Returns None for invalid or missing timestamps.
    """
    if not ts:
        return None

    try:
        # Normalize common Z suffix to explicit UTC offset
        if ts.endswith("Z"):
            dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
        else:
            dt = datetime.fromisoformat(ts)

        # Ensure timezone-aware in UTC
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=UTC)
        else:
            dt = dt.astimezone(UTC)
        return dt
    except (ValueError, AttributeError):
        return None


def _normalize_boundary(dt: datetime | None) -> datetime | None:
    """Normalize start/end boundary datetimes to timezone-aware UTC."""
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=UTC)
    return dt.astimezone(UTC)


def aggregate_productivity_metrics(
    project_dir: Path,
    start_date: datetime | None = None,
    end_date: datetime | None = None,
) -> ProductivitySummary:
    # Normalize boundaries to UTC-aware datetimes to avoid naive/aware comparison
    start_date = _normalize_boundary(start_date)
    end_date = _normalize_boundary(end_date)

    # Locate specs directory
    specs_dir = project_dir / ".auto-claude" / "specs"
    if not specs_dir.exists():
        now = datetime.now(UTC)
        return ProductivitySummary(
            period_start=start_date or now,
            period_end=end_date or now,
        )

    # Collect all spec metrics
    all_specs: list[SpecMetrics] = []
    for spec_dir in specs_dir.iterdir():
        if not spec_dir.is_dir():
            continue

        spec_metrics = _extract_spec_metrics(spec_dir)
        if not spec_metrics:
            continue

        # Apply date filters (all timestamps are now UTC-aware)
        if start_date and spec_metrics.created_at and spec_metrics.created_at < start_date:
            continue
        if end_date and spec_metrics.created_at and spec_metrics.created_at > end_date:
            continue

        all_specs.append(spec_metrics)

Comment on lines +111 to +121
// Check if cached file exists and is recent (< 5 minutes old)
let useCached = false;
if (await fileExists(summaryFile)) {
const stats = await fsPromises.stat(summaryFile);
const fileAge = Date.now() - stats.mtimeMs;
useCached = fileAge < 5 * 60 * 1000; // 5 minutes
}

let summary: ProductivitySummary;

if (useCached) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] Cache logic ignores startDate/endDate filters: the handler may return a cached productivity_summary.json even when callers requested a different date range, leading to incorrect results. Include filter parameters in the cache key (e.g. filename include start/end or hash of args) or only use the shared cache when no filters are supplied. Alternatively invalidate/refresh the cache when date filters are provided.

// Inside PRODUCTIVITY_ANALYTICS_GET_SUMMARY handler
const cacheKeyParts = ['productivity_summary'];
if (startDate) cacheKeyParts.push(`from_${startDate}`);
if (endDate) cacheKeyParts.push(`to_${endDate}`);
const summaryFile = path.join(
  analyticsDir,
  `${cacheKeyParts.join('__')}.json`
);

// ...rest of logic unchanged

Comment on lines +228 to +233
// Extract output path from Python result
const outputPath = result.output_path || options.output_path;

if (!outputPath) {
throw new Error('No output path returned from export operation');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] After calling the Python export you assume result.output_path exists (result.output_path || options.output_path). But the Python CLI/handler may return a plain string path rather than an object (see mismatch noted elsewhere). Make this robust by handling both shapes: if typeof result === 'string' use that string, if result has output_path or outputPath use that, else fall back to options.output_path and raise a clear error if none found. Update types to match the agreed shape.

// Type guard helpers
function isExportResultObject(value: unknown): value is { output_path?: string; outputPath?: string } {
  return (
    !!value &&
    (typeof (value as any).output_path === 'string' ||
      typeof (value as any).outputPath === 'string')
  );
}

// ...inside PRODUCTIVITY_ANALYTICS_EXPORT handler
const result = await executePythonAnalytics(
  project.path,
  'productivity_analytics.py',
  args
);

let outputPath: string | undefined;

if (typeof result === 'string') {
  outputPath = result;
} else if (isExportResultObject(result)) {
  outputPath = result.output_path || result.outputPath;
} else if (options.output_path) {
  outputPath = options.output_path;
}

if (!outputPath) {
  throw new Error('No output path returned from export operation');
}

return { success: true, data: outputPath };

Comment on lines +14 to +36
export interface ProductivityAnalyticsAPI {
getProductivitySummary: (projectId: string, filter?: ProductivityAnalyticsFilter) => Promise<IPCResult<ProductivitySummary>>;
getProductivityTrends: (projectId: string, filter?: ProductivityAnalyticsFilter) => Promise<IPCResult<ProductivityTrendPoint[]>>;
exportProductivityAnalytics: (projectId: string, options: ProductivityAnalyticsExportOptions) => Promise<IPCResult<{ path: string }>>;
}

/**
* Creates the Productivity Analytics API implementation
*/
export const createProductivityAnalyticsAPI = (): ProductivityAnalyticsAPI => ({
getProductivitySummary: (projectId: string, filter?: ProductivityAnalyticsFilter): Promise<IPCResult<ProductivitySummary>> => {
const startDate = filter?.start_date;
const endDate = filter?.end_date;
return invokeIpc(IPC_CHANNELS.PRODUCTIVITY_ANALYTICS_GET_SUMMARY, projectId, startDate, endDate);
},

getProductivityTrends: (projectId: string, filter?: ProductivityAnalyticsFilter): Promise<IPCResult<ProductivityTrendPoint[]>> => {
const windowDays = filter?.window_days || 30;
const granularity = filter?.granularity || 'daily';
return invokeIpc(IPC_CHANNELS.PRODUCTIVITY_ANALYTICS_GET_TRENDS, projectId, windowDays, granularity);
},

exportProductivityAnalytics: (projectId: string, options: ProductivityAnalyticsExportOptions): Promise<IPCResult<{ path: string }>> => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] Type/shape mismatch for export API: the API interface declares exportProductivityAnalytics -> Promise<IPCResult<{ path: string }>> but the IPC handler (apps/frontend/src/main/ipc-handlers/analytics-handlers.ts) registers the export handler signature as Promise<IPCResult> and returns a string output path. Align shapes: either make the handler return { output_path: string } (or { path: string }) consistently, or update the API/type to expect a string. Update both sides and tests to ensure serialization/parsing expectations match.

// apps/frontend/src/preload/api/modules/productivity-analytics-api.ts
export interface ProductivityAnalyticsAPI {
  getProductivitySummary: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ) => Promise<IPCResult<ProductivitySummary>>;
  getProductivityTrends: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ) => Promise<IPCResult<ProductivityTrendPoint[]>>;
  // Align with IPC handler which returns a string path
  exportProductivityAnalytics: (
    projectId: string,
    options: ProductivityAnalyticsExportOptions
  ) => Promise<IPCResult<string>>;
}

export const createProductivityAnalyticsAPI = (): ProductivityAnalyticsAPI => ({
  getProductivitySummary: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ): Promise<IPCResult<ProductivitySummary>> => {
    const startDate = filter?.start_date;
    const endDate = filter?.end_date;
    return invokeIpc(
      IPC_CHANNELS.PRODUCTIVITY_ANALYTICS_GET_SUMMARY,
      projectId,
      startDate,
      endDate
    );
  },

  getProductivityTrends: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ): Promise<IPCResult<ProductivityTrendPoint[]>> => {
    const windowDays = filter?.window_days || 30;
    const granularity = filter?.granularity || 'daily';
    return invokeIpc(
      IPC_CHANNELS.PRODUCTIVITY_ANALYTICS_GET_TRENDS,
      projectId,
      windowDays,
      granularity
    );
  },

  exportProductivityAnalytics: (
    projectId: string,
    options: ProductivityAnalyticsExportOptions
  ): Promise<IPCResult<string>> => {
    return invokeIpc<IPCResult<string>>(
      IPC_CHANNELS.PRODUCTIVITY_ANALYTICS_EXPORT,
      projectId,
      options
    );
  },
});

// apps/frontend/src/shared/types/ipc.ts
export interface ElectronAPI {
  // ...
  getProductivitySummary: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ) => Promise<IPCResult<ProductivitySummary>>;
  getProductivityTrends: (
    projectId: string,
    filter?: ProductivityAnalyticsFilter
  ) => Promise<IPCResult<ProductivityTrendPoint[]>>;
  // Align shape with IPC handler and API
  exportProductivityAnalytics: (
    projectId: string,
    options: ProductivityAnalyticsExportOptions
  ) => Promise<IPCResult<string>>;
  // ...
}

// apps/frontend/src/renderer/lib/browser-mock.ts
export const browserMockAPI: ElectronAPI = {
  // ...
  exportProductivityAnalytics: async () => ({
    success: true,
    data: '/mock/export/productivity',
  }),
};

Comment on lines +19 to +286
const CHART_METRICS: ChartMetric[] = [
{
key: 'completed_specs',
label: 'Completed Specs',
color: 'rgb(34, 197, 94)', // green-500
formatValue: (value: number) => value.toFixed(0),
},
{
key: 'time_saved_hours',
label: 'Time Saved (hours)',
color: 'rgb(59, 130, 246)', // blue-500
formatValue: (value: number) => value.toFixed(1),
},
{
key: 'success_rate',
label: 'Success Rate (%)',
color: 'rgb(168, 85, 247)', // purple-500
formatValue: (value: number) => (value * 100).toFixed(1),
},
];

export function TrendsChart({ trends, isLoading = false }: TrendsChartProps) {
// Calculate chart dimensions and data
const chartData = useMemo(() => {
if (!trends || trends.length === 0) return null;

const width = 800;
const height = 300;
const padding = { top: 20, right: 20, bottom: 40, left: 60 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;

// Process data for each metric
const processedMetrics = CHART_METRICS.map((metric) => {
const values = trends.map((point) => {
if (metric.key === 'success_rate') {
return point.success_rate * 100; // Convert to percentage
}
return point[metric.key] as number;
});

const maxValue = Math.max(...values);
const minValue = Math.min(...values);
const range = maxValue - minValue || 1; // Avoid division by zero

// Generate SVG path
const points = trends.map((point, index) => {
const x = padding.left + (index / (trends.length - 1 || 1)) * chartWidth;
const value = metric.key === 'success_rate' ? point.success_rate * 100 : (point[metric.key] as number);
const y = padding.top + chartHeight - ((value - minValue) / range) * chartHeight;
return { x, y, value };
});

const pathData = points
.map((point, index) => {
const command = index === 0 ? 'M' : 'L';
return `${command} ${point.x} ${point.y}`;
})
.join(' ');

// Create area path (for fill)
const areaPath = `${pathData} L ${points[points.length - 1].x} ${height - padding.bottom} L ${padding.left} ${height - padding.bottom} Z`;

return {
...metric,
points,
pathData,
areaPath,
maxValue,
minValue,
};
});

// Format dates for x-axis
const dateLabels = trends.map((point) => {
const date = new Date(point.date);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
});

return {
width,
height,
padding,
chartWidth,
chartHeight,
metrics: processedMetrics,
dateLabels,
};
}, [trends]);

if (isLoading) {
return (
<div className="rounded-lg border border-border bg-card p-6">
<div className="flex items-center gap-2 mb-4">
<TrendingUp className="h-5 w-5 text-accent" />
<h2 className="text-lg font-semibold text-foreground">Trends Over Time</h2>
</div>
<div className="h-80 flex items-center justify-center">
<div className="flex flex-col items-center gap-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-border border-t-accent" />
<p className="text-sm text-muted-foreground">Loading trends...</p>
</div>
</div>
</div>
);
}

if (!trends || trends.length === 0 || !chartData) {
return (
<div className="rounded-lg border border-border bg-card p-6">
<div className="flex items-center gap-2 mb-4">
<TrendingUp className="h-5 w-5 text-accent" />
<h2 className="text-lg font-semibold text-foreground">Trends Over Time</h2>
</div>
<div className="h-80 flex items-center justify-center">
<div className="flex flex-col items-center gap-2 text-muted-foreground">
<Calendar className="h-12 w-12 opacity-50" />
<p className="text-sm">No trend data available</p>
<p className="text-xs">Complete more tasks to see trends over time</p>
</div>
</div>
</div>
);
}

return (
<div className="rounded-lg border border-border bg-card p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<TrendingUp className="h-5 w-5 text-accent" />
<h2 className="text-lg font-semibold text-foreground">Trends Over Time</h2>
</div>

{/* Legend */}
<div className="flex items-center gap-4">
{CHART_METRICS.map((metric) => (
<div key={metric.key} className="flex items-center gap-2">
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: metric.color }}
/>
<span className="text-xs text-muted-foreground">{metric.label}</span>
</div>
))}
</div>
</div>

{/* Chart Container */}
<div className="w-full overflow-x-auto">
<svg
viewBox={`0 0 ${chartData.width} ${chartData.height}`}
className="w-full h-auto"
style={{ minHeight: '300px' }}
>
{/* Grid lines */}
{[0, 0.25, 0.5, 0.75, 1].map((fraction) => {
const y = chartData.padding.top + chartData.chartHeight * (1 - fraction);
return (
<line
key={fraction}
x1={chartData.padding.left}
y1={y}
x2={chartData.width - chartData.padding.right}
y2={y}
stroke="currentColor"
strokeWidth="1"
opacity="0.1"
className="text-muted-foreground"
/>
);
})}

{/* X-axis */}
<line
x1={chartData.padding.left}
y1={chartData.height - chartData.padding.bottom}
x2={chartData.width - chartData.padding.right}
y2={chartData.height - chartData.padding.bottom}
stroke="currentColor"
strokeWidth="2"
className="text-border"
/>

{/* Y-axis */}
<line
x1={chartData.padding.left}
y1={chartData.padding.top}
x2={chartData.padding.left}
y2={chartData.height - chartData.padding.bottom}
stroke="currentColor"
strokeWidth="2"
className="text-border"
/>

{/* Date labels (X-axis) */}
{chartData.dateLabels.map((label, index) => {
// Show fewer labels on small datasets
const showLabel =
index === 0 ||
index === chartData.dateLabels.length - 1 ||
(chartData.dateLabels.length > 5 && index % Math.ceil(chartData.dateLabels.length / 5) === 0);

if (!showLabel) return null;

const x = chartData.padding.left + (index / (trends.length - 1 || 1)) * chartData.chartWidth;
return (
<text
key={index}
x={x}
y={chartData.height - chartData.padding.bottom + 20}
textAnchor="middle"
fontSize="10"
className="fill-muted-foreground"
>
{label}
</text>
);
})}

{/* Plot lines for each metric */}
{chartData.metrics.map((metric) => (
<g key={metric.key}>
{/* Area fill */}
<path
d={metric.areaPath}
fill={metric.color}
opacity="0.1"
/>

{/* Line */}
<path
d={metric.pathData}
fill="none"
stroke={metric.color}
strokeWidth="2"
strokeLinejoin="round"
strokeLinecap="round"
/>

{/* Data points */}
{metric.points.map((point, index) => (
<g key={index}>
<circle
cx={point.x}
cy={point.y}
r="4"
fill={metric.color}
className="hover:r-6 transition-all"
/>
{/* Tooltip on hover (simplified) */}
<title>
{chartData.dateLabels[index]}: {metric.formatValue(point.value)} {metric.label}
</title>
</g>
))}
</g>
))}
</svg>
</div>

{/* Summary Stats Below Chart */}
<div className="mt-6 grid grid-cols-3 gap-4 pt-4 border-t border-border">
{CHART_METRICS.map((metric) => {
const metricData = chartData.metrics.find((m) => m.key === metric.key);
if (!metricData) return null;

const latestValue = trends[trends.length - 1][metric.key] as number;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] Inconsistent handling of success_rate causes double-percent conversion and incorrect chart labels/values. Details: CHART_METRICS.formatValue (lines ~19-37) multiplies the input by 100 expecting a 0..1 value, but chart data processing converts success_rate to a percentage (point.success_rate * 100) at lines ~52-58 and the UI 'displayValue' multiplies again at ~286-287. Pick one canonical representation: either keep success_rate as 0..1 everywhere and multiply only in the final formatter, or convert it to percentage (0..100) once during processing and update CHART_METRICS.formatValue to assume percentage input (remove the extra *100). Update all uses (points.value, tooltip text, summary display, and range formatting) to use the same representation to avoid large/wrong numbers.

const CHART_METRICS: ChartMetric[] = [
  {
    key: 'completed_specs',
    label: 'Completed Specs',
    color: 'rgb(34, 197, 94)', // green-500
    formatValue: (value: number) => value.toFixed(0),
  },
  {
    key: 'time_saved_hours',
    label: 'Time Saved (hours)',
    color: 'rgb(59, 130, 246)', // blue-500
    formatValue: (value: number) => value.toFixed(1),
  },
  {
    key: 'success_rate',
    label: 'Success Rate (%)',
    color: 'rgb(168, 85, 247)', // purple-500
    // success_rate is already a 0..1 value; keep it that way everywhere
    formatValue: (value: number) => (value * 100).toFixed(1),
  },
];

export function TrendsChart({ trends, isLoading = false }: TrendsChartProps) {
  const chartData = useMemo(() => {
    if (!trends || trends.length === 0) return null;

    const width = 800;
    const height = 300;
    const padding = { top: 20, right: 20, bottom: 40, left: 60 };
    const chartWidth = width - padding.left - padding.right;
    const chartHeight = height - padding.top - padding.bottom;

    const processedMetrics = CHART_METRICS.map((metric) => {
      const values = trends.map((point) => point[metric.key] as number);

      const maxValue = Math.max(...values);
      const minValue = Math.min(...values);
      const range = maxValue - minValue || 1;

      const steps = Math.max(1, trends.length - 1);

      const points = trends.map((point, index) => {
        const x = padding.left + (index / steps) * chartWidth;
        const value = point[metric.key] as number;
        const y = padding.top +
          chartHeight -
          ((value - minValue) / range) * chartHeight;
        return { x, y, value };
      });

      const pathData = points
        .map((point, index) => {
          const command = index === 0 ? 'M' : 'L';
          return `${command} ${point.x} ${point.y}`;
        })
        .join(' ');

      const areaPath = `${pathData} L ${points[points.length - 1].x} ${height - padding.bottom} L ${padding.left} ${height - padding.bottom} Z`;

      return {
        ...metric,
        points,
        pathData,
        areaPath,
        maxValue,
        minValue,
      };
    });

    const dateLabels = trends.map((point) => {
      const date = new Date(point.date);
      return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
    });

    return {
      width,
      height,
      padding,
      chartWidth,
      chartHeight,
      metrics: processedMetrics,
      dateLabels,
    };
  }, [trends]);

  // ...

  <div className="mt-6 grid grid-cols-3 gap-4 pt-4 border-t border-border">
    {CHART_METRICS.map((metric) => {
      const metricData = chartData.metrics.find((m) => m.key === metric.key);
      if (!metricData) return null;

      const latestValue = trends[trends.length - 1][metric.key] as number;

      return (
        <div key={metric.key} className="text-center">
          <p className="text-xs text-muted-foreground mb-1">{metric.label}</p>
          <p className="text-xl font-bold" style={{ color: metric.color }}>
            {metric.formatValue(latestValue)}
          </p>
          <p className="text-xs text-muted-foreground">
            Range: {metric.formatValue(metricData.minValue)} - {metric.formatValue(metricData.maxValue)}
          </p>
        </div>
      );
    })}
  </div>
}

Comment on lines +345 to +352
{/* Empty State */}
{!summary || summary.total_specs === 0 && (
<div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
<BarChart3 className="h-16 w-16 mb-4 opacity-50" />
<p className="text-lg font-medium">No productivity data available</p>
<p className="text-sm">Complete some tasks to see your analytics</p>
</div>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[CRITICAL_BUG] Empty state conditional uses mixed || and && without grouping which prevents the empty-state JSX from rendering when summary is null. Change the expression to explicitly group conditions, e.g. {( !summary || summary.total_specs === 0 ) && (

...
)} so the empty-state card renders when there is no summary OR when total_specs === 0.

// Empty State
{(!summary || summary.total_specs === 0) && (
  <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
    <BarChart3 className="h-16 w-16 mb-4 opacity-50" />
    <p className="text-lg font-medium">No productivity data available</p>
    <p className="text-sm">Complete some tasks to see your analytics</p>
  </div>
)}

Comment on lines +281 to +288
<p className="text-sm text-muted-foreground">Time Saved</p>
<p className="text-2xl font-bold">{summary.total_time_saved_hours.toFixed(1)}h</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Success Rate</p>
<p className="text-2xl font-bold">
{(summary.average_success_rate * 100).toFixed(1)}%
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[VALIDATION] You call toFixed() and multiply numeric fields (e.g., summary.total_time_saved_hours.toFixed(1) and (summary.average_success_rate * 100).toFixed(1)). Although types say these are numbers, the backend may return undefined/null in edge cases and calling toFixed (or *100) would throw. Add safe defaults (e.g., Number(summary.total_time_saved_hours ?? 0).toFixed(1) and guard average_success_rate) or do optional chaining + fallback to avoid runtime exceptions.

{summary && (
  <div className="rounded-lg border border-border bg-card p-6">
    <h2 className="text-lg font-semibold mb-4">Summary Metrics</h2>
    <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
      <div>
        <p className="text-sm text-muted-foreground">Total Specs</p>
        <p className="text-2xl font-bold">{summary.total_specs ?? 0}</p>
      </div>
      <div>
        <p className="text-sm text-muted-foreground">Completed</p>
        <p className="text-2xl font-bold text-green-600">{summary.completed_specs ?? 0}</p>
      </div>
      <div>
        <p className="text-sm text-muted-foreground">Time Saved</p>
        <p className="text-2xl font-bold">
          {Number(summary.total_time_saved_hours ?? 0).toFixed(1)}h
        </p>
      </div>
      <div>
        <p className="text-sm text-muted-foreground">Success Rate</p>
        <p className="text-2xl font-bold">
          {Number((summary.average_success_rate ?? 0) * 100).toFixed(1)}%
        </p>
      </div>
    </div>
  </div>
)}

Comment on lines +408 to +430
getProductivitySummary: async () => ({
success: true,
data: {
period_start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
period_end: new Date().toISOString(),
total_specs: 0,
completed_specs: 0,
in_progress_specs: 0,
failed_specs: 0,
total_time_saved_hours: 0,
total_build_time_hours: 0,
average_success_rate: 0,
first_attempt_success_rate: 0,
specs_by_type: {},
specs_by_complexity: {},
average_subtasks_per_spec: 0,
average_qa_iterations: 0,
total_subtasks_completed: 0,
specs: []
}
}),
getProductivityTrends: async () => ({ success: true, data: [] }),
exportProductivityAnalytics: async () => ({ success: true, data: { path: '/mock/export/productivity' } })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[REFACTORING] You added productivity analytics mocks which is good for browser preview. Make the mocks closer to real payloads: accept (projectId, filter) parameters and honor filter.start_date/end_date and window_days when generating trend points; return sensible non-null defaults for numeric fields to match TypeScript expectations. This reduces surprizes when switching from browser-preview to Electron (and helps tests).

// In apps/frontend/src/renderer/lib/browser-mock.ts

// Productivity analytics operations
getProductivitySummary: async (
  _projectId?: string,
  filter?: import('../../shared/types').ProductivityAnalyticsFilter
) => {
  const now = Date.now();
  const windowDays = filter?.window_days ?? 30;
  const periodEnd = filter?.end_date ? new Date(filter.end_date).getTime() : now;
  const periodStart = filter?.start_date
    ? new Date(filter.start_date).getTime()
    : periodEnd - windowDays * 24 * 60 * 60 * 1000;

  const periodStartIso = new Date(periodStart).toISOString();
  const periodEndIso = new Date(periodEnd).toISOString();

  return {
    success: true,
    data: {
      period_start: periodStartIso,
      period_end: periodEndIso,
      total_specs: 0,
      completed_specs: 0,
      in_progress_specs: 0,
      failed_specs: 0,
      total_time_saved_hours: 0,
      total_build_time_hours: 0,
      average_success_rate: 0,
      first_attempt_success_rate: 0,
      specs_by_type: {},
      specs_by_complexity: {},
      average_subtasks_per_spec: 0,
      average_qa_iterations: 0,
      total_subtasks_completed: 0,
      specs: [],
    },
  };
},

getProductivityTrends: async (
  _projectId?: string,
  filter?: import('../../shared/types').ProductivityAnalyticsFilter
) => {
  const windowDays = filter?.window_days ?? 30;
  const end = filter?.end_date ? new Date(filter.end_date) : new Date();
  const start = filter?.start_date
    ? new Date(filter.start_date)
    : new Date(end.getTime() - windowDays * 24 * 60 * 60 * 1000);

  // Return a minimal but realistic single-point trend so charts have data
  const point: import('../../shared/types').ProductivityTrendPoint = {
    date: start.toISOString(),
    total_specs: 1,
    completed_specs: 1,
    time_saved_hours: 1,
    success_rate: 1,
  };

  return { success: true, data: [point] };
},

exportProductivityAnalytics: async (
  _projectId?: string,
  _options?: import('../../shared/types').ProductivityAnalyticsExportOptions
) => ({
  success: true,
  data: { path: '/mock/export/productivity' },
}),

Comment on lines +25 to +49
// Filter specs based on all criteria
const filteredSpecs = specs.filter((spec) => {
// Apply search filter
const matchesSearch =
spec.spec_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
spec.spec_id.toLowerCase().includes(searchQuery.toLowerCase()) ||
spec.workflow_type.toLowerCase().includes(searchQuery.toLowerCase());

// Apply status filter
const matchesStatus =
statusFilter === 'all' ||
spec.status.toLowerCase() === statusFilter.toLowerCase();

// Apply complexity filter
const matchesComplexity =
complexityFilter === 'all' ||
spec.complexity.toLowerCase() === complexityFilter.toLowerCase();

// Apply type filter
const matchesType =
typeFilter === 'all' ||
spec.workflow_type.toLowerCase() === typeFilter.toLowerCase();

return matchesSearch && matchesStatus && matchesComplexity && matchesType;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[PERFORMANCE_OPTIMIZATION] filteredSpecs recalculates searchQuery.toLowerCase() multiple times per spec. Precompute a lower-cased search string once (const q = searchQuery.toLowerCase()) and reuse it when checking spec fields to reduce allocations and improve readability.

  // Filter specs based on all criteria
  const filteredSpecs = specs.filter((spec) => {
    const q = searchQuery.toLowerCase();

    // Apply search filter
    const matchesSearch =
      spec.spec_name.toLowerCase().includes(q) ||
      spec.spec_id.toLowerCase().includes(q) ||
      spec.workflow_type.toLowerCase().includes(q);

    // Apply status filter
    const matchesStatus =
      statusFilter === 'all' ||
      spec.status.toLowerCase() === statusFilter.toLowerCase();

    // Apply complexity filter
    const matchesComplexity =
      complexityFilter === 'all' ||
      spec.complexity.toLowerCase() === complexityFilter.toLowerCase();

    // Apply type filter
    const matchesType =
      typeFilter === 'all' ||
      spec.workflow_type.toLowerCase() === typeFilter.toLowerCase();

    return matchesSearch && matchesStatus && matchesComplexity && matchesType;
  });

@pantoaibot

pantoaibot Bot commented Feb 9, 2026

Copy link
Copy Markdown

Reviewed up to commit:df55a069acf08718f4f9e74b820f3bd85937b992

Additional Suggestion
apps/frontend/src/main/ipc-handlers/analytics-handlers.ts, line:36-61 Spawning the Python process uses a heuristic ('python' on Windows, 'python3' elsewhere) which is brittle across environments and virtualenvs. Prefer resolving the project's Python path via a configuration or pythonEnvManager (already present elsewhere in the app) or allow callers to override the interpreter. Also consider adding a timeout and explicit error handling/cleanup for long-running/hung Python processes to avoid leaking processes or UI freezes.
async function executePythonAnalytics(
  projectPath: string,
  scriptName: string,
  args: string[] = [],
  pythonPathOverride?: string,
  timeoutMs = 60_000
): Promise<unknown> {
  return new Promise((resolve, reject) => {
    const pythonPath = pythonPathOverride
      || process.env.AUTOCLAUDE_PYTHON
      || (process.platform === 'win32' ? 'python' : 'python3');

    const scriptPath = path.join(projectPath, 'apps', 'backend', 'analysis', scriptName);

    const proc = spawn(pythonPath, [scriptPath, ...args], {
      cwd: projectPath,
      env: { ...process.env, PYTHONPATH: path.join(projectPath, 'apps', 'backend') },
    });

    let stdout = '';
    let stderr = '';
    const timeout = setTimeout(() => {
      proc.kill();
      reject(new Error(`Python analytics timed out after ${timeoutMs}ms`));
    }, timeoutMs);

    proc.stdout.on('data', (data) => {
      stdout += data.toString();
    });

    proc.stderr.on('data', (data) => {
      stderr += data.toString();
    });

    proc.on('close', (code) => {
      clearTimeout(timeout);
      if (code !== 0) {
        reject(new Error(`Python script failed: ${stderr || `exit code ${code}`}`));
        return;
      }

      try {
        const result = JSON.parse(stdout);
        resolve(result);
      } catch (error) {
        reject(new Error(`Failed to parse Python output: ${error}`));
      }
    });

    proc.on('error', (error) => {
      clearTimeout(timeout);
      reject(new Error(`Failed to spawn Python process: ${error.message}`));
    });
  });
}

Reviewed by Panto AI


Few more points:

  • [CRITICAL_BUG] There is a mismatch/duplication of CLI flags for analytics output: earlier in the parser --analytics-output is defined but later the new flag --analytics-export-path is added and code reads args.analytics_export_path (lines ~458). This will cause the user's --analytics-output to be ignored. Consolidate to a single flag name (or support both and normalize) and update code to read the correct attribute. Also validate there are no other duplicate/conflicting analytics-related flags (analytics-format vs analytics_format naming consistency).
# In apps/backend/cli/main.py within _parse_args()
# Rename the existing merge analytics output flag to avoid confusion
parser.add_argument(
    "--merge-analytics-output",
    type=str,
    default=None,
    metavar="FILE",
    help="Output file for merge analytics export",
)

# Keep the new productivity analytics flag clearly separate
parser.add_argument(
    "--analytics-export-path",
    type=str,
    default=None,
    metavar="FILE",
    help="Export productivity analytics to file (with --analytics)",
)

# In handle_merge_analytics_export_command call
if args.merge_analytics_export:
    handle_merge_analytics_export_command(
        project_dir,
        output_path=args.merge_analytics_output,
        format=args.analytics_format,
    )
    return

# In productivity analytics handler, keep using args.analytics_export_path
if args.analytics:
    export_path = Path(args.analytics_export_path) if args.analytics_export_path else None
    handle_analytics_command(
        project_dir=project_dir,
        trends=args.analytics_trends,
        days=args.analytics_days,
        granularity=args.analytics_granularity,
        export_path=export_path,
        export_format=args.analytics_format,
    )
    return

OBenner and others added 3 commits February 10, 2026 20:11
- Use getConfiguredPythonPath/parsePythonCommand/getAugmentedEnv in
  analytics-handlers instead of hardcoded python path
- Fix operator precedence bug in ProductivityDashboard empty state
- Clarify sys.path comment in analytics_commands.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix naive/aware datetime mixing in _parse_timestamp (normalize to UTC)
- Add _normalize_boundary for start_date/end_date params
- Include date filters in analytics cache key to avoid stale results
- Handle both string and object shapes in export result parsing
- Align export API type to IPCResult<string> across all layers
- Fix double-percent conversion of success_rate in TrendsChart
- Add null guards (??0) on numeric fields in ProductivityDashboard
- Improve browser mocks with filter-aware params and proper types
- Precompute toLowerCase() in SpecBreakdownTable filter loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep both productivity-analytics and template-library additions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@OBenner
OBenner merged commit 55c2303 into develop Feb 10, 2026
17 checks passed
@OBenner
OBenner deleted the auto-claude/029-advanced-analytics-dashboard branch February 10, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant