From c81de3374f2853db71e6b101a2105867c33f12f9 Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Sun, 9 Aug 2026 00:01:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20@-reference=20button=20no-ops=20?= =?UTF-8?q?=E2=80=94=20read=20conversation=20via=20ctx.get,=20not=20the=20?= =?UTF-8?q?ctx=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cordis only grants property reads for services declared in the plugin's inject list; every other read throws "cannot get property X without inject" (the app's own plugins read 'conversation' through ctx.get for this reason). referenceInChat now resolves the conversation service lazily via ctx.get('conversation') and no-ops when absent. Verified end-to-end in headless Chrome: clicking the @file button on root/dir/file rows now appends the at-relative-path mention to the composer draft with no console errors. --- src/client/Sidebar.tsx | 17 ++++++++++------- src/context-types.ts | 7 ++++--- 2 files changed, 14 insertions(+), 10 deletions(-) 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 /**