Skip to content

Bots: Bots-only sidebar and the Bot side panel (bot chat activity C2) - #478

Merged
sambitcreate merged 23 commits into
mainfrom
claude/bot-chat-activity-c2-sidebar-panel
Oct 14, 2026
Merged

sambitcreate merged 23 commits into
mainfrom
claude/bot-chat-activity-c2-sidebar-panel

Conversation

@sambitcreate

@sambitcreate sambitcreate commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Slice C2 of Bot chat activity. It stacks on #476 (C1, the workbench shell), which stacks on #477 (B) and #475 (A). Plan: docs/superpowers/plans/2026-10-14-bot-chat-activity-c2-sidebar-panel.md.

What changes for people

  • The sidebar on Bots pages lists only Bots. It works like Grok's:

    • A pinned Bot sits at the top with a ★.
    • Bots that are working show their faces and "Thinking…".
    • A Bot waiting on you shows a soft "Needs you" pill.
    • An unread reply shows a dot.
    • You can search (accents fold for Latin script).
    • Each row has a right-click or ••• menu, which Shift+F10 and the Menu key also open.
    • Back, Search and New Bot sit at the top of the sidebar body.
  • The Bot side panel is built on C1's WorkbenchPanel, with four tabs:

    • Activity: every step, grouped by reply.
    • Files: replaces the Files dialog.
    • Memory
    • Routines

    It sits beside the chat, and floats only where the workspace panel would also float. The environment shortcut toggles it on a Bot's chat. On Bots pages the workspace surfaces, Quick View and the mini player are hidden, with their state kept.

  • Opening the panel. File chips, ••• Files and "Memory updated" now open the panel.

  • One ••• menu everywhere: Profile, Files, Memory, Routines, Advanced, Pin to top, then Delete Bot. Each item has an icon.

  • /bots opens your most recently used Bot. The list page is gone. With no Bots you see the starter page.

  • Onboarding: the Bots feature tile shows the side-panel toggle.

Under the hood

  • Main:
    • bots:live:summary gains needsYou and lastReplyAt.
    • A new desktop-only bots:attention signal carries { botId }, through an electron-free wireBotAttention seam.
    • bots:changed with a botId now re-reads only that Bot's summary.
    • The Aiden Remote wire is unchanged: test:aiden-remote passes, and BotLiveSummary has no Remote consumer.
  • Renderer:
    • One refcounted live store per Bot (use-bot-live.ts) is shared by the chat and the panel. It survives same-commit remounts.
    • Pin, last-viewed and seen-reply preferences are per device and stored in localStorage, never synced.
    • Unread compares the latest reply against the newest reply the chat has shown, using main's clock. A reply that finishes after you leave mid-stream still lights up.
    • One shared turn walker (bot-turns.ts) feeds both the chat recap and the Activity log.
  • C1 shell: WorkbenchPanel gains optional surface and tabMenu props and per-panel width bounds. The defaults keep the workspace exactly as it was; the workspace-panel and environment-focus e2e tests pass.
  • Test harness: Bots tests now run with NODE_ENV=test (renderer/main/bots/test-env.ts), so TanStack Router behaves as a client and navigation tests are real. A guard throws if the router ever falls back to server mode.

Rulings and deviations

These are recorded in spec §8/§17 and .memory/bot-chat-activity.md:

  • The refcounted store replaces the plan's context-only provider.
  • The sidebar tools sit in the sidebar body, because in the title bar they would overlap the window controls.
  • The Activity tab always shows the full log. The Chat activity level governs only the in-chat recap.
  • /bots waits up to 800 ms for summaries, then falls back to updatedAt.
  • "Erase memory" keeps the shared destructive button.

Known, parked

These are listed in spec §17:

  • summary reads are per Bot, not batched;
  • the workspace sidebar loses its search text when you leave Bots;
  • focus returns to the panel toggle, not the composer;
  • chip names show only the basename;
  • Greek tonos and Arabic harakat are not folded in search;
  • plus a few test-only gaps.

Test plan

  • tsc --noEmit, lint, test:ci-policy
  • test:bots (805), test:bot-runtime (188), test:sidebar (94), test:aiden-remote (920), test:onboarding, test:remote-chat (150)
  • Full npm test
  • Playwright, --fail-on-flaky-tests: bots-sidebar-panel (new), bots-starter, guided-setup, workspace-panel, environment-focus
  • Screenshots of the built app in light, dark and Reduce Motion: sidebar, the four tabs, the menu, empty search, focus rings
  • Owner: a VoiceOver pass over the sidebar rows (Needs you and unread in each row's name) and the panel tabs
  • Owner: a by-eye look in the running app across themes

Process

Built by subagents task by task. Each task got its own review and fix rounds, then an Opus whole-branch review, one final fix wave, and a re-review with every finding addressed.

CI notes

Run 38570083858 had two failures, neither in files this PR touches:

  • Desktop tests (core-git): scripts/worktree-file-io.test.mjs "editor operations reject a symlinked ancestor…" hit an uncaught write EPIPE, because runEditor calls child.stdin.end and the editor exits first. It is the same race seen on Bot chat activity 3/5: Extract a reusable workbench shell from the environment panel #476 and is still unfixed on main.
  • Electron E2E (3/3): tests/e2e/chat-message-queue.spec.ts:481 "a response finishing while the queue editor is open waits for the saved edit" flaked. The first attempt never saw "Saved after completion" within 10 s, and retry Use catalog-driven runtime model limits #1 passed. --fail-on-flaky-tests failed the job, as intended. (job)

I reran the whole run once. No timeouts were raised and no retries were added.

🤖 Generated with Claude Code

sambitcreate and others added 23 commits October 14, 2026 05:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tention

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e shortcut

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the last view on failure

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel sits on the C1 workbench shell beside a Bot's chat, under its own
id, surface name, width key, focus hand-off and Escape. A file request lands
only on its own Bot and only once, even across a remount; it is dropped only
when the chat reaches a different Bot, so a hop through a non-chat page keeps
a request made for the next chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…log is removed

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Activity says when the conversation couldn't be read, with Try again, and
owns its live read so an event re-renders that tab alone. Files keeps the
list and the open file on screen while they are read again, shows a failed
refresh above them, and Try again re-reads only what failed. The Activity
log and the in-chat recap share one step row; the Bot panel hides the
workbench's Open tabs list; headings sit at one level in every tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd unread

On any Bots page the sidebar lists only Bots: back to Aiden, Search and New
Bot; the pinned Bot large on top; one row per Bot, newest activity first,
with a working face, "Thinking…"/"Waiting for you", Needs you and an unread
dot; Bots on paired Macs (filtered by search); Profile and Connect apps.

- Rows re-read one Bot on bots:attention; an older summary answer that
  arrives late never replaces a newer one. Prune runs only on a settled,
  successful list read; deleting a Bot forgets its pin and view, even the
  last Bot.
- The chat marks its Bot viewed on open, on each arriving message and on
  leaving; the chat's "All Bots" back button is gone.
- The Bot menu's focus guard is per menu and set only after the action ran;
  a row's right-click menu hands focus back to the row when the panel closes.
- The old list's duplicate row menu uses the shared Bot menu; store-level
  togglePin/forget replace pin/unpin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…avigation

- bots:changed with { botId, session } re-reads only that Bot's summary; any
  other change naming a Bot re-reads it and the list; an unnamed change
  re-reads everything. bots:attention is handled once per window in the same
  sync, so two row readers never double the reads.
- Bot tests load TanStack Router's client behaviour (NODE_ENV=test before it
  is imported), so navigations mount the routed page; Memory from a row,
  leaving a chat and click-then-back now prove real navigation.
- Empty states count paired Macs' Bots; search ignores accents and case and
  reads a bounded preview; a row's preview is its description, bounded.
- The footer's Profile name starts with the visible name; rows are memoised;
  Shift+F10 / Menu opens a row's menu; deleting a row's Bot leaves focus on
  the nearest row or New Bot; a deleted Bot is never marked viewed; a failed
  refresh keeps the list with Try again; the pinned Bot shows a pin mark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…emory

/bots shows a skeleton while it waits and falls back to bot.updatedAt if a
live summary never answers. Tests for the create-flow suppression and a stale
cached list. The Bots onboarding tile's header now shows the panel toggle in
place of a back chevron. Spec rulings and deviations, plan index and project
memory record what C2 shipped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e view stay put

- Unread compares a Bot's last reply with the newest reply its chat showed
  (seenReplyAt, main's clock), so a reply that started before the person left
  and finished after is unread. lastViewed only picks the Bot /bots opens.
- Deleting a Bot that is not on screen no longer leaves the current page.
- Files hands focus to the open file's heading, and back to its row on
  All files, so Escape still closes the panel.
- A consumer joining a failed live store keeps the last view while it retries.
- Search folds only Latin accents; Devanagari or Thai marks still count.
- Delete focus follows the on-screen order, pinned Bot first; the pinned Bot
  describes its working state; the Activity scroller drops its duplicate name.
- One turn walker (bot-turns.ts) feeds both the chat rows and the Activity log.
- The Activity log skips streamed tokens; the chat reads only its own pin;
  menu actions read the open chat at choice time, so rows keep their callbacks.
- Dead surface removed (useSharedBotLive, BotLiveProvider, botLiveSummaryKey,
  RemoteBotsSection's query) and local-only names un-exported.
- The test harness fails loudly on the router's server build, mounts inside
  act, and the Bot suites wrap navigations and notifications in act.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… clear button

- The Bot panel has its own width range (400–720, default 440), so at a
  1280px window it reflows the chat instead of covering the composer. It
  floats only where the workspace panel would float too. WorkbenchPanel and
  the layout helpers take optional width bounds; the workspace's are unchanged.
- The Bot ••• menu has leading icons (MemoryCardIcon for Memory).
- The unread dot sits after the name and "Needs you" leads the second line,
  so every row's name and preview line up.
- The search field is a text input in the searchbox role, so WebKit adds no
  second clear button.
- e2e at 1280×800: the composer stays clear of the panel, the workspace surface
  is inert and unnamed while covered, and bots:attention reaches a row through
  the app's own notification sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec and memory describe the seen-reply watermark, the Bot panel's width
range, the shared turn walker and the "All Bots" exceptions (Profile's loading
and not-found shells, the chat's not-found shell); mark the pre-C2 evidence as
"(was)"; and keep every parked item from the ledger. Previews are re-read per
Bot on bots:changed and bots:attention.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@very-hermes-bot

Copy link
Copy Markdown
Collaborator

Hermes Review Bot

Confidence: 5

Engine: agy/gemini-3.8-flash-high
Review mode: full
Head: 392d8b710cc2b82bf5ef6186bd5e88541c3658c5
Generated: 2026-10-14T13:19:41+00:00
Reviews: 1

Summary

Slice C2 replaces the former /bots list page and standalone files dialog with a Grok-style Bots-only sidebar and an integrated side panel built on the shared WorkbenchPanel beside the Bot chat. On any /bots route, the sidebar lists exclusively Bots with working avatar animations, accent-folded search, a pinned hero card, "Needs you" indicators, and unread dots, while /bots itself redirects to the most recently used Bot's chat or the starter carousel when none exist. The Bot side panel introduces four fixed tabs (Activity, Files, Memory, and Routines) with independent state storage, scoped width constraints, and complete suppression of workspace environment surfaces while in Bots mode. A refcounted live store per Bot ensures that the chat transcript and the Activity tab share a single IPC subscription without conflicting or dropping updates upon remount. Maintainers should double-check the window-level useBotNotificationsSync hook mounted in RootContent to verify that live summary invalidations for bots:changed and bots:attention stay properly scoped to individual bot IDs across multi-window setups.

Confidence Score: 5/5

Fully traced and verified across all changed files, main IPC handlers, renderer state stores, preference persistence, focus management, and e2e test specifications.

📁 Important Files Changed
  • renderer/main/bots/bot-sidebar.tsx: Implements the Bots-only sidebar with search, hero pinned view, working avatars, "Needs you" badges, unread dots, context/dropdown menus, and paired Mac integration.
  • renderer/main/bots/bot-workbench.tsx: Wraps the Bot chat in the WorkbenchPanel with fixed Activity, Files, Memory, and Routines tabs, managing width bounds, tab focus, and Escape handling.
  • renderer/lib/use-bot-live.ts: Implements BotLiveStore, a refcounted per-window store ensuring the chat and Activity tab share a single bots:live:subscribe IPC feed without colliding.
  • renderer/main/bots-view.tsx: Replaces the legacy BotList index with a replace-redirect to the last-viewed or newest-active Bot, falling back to the starter carousel when no bots exist.
  • renderer/lib/bot-sidebar-preferences.ts & renderer/main/bots/use-bot-sidebar-preferences.ts: Manages local storage for per-device pinning, last-viewed timestamps, and reply-seen watermarks with versioning and validation.
  • renderer/lib/bot-notifications-sync.ts: Centralizes IPC notification handling for bots:changed and bots:attention in the renderer window, invalidating only the affected bot queries.
  • main/services/bot-runtime/live-projection.ts & main/services/bot-runtime/bot-attention.ts: Augments BotLiveSummary with needsYou and lastReplyAt, and broadcasts the lightweight bots:attention event on question/approval state changes.
  • renderer/main/bots/bot-menu.tsx: Consolidates the unified ••• menu (Profile, Files, Memory, Routines, Advanced, Pin/Unpin, Delete Bot) with focus guard handling.

Findings

No findings.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Main as Main (Bot Runtime)
    participant Sync as Renderer (BotNotificationsSync)
    participant Query as TanStack Query Cache
    participant Sidebar as BotSidebar
    participant Chat as BotChatPane
    participant Panel as BotWorkbench (BotPanel)

    Main->>Sync: notify("bots:attention", { botId })
    Sync->>Query: invalidateQueries(["bot-live-summary", botId])
    Query->>Main: invoke("bots:live:summary", botId)
    Main-->>Query: BotLiveSummary (needsYou, lastReplyAt)
    Query-->>Sidebar: re-render row (shows "Needs you" / Unread dot)
    Sidebar->>Chat: navigate to /bots/$botId/chat
    Chat->>Main: markViewed(botId, replyAt)
    Chat->>Panel: open panel tab (Activity / Files / Memory / Routines)
    Panel->>Chat: reuse shared BotLiveStore (single live subscription)
Loading
[]

Last reviewed commit: 392d8b710cc2
Reviews (1) · Comment /hermes review to trigger a new review · /hermes review full for full re-review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Three reproducible regressions need attention before merging: same-file chips can show stale contents, same-route sidebar actions can do nothing, and hiding the panel can leave its Memory menu interactive.

Reviewed changes: Reviewed the complete C2 diff against its stacked C1 base, including the desktop runtime seams, renderer behavior, tests, and mobile contract implications.

  • Bots Sidebar: Replaces the workspace sidebar on Bots pages with pinned and activity-ordered Bots, search, attention/unread indicators, shared menus, and paired-Mac rows.
  • Bot Panel: Adds Activity, Files, Memory, and Routines on the shared workbench shell, with independent state, width, focus handling, and route-aware shortcuts.
  • Live State And Navigation: Shares one live subscription per Bot, refreshes summaries per notification, extracts the shared turn walker, and redirects /bots to the most recently used Bot.
  • Coverage And Onboarding: Adds behavioral renderer/runtime tests and Electron e2e coverage, corrects the client-mode router harness, and updates the onboarding illustration.

Validation: TypeScript, 220 focused tests, and test:aiden-remote passed; three additional diagnostic probes reproduced the inline findings. Android verification was blocked by an unavailable offline Gradle plugin and an online retry reporting Tool 'shell' timed out after 120000ms. Native iOS and Electron e2e were not run on this Linux runner; hosted CI is not yet green.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment on lines +80 to +83
React.useLayoutEffect(() => {
if (pendingKey === null || pendingPath === null) return;
handledRequestKeys.set(botId, pendingKey);
setPath(pendingPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Refresh a newly requested file even when its path is already selected. If the Bot edits the file currently visible in Files, clicking the new edit chip leaves the previous contents on screen: this sets the same path, so no second bots:files:read runs. The new side-panel interaction makes this possible without closing the file viewer first.

Technical details
# Same-path file requests leave stale contents

## Affected sites
- `renderer/main/bots/bot-panel-files.tsx:80-84`: a new scoped request is marked handled but only updates `path`.
- `renderer/main/bots/bot-panel-files.tsx:108-134`: the document read depends on `active`, `botId`, `path`, and `documentAttempt`, not a newly applied request.
- `renderer/main/bots/bot-chat-pane.tsx:836`: every chip creates a new request, including later edits of the same file.

## Reproduction
1. Open `plan.md` from a chat chip and leave Files selected.
2. Have the Bot edit `plan.md`, producing another file entry/chip.
3. Click the new chip while the existing viewer remains open.
4. The viewer still shows the old document and the read count remains one. A diagnostic probe using the real chat/workbench and a mutable file-read fixture reproduced this.

## Required outcome
- Each newly applied file request must refresh its document even when its path is unchanged, while preserving the existing Bot scoping and refresh-error behavior.
- Extend the behavioral panel suite with a same-path edit whose returned contents differ; assert that clicking the new chip shows the new contents.

(bot: BotDefinition, action: BotMenuAction, returnFocus?: HTMLElement | null) => {
switch (action) {
case "profile":
void navigate({ to: "/bots/$botId", params: { botId: bot.id } });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Make same-route menu actions change the displayed subpage. Open a Bot's Profile at /bots/$botId, enter Instructions using its row, then choose Profile from that Bot's sidebar menu: Instructions stays visible because this navigation targets the unchanged URL. Advanced has the equivalent problem after a local subpage transition while the URL still has ?page=advanced.

Technical details
# Sidebar actions can be no-ops on the current Bot page

## Affected sites
- `renderer/main/bots/bot-menu.tsx:228-232`: Profile and Advanced act only through URL navigation.
- `renderer/main/bots-view.tsx:108-112`: displayed `page` is local state; it synchronizes only when `botId` or parsed `initialPage` changes.
- `renderer/main/bots-view.tsx:159-161`: Instructions, Advanced, and Memory can be entered locally without updating the URL.

## Reproduction
- Mount the actual `BotsView` and `BotSidebar` at `/bots/bot-1` with the client-mode router.
- Click Instructions, then choose Profile in the sidebar row's menu for Bot 1.
- After navigation settles, the URL remains `/bots/bot-1` and `Instructions for Planner` is still rendered. This reproduces without making any unsaved edits.

## Required outcome
- An explicit Profile or Advanced menu choice must select that surface even if its requested URL already matches the current route.
- Keep the displayed subpage and external navigation actions coordinated, and cover this with the actual routed page rather than a navigation-only fixture.


const close = React.useCallback(() => {
const active = document.activeElement;
if (active instanceof HTMLElement && active.closest(`#${BOT_PANEL_ID}`)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Include panel-owned portals in the close lifecycle. With Memory options open, environment.toggle closes and makes the panel inert, but its portalled menu remains open and interactive because focus is outside #bot-panel. Selecting Edit then opens a Memory dialog for the hidden panel instead of dismissing the panel's controls.

Technical details
# A hidden Bot panel leaves its Memory menu active

## Affected sites
- `renderer/main/bots/bot-panel-context.tsx:132-145`: close detects focus only through DOM ancestry and does not dismiss panel-owned portals.
- `renderer/main/root-view.tsx:148-150`: the environment command invokes the panel toggle while a dropdown is open.
- `renderer/main/bots/bot-memory-page.tsx:180-191`: Memory options uses the shared portalled DropdownMenu.
- `renderer/components/ui.tsx:1639-1652`: DropdownMenuContent renders through a Radix Portal.

## Reproduction
1. Open the Bot panel on Memory and focus/open an entry's Memory options menu.
2. Invoke `environment.toggle` while focus is on the portalled Edit item.
3. The panel becomes closed/inert, but Edit and Delete remain available outside it.
4. Selecting Edit opens the Edit memory dialog. A diagnostic probe reproduced this through CommandSystemProvider's `app:command` notification and the actual panel actions/content.

## Required outcome
- Hiding the panel must also dismiss its active owned overlays and restore focus to a visible non-inert target, or defer the toggle while the overlay owns the interaction.
- Add behavioral coverage for the command while a panel-child menu is focused, not only for Escape inside the panel DOM.

## Framework reference
- Radix menu content uses portal/focus-scope handling: https://github.com/radix-ui/primitives/blob/main/packages/react/menu/src/menu.tsx

Base automatically changed from claude/bot-chat-activity-c1-workbench-shell to main October 14, 2026 22:23
@sambitcreate
sambitcreate merged commit f1f416c into main Oct 14, 2026
48 of 51 checks passed
@sambitcreate
sambitcreate deleted the claude/bot-chat-activity-c2-sidebar-panel branch October 14, 2026 22:23
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.

2 participants