diff --git a/src/webview/page-antipatterns.ts b/src/webview/page-antipatterns.ts index 09b3ba6..587f57f 100644 --- a/src/webview/page-antipatterns.ts +++ b/src/webview/page-antipatterns.ts @@ -215,12 +215,26 @@ function thresholdRow(key: string, value: number, ruleId: string): ComponentChil `; } +/* ── Module-level view state ── */ +let activeRangeDays = 0; + +function buildRangeFilter(f: DateFilter): Record { + const filter: Record = { ...f }; + if (activeRangeDays > 0) { + const d = new Date(); + d.setDate(d.getDate() - activeRangeDays); + filter.fromDate = d.toISOString().slice(0, 10); + } + return filter; +} + /* ── Main render ── */ export async function renderAntiPatterns(container: HTMLElement, currentFilter: DateFilter): Promise { + const filter = buildRangeFilter(currentFilter); const [apData, ruleData] = await Promise.all([ - rpc('getAntiPatterns', currentFilter as Record), - rpc('getRuleEditor', currentFilter as Record), + rpc('getAntiPatterns', filter), + rpc('getRuleEditor', filter), ]); const patterns = apData.patterns || []; @@ -246,6 +260,14 @@ export async function renderAntiPatterns(container: HTMLElement, currentFilter:

Review health scores across practice groups, drill into individual findings, and manage the rules that detect them. Switch to the Rules tab to browse, create, or edit detection rules using the built-in DSL.

+
+ + + + + +
+
@@ -539,8 +561,17 @@ export async function renderAntiPatterns(container: HTMLElement, currentFilter: // Wire tab switching wireTabBar(container); + // Wire date range filter + container.querySelector('#antiPatternsRange')?.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('[data-range]'); + const range = btn?.dataset.range; + if (!btn || !range) return; + activeRangeDays = Number(range); + void renderAntiPatterns(container, currentFilter); + }); + // Wire up rules section - wireRulesSection(container, rules, previewMap, currentFilter); + wireRulesSection(container, rules, previewMap, filter); } /* ── Tab switching ── */ @@ -1231,4 +1262,3 @@ function renderLayerStatus(layers: RuleLayerInfo[], layerName: string): Componen if (!info.exists) return html`Directory not found`; return html`${info.ruleCount} rule${info.ruleCount !== 1 ? 's' : ''} loaded`; } - diff --git a/src/webview/page-dashboard.ts b/src/webview/page-dashboard.ts index 067577e..519cd32 100644 --- a/src/webview/page-dashboard.ts +++ b/src/webview/page-dashboard.ts @@ -14,6 +14,7 @@ import { llmAvailable } from './capabilities'; // Module-level view state — survives filter/harness changes. let activeMetric = 'requests'; +let activeRangeDays = 0; const DASHBOARD_LANGUAGE_ALIASES: Record = { py: 'python', @@ -96,6 +97,7 @@ function renderDashboardMarkup( totalSessions: number, totalLoc: number, skillCache: ReturnType, + currentFilter: DateFilter, ): void { const harnesses = harnessBreakdown.labels || []; const overallScore = scores.length > 0 ? Math.round(scores.reduce((s, g) => s + g.score, 0) / scores.length) : 0; @@ -125,8 +127,13 @@ function renderDashboardMarkup( ${harnesses.length > 0 && html`
${harnesses.map((h, i) => html`${h}`)}
`}
- ${!FF_TOKEN_REPORTING_ENABLED && html`
\u2139
Token Usage & Burndown temporarily hidden

These features are disabled until we can verify that reported numbers align with GitHub's billing data. They will be re-enabled once validated.

`} - ${scores.length > 0 && html`

Anti-Patterns Summary

View All Anti-Patterns \u2192
${scores.map(g => html`<${PracticeCard} g=${g} />`)}
`} + ${!FF_TOKEN_REPORTING_ENABLED && html`
\u2139
Token Usage & Burndown temporarily hidden

These features are disabled until we can verify that reported numbers align with GitHub's billing data. They will be re-enabled once validated.

`}
+ + + + + +
${scores.length > 0 && html`

Anti-Patterns Summary

View All Anti-Patterns \u2192
${scores.map(g => html`<${PracticeCard} g=${g} />`)}
`} ${llmAvailable() && html`

Scans your prompt history for repeated patterns that waste time re-explaining the same tasks.

${!skillCache && html`

Analyze your prompt history to discover skill opportunities.

`}
`}

Daily Activity

<${CanvasEl} id="dailyChart" height=${160} />
<${CanvasEl} id="wsChart" height=${140} title="Top Workspaces by Requests" /><${CanvasEl} id="harnessChart" height=${140} title="Requests by Harness" />
@@ -184,16 +191,27 @@ function renderDashboardSkillFinder(skillCache: ReturnType }); } +function buildRangeFilter(f: DateFilter): Record { + const filter: Record = { ...f }; + if (activeRangeDays > 0) { + const d = new Date(); + d.setDate(d.getDate() - activeRangeDays); + filter.fromDate = d.toISOString().slice(0, 10); + } + return filter; +} + export async function renderDashboard(container: HTMLElement, currentFilter: DateFilter): Promise { const emptyDaily: DailyActivity = { labels: [], values: [], sessions: [], loc: [], workspaces: [], byHarness: [] }; const emptyCodeProd: CodeProductionData = { summary: { totalAiLoc: 0, totalUserLoc: 0, totalLoc: 0, aiBlocks: 0, userBlocks: 0, aiRatio: 0, locCost2010: 0, costPerLoc: 0 }, byLanguage: { labels: [], aiLoc: [], userLoc: [] }, dailyTimeline: { labels: [], aiLoc: [], userLoc: [] }, byWorkspace: { labels: [], aiLoc: [], userLoc: [] }, dailyByWorkspace: {}, dailyByModel: {}, dailyByHarness: {} }; + const filter = buildRangeFilter(currentFilter); const [stats, daily, wsBreakdown, harnessBreakdown, antiPatterns, codeProd] = await rpcAllSettled([ - rpc<{ totalSessions: number; totalWorkspaces: number; totalRequests: number }>('getStats', currentFilter as Record), - rpc('getDailyActivity', currentFilter as Record), - rpc<{ labels: string[]; values: number[] }>('getWorkspaceBreakdown', currentFilter as Record), - rpc<{ labels: string[]; requests: number[] }>('getHarnessBreakdown', currentFilter as Record), - rpc('getAntiPatterns', currentFilter as Record), - rpc('getCodeProduction', currentFilter as Record), + rpc<{ totalSessions: number; totalWorkspaces: number; totalRequests: number }>('getStats', filter), + rpc('getDailyActivity', filter), + rpc<{ labels: string[]; values: number[] }>('getWorkspaceBreakdown', filter), + rpc<{ labels: string[]; requests: number[] }>('getHarnessBreakdown', filter), + rpc('getAntiPatterns', filter), + rpc('getCodeProduction', filter), ] as const, [ { totalSessions: 0, totalWorkspaces: 0, totalRequests: 0 }, emptyDaily, @@ -236,6 +254,7 @@ export async function renderDashboard(container: HTMLElement, currentFilter: Dat totalSessions, totalLoc, skillCache, + currentFilter, ); // ── Activity chart with switchable metrics ── @@ -295,6 +314,15 @@ export async function renderDashboard(container: HTMLElement, currentFilter: Dat renderActivityChart(); + // Range filter switching + document.getElementById('dashboardRange')?.addEventListener('click', (e) => { + const btn = (e.target as HTMLElement).closest('[data-range]'); + const range = btn?.dataset.range; + if (!btn || !range) return; + activeRangeDays = Number(range); + void renderDashboard(container, currentFilter); + }); + // Tab switching document.getElementById('activityTabs')?.addEventListener('click', (e) => { const btn = (e.target as HTMLElement).closest('[data-metric]');