feat: branch filtering, activity rollups, and usage grouping#1017
feat: branch filtering, activity rollups, and usage grouping#1017wesm wants to merge 32 commits into
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
|
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: Combined Review (
|
b13fce9 to
2682399
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
34b3e7f to
117e099
Compare
roborev: Combined Review (
|
117e099 to
aa5196a
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
b4a6c0e to
04adf03
Compare
roborev: Combined Review (
|
04adf03 to
63ca296
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
d34459c to
c69d89b
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
Exposes the scoped (project, branch) filter everywhere sessions are browsed: --branch flags on session list, session search, and activity report (rejected without --project, since a branch name only identifies work within a project); git_branch parameters on the MCP tools; and a Branch section in the web sidebar filter with multi-select, search, selected entries floated to the top, removable chips, a numeric active-filter badge on the filter button, and URL round-trip. The sidebar selection also drives the Analytics dashboard, and the usage endpoints receive the same session-scoped branch filter. Branch filters travel as opaque (project, branch) tokens because the shared filter field carries multiple pairs and project or branch names can contain commas; CLI and MCP accept two plain parameters and encode internally, so callers never see the token format. Branch metadata is ordered by most recent session activity so pickers surface current work first, and cmd/testfixture seeds git branches so e2e suites and screenshots exercise the branch surfaces with synthetic data.
The runner image's packages.microsoft.com repos intermittently fail clearsign validation and abort apt-get update; the docs job only needs ripgrep from the Ubuntu archive.
GET /api/v1/branches gains a scope param: roots (default) keeps the sidebar's root-only list, all also counts subagent and fork sessions. Activity rollups and usage totals include those sessions, so their branch controls previously omitted branches the breakdowns could surface - in Usage a fork-only branch hidden via a chart click had no unhide row. The Activity typeahead and Usage exclude dropdown now request scope=all across SQLite, PostgreSQL, and DuckDB.
Sibling worktrees share the default golangci-lint cache, and cached suggested-fixes resolve through worktree-relative paths, so a run with --fix can apply byte offsets computed against another checkout's version of a file and corrupt it in place. Pointing GOLANGCI_LINT_CACHE at a gitignored directory inside the checkout removes the shared state.
With a project filter pinned, the sidebar branch dropdown offered every project's branches; selecting a foreign one combined contradictory predicates and emptied the session list. Branch options now scope to the active project, matching the Activity page. Selecting a project already clears any selected branch via setProjectFilter's filter reset.
The sidebar branch dropdown loaded /api/v1/branches with the default roots scope, hiding branches that exist only on subagent or fork sessions (common with worktree subagents). The sidebar index matches those sessions against the branch filter, so the options list now requests scope=all like the Activity and Usage pages. Automated-only branches still appear only when automated sessions are included.
The db slow-write diagnostics (InsertMessages and friends) fire on slow CI runners while a benchmark's result line is half-printed, splicing into it and corrupting the capture; GetDailyUsage lost 4 of 6 samples that way, which benchgate treats as fatal. Slow-op logs are now suppressed under go test via testing.Testing(). benchgate also treated baseline corruption as exit 2, but the merge-base run executes code a PR cannot fix, and a benchmark corrupted only in the baseline still parses on the candidate side, where it takes the missing-baseline not-gated path. Baseline corruption now warns instead of failing; candidate corruption stays fatal.
Deselect-all in the usage branch dropdown excludes every known (project, branch) pair; BranchPairPredicate joined those as a flat OR chain, which parses left-deep and exceeds SQLite's 1000-deep expression cap, so usage summary and top-sessions returned 500 (Expression tree is too large). The predicate now nests as a balanced OR tree, keeping parse depth logarithmic with identical semantics on all backends. The kit-ui dropdown panel also had no height cap, so the uncapped branch list grew past the viewport with no way to scroll; the app wrapper now clamps the panel and scrolls it until a cap lands upstream in kit-ui.
The branch dropdown was exclusion-based, so focusing on one branch encoded as excluding every other known (project, branch) pair — a predicate that grows with the branch catalog rather than the selection, and at real catalog sizes produced enormous queries and URLs. The dropdown now uses the include mode the Model filter already uses: empty selection means all branches, and picking branches sends a git_branch include list sized by the selection. The sidebar branch filter and the local selection share one API param, so they compose by intersection; a persisted local selection with no overlap defers to the sidebar filter. The usage URL key is branch (session params own git_branch when the two merge), and saved exclude-mode state resets to all branches since an exclusion set has no include equivalent. The include-mode trigger label now resolves the item's display label instead of echoing the raw token.
Cursor usage has no session-backed project or branch, but the usage breakdown treated its empty pair as a selectable branch attribution. That made a Cursor-only cost row look filterable while the filter itself cannot recover those rows. Keep Cursor costs in totals/model/agent attribution while leaving branch attribution to session-backed usage across SQLite, PostgreSQL, and DuckDB. When the sidebar branch filter and usage-local branch selection have no overlap, both controls are visibly active. Send a fail-closed branch token for that empty intersection so usage charts reflect the active filters instead of falling back to sidebar-only results.
The attribution panel advertised every row click as "click to hide", but branch and model clicks feed include-based selections (selectedGitBranch/selectedModels), so clicking filters the dashboard to the clicked item — the opposite of hiding it. Only project and agent clicks are exclusions, so the hint, row tooltips, and treemap aria labels now describe the actual action per grouping, and a selected row's copy flips to "clear the filter" since include-mode rows stay visible while filtered. Treemap no longer hard-codes the hide copy; the caller passes the tooltip/aria formatters, per the shared-component localization rule in DESIGN.md.
Rebasing onto main's session-evidence work (#1075) crossed two refactors: main added toggleProjectKey calling the store's toggleCsv helper, while this branch had already replaced toggleCsv with the shared toggleListValue utility. The merged tip kept main's call to the deleted method, so excluding a project by key threw at click time. toggleProjectKey now uses toggleListValue like every other toggle, and the buildUsageUrlParams test literal gains the excludedProjectKeys field main's UsageUrlState requires.
…copy The cost time series fell back to a single day-total series whenever the active grouping had no breakdowns, so in branch grouping a Cursor-only archive rendered its unattributable cost as a series while the attribution panel correctly reported no branch data. Branch mode now shows the empty state instead, keeping both branch views telling the same story; other groupings keep the total fallback. The include-mode attribution hint promised only filtering, but once a row is selected its click clears that filter, so the hint flips to neutral add-or-remove copy in all locales. Coverage extends to model mode, treemap tile titles/aria labels in both selection states, and a SQLite contract test that a separator-less git_branch token (the frontend's disjoint-filter sentinel) fails closed to zero rows rather than broadening or erroring.
The usage page's branch dropdown offered every project's branches even with a sidebar project filter pinned, so picking another project's branch ANDed contradictory project and git_branch predicates into an empty report. Branch options now scope to the active project filter, mirroring the sidebar dropdown, and the query path drops off-project tokens from the stored selection at request time rather than mutating it — a selection made before pinning a project defers to the project filter and takes effect again when the filter clears.
The branch-filter work predated the French catalog added on main, so rebasing left the new locale without 19 user-facing keys and broke the catalog parity test. Add native French copy for the branch picker, active-filter, activity, and usage messages so every configured locale exposes the same interface.
The branch picker duplicated input chrome that kit-ui already owns, causing the frontend policy check to reject the PR and letting this control drift from the rest of the app. Adopt the shared search input while retaining the picker's remote multi-select behavior, and pass localized clear-action copy through its reusable component contract.
Branch attribution added git_branch to every daily-usage row, including totals-only requests that discard it. Scanning that unused text column across large histories pushed GetDailyUsage allocation volume past the benchmark gate. Select branch and machine dimensions only for breakdown requests in both SQLite and PostgreSQL, matching DuckDB's existing query shape.
The remaining hosted review findings span private project identity, usage-summary loading, and large-list rendering. Record the agreed in-place design and its non-goals before changing those cross-backend and frontend contracts.
The remaining same-head findings cross Activity identity, three usage backends, generated API contracts, store refresh behavior, and high-cardinality rendering. Record the approved task boundaries and test-first sequence so those changes preserve backend parity and can be reviewed as focused commits.
Sanitized private project labels can collapse distinct branch rows onto the same display text. Carry the opaque project key alongside that safe label so downstream branch selection and keyed rendering can remain exact without exposing raw paths.
Private project labels can sanitize to the same display value, which made range totals and branch filters collapse distinct repositories. Carry the opaque project key through daily and range-wide rows, and resolve key-qualified tokens back to raw labels only inside the service boundary.
Most Usage views need project, model, agent, and machine attribution but never consume per-branch rows. Separate branch scan columns, accumulator keys, and folds behind an explicit request flag so ordinary summaries avoid cardinality work while all three storage backends keep matching behavior.
Branch display labels are intentionally sanitized and can collide, so they cannot serve as stable chart, list, or filter identities. Expose the opaque key through the server/OpenAPI seam and use it only for identity while keeping safe project and branch labels visible.
Branch attribution can contain thousands of rows but is irrelevant to the default Usage grouping. Request it only for an active Branch view, retain the rich response across grouping switches, and coalesce rapid repeated selections without disturbing related panel state.
Branch attribution remains intentionally uncapped, so eager DOM rendering grows with archive cardinality even though only a viewport-sized slice is visible. Reuse the existing TanStack wrapper for both scrolling row surfaces while preserving the treemap cap and row interactions.
The branches endpoint now returns bounded branch-name search results rather than opaque project-qualified tokens. Document the actual unshipped contract so clients use search, project scope, limit, and has_more correctly without inventing a compatibility path.
Activity JSON now exposes opaque branch project keys, while ordinary daily usage breakdowns intentionally omit branch rows unless separately requested. Keep the public golden contracts aligned with those reviewed response semantics.
2228d33 to
bf872a3
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
Combines and supersedes #974, #978, and #979 (branch filtering, activity
rollups, and usage grouping), rebased onto current main with the original
commits and authorship preserved, plus integration fixes and review-driven
refinements. Part of #928.
What the branch does
--branchonsession list/session search/activity report(requires--project),git_branchon the MCPtools, and a sidebar Branch multi-select with search, chips, badge, and URL
round-trip that flows into Analytics and Usage filtering. Branch metadata
sorts by most recent session activity in all three backends.
by_branchrollups keyed by typedproject/branchfields across SQLite/PostgreSQL/DuckDB, a
By branchCLI section, and abranch typeahead plus Branch breakdown panel on the Activity page.
branchTotalsand per-daybranchBreakdowns,a Branch group-by for the cost chart and Attribution panel, and branch
exclusion (
exclude_git_branch) persisted in URL and localStorage.Integration with current main
Rebasing over the kit-ui migration (#957), semantic search,
--since, theusage schema fields (#989), and exclude-agent filters required merge
resolutions in the CLI flag surfaces, MCP tools, activity report struct, usage
summary structs, and the Activity/Usage Svelte components. Golden fixtures
were regenerated for the new fields, the FilterDropdown tests were ported to
the kit-ui DOM, and the new branch message keys were added to the
kolocale(added on main after these PRs were cut).
Review-driven refinements
Frontend performance was the review focus:
every search keystroke: ranking and search filtering are separate deriveds,
and selection membership is hoisted into Sets instead of splitting CSV
strings inside sort comparators.
are sub-pixel); the scrollable rail and list views remain complete so every
branch stays clickable.
every metric toggle.
Correctness fixes:
(matching the sessions and activity stores), so newly imported branches
appear in the exclude dropdown without a reload.
project change clears a branch scoped to another project — previously that
contradiction silently produced an all-zero report.
get_usage_summarytool rejects a comma-separated project listcombined with
git_branchinstead of silently returning zeros.docs/session-api.mdagain documents thegit_branchHTTP param as theopaque token from
GET /api/v1/branches(the tables had been rewritten withCLI flag semantics).
Deduplication, preserving backend parity by construction:
db.UsageBucket/db.AddUsageBucket,db.SortBranchBreakdowns, andUsageFilter.RequiresSessionScope()replace byte-identical copies in theSQLite/PostgreSQL/DuckDB usage paths, and the sessions/usage stores share one
toggleListValuehelper.Known limitations and notes for reviewers
CSV-style filters (carried over from feat(filter): expose branch filters in client surfaces #974).
just that branch's share. This mirrors the pre-existing
exclude_projectbehavior, is tested, and is consistent across backends — but the semantics
are debatable for an exclude filter and worth a product call.
summary fetch, mirroring the existing bundle-all-breakdowns design that
keeps the group-by toggle refetch-free; payload grows with per-day branch
churn.
terminationsub-condition differs subtly between SQLite and PG/DuckDB for unrecognized
termination values; each backend's current behavior was preserved.
Reviewers should look at the merge-resolution surfaces (cmd/agentsview CLI
flags, internal/mcp/tools.go, internal/activity/activity.go, the usage summary
structs) and the shared helpers in internal/db.
🤖 Generated with Claude Code