`; `children` replaces the whole row; `data-active` means selection
-- [`.Item.Title`](#chatsidebaritemtitle-_proposed---2977_---new) - `new`: no addressable title leaf exists today (#2977)
+- [`.Item.Title`](#chatsidebaritemtitle-proposed---2977---new) - `new`: the addressable title leaf - the export ships today; full row recomposition is still proposed (#2977)
- [`.Item.Menu`](#chatsidebaritemmenu---changed) - `changed`: trigger `icon` prop deleted - replaced by `.Item.Menu.Trigger`
- [`.Item.Menu.Trigger`](#chatsidebaritemmenutrigger---new) - `new`: the addressable trigger button (the icon-slot replacement)
- [`.Item.Rename`](#chatsidebaritemrename---changed) - `changed`: `icon` deleted
@@ -196,7 +196,7 @@ Per-row state computed today: `isActive = conversation.id === activeId`; the row
### `ChatSidebar.Item.Title` _(proposed - #2977)_ - `new`
-One ``: the conversation's title, truncating. Does not exist today - the title is rendered internally by `ListItem`'s `title` prop, so a composed row currently has no addressable title leaf. #2977 adds it so the default row is fully recomposable. Default content: `conversation.title`. During rename mode, `.Title` hosts the inline `` so row geometry stays stable.
+One ``: the conversation's title, truncating. The export ships from `veryfront/chat` today: a `` child moves the item's children into the row body, and a sibling `` stays in the action slot when it is direct or grouped in a fragment. Default content: `conversation.title`. Still proposed under #2977: `children` replacing the whole row, and rename mode hosting the inline `` inside `.Title` so row geometry stays stable.
**Layout:** in-flow text span; give its wrapper `min-w-0`/`flex-1` (or class the span `truncate`) for ellipsis.
diff --git a/scripts/lint/ban-chat-antipatterns.ts b/scripts/lint/ban-chat-antipatterns.ts
index a9a4394940..de873e7a9e 100644
--- a/scripts/lint/ban-chat-antipatterns.ts
+++ b/scripts/lint/ban-chat-antipatterns.ts
@@ -102,12 +102,12 @@ async function walk(
const FILE_SIZE_CEILINGS: Record = {
// Chat preset implementation lives in chat/chat-preset.tsx. This barrel grew
// only for the explicit conversation-persistence contracts exported here.
- "src/react/components/chat/chat/index.tsx": 278,
+ "src/react/components/chat/chat/index.tsx": 279,
// Message.Sources extracted to composition/message-sources.tsx.
"src/react/components/chat/chat/composition/message.tsx": 793,
- // Includes the ChatSidebar.Item menu compound (Item.Menu/.Rename/.Delete).
+ // Includes the ChatSidebar.Item compound (Item.Title/.Menu/.Rename/.Delete).
// Split responsibilities before adding more behavior to this file.
- "src/react/components/chat/chat/components/sidebar.tsx": 628,
+ "src/react/components/chat/chat/components/sidebar.tsx": 685,
// Composer state and native action leaves live in focused sibling modules.
"src/react/components/chat/chat/composition/chat-composer.tsx": 389,
"src/react/components/chat/chat/composition/chat-input-actions.tsx": 159,
diff --git a/src/chat/index.test.ts b/src/chat/index.test.ts
index eb848ee668..95dc33ce68 100644
--- a/src/chat/index.test.ts
+++ b/src/chat/index.test.ts
@@ -9,7 +9,7 @@ import type {
UseConversationChatOptions as ComponentsChatOptions,
UseConversationChatResult as ComponentsChatResult,
} from "veryfront/components/chat";
-import type { ConversationStorageLimits } from "./index.ts";
+import type { ChatSidebarItemTitleProps, ConversationStorageLimits } from "./index.ts";
import type {
UseConversationChatOptions as ReactComponentsChatOptions,
UseConversationChatResult as ReactComponentsChatResult,
@@ -28,9 +28,11 @@ const _reactComponentsChatOptions: ReactComponentsChatOptions = _componentsChatO
const _componentsChatResult = null as unknown as ComponentsChatResult;
const _reactComponentsChatResult: ReactComponentsChatResult = _componentsChatResult;
const _storageLimits: ConversationStorageLimits = chatModule.CONVERSATION_STORAGE_LIMITS;
+const _sidebarTitleProps: ChatSidebarItemTitleProps = { id: "conversation-title" };
void _reactComponentsChatOptions;
void _reactComponentsChatResult;
void _storageLimits;
+void _sidebarTitleProps;
const expectedRuntimeExports = [
// Canonical component names.
diff --git a/src/chat/index.ts b/src/chat/index.ts
index c20eab59fc..fd24a1c55e 100644
--- a/src/chat/index.ts
+++ b/src/chat/index.ts
@@ -117,6 +117,7 @@ export {
type ChatSidebarEmptyProps,
type ChatSidebarGroupProps,
type ChatSidebarItemProps,
+ type ChatSidebarItemTitleProps,
type ChatSidebarListProps,
type ChatSidebarNewButtonProps,
type ChatSidebarProps,
diff --git a/src/react/components/chat/chat.tsx b/src/react/components/chat/chat.tsx
index 4544a16841..649a764784 100644
--- a/src/react/components/chat/chat.tsx
+++ b/src/react/components/chat/chat.tsx
@@ -154,6 +154,7 @@ export {
type ChatSidebarEmptyProps,
type ChatSidebarGroupProps,
type ChatSidebarItemProps,
+ type ChatSidebarItemTitleProps,
type ChatSidebarListProps,
type ChatSidebarNewButtonProps,
type ChatSidebarProps,
diff --git a/src/react/components/chat/chat/components/sidebar.test.tsx b/src/react/components/chat/chat/components/sidebar.test.tsx
index 55a831ad4e..4ba93982d4 100644
--- a/src/react/components/chat/chat/components/sidebar.test.tsx
+++ b/src/react/components/chat/chat/components/sidebar.test.tsx
@@ -258,6 +258,255 @@ describe("ChatSidebar.Item — menu compound (E4 acid test)", () => {
});
});
+describe("ChatSidebar.Item.Title: composable row label", () => {
+ it("exposes the Title leaf off the compound", () => {
+ assert(typeof ChatSidebar.Item.Title === "function", "Item.Title is addressable");
+ });
+
+ it("composes the row label alongside a sibling", () => {
+ const html = renderToString(
+ {}}
+ onDelete={() => {}}
+ >
+
+
+
+ badge
+
+
+ ,
+ );
+ assert(html.includes(">Row title<"), "the Title leaf renders the conversation title");
+ assert(html.includes("data-badge"), "the sibling badge renders next to the title");
+ const primaryActionClass = html.match(/