3d0ac691af
The roster already has sections, but only automatic ones: one per gateway connection plus the group-chat bucket. Those answer "where does this bot run", which is not the question being asked when someone wants two client bots filed together under "Clients" and the internal ones under "Team". This adds a second axis that composes with the first: gateway sections keep the top level whenever more than one connection is showing, and user sections group the flat list underneath. Design choices, each deliberate: - Membership lives on the BOT (`ui_meta.sectionId`), not as a member list on the section. A bot can only be in one place, deleting a section cannot orphan anybody, and the assignment rides the same profile.yaml sync every other bot setting already uses, so it follows the profile to another machine. Section records (id, name, icon) live in plugin storage. - "Unassigned" is not a section. It is whatever is left, always drawn last, and it is where members of a deleted section land. No record, so nothing to keep in sync. - Three gestures, one rule: drag a row onto a section heading; cmd/ctrl-click and shift-click build a multi-selection (shift ranges in DOCUMENT order, anchored Finder-style); and the row's context menu gets "Move to section…" with the same targets the drag would use. Dragging a row that is part of the selection drags the whole selection. - The drag uses a private MIME type, so a bot dropped on the composer or the transcript is simply not a valid payload there instead of pasting its key as text. - Section headings rename inline (double-click / menu), reorder, hide their glyph, and delete (keeping their bots). Right-click and the ⋯ button open the same menu so neither can drift. With no sections created the roster renders exactly as before. Tests: user-sections.test.ts covers the pure model (normalisation, grouping with unknown/deleted sections falling to Unassigned, drag payload round-trip). The existing hermes-bots suite passes; tsc and eslint clean.