Sidebar: show every profile at once - #83052
Merged
Merged
Conversation
…age totals `projects.tree` answers for the backend's own profile, so the grouped sidebar had nothing to draw once the user asked to see every profile. Run the same authoritative builder once per profile against that profile's state.db and merge the results by folder, so one checkout is one group no matter how many profiles work in it, and the owning profile rides on each session row where the badge and filter can read it. Group totals are summed in SQL rather than over the loaded page — a number that shrank as you scrolled would be worse than no number. Scope the batched sidebar slices while we're here: cron and messaging came back cross-profile unconditionally, which is why a concrete profile showed another profile's Telegram threads and cronjobs. Closes #65710 Closes #42651 Closes #70629
The sort key was applied where the flat recents list is assembled, so it did nothing at all once rows moved into groups: picking "cost" while grouped by project or profile left every lane in the order the backend sent it. Rank in a store instead, above any one view, so a grouped surface can order the rows it owns by the same key.
All-profiles mode listed a flat page of chats and stopped there: the project tree was the active profile's, grouping and filtering had no notion of an owner, and each profile lane paged itself against a separate endpoint. Multi-agent workflows live across profiles, so the sidebar now treats the owner as a first-class axis. Group by profile (the default in this scope, with its own persisted choice so flipping the rail doesn't reset how you read one profile), filter by profile, and start or import one from the same menu. Profile groups take the project row's shape rather than a hand-rolled header, preview the same three sessions a project does, and carry their whole tokens-and-spend total in the slot the kebab hovers over. Grouped lanes now rank by the active sort key, before they trim themselves, so the rows a group hides are the ones the sort ranked last. Defaults live in one const: the sidebar ships grouped by date, sorted by recency, with the timestamp pinned — and "Reset to defaults" puts back exactly that.
Contributor
૮ >ﻌ< ა ci reviewrunning on 7e1f4f6 — fix(desktop): ship the sidebar grouped by date in every scop waiting for jobs to start… |
…list A thumb parked on a list you aren't touching is chrome, not information, and the sidebar stacks several scrollers so it draws several of them at once. Fade them in on hover instead, sharing the existing scrollbar colors and the webkit/Firefox split rather than styling a second kind of bar. Only the thumb's color changes, so the reserved gutter still keeps rows from shifting sideways.
OutThisLife
enabled auto-merge
August 10, 2026 08:24
OutThisLife
disabled auto-merge
August 10, 2026 08:31
The PR and profile chips rendered in the row body, left of the kebab's own column: they never sat flush right and never handed their space to the kebab on hover, so a row showing only a PR left a hole where the age would have been. Both now join the tokens/cost/age figures in the actions slot, and the kebab covers the end of it — losing whichever item reads last, not the whole slot.
The all-profiles scope defaulted to grouping by profile, so "Reset to defaults" handed back a grouping the user never picked. Both scopes now ship by date, and a reset clears the scope you are not looking at too — otherwise flipping the rail restored the customization the reset was supposed to undo. Hovering a row's PR chip also holds the kebab back now: the chip is a link, and the button that covers the end of the trailing slot was taking the click.
OutThisLife
force-pushed
the
bb/sidebar-all-profiles
branch
from
August 10, 2026 08:50
675de58 to
7e1f4f6
Compare
OutThisLife
enabled auto-merge
August 10, 2026 08:54
8 tasks
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…l-profiles Sidebar: show every profile at once
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The desktop sidebar could already flip to all-profiles, but it only listed a flat page of chats: the project tree belonged to the active profile, grouping and filtering had no notion of an owner, and each profile lane paged itself. That makes the multi-agent workflow this exists for — several profiles working at once, the way Discord threads do — unreadable. This makes the owning profile a first-class axis of the sidebar, and fixes a few things that scope touched on the way.
What's new
A cross-profile project tree.
projects.treeover JSON-RPC answers for the backend's own profile, so it could only ever describe a slice of this view.GET /api/profiles/projects/treeruns the same authoritative builder once per profile against that profile'sstate.dband merges the results by folder — one checkout is one group, however many profiles work in it, and__no_project__is one Home rather than one per profile. Discovery stays off: an empty repo is the same repo in every profile, and the scan is the one part of the builder that writes.Grouping and filtering by profile. Profile is a grouping option like date or project (picking it turns the scope on, since that's the ask), and profiles are a filter alongside status, project and PR. The all-profiles scope keeps its own persisted grouping, so flipping the rail doesn't reset how you read a single profile.
Profile groups look like project groups, because they do the same job: the shared
SidebarGroupRow, the profile glyph the rest of the app uses, the same three-session preview, clicking the label scopes to that profile the way clicking a project enters it. New/import profile moved into the same menu.Tokens and spend on group headers, for the Show options that count something. Summed in SQL over the whole project or profile rather than over the loaded page — a total that shrank as you scrolled would be worse than no total. The figures sit in the slot the kebab and the "+" hover over, the trade the session row already makes.
Scrollbars fade out until you're in the list. The sidebar stacks several scrollers, so it drew several thumbs at once on a list nobody was touching. They share the app's existing scrollbar colors and only change the thumb's color, so the reserved gutter still keeps rows from shifting sideways.
Fixes
Sorting did nothing inside groups. The sort key was applied where the flat list is assembled, so picking "cost" while grouped by project or profile left every lane in backend order. Ranking now lives in a store above any one view, and each surface applies it to the rows it owns — including before a group trims itself to a preview, so the rows it hides are the ones the sort ranked last.
Cron and messaging ignored the sidebar's scope. The batched sidebar endpoint returned them cross-profile unconditionally, which is why a concrete profile showed another profile's Telegram threads and cronjobs. Every slice answers to the one scope now, and
allis how the caller asks for everything. Closes #65710, #42651, #70629.A repo scan could clobber the cross-profile tree, because it refreshed the tree for its own gateway rather than through the active scope.
Row metadata had two homes. The PR and profile chips rendered in the row body while tokens, cost and age sat in the actions slot, so the chips were never flush right and never handed their space to the kebab — a row showing only a PR left a hole where the age would have been, and the hover-only age reserved 48px that re-truncated the title under the cursor. Everything the Show menu switches on now shares one right-aligned slot, the kebab covers the end of it, and hovering the PR chip holds the kebab back so the link is still clickable.
Upgrading
All-profiles is off by default and everything above is behind it, with one deliberate exception: the sidebar now ships with the timestamp pinned on each row instead of revealed on hover. Both scopes ship grouped by date, defaults live in one const, and "Reset to defaults" restores exactly that — in both scopes, so flipping the rail can't hand back the grouping you just cleared.
Test plan
apps/desktop: full vitest suite (4647 passing), including new coverage for the ranking store,rankSessions, preview ranking before the trim, and the shipped defaults + reset in both scopes.scripts/run_tests.sh tests/hermes_cli/test_profiles_sidebar_scope.py tests/hermes_cli/test_profiles.py tests/tui_gateway— 470 passing.