Skip to content

feat: quick-add agent popover with two entry points#626

Draft
tellaho wants to merge 62 commits into
mainfrom
tho/quick-add-agent-popover
Draft

feat: quick-add agent popover with two entry points#626
tellaho wants to merge 62 commits into
mainfrom
tho/quick-add-agent-popover

Conversation

@tellaho
Copy link
Copy Markdown
Collaborator

@tellaho tellaho commented May 20, 2026

Summary

Replace the "click + → full dialog" flow with a lightweight quick-add popover that makes adding an agent to a channel a one-click action. The full dialog stays as an advanced escape hatch via "More options…".

What changed

New: QuickAddAgentPopover component

  • Lightweight Radix popover (dropdown-menu energy)
  • Shows agents sorted by state: running (not in channel) → running (already here, muted) → available personas
  • One-click to attach a running agent or spin up a persona
  • "More options…" at the bottom opens the existing AddChannelBotDialog
  • Arrow-key navigation via roving focus
  • Valid ARIA: role="listbox" scoped to item list only

Two trigger points, same popover

  • The + button in ChannelMembersBar now opens the popover (not the dialog directly)
  • New "Add agent" ghost button at the bottom of the sidebar's Bots section

Dialog ownership lifted

  • Single AddChannelBotDialog instance owned by ChannelScreen
  • Both trigger points share it via props — no duplicate dialog risk

Extracted utilities

  • sortProviders — shared "goose first" provider sort
  • safeBotName — defensive pickBotName wrapper with fallback chain

E2E tests updated

  • Tests now click through popover → "More options…" before asserting on the full dialog

Key decisions

  • Uses useBotRecents for smart ordering (recents float to top)
  • Running agents use useAttachManagedAgentToChannelMutation
  • Personas use useCreateChannelManagedAgentMutation (leverages existing reuse logic from PR fix: prevent agent proliferation when adding bots to channels #621)
  • Force-new-instance stays buried in the full dialog — not in the popover
  • Null channelId guard: popover renders children-only, handlers early-return
  • Discriminated union types enforce valid item states at compile time

tellaho added 30 commits May 21, 2026 19:16
Replace the direct + → full dialog flow with a lightweight Radix popover
that shows available agents sorted by state (running > configured > catalog).
One click to add. The full AddChannelBotDialog is still accessible via
'More options…' at the bottom of the popover.

Two trigger points:
- The existing + button in ChannelMembersBar now opens the popover
- A new 'Add agent' button at the bottom of the sidebar Bots section

The sidebar already had People/Bots section headers — the inline add
affordance lives at the bottom of the Bots section.

Actions:
- Running agent not in channel → useAttachManagedAgentToChannelMutation
- Persona without running agent → useCreateChannelManagedAgentMutation
  (leverages existing reuse logic from agentReuse.ts)

The popover uses useBotRecents for smart ordering and pickBotName for
instance naming. Force-new-instance stays buried in the full dialog.
Critical fixes:
1. Null channelId guard — popover renders children-only (no popover
   content) when channelId is null. Action handlers also early-return.
2. pickBotName fallback — safeBotName() wraps pickBotName with a
   defensive fallback to persona.displayName or 'Agent'.
3. E2E test updates — tests now click through the quick-add popover's
   'More options…' button before asserting on the full dialog.
4. Duplicate AddChannelBotDialog — removed the sidebar's own dialog
   instance. Dialog state is now lifted to ChannelScreen and passed
   down via props. Only one dialog instance exists (in ChannelMembersBar).

Medium fixes:
5. Discriminated union types — QuickAddAgentItem is now a proper
   tagged union (RunningAvailableItem | RunningInChannelItem | PersonaItem).
   No more optional agent?/persona? fields.
6. Extracted sortProviders utility — shared between ChannelMembersBar
   and any future consumer. Single source of truth for provider ordering.
7. Emerald colors kept — matches existing presence pattern (bg-emerald-500)
   used throughout the codebase. No design token exists for 'running' status.
8. Keyboard navigation — added arrow-key roving focus via onKeyDown handler
   on the popover container, targeting [data-quick-add-item] buttons.
9. Eager queries — channel members query already gated by popover open state.
   Other queries (agents, personas, providers) are globally cached by
   ChannelMembersBar which is always mounted — no extra network cost.
The 'More options…' button is not a listbox option, so the listbox
role belongs on the inner div that contains only the agent option items.
Reorder header action buttons so the Quick Add Agent popover trigger
(+ icon) is positioned directly before the manage channel button,
making it more discoverable next to the channel settings.

Also fix pre-existing lint: add role=menu to popover container and
format QuickAddAgentPopover.tsx per biome rules.
Add a compact '+' icon button immediately left of the ellipsis (⋯)
bulk-actions menu in the Bots section header of the Members side pane.
Remove the old full-width 'Add agent' button from below the bots list.

The trigger opens the same QuickAddAgentPopover as before — just in a
more discoverable, compact location matching the ⋯ button style.
Change DropdownMenuContent from overflow-y-auto to overflow-hidden so
hover/focus backgrounds on menu items are clipped by the container's
rounded corners. Instances that need scrolling (e.g. PulseView) already
pass overflow-y-auto via className which overrides the y-axis.

Matches the existing SubContent pattern which already uses overflow-hidden.
Add overflow-hidden to PopoverContent so hover backgrounds on the
bottom item stay clipped within the rounded corners. Also restore
role=menu on the keyboard-navigable container (lost during revert).
Transform the Quick Add Agent popover into a hybrid interaction model:

- Single-click on an individual agent still adds immediately (fast path)
- Clicking a team row selects all its personas and enters multi-select mode
- In multi-select mode, clicking agents toggles checkbox selection
- Sticky 'Add (N)' confirm button appears at the bottom in multi-select
- Teams section at the top shows usable teams with member count
- Scroll container clips mid-item (max-h-[13.75rem]) to hint at more content
- Nothing pre-selected; running agents still sort to top via recency
- Batch add uses useCreateChannelManagedAgentsMutation for new personas
  and individual attach for already-running agents
Relocate the batch confirm button from the bottom sticky area into the
'Add agent' header row, anchored right. Uses Button size=sm (smallest
variant). Only renders when multi-select mode is active with 1+ items
selected.
…kboxes

- Agents now appear indented under their team header as a visual unit
- Ungrouped agents (not in any team) show in a separate section below
- Checkboxes hidden by default, fade in on hover (group-hover)
- Clicking checkbox enters multi-select mode; clicking row still does
  single-add (fast path preserved for ungrouped agents)
- Team header click selects all members and enters multi-select mode
- Once in multi-select mode, all checkboxes stay visible
- Running agents still sort to top within their section
Remove team headers, nesting, multi-select mode, checkboxes, batch
mutations, and team-related imports. The popover returns to its core
purpose: a flat, smartly-ordered list where single-click immediately
adds an agent. Teams and batch selection will live in the 'More options'
dialog instead.

Keeps: overflow-hidden fix, role=menu, smart ordering (running first,
recents, then available), scroll container with mid-item clip.
Restructure the dialog from a flat wall of fields into three layers:

1. Primary (always visible): Persona/team selection + runtime picker.
   These are the two decisions that matter most.

2. Customize (collapsed): Bot name, system prompt, respond-to allowlist.
   Power user controls that don't block the happy path.

3. Advanced (collapsed): Backend provider selection, provider config.
   Expert territory hidden by default.

Uses a simple DisclosureSection component with chevron toggle. All
existing functionality preserved — just layered behind progressive
disclosure. Description updated to match the new vibe.
- 'Select' button in the header reveals team filter chips on click
- Title fades out, team chips animate in from right using motion/react
- Clicking a team chip auto-selects all its member agents
- Multi-select mode shows checkboxes + 'Add (N)' confirm button
- Single-click fast path preserved when not in select mode
- Team chips scroll horizontally when overflowing
- Exiting: deselect all returns to default title state
- Team chips now use shadcn Toggle component (outline variant) with
  visual pressed state via data-[state=on]
- Toggles are selectable/deselectable — clicking again removes the team
- 'Select' button becomes 'Cancel' when active — clears all selections
  and exits select mode
- 'Add (N)' button moved to the bottom of the popover, below More Options
- Full-width button, only visible when items are selected
…ation

- Remove auto-exit from select mode when selectedKeys empties — only
  Cancel button exits select mode now.
- Stagger team Toggle animations: first chip fades in immediately,
  subsequent chips delay by index * 50ms for a fan-out effect.
…ion fix

- Select button uses Button variant='secondary', switches to 'default'
  (primary) when active for clear visual state change
- Checkboxes animate in with fade + scale (motion.div) when entering
  select mode
- Agent name uses min-w-0 + truncate for proper text overflow when
  checkbox pushes content over
…sibility

- Label stays 'Select' in both states (no more 'Cancel' label)
- Inactive: variant='outline' (visible against bg-popover)
- Active: variant='default' (primary) for clear state change
- Popover uses bg-popover token (hsl 220 23% 95% light / 232 23% 18% dark)
…clash

The outline variant includes bg-background which creates a visible
contrast block against bg-popover. Use ghost variant with explicit
border-input border for a truly transparent-bg outlined button.
Checkboxes were too busy in the agent list. Selection is now
indicated only by the bg-accent/50 highlight on selected rows.
Button already uses size=sm.
- First team chip: opacity-only animation (no x translate)
- Second+ chips: translate from x:12 + fade, staggered by index
- Header container: px-3 py-1.5 with gap-2 for consistent spacing
  around the Select button on all sides
Three primary buttons competing looked unfocused. New hierarchy:
- Add (N) button: primary (the CTA)
- Toggle chips selected: bg-accent (visible but not competing)
- Select button active: secondary (subtle elevation, not primary)
…efault toggle colors

- Button stays ghost+border (outline look) in both states
- Label changes: 'Select' → 'Cancel' when active
- Toggle chips use default data-[state=on]:bg-primary (removed accent override)
- 'Add agent' title stays visible always; 's' fades in when select mode
  activates ('Add agents')
- Team toggles appear to the right of the title, scroll horizontally
- Right-edge gradient (from-popover to-transparent) hints at more
  content when overflowing
The width:0→auto animation caused the 's' to render broken.
Just fade it in with opacity — it's one character, doesn't need
a width transition.
…ation

- Toggles sit in a separate row that slides in/out (height animation)
- Agent list uses motion layout to smoothly translate down when row appears
- Header stays clean: just title + Select/Cancel button
- Gradient scroll affordance on the toggle row's right edge
tellaho added 29 commits May 21, 2026 19:17
…with gradient, remove 's' animation and border-2
…ontent in gapped div — no extra left padding when checkbox hidden
- Add optimistic onMutate to useAttachManagedAgentToChannelMutation
  (instant member insertion, rollback on error)
- Add onSuccess cache-set to create/batch mutations (member appears
  as soon as server confirms, before invalidation round-trip)
- Decompose QuickAddAgentPopover (816→271 lines) into:
  - useQuickAddAgentItems.ts (data fetching + item list building)
  - useQuickAddAgentActions.ts (handlers + multi-select state)
  - QuickAddAgentItemRow.tsx (memoized item row component)
- Fix ARIA: remove conflicting role="menu" on container, add
  role="group" with aria-label for keyboard navigation wrapper
- Fix dead team toggles: filter out teams whose members are all
  already in the channel
Mirror of the attach optimistic update. onMutate filters the member
out of the cache immediately, rolls back on error, invalidates on
settle to reconcile with server.
Adds a sortedBots memo in MembersSidebar that prioritizes online
agents at the top. Within each presence group (online vs not-online),
agents sort alphabetically by display name. No more recency-based
reordering — the list stays stable.
The single-agent create path was calling createChannelManagedAgent
without context, so the reuse guard (findReusablePersonaAgent) never
fired — always spawning a new keypair + process. Now fetches
managedAgents + channelMemberPubkeys before calling create, matching
the batch path's behavior.
@tellaho tellaho force-pushed the tho/quick-add-agent-popover branch from 2f91f86 to 8cf10d3 Compare May 22, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant