From 513a50f2e8d9f7311fa5b97cfb5ca1b1dc03ab6a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Padez?=
Date: Wed, 26 Aug 2026 07:50:55 +0000
Subject: [PATCH] a bot is a card, not a conversation in a list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
it looked like the generic chat panel because it WAS one, and that leaks the
implementation at exactly the wrong moment: a bot is a chat underneath, and
nobody using this should have to hold that. the session list says
"conversation, last touched" because that is what a conversation is.
so it gets Command HQ's object instead — a card with a name, an id pill, rename
and delete, selected ring, controls on hover. same arrangement: the whole card
is the link with the buttons layered over it, so a click anywhere opens it and
the controls still work.
the pill is the first eight characters of the session, which is also what an
@mention carries. that is the point rather than a coincidence — the thing shown
on the card is the thing typed to reference it.
the panel's own header picker is gone. the cards are the list, and a second
picker in the panel bar is the same choice offered twice. command hq does not
have one either.
deleting the selected bot navigates back to /bots, or the route points at
something that no longer exists and renders an empty chat.
Co-Authored-By: Claude Opus 5
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
---
.../Screens/Dashboard/Bots/BotsList.tsx | 166 +++++++++++++++---
.../Screens/Dashboard/Bots/BotsScreen.tsx | 10 +-
2 files changed, 141 insertions(+), 35 deletions(-)
diff --git a/src/apps/officer-web/Screens/Dashboard/Bots/BotsList.tsx b/src/apps/officer-web/Screens/Dashboard/Bots/BotsList.tsx
index 6051ac9e..1833aec1 100644
--- a/src/apps/officer-web/Screens/Dashboard/Bots/BotsList.tsx
+++ b/src/apps/officer-web/Screens/Dashboard/Bots/BotsList.tsx
@@ -1,22 +1,143 @@
-import { Bot, Plus } from 'lucide-react';
-import { Link, useParams } from 'react-router';
-import { useClaudeSessions } from 'state/useClaudeSessions';
+import { useState } from 'react';
+import { Bot, Check, Pencil, Plus, Trash2, X } from 'lucide-react';
+import { Link, useNavigate, useParams } from 'react-router';
+import { useClaudeSessions, type ClaudeSessionSummary } from 'state/useClaudeSessions';
/**
- * The bots, down the left — the same shape Command HQ gives commanders.
+ * The bots, as cards — the same object Command HQ gives a commander.
*
- * Every bot is a chat in `$DATA_PATH//bots`, so this is that directory's session list and nothing
- * more. No table, no rows to keep in step: the list IS the conversations.
+ * ── Why not the chat session list ──
*
- * Rows are real links to `/bots/:sessionId`, per `docs/navigation-audit.md` — a bot is addressable, so
- * cmd-click opens one in a tab and a reload keeps the one you were reading.
+ * A bot IS a chat underneath, and that is an implementation detail nobody using this should have to hold.
+ * The generic session list says "conversation, last touched at" because that is what a conversation is. A
+ * bot is a thing you name, check on, and eventually delete — so it gets a card with its name, its id, and
+ * whether it is watching, exactly as a commander does.
+ *
+ * ── The id pill ──
+ *
+ * A commander shows `#54`, its directory. A bot has no directory of its own — they share one — so its id
+ * is the first eight characters of its session, which is also what an `@mention` carries. Seeing the same
+ * eight characters in both places is the point: the pill is what you type to reference it.
*/
-const NEW = '/bots';
+const shortId = (id: string) => id.slice(0, 8);
+
+type CardProps = {
+ bot: ClaudeSessionSummary;
+ selected: boolean;
+ onRename: (id: string, title: string) => Promise;
+ onDelete: (id: string) => Promise;
+};
+
+const BotCard = ({ bot, selected, onRename, onDelete }: CardProps) => {
+ const navigate = useNavigate();
+ const [confirming, setConfirming] = useState(false);
+ const [renaming, setRenaming] = useState(false);
+ const [draft, setDraft] = useState(bot.title);
+
+ const commit = async () => {
+ const next = draft.trim();
+ setRenaming(false);
+ if (next && next !== bot.title) await onRename(bot.id, next);
+ };
+
+ return (
+
+ {/* The whole card is the link, with the controls layered above it — same arrangement as a commander
+ card, so a click anywhere opens the bot and the buttons still work. */}
+ {!renaming && }
+
+