Repository navigation
Add an "AI Chats" sidebar section listing conversations with a "+ New chat" button #2153
Description
Summary
Add an "AI Chats" section to the navigation sidebar, listing the user's existing ai-chat nodes with a "+ New chat" button at the bottom.
This is its own top-level section — a sibling of Collections and Schema Types, not an entry inside Schema Types. It gets its own collapsible header in the sidebar, expanding and collapsing the same way those two do. Do not implement this by adding ai-chat to SIDENAV_CORE_TYPES; see Why below.
Why
ai-chat currently has no sidebar presence at all. It is not in SIDENAV_CORE_TYPES (packages/desktop-app/src/lib/stores/schemas.svelte.ts:24, which holds only task, project, skill, person, agent-guidance), so it does not appear under Schema Types the way other entity types do. Today the only user-facing way to start a conversation is the /ai-chat slash command.
That matters because core#2152 removes /ai-chat — entity types are not slash-creatable per the rule recorded in core#2133 / core#2151. This issue should land before core#2152 removes the slash command, or there will be a window with no user-facing way to create a chat.
A dedicated top-level section is required rather than adding ai-chat to SIDENAV_CORE_TYPES. Conversations are a primary, frequently-revisited surface and deserve their own place in the sidebar, and the generic Schema Types route would list chats in a query-table type view rather than as a browsable conversation list with an inline create button. ai-chat should stay out of SIDENAV_CORE_TYPES.
Design
Mirror the Collections section (navigation-sidebar.svelte:380–523) — it is the existing pattern that has both a list and a create affordance.
Section shell
Collapsible.Rootwithopen={aiChatsExpanded}/onOpenChange={setAiChatsExpanded}Collapsible.Triggerwithclass="nav-item", anaria-labelthat flips betweenExpand AI Chats/Collapse AI Chats, anav-iconsvg, and<span class="nav-label">AI Chats</span>- A collapsed-sidebar branch showing just the icon, matching the
{:else}arm the other sections use - Use the existing
aiSquareicon already registered for this type (design/icons/registry.ts:121) rather than a new inline svg, if it fits the nav-icon treatment
Expansion state
Add aiChatsExpanded to packages/desktop-app/src/lib/stores/layout.svelte.ts alongside collectionsExpanded / schemaTypesExpanded — the interface field, the default (false), the persisted-hydration line (persisted.aiChatsExpanded ?? false), the setAiChatsExpanded method, and the module-level export. Expansion state is persisted, so this must round-trip like the others.
Chat list
Load via backendAdapter.queryNodes({ nodeType: 'ai-chat' }). Follow the collections.svelte.ts store shape (a data store with a load* method, loaded from onMount in the sidebar alongside loadCollections() / loadSchemas()).
- Display each chat's
contentas its label, falling back to something like "Untitled chat" when empty — chats are created with empty content and titles are generated later (see core#1698, idle-gated background titling), so blank names are the normal early state, not an edge case - Clicking a chat opens it in a tab. Reuse the existing tab-reuse logic in
handleSchemaClick(:247): if a tab for thatnodeIdexists,setActiveTab; otherwiseaddTabintogetTargetPaneId(). Note that helper hardcodesnodeType: 'query'; a chat tab needs to route toAiChatNodeViewer, so pass the right type rather than copying it verbatim - Decide ordering and cap — most-recently-modified first, and a sensible limit with overflow handling, since an unbounded list will not stay small.
queryNodesaccepts alimit
"+ New chat"
Bottom of the expanded list, styled like the + New collection button (:509–517).
Unlike collections, no name prompt — collections need an inline input because a name is required up front; a chat's title comes from its content later. Clicking should create immediately and open the new chat.
createSchemaInstance('ai-chat') (services/schema-authoring.ts:42) already does the right thing: it mints the node and returns it hydrated. ai-chat is not in NAME_AS_CONTENT_TYPES, so content starts empty, which is correct here. Defaults are safe — nodeToAiChatNode fills status ?? 'active' and messages: [] (types/ai-chat-node.ts:101), so a bare create needs no seeded properties.
Handle the create-in-flight state (disable the button, surface failures) the way Collections does with createBusy / createError.
Acceptance Criteria
- "AI Chats" renders as its own top-level sidebar section, a sibling of Collections and Schema Types — not nested under Schema Types
-
ai-chatis NOT added toSIDENAV_CORE_TYPES; it does not appear in the Schema Types list - The section expands/collapses like Collections and Schema Types
-
aiChatsExpandedadded to the layout store and persists across restarts - Collapsed-sidebar state shows the icon-only variant consistent with the other sections
- Expanded section lists existing ai-chat nodes, most-recent-first, with a defined limit
- Chats with empty content show a readable placeholder label
- Clicking a chat opens it in a tab via
AiChatNodeViewer; an already-open chat activates its existing tab instead of duplicating it - "+ New chat" creates an ai-chat node immediately (no name prompt) and opens it
- Create failures surface an error and re-enable the button
- New chats appear in the list without requiring a restart
- Tests: layout-store persistence of the new flag, and the list/create behavior of the section
- No raw
console.*— usecreateLogger
Related
- core#2152 — removes
/ai-chat; this issue should land first - core#2133 / core#2151 — the primitives-plus-task slash-command rule and its recorded exceptions
- core#1698 — auto-generated chat titles, which is what makes blank chat names temporary
Activity
Add AI Chats sidebar section with list and + New chat (#2178)
mstomar125 commentedon Aug 20, 2026
Done — merged in PR #2178 (squashed, merge SHA
dc387ff3c750f5a860a2fb0f50b82e4c4cc331be).What shipped
navigation-sidebar.svelte, a sibling of Collections and Schema Types (ai-chatwas NOT added toSIDENAV_CORE_TYPES).aiChatsDatastore (stores/ai-chats.svelte.ts): loads viabackendAdapter.queryNodes({ nodeType: 'ai-chat' })(fetched unbounded, then sorted client-side bymodifiedAtand capped at 50 for display — see note below on why the fetch itself isn't limited), andcreateChat()backed bycreateSchemaInstance('ai-chat')withcreateBusy/createErrorfor the "+ New chat" button's in-flight/failure states.layout.svelte.tsgainedaiChatsExpanded(interface field, defaultfalse, persisted-hydration,setAiChatsExpanded, module export);layout-persistence-service.tsbumped to schema version 4 with a v3→v4 migration.handleSchemaClick, routed tonodeType: 'ai-chat'so it opens viaAiChatNodeViewer. Empty-content chats show "Untitled chat".database.svelte.tsreloadsaiChatsDataon database switch, mirroringcollectionsData/schemasData.An adversarial review (code-review skill) caught 2 real correctness bugs that were fixed before merge:
createChat()had no guard against a database switch landing mid-create, so a new chat could be written into a store now representing a different database — fixed with a#generationcounter mirroringcollectionsData's guard aroundcreateCollection.limit: 50toqueryNodesbefore sorting; since the backend query has no order-by, that could silently truncate away genuinely-recent chats once a user passed 50 total. Fixed by fetching unbounded and slicing to the display cap only after sorting (matchescollectionsData.loadCollections()'s own unbounded fetch).Acceptance criteria
ai-chatNOT added toSIDENAV_CORE_TYPESaiChatsExpandedadded to layout store, persists across restartsAiChatNodeViewer; already-open chat activates existing tab instead of duplicatingconsole.*— usescreateLoggerThis lands before core#2152 (removal of
/ai-chat), so there's no gap in how users create chats.