dsh-coding-sidebar 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +48229 -0
- package/lib/client-mermaid.js +200745 -0
- package/lib/client-registry.js +21867 -0
- package/lib/client-terminal.js +9361 -0
- package/lib/client.js +21867 -0
- package/lib/index.js +4556 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +13 -0
- package/lib/types/client/FileTree.d.ts +38 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +10 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +24 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +275 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +131 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +59 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +25 -0
- package/lib/types/client/intercept.d.ts +46 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +452 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +38 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +85 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +439 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +36 -0
- package/lib/types/client/state.d.ts +432 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +605 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +20 -0
- package/lib/types/prefs-shared.d.ts +236 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +172 -0
- package/scripts/install.ps1 +306 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +558 -0
- package/src/client/FileTree.tsx +669 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +717 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1163 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +645 -0
- package/src/client/Sidebar.tsx +1370 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +275 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +357 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +340 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +358 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +152 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +427 -0
- package/src/client/intercept.tsx +188 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +24 -0
- package/src/client/layout.css +90 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +368 -0
- package/src/client/locales-de.ts +353 -0
- package/src/client/locales-fr.ts +360 -0
- package/src/client/locales-hi.ts +367 -0
- package/src/client/locales-id.ts +365 -0
- package/src/client/locales-it.ts +358 -0
- package/src/client/locales-ja.ts +367 -0
- package/src/client/locales-ko.ts +359 -0
- package/src/client/locales-nl.ts +365 -0
- package/src/client/locales-pl.ts +369 -0
- package/src/client/locales-pt.ts +350 -0
- package/src/client/locales-ru.ts +365 -0
- package/src/client/locales-sv.ts +350 -0
- package/src/client/locales-th.ts +367 -0
- package/src/client/locales-tr.ts +367 -0
- package/src/client/locales-vi.ts +367 -0
- package/src/client/locales-zh-HK.ts +382 -0
- package/src/client/locales-zh-MO.ts +382 -0
- package/src/client/locales-zh-TW.ts +382 -0
- package/src/client/locales.ts +894 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +52 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +143 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +183 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +96 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +210 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +859 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2788 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +331 -0
- package/src/client/state.ts +1497 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +154 -0
- package/src/context-types.ts +611 -0
- package/src/fs-operations.ts +95 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1291 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +75 -0
- package/src/prefs-shared.ts +280 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client half of dsh-coding-sidebar: resolves the user's "Side card"
|
|
3
|
+
* preferences through the plugin's own fenced settings route, mounts the
|
|
4
|
+
* right sidebar portal (inside an error boundary so a rendering failure
|
|
5
|
+
* shows an error strip instead of a blank panel), registers the turn-tail
|
|
6
|
+
* interception, and contributes the Side card settings section to the DSH
|
|
7
|
+
* Settings shell. Requires the runtime's slots and sessions services; the
|
|
8
|
+
* bundle itself is a module-table consumer only (react + ui-primitives +
|
|
9
|
+
* xterm, all provided or inlined).
|
|
10
|
+
*/
|
|
11
|
+
import { createElement } from 'react'
|
|
12
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
13
|
+
import type { Context } from '../context-types.ts'
|
|
14
|
+
import { allLeaves, createSidebarStore, isAgentTabId } from './state.ts'
|
|
15
|
+
import { createBetterSidebarService, matchUrlTarget } from './service.ts'
|
|
16
|
+
import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-loader.ts'
|
|
17
|
+
import { registerBuiltins } from './builtins/index.ts'
|
|
18
|
+
import { Sidebar } from './Sidebar.tsx'
|
|
19
|
+
import { RenderBoundary } from './RenderBoundary.tsx'
|
|
20
|
+
import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
|
|
21
|
+
import { registerLinkInterception } from './link-intercept.ts'
|
|
22
|
+
import { registerImeGuard } from './ime-guard.ts'
|
|
23
|
+
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
|
24
|
+
import { loadExternalDisable, loadPrefs } from './prefs.ts'
|
|
25
|
+
import { SideCardSection } from './SideCardSection.tsx'
|
|
26
|
+
import { api } from './api.ts'
|
|
27
|
+
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en,
|
|
28
|
+
ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
|
|
29
|
+
zhHK, zhTW, zhMO,
|
|
30
|
+
} from './locales.ts'
|
|
31
|
+
import css from './sidebar.module.css'
|
|
32
|
+
import './layout.css'
|
|
33
|
+
|
|
34
|
+
/** Services required before mounting (provided by the client runtime; the
|
|
35
|
+
* locale service backs the sidebar's copy — see locales.ts). `modules`
|
|
36
|
+
* (rc.8+) is the client module system the chunk loader resolves its
|
|
37
|
+
* externals through — Cordis guards service access without inject.
|
|
38
|
+
* `remote` + `remote.session` pin this build to the 0.1.2-alpha.1+ carrier:
|
|
39
|
+
* the open-path interception wraps the session namespace, and the deep path
|
|
40
|
+
* is validated AS A WHOLE by the traceable service proxy (a bare ctx.get
|
|
41
|
+
* clears the first hop, then `cannot get property "remote.session" without
|
|
42
|
+
* inject` on the second). Cordis inject is all-required, so a carrier
|
|
43
|
+
* without these never mounts this plugin — same policy as `modules`. */
|
|
44
|
+
export const inject = ['slots', 'sessions', 'connection', 'workspaces', 'locale', 'modules', 'remote', 'remote.session']
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Error boundary over the sidebar tree (root scope): a render error in the
|
|
48
|
+
* sidebar SHELL itself must never blank the page silently — the shared
|
|
49
|
+
* RenderBoundary shows a dismissible error strip and logs the stack. The
|
|
50
|
+
* per-tab scope (Sidebar.tsx) catches viewer/editor crashes first; this root
|
|
51
|
+
* boundary stays as the last resort for Workbench/shell errors.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Client plugin body.
|
|
55
|
+
* @param ctx - the client cordis context (slots, sessions).
|
|
56
|
+
*/
|
|
57
|
+
export function apply(ctx: Context): void {
|
|
58
|
+
// The sidebar follows the DSH i18n system: attach the locale service so
|
|
59
|
+
// the module-level t()/isZh() resolve the Host-backed language preference
|
|
60
|
+
// (and switch live — the Sidebar root subscribes to it), and register the
|
|
61
|
+
// plugin's dictionaries into the shared locale registry. The disposers
|
|
62
|
+
// run on fiber disposal, so re-activation (HMR) re-registers cleanly.
|
|
63
|
+
attachLocale(ctx.locale)
|
|
64
|
+
ctx.effect(() => {
|
|
65
|
+
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
|
|
66
|
+
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
|
|
67
|
+
return () => { offZh(); offEn() }
|
|
68
|
+
}, 'dsh-coding-sidebar: dictionaries')
|
|
69
|
+
|
|
70
|
+
// Opt-in third-language support through @huanlin/dsh-plugin-better-locale.
|
|
71
|
+
// When that plugin is installed, it publishes `ctx.betterLocale` (the
|
|
72
|
+
// override store) and patches LocaleRuntime.prototype.lookup to consult
|
|
73
|
+
// it. We mirror the same override awareness into the sidebar's own `t()`:
|
|
74
|
+
// attachBetterLocale() makes t() consult the store's getOverride first,
|
|
75
|
+
// so the sidebar's chrome (which bypasses ctx.locale and calls t()
|
|
76
|
+
// directly) also switches to the override language. We also register
|
|
77
|
+
// the ja dict with the better-locale store so external callers of
|
|
78
|
+
// ctx.locale.lookup('betterSidebar', key) get the override text too.
|
|
79
|
+
//
|
|
80
|
+
// Activation-order-safe: ctx.get('betterLocale') is a non-reactive read
|
|
81
|
+
// (cordis only re-evaluates declared `inject` deps). If better-locale
|
|
82
|
+
// activates after dsh-coding-sidebar, the initial read returns undefined.
|
|
83
|
+
// We subscribe to the locale revision — better-locale bumps it on
|
|
84
|
+
// activation (when a persisted override exists) and on every override
|
|
85
|
+
// switch — and re-check ctx.get on each bump, attaching + registering
|
|
86
|
+
// the ja dict once the store becomes available.
|
|
87
|
+
ctx.effect(() => {
|
|
88
|
+
let dispose: (() => void) | undefined
|
|
89
|
+
const sync = (): void => {
|
|
90
|
+
dispose?.()
|
|
91
|
+
dispose = undefined
|
|
92
|
+
const store = ctx.get('betterLocale') as
|
|
93
|
+
| {
|
|
94
|
+
readonly active: string | undefined
|
|
95
|
+
getOverride(dshActive: string, ns: string, key: string): string | undefined
|
|
96
|
+
isOverrideActive(dshActive: string): boolean
|
|
97
|
+
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
|
|
98
|
+
subscribe(listener: () => void): () => void
|
|
99
|
+
}
|
|
100
|
+
| undefined
|
|
101
|
+
attachBetterLocale(store)
|
|
102
|
+
if (store !== undefined) {
|
|
103
|
+
dispose = store.register(LOCALE_NS, {
|
|
104
|
+
ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
|
|
105
|
+
'zh-HK': zhHK, 'zh-TW': zhTW, 'zh-MO': zhMO,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Initial check (picks up the store if better-locale activated first).
|
|
110
|
+
sync()
|
|
111
|
+
// Re-check on every locale revision bump (better-locale bumps when it
|
|
112
|
+
// activates with a persisted override, and when the user switches).
|
|
113
|
+
const unsubscribe = ctx.locale.subscribe(sync)
|
|
114
|
+
return () => {
|
|
115
|
+
unsubscribe()
|
|
116
|
+
dispose?.()
|
|
117
|
+
attachBetterLocale(undefined)
|
|
118
|
+
}
|
|
119
|
+
}, 'dsh-coding-sidebar: better-locale lazy integration')
|
|
120
|
+
// One store instance per activation: production code creates it only here,
|
|
121
|
+
// then hands it to the mounted panel and closes over it in the slot
|
|
122
|
+
// registrations (the official createXXXStore() factory rule — no
|
|
123
|
+
// module-level singleton).
|
|
124
|
+
const sidebarStore = createSidebarStore()
|
|
125
|
+
// The sidebar registry service: external plugins register tab types and
|
|
126
|
+
// file previewers through `ctx.betterSidebar.registerTab/registerFileViewer`.
|
|
127
|
+
// Published before the panel mounts so consumers injecting 'betterSidebar'
|
|
128
|
+
// are ready by the time the sidebar renders.
|
|
129
|
+
const service = createBetterSidebarService(sidebarStore)
|
|
130
|
+
ctx.provide('betterSidebar', service)
|
|
131
|
+
// Terminal tab titles use the host's effective shell name (e.g. bash/zsh)
|
|
132
|
+
// instead of "Terminal 1". Start with a safe fallback and replace it as
|
|
133
|
+
// soon as the host shell info resolves. Tabs created before the response
|
|
134
|
+
// arrives keep the fallback title, so also retitle any already-open UI
|
|
135
|
+
// terminal tabs that still carry it.
|
|
136
|
+
const fallbackTitle = t('terminal')
|
|
137
|
+
let terminalTitle = fallbackTitle
|
|
138
|
+
void api.shellGet().then(({ name }) => {
|
|
139
|
+
terminalTitle = name
|
|
140
|
+
const snapshot = service.getSnapshot()
|
|
141
|
+
if (snapshot.state === undefined) return
|
|
142
|
+
const tabs = allLeaves(snapshot.state.splits).flatMap(leaf => leaf.tabs)
|
|
143
|
+
for (const tab of tabs) {
|
|
144
|
+
if (tab.type === 'terminal' && !isAgentTabId(tab.id) && tab.title === fallbackTitle) {
|
|
145
|
+
service.updateTab(tab.id, { title: name })
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}).catch(() => { /* keep fallback */ })
|
|
149
|
+
// Register the plugin's own built-in tabs and viewers through the same
|
|
150
|
+
// service (eating our own dogfood). The disposer unregisters them on
|
|
151
|
+
// fiber disposal (HMR-safe).
|
|
152
|
+
ctx.effect(
|
|
153
|
+
() => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
|
|
154
|
+
'dsh-coding-sidebar: register built-in tabs and viewers',
|
|
155
|
+
)
|
|
156
|
+
// A failure anywhere in the client lifecycle must never take the app down
|
|
157
|
+
// silently: log with the plugin prefix and pin a visible diagnostic strip
|
|
158
|
+
// to the page so a blank panel is never the only symptom.
|
|
159
|
+
const fail = (phase: string, error: unknown): void => {
|
|
160
|
+
console.error(`[dsh-coding-sidebar] ${phase} error:`, error)
|
|
161
|
+
try {
|
|
162
|
+
const bar = document.createElement('div')
|
|
163
|
+
bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
|
|
164
|
+
+ 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f2a1a1;background:#1b1b22;'
|
|
165
|
+
+ 'border:1px solid #f2a1a1;border-radius:8px;white-space:pre-wrap'
|
|
166
|
+
bar.textContent = `[dsh-coding-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
|
|
167
|
+
document.body.appendChild(bar)
|
|
168
|
+
} catch {
|
|
169
|
+
// Nothing left to report with.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
// rc.8+ exposes the client module system as the `ctx.modules` service
|
|
174
|
+
// (no window.__DSH_MODULES__ page global anymore); the chunk loader needs
|
|
175
|
+
// it to resolve its externals, so inject it before anything can load a
|
|
176
|
+
// lazy chunk. The loader falls back to the rc.7 global when absent.
|
|
177
|
+
setChunkModuleSystem(ctx.modules)
|
|
178
|
+
// Fresh chunk state for this activation: drop per-test fixtures and
|
|
179
|
+
// revalidate loaded chunk scripts against the bundle route's ETags —
|
|
180
|
+
// unchanged chunks keep their resolved exports (no re-inject /
|
|
181
|
+
// re-execute on HMR), changed ones are dropped for a clean re-fetch.
|
|
182
|
+
void revalidateChunksOnReactivate()
|
|
183
|
+
ctx.effect(() => {
|
|
184
|
+
let disposed = false
|
|
185
|
+
let root: Root | undefined
|
|
186
|
+
let host: HTMLDivElement | undefined
|
|
187
|
+
let mounted = false
|
|
188
|
+
let bodyObserver: MutationObserver | undefined
|
|
189
|
+
let hostCheckFrame: number | null = null
|
|
190
|
+
const unmount = (): void => {
|
|
191
|
+
if (!mounted) return
|
|
192
|
+
mounted = false
|
|
193
|
+
bodyObserver?.disconnect()
|
|
194
|
+
bodyObserver = undefined
|
|
195
|
+
if (hostCheckFrame !== null) {
|
|
196
|
+
cancelAnimationFrame(hostCheckFrame)
|
|
197
|
+
hostCheckFrame = null
|
|
198
|
+
}
|
|
199
|
+
root?.unmount()
|
|
200
|
+
root = undefined
|
|
201
|
+
host?.remove()
|
|
202
|
+
host = undefined
|
|
203
|
+
}
|
|
204
|
+
/** Re-attach the host if the page (a desktop shell wrapper, SPA
|
|
205
|
+
* navigation, …) ever removes it from <body>. Cheap: childList only,
|
|
206
|
+
* no subtree, no attribute filtering. */
|
|
207
|
+
const guardAnchor = (): void => {
|
|
208
|
+
if (bodyObserver !== undefined) return
|
|
209
|
+
bodyObserver = new MutationObserver(() => {
|
|
210
|
+
if (host !== undefined && !document.body.contains(host)) {
|
|
211
|
+
document.body.appendChild(host)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
bodyObserver.observe(document.body, { childList: true })
|
|
215
|
+
}
|
|
216
|
+
/** One-shot geometry self-check: if the host page transforms
|
|
217
|
+
* <html>/<body> itself (exotic shells), a fixed panel host would
|
|
218
|
+
* track the transformed box instead of the viewport. Flip the
|
|
219
|
+
* degraded mode and pin the host to the viewport every frame until
|
|
220
|
+
* the ancestor transform is actually gone. The normal path (no
|
|
221
|
+
* page-level transform) never runs the sync loop. */
|
|
222
|
+
const scheduleHostCheck = (): void => {
|
|
223
|
+
hostCheckFrame ??= requestAnimationFrame(() => {
|
|
224
|
+
hostCheckFrame = null
|
|
225
|
+
const layer = host?.querySelector<HTMLElement>('[data-dsh-panel-host]')
|
|
226
|
+
if (layer === null || layer === undefined) return
|
|
227
|
+
const rect = layer.getBoundingClientRect()
|
|
228
|
+
const mismatched = Math.abs(rect.left) > 8 || Math.abs(rect.top) > 8
|
|
229
|
+
|| Math.abs(rect.width - window.innerWidth) > 8 || Math.abs(rect.height - window.innerHeight) > 8
|
|
230
|
+
if (!mismatched) {
|
|
231
|
+
layer.removeAttribute('data-dsh-panel-host-degraded')
|
|
232
|
+
layer.style.transform = ''
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
layer.setAttribute('data-dsh-panel-host-degraded', '')
|
|
236
|
+
console.warn('[dsh-coding-sidebar] panel host geometry mismatch — a page-level transform was detected; using degraded viewport sync')
|
|
237
|
+
// Track our own compensating translation so the loop judges the
|
|
238
|
+
// UNCORRECTED geometry: clearing degraded mode must wait for the
|
|
239
|
+
// ancestor transform to actually disappear — the frame right after
|
|
240
|
+
// our correction applies would otherwise look "fixed" and the
|
|
241
|
+
// offset would return immediately (CR #232 P1).
|
|
242
|
+
let applied = { x: 0, y: 0 }
|
|
243
|
+
const sync = (): void => {
|
|
244
|
+
const r = layer.getBoundingClientRect()
|
|
245
|
+
const rawLeft = r.left - applied.x
|
|
246
|
+
const rawTop = r.top - applied.y
|
|
247
|
+
if (Math.abs(rawLeft) <= 1 && Math.abs(rawTop) <= 1
|
|
248
|
+
&& Math.abs(r.width - window.innerWidth) <= 1 && Math.abs(r.height - window.innerHeight) <= 1) {
|
|
249
|
+
layer.removeAttribute('data-dsh-panel-host-degraded')
|
|
250
|
+
layer.style.transform = ''
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
const next = { x: -rawLeft, y: -rawTop }
|
|
254
|
+
if (next.x !== applied.x || next.y !== applied.y) {
|
|
255
|
+
applied = next
|
|
256
|
+
layer.style.transform = `translate(${applied.x}px, ${applied.y}px)`
|
|
257
|
+
}
|
|
258
|
+
hostCheckFrame = requestAnimationFrame(sync)
|
|
259
|
+
}
|
|
260
|
+
hostCheckFrame = requestAnimationFrame(sync)
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
const mount = (): void => {
|
|
264
|
+
if (mounted || disposed) return
|
|
265
|
+
try {
|
|
266
|
+
host = document.createElement('div')
|
|
267
|
+
host.setAttribute('data-dsh-better-sidebar', '')
|
|
268
|
+
document.body.appendChild(host)
|
|
269
|
+
root = createRoot(host)
|
|
270
|
+
root.render(createElement(RenderBoundary, { className: css.boundaryError }, createElement(Sidebar, { ctx, store: sidebarStore })))
|
|
271
|
+
mounted = true
|
|
272
|
+
guardAnchor()
|
|
273
|
+
scheduleHostCheck()
|
|
274
|
+
} catch (error) {
|
|
275
|
+
fail('mount', error)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const sync = async (): Promise<void> => {
|
|
279
|
+
if (disposed) return
|
|
280
|
+
// Resolve the user's side card prefs BEFORE the first session seeds,
|
|
281
|
+
// so a brand-new conversation opens (or stays closed) at the chosen
|
|
282
|
+
// width from first paint. A settings route failure falls back to the
|
|
283
|
+
// schema defaults; the sidebar still mounts (a stalled wire gives up
|
|
284
|
+
// after the timeout and mounts on the defaults).
|
|
285
|
+
const prefs = await Promise.race([
|
|
286
|
+
loadPrefs(api),
|
|
287
|
+
new Promise<null>(resolve => { const timer = window.setTimeout(() => resolve(null), 2000) }),
|
|
288
|
+
])
|
|
289
|
+
if (prefs !== null) sidebarStore.setPrefs(prefs)
|
|
290
|
+
if (disposed) return
|
|
291
|
+
// Mutual exclusion with the dsh-web-ui family right panel: while the
|
|
292
|
+
// aionui-panel provider is selected, the sidebar must not mount at
|
|
293
|
+
// all. Re-evaluated on every settings-document update (live switch).
|
|
294
|
+
const suspended = await loadExternalDisable(api)
|
|
295
|
+
if (disposed) return
|
|
296
|
+
sidebarStore.setSuspended(suspended)
|
|
297
|
+
if (suspended) unmount()
|
|
298
|
+
else mount()
|
|
299
|
+
}
|
|
300
|
+
void sync()
|
|
301
|
+
// Live re-evaluation: the runtime broadcasts settings-document updates
|
|
302
|
+
// (the aionui card saves through the same document). Best effort —
|
|
303
|
+
// deployments without the 'remote' service fall back to boot-time
|
|
304
|
+
// evaluation only.
|
|
305
|
+
const remote = ctx.get('remote') as { $on?: (event: string, listener: () => void) => () => void } | undefined
|
|
306
|
+
const offRemote = remote?.$on?.('settings/document-updated', () => { void sync() })
|
|
307
|
+
return () => {
|
|
308
|
+
disposed = true
|
|
309
|
+
offRemote?.()
|
|
310
|
+
unmount()
|
|
311
|
+
}
|
|
312
|
+
}, 'dsh-coding-sidebar: sidebar mount')
|
|
313
|
+
|
|
314
|
+
ctx.effect(
|
|
315
|
+
() => {
|
|
316
|
+
try {
|
|
317
|
+
return registerTurnTailInterception(ctx, sidebarStore)
|
|
318
|
+
} catch (error) {
|
|
319
|
+
fail('interception', error)
|
|
320
|
+
return () => {}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
'dsh-coding-sidebar: turn-tail interception',
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
ctx.effect(
|
|
327
|
+
() => {
|
|
328
|
+
try {
|
|
329
|
+
return registerOpenPathInterception(ctx, sidebarStore)
|
|
330
|
+
} catch (error) {
|
|
331
|
+
fail('interception', error)
|
|
332
|
+
return () => {}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
'dsh-coding-sidebar: open-path interception',
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
ctx.effect(
|
|
339
|
+
() => {
|
|
340
|
+
try {
|
|
341
|
+
// External http(s) links in the chat/GUI open the sidebar instead
|
|
342
|
+
// of a new window. Gated on the browserInterceptLinks MASTER pref,
|
|
343
|
+
// the URL's protocol flag (browserInterceptHttp / Https — https
|
|
344
|
+
// defaults OFF: most https sites refuse iframe embedding), and the
|
|
345
|
+
// target tab's enable switch; Ctrl/Cmd+click always bypasses. The
|
|
346
|
+
// target is the first registered tab whose `urlTarget` claims the
|
|
347
|
+
// URL (enabled tabs only), else the built-in browser tab.
|
|
348
|
+
const urlTargetOf = (url: URL): string | undefined => {
|
|
349
|
+
const prefs = sidebarStore.getPrefs()
|
|
350
|
+
const enabled = service.getTabs().filter(tab => prefs.tabsEnabled[tab.id] !== false)
|
|
351
|
+
return matchUrlTarget(enabled, url)?.id
|
|
352
|
+
}
|
|
353
|
+
return registerLinkInterception({
|
|
354
|
+
takeoverEnabled: (url) => {
|
|
355
|
+
if (sidebarStore.getSuspended()) return false
|
|
356
|
+
const prefs = sidebarStore.getPrefs()
|
|
357
|
+
if (prefs.browserInterceptLinks === false) return false
|
|
358
|
+
const protocolOn = url.protocol === 'https:'
|
|
359
|
+
? prefs.browserInterceptHttps !== false
|
|
360
|
+
: prefs.browserInterceptHttp !== false
|
|
361
|
+
if (!protocolOn) return false
|
|
362
|
+
// A plugin claim is the target (already enabled-filtered);
|
|
363
|
+
// otherwise the built-in browser must be enabled.
|
|
364
|
+
return urlTargetOf(url) !== undefined || prefs.tabsEnabled['browser'] !== false
|
|
365
|
+
},
|
|
366
|
+
openInSidebar: (url) => {
|
|
367
|
+
let title: string | undefined
|
|
368
|
+
try { title = new URL(url).hostname } catch { /* keep the default title */ }
|
|
369
|
+
const type = urlTargetOf(new URL(url)) ?? 'browser'
|
|
370
|
+
ctx.get('betterSidebar')?.openTab({ type, url, title })
|
|
371
|
+
},
|
|
372
|
+
selfOrigin: window.location.origin,
|
|
373
|
+
})
|
|
374
|
+
} catch (error) {
|
|
375
|
+
fail('interception', error)
|
|
376
|
+
return () => {}
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
'dsh-coding-sidebar: link interception',
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
// The IME guard: composition keys (candidate arrows, confirm, cancel)
|
|
383
|
+
// belong to the input method, never to page JS. Inlined third-party UI
|
|
384
|
+
// (formerly Univer's office controls, #562 regression) has shipped
|
|
385
|
+
// unguarded keydown handlers that hijack ArrowUp/ArrowDown and break
|
|
386
|
+
// Chinese input; the document-capture guard neutralizes the whole class
|
|
387
|
+
// before React or any native listener sees the event. Registered as
|
|
388
|
+
// early as possible so no other capture-phase listener can win the
|
|
389
|
+
// ordering race.
|
|
390
|
+
ctx.effect(
|
|
391
|
+
() => {
|
|
392
|
+
try {
|
|
393
|
+
return registerImeGuard()
|
|
394
|
+
} catch (error) {
|
|
395
|
+
fail('ime guard', error)
|
|
396
|
+
return () => {}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
'dsh-coding-sidebar: IME composition guard',
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// DSH 0.1.x does not yet carry an icon through the settings.section
|
|
403
|
+
// registration contract: its shell renders a generic gear for every
|
|
404
|
+
// external section. Mark only this plugin's localized nav row so
|
|
405
|
+
// layout.css can paint the requested Side card SVG; the disposer clears
|
|
406
|
+
// the marker for HMR / plugin disable.
|
|
407
|
+
ctx.effect(
|
|
408
|
+
() => registerSettingsNavIcon(() => t('settingsNav')),
|
|
409
|
+
'dsh-coding-sidebar: settings navigation icon',
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
// The "Side card" settings section: appears in the DSH Settings shell
|
|
413
|
+
// once the shell's declaration is on the ledger (slots.inject waits for
|
|
414
|
+
// it); the section reads/writes the prefs through the plugin's own
|
|
415
|
+
// fenced settings route, keeps the shared store in sync, and renders the
|
|
416
|
+
// declarative enable/disable inventory from the tab/viewer registry.
|
|
417
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
418
|
+
name: 'settings.section',
|
|
419
|
+
id: 'dsh-coding-sidebar',
|
|
420
|
+
order: 100,
|
|
421
|
+
label: () => t('settingsNav'),
|
|
422
|
+
inject: () => ({ store: sidebarStore, service }),
|
|
423
|
+
}, SideCardSection))
|
|
424
|
+
} catch (error) {
|
|
425
|
+
fail('load', error)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interception of the chat's produced-files row: the turn-tail chain entry
|
|
3
|
+
* that replaces ui-deliverables' row when the closing turn produced files.
|
|
4
|
+
* The takeover looks identical (same chip row); the chips open the file in
|
|
5
|
+
* the sidebar instead of the host OS. Priority -1 runs before the default-0
|
|
6
|
+
* deliverables entry; when nothing was produced the selector returns null
|
|
7
|
+
* and the original row renders unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import { IconCodeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
10
|
+
import type { Context, SidebarRemoteService } from '../context-types.ts'
|
|
11
|
+
import { firstLeaf, revealPaths, togglePanel, type SidebarStore } from './state.ts'
|
|
12
|
+
import { t } from './locales.ts'
|
|
13
|
+
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
|
14
|
+
import { wrapOpenPath, wrapRemoteOpenPath } from './openpath-intercept.ts'
|
|
15
|
+
import css from './sidebar.module.css'
|
|
16
|
+
|
|
17
|
+
/** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
|
|
18
|
+
export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: string, path: string): void {
|
|
19
|
+
const summary = ctx.sessions.list.getSnapshot().byId[sessionId]
|
|
20
|
+
const absolute = resolveSidebarPath(summary?.cwd, path)
|
|
21
|
+
const at = Math.max(absolute.lastIndexOf('/'), absolute.lastIndexOf('\\'))
|
|
22
|
+
const title = at === -1 ? absolute : absolute.slice(at + 1)
|
|
23
|
+
// Route through the sidebar service so the editor descriptor's dedupeKey
|
|
24
|
+
// (per-path) applies; the id is path-derived so multiple editors coexist.
|
|
25
|
+
ctx.get('betterSidebar')?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The produced files the turn-tail selector last matched for the visible
|
|
30
|
+
* session. The "Show in folder" gesture carries no file path of its own
|
|
31
|
+
* (`'.'`), so the reveal highlights exactly these rows when available.
|
|
32
|
+
*/
|
|
33
|
+
let lastProduced: readonly string[] = []
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Reveal the produced files in the sidebar explorer: expand their parent
|
|
37
|
+
* directories, highlight the rows, and focus the explorer tab (expanding the
|
|
38
|
+
* hosting panel when it is collapsed). Unknown files fall back to revealing
|
|
39
|
+
* the workspace root itself.
|
|
40
|
+
*/
|
|
41
|
+
export function revealInExplorer(
|
|
42
|
+
ctx: Context,
|
|
43
|
+
store: SidebarStore,
|
|
44
|
+
sessionId: string,
|
|
45
|
+
files: readonly string[],
|
|
46
|
+
): void {
|
|
47
|
+
const summary = ctx.sessions.list.getSnapshot().byId[sessionId]
|
|
48
|
+
const cwd = summary?.cwd
|
|
49
|
+
// Deliverables report paths as-is (often relative to the session cwd), but
|
|
50
|
+
// the explorer tree and revealPaths work on absolute paths — resolve every
|
|
51
|
+
// target so the ancestors expand and the row actually matches.
|
|
52
|
+
const targets = files.length > 0
|
|
53
|
+
? files.map(path => resolveSidebarPath(cwd, path))
|
|
54
|
+
: cwd === undefined ? [] : [cwd]
|
|
55
|
+
store.reduce(state => revealPaths(state, cwd, targets))
|
|
56
|
+
// A type-only open never auto-expands the panel (only content opens do,
|
|
57
|
+
// see service.openTab) — so a reveal opens the panel itself when it is
|
|
58
|
+
// collapsed, exactly like the subagent auto-open flows, or the highlight
|
|
59
|
+
// would be set on an invisible panel.
|
|
60
|
+
store.reduce(s => (s.panelOpen ? s : togglePanel(s)))
|
|
61
|
+
// Pin the landing to the first pane: the files window must appear in the
|
|
62
|
+
// panel that just expanded, not wherever the user last touched.
|
|
63
|
+
store.reduce(s => ({ ...s, activePane: firstLeaf(s.splits).id }))
|
|
64
|
+
// Focus the single-instance editor home tab (the files window) where the
|
|
65
|
+
// reveal highlight renders. Read via ctx.get like every other internal
|
|
66
|
+
// consumer (#357): the provider is not on this fiber chain, so a direct
|
|
67
|
+
// ctx.betterSidebar read can throw before optional chaining applies.
|
|
68
|
+
ctx.get('betterSidebar')?.openTab({ type: 'editor', title: t('files') })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The intercepted produced-files row (visual twin of the deliverables chips). */
|
|
72
|
+
export function SidebarProducedFiles(props: {
|
|
73
|
+
matched: readonly string[]
|
|
74
|
+
openInSidebar: (path: string) => void
|
|
75
|
+
/** Reveal the produced files in the explorer ("Show in folder" twin). */
|
|
76
|
+
onShowInFolder: (files: readonly string[]) => void
|
|
77
|
+
}) {
|
|
78
|
+
const { matched, openInSidebar, onShowInFolder } = props
|
|
79
|
+
const shown = matched.slice(0, 6)
|
|
80
|
+
const hidden = matched.length - shown.length
|
|
81
|
+
return (
|
|
82
|
+
<div className={css.producedRow}>
|
|
83
|
+
<span className={css.producedLabel}>{t('produced')}</span>
|
|
84
|
+
{shown.map(path => {
|
|
85
|
+
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
86
|
+
const name = at === -1 ? path : path.slice(at + 1)
|
|
87
|
+
return (
|
|
88
|
+
<button
|
|
89
|
+
key={path}
|
|
90
|
+
type="button"
|
|
91
|
+
className={css.producedChip}
|
|
92
|
+
title={path}
|
|
93
|
+
onClick={() => { openInSidebar(path) }}
|
|
94
|
+
>
|
|
95
|
+
<IconCodeOutline16 size={12} />
|
|
96
|
+
<span>{name}</span>
|
|
97
|
+
</button>
|
|
98
|
+
)
|
|
99
|
+
})}
|
|
100
|
+
{hidden > 0 && <span className={css.producedMore}>+{hidden}</span>}
|
|
101
|
+
{hidden > 0 && (
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
className={css.producedMore}
|
|
105
|
+
style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: 2 }}
|
|
106
|
+
onClick={() => { onShowInFolder(matched) }}
|
|
107
|
+
>
|
|
108
|
+
{t('showInFolder')}
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Register the turn-tail interception (returns the disposer).
|
|
117
|
+
*
|
|
118
|
+
* The slot is a CHILD slot the host's ui-conversation declares in its
|
|
119
|
+
* `conversation.chat.node` children table (kind: chain, scope: session).
|
|
120
|
+
* Registering it directly races the declaration — the ui-slots core's
|
|
121
|
+
* load-time validation throws "not declared (a parent entry's children
|
|
122
|
+
* table must declare it)" when the parent entry is not on the ledger yet.
|
|
123
|
+
* slots.inject waits for the declaration: the callback runs synchronously
|
|
124
|
+
* when the slot is already declared, otherwise it runs inside the declaring
|
|
125
|
+
* register() call once the declaration commits; declaration collapse
|
|
126
|
+
* disposes the entry and a later declaration re-registers it. This mirrors
|
|
127
|
+
* @deepseek-ai/dsh-client-ui-deliverables' registration of the same slot.
|
|
128
|
+
*/
|
|
129
|
+
export function registerTurnTailInterception(ctx: Context, store: SidebarStore): () => void {
|
|
130
|
+
return ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({
|
|
131
|
+
name: 'conversation.chat.turnTail',
|
|
132
|
+
// Decline the takeover while the editor tab type is disabled in the side
|
|
133
|
+
// card settings: the produced-files row falls back to the default
|
|
134
|
+
// deliverables behavior instead of offering chips that cannot open. Also
|
|
135
|
+
// while the sidebar is externally disabled (aionui-panel chosen).
|
|
136
|
+
select: (owner) => {
|
|
137
|
+
if (store.getSuspended()) return null
|
|
138
|
+
if (store.getPrefs().tabsEnabled['editor'] === false) return null
|
|
139
|
+
const matched = selectProducedFiles(owner)
|
|
140
|
+
if (matched !== null) lastProduced = matched
|
|
141
|
+
return matched
|
|
142
|
+
},
|
|
143
|
+
priority: -1,
|
|
144
|
+
registrant: 'dsh-coding-sidebar',
|
|
145
|
+
inject: (sessionId: string) => ({
|
|
146
|
+
openInSidebar: (path: string) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
147
|
+
onShowInFolder: (files: readonly string[]) => { revealInExplorer(ctx, store, sessionId, files) },
|
|
148
|
+
}),
|
|
149
|
+
}, SidebarProducedFiles))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Register the chat file-open interception: wraps BOTH file-open doors so
|
|
154
|
+
* opens land in the sidebar editor instead of the Host OS — the folder-reveal
|
|
155
|
+
* gesture ("Show in folder" passes `'.'`) is the one exception, routed to the
|
|
156
|
+
* explorer. The doors: `ctx.workspaces.openPath` (the pre-0.1.2 funnel) and
|
|
157
|
+
* `ctx.remote.session.openWorkspacePath` (the 0.1.2-alpha.1 funnel, a direct
|
|
158
|
+
* RPC that left the old door dead); each is wrapped only when present, so one
|
|
159
|
+
* build intercepts baselines on either side of the migration. Gated by BOTH
|
|
160
|
+
* the `interceptOpenPath` pref and the editor tab's enable switch; declined
|
|
161
|
+
* opens fall through to the original method. Returns the disposer restoring
|
|
162
|
+
* both doors (HMR-safe).
|
|
163
|
+
*/
|
|
164
|
+
export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
|
|
165
|
+
const deps = {
|
|
166
|
+
takeoverEnabled: () => !store.getSuspended()
|
|
167
|
+
&& store.getPrefs().interceptOpenPath !== false
|
|
168
|
+
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
|
169
|
+
currentSessionId: () => ctx.sessions.list.getSnapshot().current,
|
|
170
|
+
openInSidebar: (path: string, sessionId: string) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
171
|
+
revealInExplorer: (_path: string, sessionId: string) => { revealInExplorer(ctx, store, sessionId, lastProduced) },
|
|
172
|
+
}
|
|
173
|
+
const disposeOld = wrapOpenPath(ctx.workspaces, deps)
|
|
174
|
+
// Optional probe: the Remote carrier is absent on pre-migration baselines
|
|
175
|
+
// (and the wrap itself no-ops when the method is missing). Read via
|
|
176
|
+
// ctx.get like every other optional service (same recipe as the
|
|
177
|
+
// settings/document-updated listener): a direct `ctx.remote` property
|
|
178
|
+
// read throws under cordis's inject enforcement ("cannot get property
|
|
179
|
+
// ... without inject") — ctx.get just returns undefined instead.
|
|
180
|
+
const remote = ctx.get('remote') as SidebarRemoteService | undefined
|
|
181
|
+
const disposeRemote = remote === undefined
|
|
182
|
+
? () => {}
|
|
183
|
+
: wrapRemoteOpenPath(remote.session, deps)
|
|
184
|
+
return () => {
|
|
185
|
+
disposeOld()
|
|
186
|
+
disposeRemote()
|
|
187
|
+
}
|
|
188
|
+
}
|