diff --git a/src/client/Sidebar.tsx b/src/client/Sidebar.tsx index 8f41251..e6d806d 100644 --- a/src/client/Sidebar.tsx +++ b/src/client/Sidebar.tsx @@ -13,7 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useSyncExternalStore } from 'react' import clsx from 'clsx' import { IconChevronRightOutline14, IconFullscreenOutline16, IconPanelLeftOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives' -import type { Context } from '../context-types.ts' +import type { Context, SidebarConversation } from '../context-types.ts' import { allLeaves, closeTab, leafWithTab, mapLeaf, moveTab, moveTabToEdge, openTab, resizeSplit, sidebarStore, setWidth, toggleExpanded, togglePanel, @@ -187,18 +187,21 @@ export function Sidebar(props: { ctx: Context }) { /** * The explorer's @-reference button: append `@` to the - * session's composer draft (space-separated). Resolves the session-scope - * ctx and the conversation input service at click time; a missing service - * or scope degrades to a logged no-op, never a crash. Defined above the - * no-session early return — a hook must never sit behind a conditional - * return (React counts hooks per render). + * session's composer draft (space-separated). The conversation service is + * resolved lazily through `ctx.get` (the inject-free read — the app's own + * plugins read 'conversation' the same way); a missing service or scope + * degrades to a logged no-op, never a crash. Defined above the no-session + * early return — a hook must never sit behind a conditional return + * (React counts hooks per render). */ const referenceInChat = useCallback((path: string): void => { if (sessionId === undefined) return try { const actx = ctx.sessions.scope(sessionId) if (actx === undefined) return - const input = ctx.conversation.input.for(actx) + const conversation = ctx.get('conversation') as SidebarConversation | undefined + if (conversation === undefined) return + const input = conversation.input.for(actx) const mention = `@${relativeTo(cwd ?? '', path)}` const draft = input.state.getSnapshot().draft input.setDraft(draft.trim() === '' ? mention : `${draft} ${mention}`) diff --git a/src/context-types.ts b/src/context-types.ts index ecf8363..3bfe11a 100644 --- a/src/context-types.ts +++ b/src/context-types.ts @@ -9,7 +9,9 @@ * - httpServer: @deepseek-ai/dsh-host-webserver * - sessions: host side @deepseek-ai/dsh-session (SessionStore), client * side the runtime ISessions list feed - * - conversation: client side ui-conversation's IConversation (composer draft) + * - conversation: client side ui-conversation's IConversation (composer + * draft), read lazily through `ctx.get` — cross-plugin service reads need + * an inject declaration, so the direct property is never typed here * - loader: @cordisjs/plugin-loader (entry options) * - slots: the client runtime SlotsService * - effect: the DSH-vendored cordis lifecycle helper @@ -116,7 +118,7 @@ export interface SidebarSessionInput { setDraft(text: string): void } -/** The conversation service face (mirror of ui-conversation's IConversation). */ +/** The composer draft face the sidebar reaches through `ctx.get('conversation')`. */ export interface SidebarConversation { input: { for(actx: Context): SidebarSessionInput @@ -127,7 +129,6 @@ declare module 'cordis' { interface Context { httpServer: SidebarHttpServer sessions: SidebarSessionStore & SidebarSessionsService - conversation: SidebarConversation loader: SidebarLoader slots: SidebarSlotsService /**