/** * Interception of the chat's produced-files row: the turn-tail chain entry * that replaces ui-deliverables' row when the closing turn produced files. * The takeover looks identical (same chip row); the chips open the file in * the sidebar instead of the host OS. Priority -1 runs before the default-0 * deliverables entry; when nothing was produced the selector returns null * and the original row renders unchanged. */ import { IconCodeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives' import type { Context } from '../context-types.ts' import { revealPaths, type SidebarStore } from './state.ts' import { t } from './locales.ts' import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts' import css from './sidebar.module.css' /** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */ export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: string, path: string): void { const summary = ctx.sessions.list.getSnapshot().byId[sessionId] const absolute = resolveSidebarPath(summary?.cwd, path) const at = Math.max(absolute.lastIndexOf('/'), absolute.lastIndexOf('\\')) const title = at === -1 ? absolute : absolute.slice(at + 1) // Route through the sidebar service so the editor descriptor's dedupeKey // (per-path) applies; the id is path-derived so multiple editors coexist. ctx.get('betterSidebar')?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` }) } /** * Reveal the produced files in the sidebar explorer: expand their parent * directories, highlight the rows, and focus the explorer tab. Unknown * files fall back to revealing the workspace root itself. */ export function revealInExplorer( ctx: Context, store: SidebarStore, sessionId: string, files: readonly string[], ): void { const summary = ctx.sessions.list.getSnapshot().byId[sessionId] const cwd = summary?.cwd // Deliverables report paths as-is (often relative to the session cwd), but // the explorer tree and revealPaths work on absolute paths — resolve every // target so the ancestors expand and the row actually matches. const targets = files.length > 0 ? files.map(path => resolveSidebarPath(cwd, path)) : cwd === undefined ? [] : [cwd] store.reduce(state => revealPaths(state, cwd, targets)) // Focus the single-instance editor home tab (the files window) where the // reveal highlight renders. Read via ctx.get like every other internal // consumer (#357): the provider is not on this fiber chain, so a direct // ctx.betterSidebar read can throw before optional chaining applies. ctx.get('betterSidebar')?.openTab({ type: 'editor', title: t('files') }) } /** The intercepted produced-files row (visual twin of the deliverables chips). */ export function SidebarProducedFiles(props: { matched: readonly string[] openInSidebar: (path: string) => void /** Reveal the produced files in the explorer ("Show in folder" twin). */ onShowInFolder: (files: readonly string[]) => void }) { const { matched, openInSidebar, onShowInFolder } = props const shown = matched.slice(0, 6) const hidden = matched.length - shown.length return (