Skip to content

feat: branch filtering, activity rollups, and usage grouping - #1017

Open
wesm wants to merge 7 commits into
mainfrom
feat/branch-filtering
Open

feat: branch filtering, activity rollups, and usage grouping#1017
wesm wants to merge 7 commits into
mainfrom
feat/branch-filtering

Conversation

@wesm

@wesm wesm commented Jul 7, 2026

Copy link
Copy Markdown
Member

Adds Git branch as a first-class filter and grouping dimension across session
discovery, Activity, and Usage. This combines and supersedes #974, #978, and
#979 as part of #928.

Branch identity remains project-qualified through opaque tokens, so same-named
branches in different repositories stay independent. The stable
/api/v1/branches contract continues to return project, branch, and token;
bounded, recency-ordered picker search now uses the additive
/api/v1/branch-names endpoint.

Session search, the sidebar, HTTP clients, and MCP tools can filter by branch.
Picker suggestions follow the active project and session scope, retain the
branchless option outside bounded results, and recover safely from stale
archive-specific identities.

Activity now provides branch rollups across SQLite, PostgreSQL, and DuckDB. Its
branch panel shows the 39 highest-value rows and combines the remainder into an
Other row, keeping large archives responsive without misrepresenting totals.

Usage can group charts and attribution by branch with selection shared across
rows, URLs, and saved preferences. Branch data is loaded lazily without
interrupting comparisons; totals-only comparison queries skip branch buckets,
and unattributed usage remains visible so percentages reconcile with summary
totals.

Branch selections are include-based and intersect with the sidebar branch
filter when both are active. Large explicit selections can still increase URL
and request size, while branch breakdown payloads grow with per-day branch
churn; both costs are opt-in.

@roborev-ci

roborev-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (8a0c66b)

Branch filtering looks mostly solid, but there are two medium-severity parity/UI gaps to fix before merge.

Medium

  • frontend/src/lib/components/filters/SessionFilterControl.svelte:104
    The branch dropdown offers every branch even when a project filter is active. Because the backend ANDs the project filter with opaque (project, branch) branch tokens, selecting a branch from another project creates a contradictory filter and returns an empty view.
    Fix: Scope visible branch options to the active project, and clear or disable selected branch tokens whose project no longer matches.

  • cmd/agentsview/usage.go:557
    The daemon path for dailyUsageQuery serializes project, machine, model, and other filters but drops GitBranch and the new ExcludeGitBranch. The local backend passes those fields through, so branch-filtered daily usage can return different results depending on whether a daemon is used.
    Fix: Add git_branch and exclude_git_branch query params in fetchHTTPDailyUsage and cover the serialization in the HTTP daily usage tests.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 12m13s

@roborev-ci

roborev-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (d1653e1)

Summary verdict: One medium issue should be addressed before merging.

Medium

  • frontend/src/lib/stores/activity.svelte.ts:237
    The new Activity branch filter loads options from /api/v1/branches, but that endpoint is backed by GetBranches, which filters out subagent and fork sessions. Activity rollups include both subagents and forks, and usage totals include fork usage, so branch rollups can contain branches that never appear in the dropdown. In Activity they cannot be selected or excluded, and in Usage a fork-only branch can become impossible to re-enable individually after it is hidden.

    Suggested fix: Add a branch metadata mode or endpoint for Activity/Usage that includes the same relationship scope as those reports, while keeping the existing root-only list for the sidebar.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 13m13s

@roborev-ci

roborev-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (d316550)

Summary verdict: One medium-severity issue should be addressed before merge.

Medium

  • frontend/src/lib/components/filters/SessionFilterControl.svelte:96
    • Problem: The sidebar branch dropdown ranks and shows all branches even when a project filter is active. Users can select a branch token from another project, silently combining contradictory predicates and producing an empty session list.
    • Fix: Scope visible branch options to sessions.filters.project when set, and clear or reject incompatible selected branch tokens when project state changes.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 15m56s

@roborev-ci

roborev-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown

roborev: Combined Review (480664b)

No blocking security issues found, but one medium backend correctness issue should be fixed before merge.

Medium

  • internal/duckdb/store.go:636 - GetBranches calls s.duck.QueryContext directly, bypassing the store’s queryContext wrapper used for DuckDB remote/quack connections. In that mode, the branch metadata query can run against the wrong connection path and fail instead of using the attached remote query mechanism.
    • Fix: Use s.queryContext(ctx, ...), matching GetMachines and the other DuckDB store query methods.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 17m18s

@roborev-ci

roborev-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown

roborev: Combined Review (5bcdfc8)

Summary verdict: one medium issue remains; no high or critical findings were reported.

Medium

  • frontend/src/lib/stores/sessions.svelte.ts:711: The sidebar loads branch metadata with scope: "all", so it can offer branches that exist only on subagent/fork sessions. The sidebar session query still applies git_branch predicates to root candidates, then includes descendants unfiltered, so selecting a sub-session-only branch can produce an empty sidebar even though the option was offered.
    • Fix: Keep the sidebar branch list scoped to root sessions, or update sidebar root selection to include roots whose descendant tree contains a matching branch while still returning the full matched tree for display.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 15m3s

@wesm

wesm commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

This branch needs more work (I have over 1000 branches in my session database, and there are performance sharp edges) so I'm going to ship 0.37.0 and then get this merged when I can

@roborev-ci

roborev-ci Bot commented Jul 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (b13fce9)

Summary verdict: Two medium issues remain; no high or critical findings were reported.

Medium

  • Location: frontend/src/lib/stores/sessions.svelte.ts:710
    Problem: The sidebar requests branch options with scope: "all", so branches that exist only on subagent/fork sessions are offered, but the sidebar session query still applies git_branch only to root candidates. Selecting one of those child-only branch options can return an empty sidebar even though the dropdown offered it as valid.
    Fix: Either request root-scoped branches for the sidebar, or update sidebar-index filtering across backends to match roots whose descendants have the selected branch.

  • Location: frontend/src/lib/stores/usage.svelte.ts:338
    Problem: When the sidebar branch filter and usage page branch selection do not overlap, effectiveGitBranch silently falls back to the sidebar branch, so the usage page can show a selected branch that is not actually applied to the API request.
    Fix: Make the effective filter reflect the true intersection, or clear/scope the local usage branch selection when it conflicts with the sidebar filter.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 13m53s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from b13fce9 to 2682399 Compare July 10, 2026 02:57
@roborev-ci

roborev-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (2682399)

Changes requested: two medium-severity branch filtering issues can produce misleading usage results.

Medium

  • internal/db/usage.go:932 — Cursor usage is assigned the synthetic pair ("", ""), making it appear as a clickable “no branch” attribution. Selecting it filters out Cursor rows and may show unrelated session-backed usage with empty project and branch. Represent Cursor usage as explicitly unattributed and non-filterable, or add a special filter token handled consistently across SQLite, PostgreSQL, and DuckDB. Add a cursor-only branch attribution test.

  • frontend/src/lib/stores/usage.svelte.ts:328 — When sidebar and usage-local branch selections do not overlap, effectiveGitBranch ignores the usage-local selection while both controls remain visibly active. Use the true intersection and show no results, or clear/reconcile incompatible selections.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 5m43s

@roborev-ci

roborev-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (52a78ed)

The changes are generally sound, but one medium-severity UI behavior mismatch should be fixed.

Medium

  • Misleading branch-row actionfrontend/src/lib/components/usage/AttributionPanel.svelte:103
    The panel says “click to hide,” but clicking a branch uses the new include-based toggleBranch, filtering to only that branch and hiding all others—the opposite of the advertised action. Update the copy and behavior to accurately represent drill-down/selection, or maintain separate exclusion state for attribution-row clicks.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 4m12s

@roborev-ci

roborev-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (34b3e7f)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 3m39s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from 34b3e7f to 117e099 Compare July 12, 2026 19:30
@roborev-ci

roborev-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (117e099)

Medium-severity issues remain in branch attribution and sync-time scalability.

Medium

  • Arbitrary branch attribution for deduplicated usageinternal/db/usage.go:1846-1883, internal/activity/activity.go:750-760
    Branch costs are assigned after global first-seen usage deduplication. When resumed or forked sessions contain the same deduplication key on different branches, lexicographic session-ID ordering can charge shared historical usage to an arbitrary branch. This can make activity and usage totals disagree with the branch where usage originated. Define a branch-aware survivor policy—such as preferring the original/root session—and apply it consistently across SQLite, PostgreSQL, DuckDB, and activity aggregation. Add a parent/fork fixture covering duplicate usage keys across branches.

  • Archive-wide branch refresh after every syncinternal/db/sessions.go:2654-2673, frontend/src/lib/stores/activity.svelte.ts:485-487, frontend/src/lib/stores/sessions.svelte.ts:1213-1231
    Once branch controls are loaded, every sync completion reruns GetBranches, grouping and sorting all qualifying sessions by activity. Updates to one active session therefore trigger work proportional to the entire archive. Refresh metadata only when affected sessions introduce or change a (project, branch) pair, or maintain an incremental branch catalog. At minimum, debounce and lazily invalidate the query, with a regression test comparing small and large archives.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 4m48s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from 117e099 to aa5196a Compare July 12, 2026 19:57
@roborev-ci

roborev-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (aa5196a)

Changes look sound overall, but the Usage branch filter can create conflicting project/branch selections.

Medium

  • frontend/src/lib/components/usage/UsagePage.svelte:59 — The branch dropdown includes branches from all projects even when a project filter is active. Selecting or restoring a branch from another project applies incompatible predicates and produces an unexplained empty report. The persisted selectedGitBranch also survives project changes. Scope branch options to sessions.filters.project and reconcile selected branch tokens when the project changes, retaining only branches belonging to that project or clearing the selection.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 6m43s

@roborev-ci

roborev-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (7ce4693)

The PR has one medium-severity issue: project filtering can conflict with branch selection.

Medium

  • frontend/src/lib/components/usage/UsagePage.svelte:59 — The branch dropdown includes branches from every project even when a project filter is active. Selecting another project’s branch sends contradictory project and git_branch predicates, unexpectedly producing an empty report. Scope branch options to sessions.filters.project, and clear or reconcile selected branch tokens when the project changes.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 4m52s

@roborev-ci

roborev-ci Bot commented Jul 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (73adffb)

Medium-severity performance issue found in branch dropdown rendering.

Medium

  • Eager rendering of all branch rowsfrontend/src/lib/components/filters/SessionFilterControl.svelte:450, frontend/src/lib/components/usage/FilterDropdown.svelte:127

    Both dropdowns render every branch eagerly. Archives containing thousands of branches will create thousands of buttons when opened; scrolling limits panel height but does not constrain DOM creation or reactive work.

    Suggested fix: Virtualize the rows, or initially render a bounded recent subset with search or on-demand expansion.


Reviewers: 2 done | Synthesis: codex, 6s | Total: 5m31s

@roborev-ci

roborev-ci Bot commented Jul 14, 2026

Copy link
Copy Markdown

roborev: Combined Review (b4a6c0e)

The PR needs changes: two medium-severity issues can merge distinct projects and misrepresent branch-attributed usage.

Medium

  • Opaque project identity missing from branch rollupsinternal/db/usage.go:1610, internal/activity/activity.go:194

    Sanitization converts path-based project labels to "". As a result, distinct projects on the same branch can be merged in usage totals, collide as frontend keys, and generate unusable empty-project filter tokens.

    Fix: Propagate project_key through branch breakdowns and totals. Use it for aggregation, rendering identity, and filter resolution while retaining the sanitized label for display.

  • Branch attribution excludes non-session usagefrontend/src/lib/components/usage/AttributionPanel.svelte:54

    Cursor events and other non-session usage are omitted, and percentages are calculated only from branch-attributed cost. Cursor-only periods therefore show “no data,” while mixed periods can misleadingly show a branch as 100% of usage.

    Fix: Add a non-filterable “Unattributed” row for the difference between total cost and branch totals, and calculate percentages against the complete total.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 7m26s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from b4a6c0e to 04adf03 Compare July 15, 2026 01:31
@roborev-ci

roborev-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown

roborev: Combined Review (04adf03)

Code review identified two medium-severity branch-filter identity and reconciliation issues; no critical or high-severity findings.

Medium

  • internal/db/usage.go:1674 — Branch rows sanitize Project without retaining an opaque identity. Absolute-path projects become "", causing foldBranchTotals to merge distinct projects that share a branch. AttributionPanel.svelte:56 then constructs filters such as "\x1fmain", which match only an empty project. Activity branch rows can likewise lose identity and produce duplicate keyed rows.

    • Fix: Carry a stable project key and opaque branch-filter identity alongside the display label. Use these fields for aggregation, frontend keys, and filtering.
  • frontend/src/lib/branchFilters.ts:59 — Legacy selection reconciliation keeps only the first project-qualified token for a shared branch name. For example, selecting another branch while both project-a/main and project-b/main are active silently removes project-b/main.

    • Fix: Preserve every current token whose normalized branch remains selected, or deliberately migrate the entire equivalence class to the plain branch-name representation.

Reviewers: 2 done | Synthesis: codex, 13s | Total: 8m36s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from 04adf03 to 63ca296 Compare July 19, 2026 15:01
@roborev-ci

roborev-ci Bot commented Jul 19, 2026

Copy link
Copy Markdown

roborev: Combined Review (7cfd1c7)

Review found three medium-severity issues involving branch identity, API compatibility, and unattributed usage costs.

Medium

  • internal/db/usage.go:1650, internal/activity/activity.go:196 — Branch rollups lack an opaque project key. Sanitization blanks path- or URL-shaped project labels, so distinct projects on the same branch can collide in frontend identities, merge in foldBranchTotals, or create duplicate Svelte keys. Clicking these rows creates a token scoped to an empty project and returns no matching data. Carry a pre-sanitization project_key or opaque filter token through branch breakdowns and totals, use it for frontend keys, and resolve it to the raw project when filtering.

  • internal/server/huma_routes_metadata.go:50/api/v1/branches replaces the existing response contract with deduplicated {branch} entries, removing project and token. This breaks released clients expecting scoped tokens, while the documentation still directs callers to use the removed token. Preserve the existing endpoint and add a separate bounded branch-name search endpoint, or version the breaking response and update documentation and clients together.

  • frontend/src/lib/components/usage/AttributionPanel.svelte:54 — Branch attribution uses branchTotals, which excludes cursor usage, and calculates percentages against that partial subtotal. Branches may therefore appear to represent 100% of cost while a substantial portion of totals.totalCost remains unattributed. Add a non-filterable “Unattributed” row for totalCost - sum(branchTotals.cost) and include it in the percentage calculation.


Reviewers: 2 done | Synthesis: codex, 13s | Total: 12m34s

@roborev-ci

roborev-ci Bot commented Jul 20, 2026

Copy link
Copy Markdown

roborev: Combined Review (d34459c)

Branch attribution has three medium-severity correctness issues involving lost project identity and incomplete cost visualization.

Medium

  • Stable project identity is discarded

    • Location: internal/db/usage.go:1712, internal/activity/activity.go:108
    • Absolute-path projects become empty display labels, causing same-named branches to merge, duplicate frontend keys, and attribution links that cannot match sessions.
    • Fix: Carry an opaque project_key or branch token alongside the display label and use it for aggregation, keyed rendering, and filtering.
  • Cursor usage is omitted from branch attribution

    • Location: frontend/src/lib/components/usage/AttributionPanel.svelte:54
    • Cursor-only ranges incorrectly show “No data,” while mixed ranges calculate percentages against an incomplete subtotal.
    • Fix: Add a non-filterable “Unattributed” row for the difference from summary.totals.totalCost, and calculate percentages against the complete total.
  • Treemap truncation misrepresents omitted costs

    • Location: frontend/src/lib/components/usage/AttributionPanel.svelte:95
    • Limiting the treemap to 40 rows causes the visible subset to fill the chart, making omitted branches appear to contribute no cost.
    • Fix: Aggregate omitted entries into an “Other” tile or reserve proportional area for them.

Reviewers: 2 done | Synthesis: codex, 10s | Total: 10m4s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from d34459c to c69d89b Compare July 20, 2026 20:49
@roborev-ci

roborev-ci Bot commented Jul 20, 2026

Copy link
Copy Markdown

roborev: Combined Review (c69d89b)

The PR needs changes: three medium-severity issues affect branch identity, API compatibility, and usage-page scalability.

Medium

  • Branch identities collide across projectsinternal/db/usage.go:1712, internal/activity/activity.go:108
    Branch rollups sanitize the project label without retaining an opaque project key. Absolute-path and URL projects sanitize to "", causing same-named branches from different projects to merge in usage totals, duplicate Activity UI keys, and produce empty-project filter tokens that cannot match the original session.
    Fix: Carry a project_key or opaque compound branch identity through branch breakdowns and use it for aggregation, UI keys, and filter resolution. Use the sanitized project string only for display.

  • Existing branches API contract is brokeninternal/server/huma_routes_metadata.go:50, docs/session-api.md:123
    /api/v1/branches changes from unbounded (project, branch, token) rows to at most 100 deduplicated branch names. Existing consumers lose required fields and cannot enumerate the full catalog, while the documentation still directs callers to retrieve opaque tokens from this endpoint.
    Fix: Preserve the existing response by default and introduce a separate/versioned search endpoint or opt-in bounded mode. Update the documentation and generated client accordingly.

  • Default usage summaries process and render an unbounded branch dimensioninternal/db/usage.go:1990, frontend/src/lib/components/usage/AttributionPanel.svelte:225
    Because breakdowns defaults to true, every usage-summary request partitions, aggregates, and serializes all branches even when grouping by project, model, or agent. Although the treemap is capped, the side rail and list still render every branch, so routine usage-page work scales poorly for archives with thousands of branches.
    Fix: Request branch breakdowns only when branch grouping is selected, or load them lazily, and virtualize or bound the rendered branch list.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 12m53s

@roborev-ci

roborev-ci Bot commented Jul 21, 2026

Copy link
Copy Markdown

roborev: Combined Review (2228d33)

Branch filtering is generally sound, but four medium-severity correctness and performance issues should be addressed.

Medium

  • frontend/src/lib/stores/usage.svelte.ts:749 — The lazy branch-breakdown refresh replaces this.summary with a response lacking comparison, despite preserveRelatedState, and aborts any in-flight comparison without restarting it. Selecting Branch therefore removes the “vs prior” metric until another full refresh. Preserve and merge the existing comparison into the branch-rich response, or refetch it when unavailable; add a regression test.

  • frontend/src/lib/branchFilters.ts:57-104 — Branch reconciliation and intersection collapse project-qualified tokens by branch name. Retaining both project-a/main and project-b/main, or intersecting plain main with both qualified filters, keeps only the first and silently excludes valid project/branch pairs. Preserve all compatible qualified tokens and deduplicate only exact token identities; add multi-project, same-branch tests for both helpers.

  • internal/db/usage.go:2037, internal/db/usage.go:2377, internal/postgres/usage.go:1395, internal/postgres/usage.go:1710 — With breakdowns=false&branch_breakdowns=true, SQLite and PostgreSQL take the breakdown path without populating agent breakdowns. CopilotAICredits is then calculated from empty agent slices and incorrectly becomes zero. Calculate AI credits directly from the accumulator, consistent with the fast and DuckDB paths, and test branch-only requests on both supported backends.

  • frontend/src/lib/stores/usage.svelte.ts:319baseParams() enables branch breakdowns for every Usage endpoint while Branch grouping is active. Full refreshes make prior-period comparison and pairwise queries build large branch maps that their responses discard, multiplying work on branch-heavy archives. Enable branchBreakdowns only for summary requests and defensively clear it from comparison and pairwise filters before calling GetDailyUsage.


Reviewers: 2 done | Synthesis: codex, 13s | Total: 14m49s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from 2228d33 to bf872a3 Compare July 22, 2026 03:35
@roborev-ci

roborev-ci Bot commented Jul 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (bf872a3)

Review found three medium-severity correctness issues involving branch-filter identity, unattributed costs, and backend parity.

Medium

  • frontend/src/lib/stores/usage.svelte.ts:351, 537 — Exact (project_key, branch) selections are not preserved. Project scoping compares the opaque project key with the raw project label and drops the branch filter, while selection logic normalizes qualified tokens to branch names, conflating same-named branches from different projects. Keep project-key-qualified tokens exact, restrict normalization to plain picker values, and add coverage for active project filters and duplicate branch names across projects.

  • frontend/src/lib/components/usage/AttributionPanel.svelte:55 — Branch attribution renders only branchTotals, although cursor usage is excluded from branch breakdowns. Normalizing percentages over that partial sum makes the panel misleadingly account for 100% of total cost. Add a non-filterable “Unattributed” remainder calculated from summary.totals.totalCost - sum(branchTotals).

  • internal/db/usage.go:2473; internal/postgres/usage.go:1834 — With branch_breakdowns=true&breakdowns=false, SQLite and PostgreSQL calculate Copilot AI credits from empty agent breakdowns and return zero, while DuckDB uses the accumulator correctly. Calculate AI credits directly from accumulated agent/cost keys in the branch-breakdown path to preserve independent flags and backend parity.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 10m41s

@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (bf872a3)

Medium-severity issues remain in branch usage loading, credit accounting, and attribution display.

Medium

  • Race-prone lazy branch loadingfrontend/src/lib/stores/usage.svelte.ts:818
    Selecting Branch while the initial summary is loading returns immediately and is never retried, leaving branch views empty until refresh. When a summary already exists, enrichment via fetchSummary aborts in-flight comparison and pairwise requests without restarting them because loadComparison is false.
    Fix: Queue branch enrichment until initial loading completes and merge it through a dedicated request that does not invalidate unrelated panels, or explicitly restart aborted requests.

  • Incorrect Copilot AI credits in branch-only modeinternal/db/usage.go:2473, internal/postgres/usage.go:1834
    With breakdowns=false&branch_breakdowns=true, SQLite and PostgreSQL calculate credits from empty AgentBreakdowns, yielding zero despite nonzero Copilot usage. DuckDB calculates directly from the accumulator, breaking backend parity.
    Fix: Calculate credits from the agent-bearing accumulator independently of emitted breakdown rows, and add a branch-only regression test.

  • Branch attribution hides unattributed costfrontend/src/lib/components/usage/AttributionPanel.svelte:55
    Cursor or otherwise unattributable cost is omitted, so the panel can show “No data” despite real cost or display visible branches as 100% while hiding part of the total.
    Fix: Add an “Unattributed” row for summary.totals.totalCost - sum(branchTotals.cost) and include it in percentages, treemap, rail, and list rendering.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 7m52s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from bf872a3 to 1e00449 Compare July 27, 2026 18:30
@roborev-ci

roborev-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (1e00449)

Branch filtering is generally sound, but three medium-severity issues should be addressed before merging.

Medium

  • frontend/src/lib/branchFilters.ts:72scopeBranchFilterValues compares qualified tokens against the raw project label, but usage attribution qualifies tokens with opaque pl1:sha256: project keys. Selecting the matching project can therefore discard the active branch filter and broaden results to every branch in that project. Preserve opaque project-key tokens during scoping or resolve them before comparison, and add coverage for a project-key-qualified branch combined with a project filter.

  • frontend/src/lib/stores/usage.svelte.ts:728 — A focused branch-breakdown refresh does not actually preserve related state: fetchSummary aborts comparison and pairwise requests, then replaces this.summary with a response lacking comparison. Switching to Branch can remove the prior-period comparison and leave an in-flight pairwise panel unpopulated. When preserveRelatedState is enabled, retain the existing comparison and valid same-filter requests, or restart them after accepting the branch-rich summary.

  • internal/server/huma_routes_usage.go:271; internal/service/direct.go:732 — With Branch grouping active, branch_breakdowns=true propagates into prior-period and pairwise calculations. Those paths disable normal breakdowns but leave branch breakdowns enabled, causing unused and potentially expensive aggregation across thousands of branches. Set BranchBreakdowns = false alongside Breakdowns = false for comparison and pairwise filters, or omit the parameter from those frontend requests.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 11m55s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from 1e00449 to 50015a4 Compare July 27, 2026 20:10
@roborev-ci

roborev-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (50015a4)

Medium-severity issues remain in branch selection, refresh state, and query efficiency.

Medium

  • frontend/src/lib/stores/usage.svelte.ts:728 — A focused branch-breakdown refresh overwrites summary, dropping its existing comparison. It also aborts in-flight comparison/pairwise requests without restarting them because loadComparison is false. Switching to Branch can therefore hide comparison data until a full refresh.

    • Preserve and merge the existing comparison into the enriched summary. Avoid invalidating unrelated requests, or restart invalidated requests. Add coverage for completed and in-flight comparisons.
  • frontend/src/lib/stores/usage.svelte.ts:533toggleBranch and isBranchSelected normalize qualified (project_key, branch) tokens to branch names. Selections such as project-a/main and project-b/main are treated as identical, preventing independent selection and potentially removing both.

    • Preserve exact identity for project-qualified tokens; normalize only plain branch-picker values. Test same-named branches from different projects.
  • internal/server/huma_routes_usage.go:271; internal/service/direct.go:732 — Auxiliary comparison queries clear Breakdowns but leave BranchBreakdowns enabled. In Branch mode, prior-period and pairwise queries unnecessarily aggregate every branch even though only totals are used, multiplying expensive high-cardinality work.

    • Set BranchBreakdowns = false on prior, left, and right filters before calling GetDailyUsage.

Reviewers: 2 done | Synthesis: codex, 14s | Total: 10m26s

@roborev-ci

roborev-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (d48073e)

Branch filtering and rollups have five medium-severity issues requiring fixes; no security vulnerabilities were identified.

Medium

  • frontend/src/lib/stores/usage.svelte.ts:728 — Branch enrichment uses the general summary refresh path, aborting comparison and pairwise requests and potentially replacing the summary with a response lacking comparison. Make enrichment independent, tie it to the current summary generation, and merge branch data without aborting related requests.

  • frontend/src/lib/stores/usage.svelte.ts:533toggleBranch and isBranchSelected reduce qualified tokens to branch names, so branches such as project-a/main and project-b/main cannot be selected independently. Compare complete tokens when both are qualified; normalize by branch name only for unqualified picker values.

  • frontend/src/lib/stores/usage.svelte.ts:818 — Selecting Branch while the initial summary loads returns early because summary is null, and enrichment is never retriggered after loading. Record the pending request or trigger enrichment whenever a non-rich summary commits while Branch grouping is active.

  • internal/db/usage.go:2548, internal/postgres/usage.go:1907 — With branch_breakdowns=true and breakdowns=false, SQLite and PostgreSQL calculate Copilot AI credits from empty AgentBreakdowns, returning zero and disagreeing with DuckDB. Calculate credits directly from accumulated per-agent costs regardless of the ordinary-breakdown flag.

  • internal/server/huma_routes_usage.go:271, internal/service/direct.go:732 — Prior-period and pairwise comparison filters disable ordinary breakdowns but retain BranchBreakdowns, causing totals-only queries to aggregate and materialize every branch. Disable BranchBreakdowns for prior-period and pairwise filters before querying.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 13m19s

@mariusvniekerk
mariusvniekerk force-pushed the feat/branch-filtering branch from d48073e to 5af56d7 Compare July 31, 2026 19:29
@roborev-ci

roborev-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown

roborev: Combined Review (5af56d7)

The branch-aware filtering changes have four medium-severity correctness, race, and performance issues; no security vulnerabilities were identified.

Medium

  • Credits incorrectly become zero without agent breakdowns
    Location: internal/db/usage.go:2631, internal/postgres/usage.go:1968
    With breakdowns=false&branch_breakdowns=true, agent breakdowns remain empty, but SQLite and PostgreSQL calculate CopilotAICredits exclusively from them. DuckDB correctly calculates credits from the usage accumulator.
    Fix: Calculate credits from the usage accumulator and add parity coverage for this flag combination.

  • Qualified branch selections can be silently dropped
    Location: frontend/src/lib/branchFilters.ts:53
    Reconciliation consumes each normalized branch name once. If a legacy selection contains alpha/main and beta/main, adding another branch through the picker drops one existing main token and changes the filter results.
    Fix: Preserve every qualified token whose normalized branch remains selected, then add plain tokens only for names not already represented.

  • Selecting Branch during initial loading can leave views empty
    Location: frontend/src/lib/stores/usage.svelte.ts:859
    ensureBranchBreakdowns returns while summary is null, so selecting Branch during an in-flight non-branch summary request does not fetch branch data. The Branch views remain empty after that response until another refresh.
    Fix: Queue or restart the pending summary with branchBreakdowns: true, with a deferred-response test covering this race.

  • Unused branch aggregates are built for comparison requests
    Location: frontend/src/lib/stores/usage.svelte.ts:887, frontend/src/lib/stores/usage.svelte.ts:927
    branchBreakdowns: true is forwarded to comparison and pairwise requests. Their backend paths disable ordinary breakdowns but retain branch breakdowns, causing prior-period and pairwise queries to build unused aggregates, which can be costly for branch-heavy archives.
    Fix: Remove branchBreakdowns from non-summary requests and defensively set BranchBreakdowns = false in comparison and pairwise backend filters.


Reviewers: 2 done | Synthesis: codex, 15s | Total: 12m26s

@wesm
wesm force-pushed the feat/branch-filtering branch from 5af56d7 to 46ac5c9 Compare August 1, 2026 10:19
@roborev-ci

roborev-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown

roborev: Combined Review (46ac5c9)

Branch filtering introduces four medium-severity correctness and performance issues.

Medium

  • frontend/src/lib/stores/usage.svelte.ts:867 — Lazy branch enrichment calls fetchSummary, which advances the summary version, aborts comparison and pairwise requests, and replaces the summary without its completed comparison. Since loadComparison is false, the lost or aborted data is not reloaded. Fetch enrichment independently against the current summary generation, then merge the branch-rich response without aborting related requests or discarding comparison.

  • frontend/src/lib/stores/usage.svelte.ts:544 — Branch selection normalizes qualified tokens to branch names, causing values such as project-a/main and project-b/main to collide. Selecting one can clear the other, and both may appear selected when only one is active. Compare complete tokens when both values are project-qualified; normalize by branch name only when at least one value is unqualified.

  • frontend/src/lib/branchFilters.ts:72 — Attribution creates branch tokens qualified by opaque project_key, while project filters use raw project labels. scopeBranchFilterValues compares them directly and drops the branch token, so selecting an attribution branch has no effect when its project is already pinned. Preserve opaque qualified tokens for server-side resolution, or resolve project identity equivalence before client-side scoping.

  • internal/server/huma_routes_usage.go:274, internal/service/direct.go:732 — Comparison and pairwise queries disable ordinary breakdowns but leave BranchBreakdowns enabled. With Branch grouping active, totals-only queries unnecessarily construct potentially thousands of branch buckets. Set BranchBreakdowns = false on the prior-period filter and both pairwise filters before calling GetDailyUsage.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 12m35s

Make branch identity usable across session discovery, activity analysis, and usage attribution so users can inspect work by branch without losing backend parity or fast default query paths.

Squashed follow-ups:

- fix(usage): preserve comparison work during lazy branch enrichment
- fix(usage): keep project-qualified branches independently selectable
- perf(usage): skip branch buckets for totals-only comparisons

Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
Co-authored-by: Marius van Niekerk <marius.v.niekerk@gmail.com>
@wesm
wesm force-pushed the feat/branch-filtering branch from 46ac5c9 to bd46820 Compare August 1, 2026 12:01
@roborev-ci

roborev-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown

roborev: Combined Review (bd46820)

Medium-severity issues remain in branch attribution and branch selection.

Medium

  • frontend/src/lib/components/usage/AttributionPanel.svelte:62 — Branch attribution uses only branchTotals, while cursor usage is included in overall totals but excluded from branch breakdowns. This can show “No data” despite nonzero usage or percentages that omit part of the total. Add an “Unattributed” row equal to the summary total minus summed branch totals, mirroring CostTimeSeriesChart.

  • frontend/src/lib/components/shared/BranchPicker.svelte:70 — The no-branch option appears only when the empty branch is included in the server’s bounded 100-result page. With more recent branches beyond that limit, users cannot find it through substring search. Surface branchless availability separately or pin the no-branch option outside the bounded results.


Reviewers: 2 done | Synthesis: codex, 16s | Total: 16m2s

Branch totals do not cover cursor usage, and server branch discovery is deliberately bounded. Without explicit fallbacks, the UI can understate percentages or hide branchless selection entirely.

Represent the unattributed remainder in branch summaries and reserve the branchless picker entry outside capped search results so filters and totals remain usable for large histories.
@roborev-ci

roborev-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown

roborev: Combined Review (5bc5509)

The PR has one medium-severity correctness issue; no critical or high-severity findings or security regressions were identified.

Medium

  • Branch enrichment can be lost or overwrittenfrontend/src/lib/stores/usage.svelte.ts:852

    Branch enrichment is discarded while the summary is loading, and an in-flight non-branch summary can overwrite an enrichment response because both use the same summary version. Switching to Branch in either case leaves charts showing all usage as “Unattributed” until another toggle or refresh.

    Suggested fix: Persist the branch-breakdown intent, trigger enrichment after every non-rich summary completes, and prevent non-rich responses from overwriting a completed rich response. Add tests covering switches during initial loading and cached refetches.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 22m38s

Branch selection can change while an ordinary summary is already in flight. Without generation-aware reconciliation, that response can either suppress the requested enrichment or overwrite richer data that completed first.

Treat the current grouping as durable intent and only let responses replace branch-rich state when they belong to a newer generation.
@roborev-ci

roborev-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown

roborev: Combined Review (cffeb3f)

Code changes need revision due to two medium-severity branch-filtering issues.

Medium

  • frontend/src/lib/branchFilters.ts:68,113 — Reconciliation and intersection collapse project-qualified tokens that share a branch name. Intersecting plain main with project-a/main and project-b/main retains only the first project because the inner loop breaks, making results order-dependent and silently excluding sessions. Preserve every qualified token matching a selected plain branch, deduplicate afterward, and test same-name branches across multiple projects in both operand orders.

  • frontend/src/lib/stores/usage.svelte.ts:156,179,829 — Archive-scoped project keys embedded in branch selections are persisted globally and added to URLs, while unknown-key recovery handles only excludedProjectKeys. Switching archives, removing a project, or opening a stale shared URL can leave usage requests failing or showing stale cached data. Scope persisted branch filters to the archive/server, store plain branch names, or clear invalid qualified tokens and retry when the backend reports an unknown project key.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 19m49s

Plain branch selections can represent several project-qualified identities, and persisted opaque keys can outlive the archive that created them. Collapsing either case silently narrows results or leaves cached usage stale after a 400 response.

Keep every qualified match, and recover unknown keys in stages so valid precision is retained before falling back to portable branch names.
@roborev-ci

roborev-ci Bot commented Aug 1, 2026

Copy link
Copy Markdown

roborev: Combined Review (3061fc8)

Changes need fixes: two medium-severity usage UI issues could produce empty reports or misleading attribution.

Medium

  • frontend/src/lib/components/usage/UsagePage.svelte:114 — Branch suggestions include one-shot and automated sessions even when the active usage report excludes them, so selecting a suggested branch can produce an empty report. Pass sessions.filters.includeOneShot and includeAutomated to searchBranches.

  • frontend/src/lib/components/usage/AttributionPanel.svelte:137 — The treemap discards rows after the top 40, then normalizes the retained rows to fill the canvas, misleadingly implying they represent all usage. Aggregate omitted rows into an “Other” tile or preserve their proportion as unused area.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 13m53s

wesm added 2 commits August 1, 2026 19:17
Branch suggestions must use the same one-shot and automation scope as the report they filter; otherwise the picker can offer branches that yield an empty dashboard.\n\nLarge treemaps also need to preserve the omitted share of the total. Aggregate overflow into a non-interactive Other tile so the capped visualization remains proportional without implying that hidden rows do not exist.
The branch-filtering pull request should not carry implementation plans and design notes from unrelated completed work. Removing these branch-only artifacts keeps the review surface focused and matches the requested publication scope.
@roborev-ci

roborev-ci Bot commented Aug 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (a6ddc27)

Changes requested: three medium-severity API contract, documentation, and frontend scalability issues need resolution.

Medium

  • internal/db/sessions.go:3462 — The stable v1 /api/v1/branches response removes the existing project and token fields and collapses project/branch pairs into branch names. This breaks existing clients and the documented additive-only API contract. Preserve the endpoint’s current schema and semantics; expose bounded branch-name search through a new endpoint or versioned contract.

  • docs/session-api.md:123 — The documentation conflicts with the implementation: it claims ordering by session count, shows a nonexistent session_count field, and later tells clients to obtain opaque tokens from an endpoint that no longer returns them. Update the page to consistently describe the actual fields, recency ordering, and name-based filtering.

  • frontend/src/lib/components/activity/Breakdowns.svelte:200 — The uncapped by_branch collection renders one DOM row per branch. Archives with thousands of branches could cause excessive rendering work and an unwieldy panel. Virtualize the list or cap visible rows with an aggregated “Other” row, and add coverage for large branch lists.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 28m1s

The branch picker needs bounded name search, but changing the stable v1 metadata endpoint would break clients that depend on project-qualified tokens. Keep that contract intact and expose picker-oriented search through an additive route.\n\nLarge archives also need a bounded Activity branch panel. Preserve the report total by aggregating overflow into an Other row instead of rendering an unbounded list or silently dropping data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants