diff --git a/src/client/DiffTab.tsx b/src/client/DiffTab.tsx
index 345c2fb..585c27c 100644
--- a/src/client/DiffTab.tsx
+++ b/src/client/DiffTab.tsx
@@ -16,7 +16,7 @@ import type { SidebarDiffRef } from './state.ts'
import { DiffFiles } from './diff/DiffFiles.tsx'
import { displayPath, foldRowsFromContents, type DiffFile, type DiffRow, type FoldSegment } from './diff/rows.ts'
import { t } from './locales.ts'
-import { resolveSidebarPath } from './produced-files.ts'
+import { resolveSidebarPath } from './paths.ts'
import css from './sidebar.module.css'
/** The loaded diff surface (untracked content rendered as a full addition). */
diff --git a/src/client/EditorHost.tsx b/src/client/EditorHost.tsx
index 2181739..ab8da93 100644
--- a/src/client/EditorHost.tsx
+++ b/src/client/EditorHost.tsx
@@ -33,13 +33,13 @@ import { FenceErrorNotice } from './FenceErrorNotice.tsx'
import { planFirstMatch, planFsReadOutcome, type EditorLoadAction } from './editor-load.ts'
import { baseName } from './FileTree.tsx'
import { createFrameBatcher } from './frame-batcher.ts'
-import { openSidebarFile } from './intercept.tsx'
+import { openSidebarFile } from './sidebar-file.ts'
import { openWithSshActive, openWithUrl, parseOpenWithConfig, resolveOpenWithTargets } from './open-with.ts'
import { updatePluginSettings } from './plugin-settings.ts'
import { TreePanel } from './TreePanel.tsx'
import { t } from './locales.ts'
import { relativeTo } from './paths.ts'
-import { resolveSidebarPath } from './produced-files.ts'
+import { resolveSidebarPath } from './paths.ts'
import { closePathTabs, retargetPathTabs } from './tree-mutations.ts'
import type { EditorToolbarControls, EditorToolbarState, FileViewerDescriptor } from './service.ts'
import { firstLeaf, insertLeafAt, leafWithTab, mintTabId, type SidebarStore, type SidebarTab } from './state.ts'
@@ -162,13 +162,13 @@ export function EditorHost(props: {
if (inPlace) {
ctx.get('betterSidebar')?.updateTab(tab.id, { path: absolute, title: baseName(absolute) })
} else {
- openSidebarFile(ctx, store, scope.sessionId, absolute)
+ openSidebarFile(ctx, scope.sessionId, absolute)
}
}
/** The context menu's explicit "new tab" escape (per-path dedupe). */
const openFileNewTab = (absolute: string): void => {
- openSidebarFile(ctx, store, scope.sessionId, absolute)
+ openSidebarFile(ctx, scope.sessionId, absolute)
}
/**
diff --git a/src/client/TreePanel.tsx b/src/client/TreePanel.tsx
index 8134ac1..48ebac2 100644
--- a/src/client/TreePanel.tsx
+++ b/src/client/TreePanel.tsx
@@ -25,7 +25,7 @@ import { FileTree } from './FileTree.tsx'
import { IconUploadOutline16 } from './icons.tsx'
import type { OpenWithTarget } from './open-with.ts'
import { t } from './locales.ts'
-import { resolveSidebarPath } from './produced-files.ts'
+import { resolveSidebarPath } from './paths.ts'
import { UploadOverlay } from './UploadOverlay.tsx'
import {
summarizeResults, uploadHintText, uploadItemsFromFiles, uploadToDir,
diff --git a/src/client/builtins/tabs.tsx b/src/client/builtins/tabs.tsx
index 55e12ec..70b2b71 100644
--- a/src/client/builtins/tabs.tsx
+++ b/src/client/builtins/tabs.tsx
@@ -15,7 +15,7 @@ import {
} from './tab-icons.tsx'
import { allLeaves, isAgentTabId, type SidebarState } from '../state.ts'
import { t } from '../locales.ts'
-import { openSidebarFile } from '../intercept.tsx'
+import { openSidebarFile } from '../sidebar-file.ts'
import { EditorHost } from '../EditorHost.tsx'
import { OpenWithSettings } from '../open-with-settings.tsx'
import { lazyChunkComponent } from '../lazy-chunk.tsx'
@@ -166,7 +166,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
scope={scope}
tab={tab}
visible={visible}
- onOpenFile={(path) => { openSidebarFile(ctx, store, scope.sessionId, path) }}
+ onOpenFile={(path) => { openSidebarFile(ctx, scope.sessionId, path) }}
onOpenDiff={onOpenDiff}
/>
),
diff --git a/src/client/changes/DiffPane.tsx b/src/client/changes/DiffPane.tsx
index e7c1d80..3c576f6 100644
--- a/src/client/changes/DiffPane.tsx
+++ b/src/client/changes/DiffPane.tsx
@@ -14,7 +14,7 @@ import type { SessionScope } from '../api.ts'
import { api, htmlUrl } from '../api.ts'
import { t } from '../locales.ts'
import { baseName } from '../paths.ts'
-import { resolveSidebarPath } from '../produced-files.ts'
+import { resolveSidebarPath } from '../paths.ts'
import { HTML_IFRAME_SANDBOX } from '../html-preview.ts'
import type { SidebarDiffRef, SidebarTab } from '../state.ts'
import { DiffRows, ReadRows } from '../diff/DiffRows.tsx'
diff --git a/src/client/changes/GitLens.tsx b/src/client/changes/GitLens.tsx
index 21840a6..d4b43cd 100644
--- a/src/client/changes/GitLens.tsx
+++ b/src/client/changes/GitLens.tsx
@@ -19,7 +19,7 @@ import type { GitLogEntry, GitStatusEntry, GitStatusResult, GitWorktree, Session
import { api } from '../api.ts'
import { usePolling } from '../use-polling.ts'
import { baseName, isWithinWorkspace, relativeTo } from '../paths.ts'
-import { resolveSidebarPath } from '../produced-files.ts'
+import { resolveSidebarPath } from '../paths.ts'
import { relativeTime, t } from '../locales.ts'
import type { SidebarDiffRef, SidebarStore } from '../state.ts'
import css from './changes.module.css'
diff --git a/src/client/index.tsx b/src/client/index.tsx
index d5c82a3..9b622d5 100644
--- a/src/client/index.tsx
+++ b/src/client/index.tsx
@@ -2,9 +2,8 @@
* Client half of dsh-better-sidebar: resolves the user's "Side card"
* preferences through the plugin's own fenced settings route, mounts the
* right sidebar portal (inside an error boundary so a rendering failure
- * shows an error strip instead of a blank panel), registers the turn-tail
- * interception, and contributes the Side card settings section to the DSH
- * Settings shell. Requires the runtime's slots and sessions services; the
+ * shows an error strip instead of a blank panel), and contributes the Side
+ * card settings section to the DSH Settings shell. Requires the runtime's slots and sessions services; the
* bundle itself is a module-table consumer only (react + ui-primitives +
* xterm, all provided or inlined).
*/
@@ -17,7 +16,6 @@ import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-load
import { registerBuiltins } from './builtins/index.ts'
import { Sidebar } from './Sidebar.tsx'
import { RenderBoundary } from './RenderBoundary.tsx'
-import { registerTurnTailInterception } from './intercept.tsx'
import { createNativeTabRecords } from './native/tab-adapter.tsx'
import { registerNativeSurface } from './native/index.ts'
import { registerBottomToggle } from './sidebar/bottom-toggle.tsx'
@@ -38,9 +36,7 @@ import './layout.css'
* (rc.8+) is the client module system the chunk loader resolves its
* externals through; `connection` (0.1.2-alpha.2+) is the Remote transport's
* recovery lifecycle the side chat's disconnect banner reads — Cordis guards
- * service access without inject. The `remote.session` namespace is NOT here:
- * it mounts asynchronously, so the open-path interception reaches it through
- * `ctx.inject` (see intercept.tsx). */
+ * service access without inject. */
export const inject = ['slots', 'sessions', 'locale', 'modules', 'connection']
/**
@@ -130,6 +126,29 @@ export function apply(ctx: Context): void {
attachBetterLocale(undefined)
}
}, 'dsh-better-sidebar: better-locale lazy integration')
+ // A failure anywhere in the client lifecycle must never take the app down
+ // silently: log with the plugin prefix and pin a visible diagnostic strip
+ // to the page so a blank panel is never the only symptom. This strip is
+ // the last-resort reporter (no CSS module is reachable from here), so its
+ // colors go through skin token chains with the previous hexes as the
+ // chain tails — worst case (no skin tokens on the page) it renders
+ // byte-identical to the old hardcoded bar, and any `--dsw-alias-*` skin
+ // re-themes it (guide §12: no hardcoded colors).
+ const fail = (phase: string, error: unknown): void => {
+ console.error(`[dsh-better-sidebar] ${phase} error:`, error)
+ try {
+ const bar = document.createElement('div')
+ bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
+ + 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;'
+ + 'color:var(--dsw-alias-state-error-primary,#f2a1a1);'
+ + 'background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-base,#1b1b22));'
+ + 'border:1px solid var(--dsw-alias-state-error-primary,#f2a1a1);border-radius:8px;white-space:pre-wrap'
+ bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
+ document.body.appendChild(bar)
+ } catch {
+ // Nothing left to report with.
+ }
+ }
// One store instance per activation: production code creates it only here,
// then hands it to the mounted panel and closes over it in the slot
// registrations (the official createXXXStore() factory rule — no
@@ -149,7 +168,10 @@ export function apply(ctx: Context): void {
const nativeSurface = createNativeSurface(ctx, nativeRecords)
service.setSurface(nativeSurface)
ctx.effect(
- () => registerNativeSurface({ ctx, store: sidebarStore, service, records: nativeRecords }),
+ () => registerNativeSurface({
+ ctx, store: sidebarStore, service, records: nativeRecords,
+ reportFailure: (phase, error) => { fail(`native ${phase}`, error) },
+ }),
'dsh-better-sidebar: native right-Sidebar registrations',
)
// The bottom workbench's expand/collapse button in DSH's session header
@@ -188,29 +210,6 @@ export function apply(ctx: Context): void {
() => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
'dsh-better-sidebar: register built-in tabs and viewers',
)
- // A failure anywhere in the client lifecycle must never take the app down
- // silently: log with the plugin prefix and pin a visible diagnostic strip
- // to the page so a blank panel is never the only symptom. This strip is
- // the last-resort reporter (no CSS module is reachable from here), so its
- // colors go through skin token chains with the previous hexes as the
- // chain tails — worst case (no skin tokens on the page) it renders
- // byte-identical to the old hardcoded bar, and any `--dsw-alias-*` skin
- // re-themes it (guide §12: no hardcoded colors).
- const fail = (phase: string, error: unknown): void => {
- console.error(`[dsh-better-sidebar] ${phase} error:`, error)
- try {
- const bar = document.createElement('div')
- bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
- + 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;'
- + 'color:var(--dsw-alias-state-error-primary,#f2a1a1);'
- + 'background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-base,#1b1b22));'
- + 'border:1px solid var(--dsw-alias-state-error-primary,#f2a1a1);border-radius:8px;white-space:pre-wrap'
- bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
- document.body.appendChild(bar)
- } catch {
- // Nothing left to report with.
- }
- }
try {
// rc.8+ exposes the client module system as the `ctx.modules` service;
// the chunk loader needs it to resolve its externals, so inject it
@@ -354,18 +353,6 @@ export function apply(ctx: Context): void {
}
}, 'dsh-better-sidebar: sidebar mount')
- ctx.effect(
- () => {
- try {
- return registerTurnTailInterception(ctx, sidebarStore)
- } catch (error) {
- fail('interception', error)
- return () => {}
- }
- },
- 'dsh-better-sidebar: turn-tail interception',
- )
-
ctx.effect(
() => {
try {
diff --git a/src/client/intercept.tsx b/src/client/intercept.tsx
deleted file mode 100644
index b4f130b..0000000
--- a/src/client/intercept.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * 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 (
-