@keith9681/dsh-sidebar 0.15.2
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 +0 -0
- package/README_EN.md +0 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +34651 -0
- package/lib/client-mermaid.js +200629 -0
- package/lib/client-registry.js +13388 -0
- package/lib/client-terminal.js +8904 -0
- package/lib/client.js +13388 -0
- package/lib/index.js +3959 -0
- package/lib/invariant.js +22 -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 +22 -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 +12 -0
- package/lib/types/client/FileTree.d.ts +36 -0
- package/lib/types/client/GitView.d.ts +8 -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 +43 -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 +23 -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 +268 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +12 -0
- package/lib/types/client/browser.d.ts +50 -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 +124 -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/editable-state.d.ts +27 -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 +57 -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 +19 -0
- package/lib/types/client/intercept.d.ts +33 -0
- package/lib/types/client/lang.d.ts +12 -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.d.ts +366 -0
- package/lib/types/client/mermaid-blocks.d.ts +41 -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 +41 -0
- package/lib/types/client/paths.d.ts +35 -0
- package/lib/types/client/pdf-types.d.ts +2 -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 +23 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +440 -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 +30 -0
- package/lib/types/client/state.d.ts +359 -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 +33 -0
- package/lib/types/client/terminal-font.d.ts +20 -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 +603 -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 +82 -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/prefs-shared.d.ts +225 -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 +31 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +173 -0
- package/scripts/install.ps1 +289 -0
- package/scripts/install.sh +287 -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 +253 -0
- package/src/client/DiffTab.tsx +110 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +525 -0
- package/src/client/FileTree.tsx +653 -0
- package/src/client/GitView.tsx +558 -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 +1152 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +644 -0
- package/src/client/Sidebar.tsx +1148 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +305 -0
- package/src/client/TerminalView.tsx +383 -0
- package/src/client/TextEditor.tsx +500 -0
- package/src/client/TreePanel.tsx +273 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +335 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +51 -0
- package/src/client/browser.ts +119 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +331 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +351 -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/editable-state.ts +70 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +161 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +369 -0
- package/src/client/intercept.tsx +110 -0
- package/src/client/lang.ts +103 -0
- package/src/client/layout.css +94 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales.ts +734 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +401 -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 +62 -0
- package/src/client/paths.ts +47 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +87 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +207 -0
- package/src/client/produced-files.ts +91 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +838 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2577 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +322 -0
- package/src/client/state.ts +1175 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +46 -0
- package/src/client/terminal-font.ts +28 -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 +155 -0
- package/src/context-types.ts +610 -0
- package/src/fs-operations.ts +99 -0
- package/src/fs-search.ts +86 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +244 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1132 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/prefs-shared.ts +268 -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 +479 -0
- package/src/trust-fence.ts +77 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client half of dsh-better-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, t, zh, en } from './locales.ts'
|
|
28
|
+
import css from './sidebar.module.css'
|
|
29
|
+
import './layout.css'
|
|
30
|
+
|
|
31
|
+
/** Services required before mounting (provided by the client runtime; the
|
|
32
|
+
* locale service backs the sidebar's copy — see locales.ts). `modules`
|
|
33
|
+
* (rc.8+) is the client module system the chunk loader resolves its
|
|
34
|
+
* externals through — Cordis guards service access without inject. */
|
|
35
|
+
export const inject = ['slots', 'sessions', 'connection', 'workspaces', 'locale', 'modules']
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Error boundary over the sidebar tree (root scope): a render error in the
|
|
39
|
+
* sidebar SHELL itself must never blank the page silently — the shared
|
|
40
|
+
* RenderBoundary shows a dismissible error strip and logs the stack. The
|
|
41
|
+
* per-tab scope (Sidebar.tsx) catches viewer/editor crashes first; this root
|
|
42
|
+
* boundary stays as the last resort for Workbench/shell errors.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Client plugin body.
|
|
46
|
+
* @param ctx - the client cordis context (slots, sessions).
|
|
47
|
+
*/
|
|
48
|
+
export function apply(ctx: Context): void {
|
|
49
|
+
// The sidebar follows the DSH i18n system: attach the locale service so
|
|
50
|
+
// the module-level t()/isZh() resolve the Host-backed language preference
|
|
51
|
+
// (and switch live — the Sidebar root subscribes to it), and register the
|
|
52
|
+
// plugin's dictionaries into the shared locale registry. The disposers
|
|
53
|
+
// run on fiber disposal, so re-activation (HMR) re-registers cleanly.
|
|
54
|
+
attachLocale(ctx.locale)
|
|
55
|
+
ctx.effect(() => {
|
|
56
|
+
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
|
|
57
|
+
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
|
|
58
|
+
return () => { offZh(); offEn() }
|
|
59
|
+
}, 'dsh-better-sidebar: dictionaries')
|
|
60
|
+
// One store instance per activation: production code creates it only here,
|
|
61
|
+
// then hands it to the mounted panel and closes over it in the slot
|
|
62
|
+
// registrations (the official createXXXStore() factory rule — no
|
|
63
|
+
// module-level singleton).
|
|
64
|
+
const sidebarStore = createSidebarStore()
|
|
65
|
+
// The sidebar registry service: external plugins register tab types and
|
|
66
|
+
// file previewers through `ctx.betterSidebar.registerTab/registerFileViewer`.
|
|
67
|
+
// Published before the panel mounts so consumers injecting 'betterSidebar'
|
|
68
|
+
// are ready by the time the sidebar renders.
|
|
69
|
+
const service = createBetterSidebarService(sidebarStore)
|
|
70
|
+
ctx.provide('betterSidebar', service)
|
|
71
|
+
// Terminal tab titles use the host's effective shell name (e.g. bash/zsh)
|
|
72
|
+
// instead of "Terminal 1". Start with a safe fallback and replace it as
|
|
73
|
+
// soon as the host shell info resolves. Tabs created before the response
|
|
74
|
+
// arrives keep the fallback title, so also retitle any already-open UI
|
|
75
|
+
// terminal tabs that still carry it.
|
|
76
|
+
const fallbackTitle = t('terminal')
|
|
77
|
+
let terminalTitle = fallbackTitle
|
|
78
|
+
void api.shellGet().then(({ name }) => {
|
|
79
|
+
terminalTitle = name
|
|
80
|
+
const snapshot = service.getSnapshot()
|
|
81
|
+
if (snapshot.state === undefined) return
|
|
82
|
+
const tabs = allLeaves(snapshot.state.splits)
|
|
83
|
+
.concat(allLeaves(snapshot.state.bottomSplits))
|
|
84
|
+
.flatMap(leaf => leaf.tabs)
|
|
85
|
+
for (const tab of tabs) {
|
|
86
|
+
if (tab.type === 'terminal' && !isAgentTabId(tab.id) && tab.title === fallbackTitle) {
|
|
87
|
+
service.updateTab(tab.id, { title: name })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}).catch(() => { /* keep fallback */ })
|
|
91
|
+
// Register the plugin's own built-in tabs and viewers through the same
|
|
92
|
+
// service (eating our own dogfood). The disposer unregisters them on
|
|
93
|
+
// fiber disposal (HMR-safe).
|
|
94
|
+
ctx.effect(
|
|
95
|
+
() => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
|
|
96
|
+
'dsh-better-sidebar: register built-in tabs and viewers',
|
|
97
|
+
)
|
|
98
|
+
// A failure anywhere in the client lifecycle must never take the app down
|
|
99
|
+
// silently: log with the plugin prefix and pin a visible diagnostic strip
|
|
100
|
+
// to the page so a blank panel is never the only symptom.
|
|
101
|
+
const fail = (phase: string, error: unknown): void => {
|
|
102
|
+
console.error(`[dsh-better-sidebar] ${phase} error:`, error)
|
|
103
|
+
try {
|
|
104
|
+
const bar = document.createElement('div')
|
|
105
|
+
bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
|
|
106
|
+
+ 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f2a1a1;background:#1b1b22;'
|
|
107
|
+
+ 'border:1px solid #f2a1a1;border-radius:8px;white-space:pre-wrap'
|
|
108
|
+
bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
|
|
109
|
+
document.body.appendChild(bar)
|
|
110
|
+
} catch {
|
|
111
|
+
// Nothing left to report with.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
// rc.8+ exposes the client module system as the `ctx.modules` service
|
|
116
|
+
// (no window.__DSH_MODULES__ page global anymore); the chunk loader needs
|
|
117
|
+
// it to resolve its externals, so inject it before anything can load a
|
|
118
|
+
// lazy chunk. The loader falls back to the rc.7 global when absent.
|
|
119
|
+
setChunkModuleSystem(ctx.modules)
|
|
120
|
+
// Fresh chunk state for this activation: drop per-test fixtures and
|
|
121
|
+
// revalidate loaded chunk scripts against the bundle route's ETags —
|
|
122
|
+
// unchanged chunks keep their resolved exports (no re-inject /
|
|
123
|
+
// re-execute on HMR), changed ones are dropped for a clean re-fetch.
|
|
124
|
+
void revalidateChunksOnReactivate()
|
|
125
|
+
ctx.effect(() => {
|
|
126
|
+
let disposed = false
|
|
127
|
+
let root: Root | undefined
|
|
128
|
+
let host: HTMLDivElement | undefined
|
|
129
|
+
let mounted = false
|
|
130
|
+
let bodyObserver: MutationObserver | undefined
|
|
131
|
+
let hostCheckFrame: number | null = null
|
|
132
|
+
const unmount = (): void => {
|
|
133
|
+
if (!mounted) return
|
|
134
|
+
mounted = false
|
|
135
|
+
bodyObserver?.disconnect()
|
|
136
|
+
bodyObserver = undefined
|
|
137
|
+
if (hostCheckFrame !== null) {
|
|
138
|
+
cancelAnimationFrame(hostCheckFrame)
|
|
139
|
+
hostCheckFrame = null
|
|
140
|
+
}
|
|
141
|
+
root?.unmount()
|
|
142
|
+
root = undefined
|
|
143
|
+
host?.remove()
|
|
144
|
+
host = undefined
|
|
145
|
+
}
|
|
146
|
+
/** Re-attach the host if the page (a desktop shell wrapper, SPA
|
|
147
|
+
* navigation, …) ever removes it from <body>. Cheap: childList only,
|
|
148
|
+
* no subtree, no attribute filtering. */
|
|
149
|
+
const guardAnchor = (): void => {
|
|
150
|
+
if (bodyObserver !== undefined) return
|
|
151
|
+
bodyObserver = new MutationObserver(() => {
|
|
152
|
+
if (host !== undefined && !document.body.contains(host)) {
|
|
153
|
+
document.body.appendChild(host)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
bodyObserver.observe(document.body, { childList: true })
|
|
157
|
+
}
|
|
158
|
+
/** One-shot geometry self-check: if the host page transforms
|
|
159
|
+
* <html>/<body> itself (exotic shells), a fixed panel host would
|
|
160
|
+
* track the transformed box instead of the viewport. Flip the
|
|
161
|
+
* degraded mode and pin the host to the viewport every frame until
|
|
162
|
+
* the ancestor transform is actually gone. The normal path (no
|
|
163
|
+
* page-level transform) never runs the sync loop. */
|
|
164
|
+
const scheduleHostCheck = (): void => {
|
|
165
|
+
hostCheckFrame ??= requestAnimationFrame(() => {
|
|
166
|
+
hostCheckFrame = null
|
|
167
|
+
const layer = host?.querySelector<HTMLElement>('[data-dsh-panel-host]')
|
|
168
|
+
if (layer === null || layer === undefined) return
|
|
169
|
+
const rect = layer.getBoundingClientRect()
|
|
170
|
+
const mismatched = Math.abs(rect.left) > 8 || Math.abs(rect.top) > 8
|
|
171
|
+
|| Math.abs(rect.width - window.innerWidth) > 8 || Math.abs(rect.height - window.innerHeight) > 8
|
|
172
|
+
if (!mismatched) {
|
|
173
|
+
layer.removeAttribute('data-dsh-panel-host-degraded')
|
|
174
|
+
layer.style.transform = ''
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
layer.setAttribute('data-dsh-panel-host-degraded', '')
|
|
178
|
+
console.warn('[dsh-better-sidebar] panel host geometry mismatch — a page-level transform was detected; using degraded viewport sync')
|
|
179
|
+
// Track our own compensating translation so the loop judges the
|
|
180
|
+
// UNCORRECTED geometry: clearing degraded mode must wait for the
|
|
181
|
+
// ancestor transform to actually disappear — the frame right after
|
|
182
|
+
// our correction applies would otherwise look "fixed" and the
|
|
183
|
+
// offset would return immediately (CR #232 P1).
|
|
184
|
+
let applied = { x: 0, y: 0 }
|
|
185
|
+
const sync = (): void => {
|
|
186
|
+
const r = layer.getBoundingClientRect()
|
|
187
|
+
const rawLeft = r.left - applied.x
|
|
188
|
+
const rawTop = r.top - applied.y
|
|
189
|
+
if (Math.abs(rawLeft) <= 1 && Math.abs(rawTop) <= 1
|
|
190
|
+
&& Math.abs(r.width - window.innerWidth) <= 1 && Math.abs(r.height - window.innerHeight) <= 1) {
|
|
191
|
+
layer.removeAttribute('data-dsh-panel-host-degraded')
|
|
192
|
+
layer.style.transform = ''
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
const next = { x: -rawLeft, y: -rawTop }
|
|
196
|
+
if (next.x !== applied.x || next.y !== applied.y) {
|
|
197
|
+
applied = next
|
|
198
|
+
layer.style.transform = `translate(${applied.x}px, ${applied.y}px)`
|
|
199
|
+
}
|
|
200
|
+
hostCheckFrame = requestAnimationFrame(sync)
|
|
201
|
+
}
|
|
202
|
+
hostCheckFrame = requestAnimationFrame(sync)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
const mount = (): void => {
|
|
206
|
+
if (mounted || disposed) return
|
|
207
|
+
try {
|
|
208
|
+
host = document.createElement('div')
|
|
209
|
+
host.setAttribute('data-dsh-better-sidebar', '')
|
|
210
|
+
document.body.appendChild(host)
|
|
211
|
+
root = createRoot(host)
|
|
212
|
+
root.render(createElement(RenderBoundary, { className: css.boundaryError }, createElement(Sidebar, { ctx, store: sidebarStore })))
|
|
213
|
+
mounted = true
|
|
214
|
+
guardAnchor()
|
|
215
|
+
scheduleHostCheck()
|
|
216
|
+
} catch (error) {
|
|
217
|
+
fail('mount', error)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const sync = async (): Promise<void> => {
|
|
221
|
+
if (disposed) return
|
|
222
|
+
// Resolve the user's side card prefs BEFORE the first session seeds,
|
|
223
|
+
// so a brand-new conversation opens (or stays closed) at the chosen
|
|
224
|
+
// width from first paint. A settings route failure falls back to the
|
|
225
|
+
// schema defaults; the sidebar still mounts (a stalled wire gives up
|
|
226
|
+
// after the timeout and mounts on the defaults).
|
|
227
|
+
const prefs = await Promise.race([
|
|
228
|
+
loadPrefs(api),
|
|
229
|
+
new Promise<null>(resolve => { const timer = window.setTimeout(() => resolve(null), 2000) }),
|
|
230
|
+
])
|
|
231
|
+
if (prefs !== null) sidebarStore.setPrefs(prefs)
|
|
232
|
+
if (disposed) return
|
|
233
|
+
// Mutual exclusion with the dsh-web-ui family right panel: while the
|
|
234
|
+
// aionui-panel provider is selected, the sidebar must not mount at
|
|
235
|
+
// all. Re-evaluated on every settings-document update (live switch).
|
|
236
|
+
const suspended = await loadExternalDisable(api)
|
|
237
|
+
if (disposed) return
|
|
238
|
+
sidebarStore.setSuspended(suspended)
|
|
239
|
+
if (suspended) unmount()
|
|
240
|
+
else mount()
|
|
241
|
+
}
|
|
242
|
+
void sync()
|
|
243
|
+
// Live re-evaluation: the runtime broadcasts settings-document updates
|
|
244
|
+
// (the aionui card saves through the same document). Best effort —
|
|
245
|
+
// deployments without the 'remote' service fall back to boot-time
|
|
246
|
+
// evaluation only.
|
|
247
|
+
const remote = ctx.get('remote') as { $on?: (event: string, listener: () => void) => () => void } | undefined
|
|
248
|
+
const offRemote = remote?.$on?.('settings/document-updated', () => { void sync() })
|
|
249
|
+
return () => {
|
|
250
|
+
disposed = true
|
|
251
|
+
offRemote?.()
|
|
252
|
+
unmount()
|
|
253
|
+
}
|
|
254
|
+
}, 'dsh-better-sidebar: sidebar mount')
|
|
255
|
+
|
|
256
|
+
ctx.effect(
|
|
257
|
+
() => {
|
|
258
|
+
try {
|
|
259
|
+
return registerTurnTailInterception(ctx, sidebarStore)
|
|
260
|
+
} catch (error) {
|
|
261
|
+
fail('interception', error)
|
|
262
|
+
return undefined
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
'dsh-better-sidebar: turn-tail interception',
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
ctx.effect(
|
|
269
|
+
() => {
|
|
270
|
+
try {
|
|
271
|
+
return registerOpenPathInterception(ctx, sidebarStore)
|
|
272
|
+
} catch (error) {
|
|
273
|
+
fail('interception', error)
|
|
274
|
+
return undefined
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
'dsh-better-sidebar: open-path interception',
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
ctx.effect(
|
|
281
|
+
() => {
|
|
282
|
+
try {
|
|
283
|
+
// External http(s) links in the chat/GUI open the sidebar instead
|
|
284
|
+
// of a new window. Gated on the browserInterceptLinks MASTER pref,
|
|
285
|
+
// the URL's protocol flag (browserInterceptHttp / Https — https
|
|
286
|
+
// defaults OFF: most https sites refuse iframe embedding), and the
|
|
287
|
+
// target tab's enable switch; Ctrl/Cmd+click always bypasses. The
|
|
288
|
+
// target is the first registered tab whose `urlTarget` claims the
|
|
289
|
+
// URL (enabled tabs only), else the built-in browser tab.
|
|
290
|
+
const urlTargetOf = (url: URL): string | undefined => {
|
|
291
|
+
const prefs = sidebarStore.getPrefs()
|
|
292
|
+
const enabled = service.getTabs().filter(tab => prefs.tabsEnabled[tab.id] !== false)
|
|
293
|
+
return matchUrlTarget(enabled, url)?.id
|
|
294
|
+
}
|
|
295
|
+
return registerLinkInterception({
|
|
296
|
+
takeoverEnabled: (url) => {
|
|
297
|
+
if (sidebarStore.getSuspended()) return false
|
|
298
|
+
const prefs = sidebarStore.getPrefs()
|
|
299
|
+
if (prefs.browserInterceptLinks === false) return false
|
|
300
|
+
const protocolOn = url.protocol === 'https:'
|
|
301
|
+
? prefs.browserInterceptHttps !== false
|
|
302
|
+
: prefs.browserInterceptHttp !== false
|
|
303
|
+
if (!protocolOn) return false
|
|
304
|
+
// A plugin claim is the target (already enabled-filtered);
|
|
305
|
+
// otherwise the built-in browser must be enabled.
|
|
306
|
+
return urlTargetOf(url) !== undefined || prefs.tabsEnabled['browser'] !== false
|
|
307
|
+
},
|
|
308
|
+
openInSidebar: (url) => {
|
|
309
|
+
let title: string | undefined
|
|
310
|
+
try { title = new URL(url).hostname } catch { /* keep the default title */ }
|
|
311
|
+
const type = urlTargetOf(new URL(url)) ?? 'browser'
|
|
312
|
+
ctx.betterSidebar?.openTab({ type, url, title })
|
|
313
|
+
},
|
|
314
|
+
selfOrigin: window.location.origin,
|
|
315
|
+
})
|
|
316
|
+
} catch (error) {
|
|
317
|
+
fail('interception', error)
|
|
318
|
+
return undefined
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
'dsh-better-sidebar: link interception',
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// The IME guard: composition keys (candidate arrows, confirm, cancel)
|
|
325
|
+
// belong to the input method, never to page JS. Inlined third-party UI
|
|
326
|
+
// (formerly Univer's office controls, #562 regression) has shipped
|
|
327
|
+
// unguarded keydown handlers that hijack ArrowUp/ArrowDown and break
|
|
328
|
+
// Chinese input; the document-capture guard neutralizes the whole class
|
|
329
|
+
// before React or any native listener sees the event. Registered as
|
|
330
|
+
// early as possible so no other capture-phase listener can win the
|
|
331
|
+
// ordering race.
|
|
332
|
+
ctx.effect(
|
|
333
|
+
() => {
|
|
334
|
+
try {
|
|
335
|
+
return registerImeGuard()
|
|
336
|
+
} catch (error) {
|
|
337
|
+
fail('ime guard', error)
|
|
338
|
+
return undefined
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
'dsh-better-sidebar: IME composition guard',
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
// DSH 0.1.x does not yet carry an icon through the settings.section
|
|
345
|
+
// registration contract: its shell renders a generic gear for every
|
|
346
|
+
// external section. Mark only this plugin's localized nav row so
|
|
347
|
+
// layout.css can paint the requested Side card SVG; the disposer clears
|
|
348
|
+
// the marker for HMR / plugin disable.
|
|
349
|
+
ctx.effect(
|
|
350
|
+
() => registerSettingsNavIcon(() => t('settingsNav')),
|
|
351
|
+
'dsh-better-sidebar: settings navigation icon',
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
// The "Side card" settings section: appears in the DSH Settings shell
|
|
355
|
+
// once the shell's declaration is on the ledger (slots.inject waits for
|
|
356
|
+
// it); the section reads/writes the prefs through the plugin's own
|
|
357
|
+
// fenced settings route, keeps the shared store in sync, and renders the
|
|
358
|
+
// declarative enable/disable inventory from the tab/viewer registry.
|
|
359
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
360
|
+
name: 'settings.section',
|
|
361
|
+
id: 'better-sidebar',
|
|
362
|
+
order: 100,
|
|
363
|
+
label: () => t('settingsNav'),
|
|
364
|
+
inject: () => ({ store: sidebarStore, service }),
|
|
365
|
+
}, SideCardSection))
|
|
366
|
+
} catch (error) {
|
|
367
|
+
fail('load', error)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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 } from '../context-types.ts'
|
|
11
|
+
import type { SidebarStore } from './state.ts'
|
|
12
|
+
import { t } from './locales.ts'
|
|
13
|
+
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
|
14
|
+
import { wrapOpenPath } 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.betterSidebar?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The intercepted produced-files row (visual twin of the deliverables chips). */
|
|
29
|
+
export function SidebarProducedFiles(props: {
|
|
30
|
+
matched: readonly string[]
|
|
31
|
+
openInSidebar: (path: string) => void
|
|
32
|
+
}) {
|
|
33
|
+
const { matched, openInSidebar } = props
|
|
34
|
+
const shown = matched.slice(0, 6)
|
|
35
|
+
const hidden = matched.length - shown.length
|
|
36
|
+
return (
|
|
37
|
+
<div className={css.producedRow}>
|
|
38
|
+
<span className={css.producedLabel}>{t('produced')}</span>
|
|
39
|
+
{shown.map(path => {
|
|
40
|
+
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
41
|
+
const name = at === -1 ? path : path.slice(at + 1)
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
key={path}
|
|
45
|
+
type="button"
|
|
46
|
+
className={css.producedChip}
|
|
47
|
+
title={path}
|
|
48
|
+
onClick={() => { openInSidebar(path) }}
|
|
49
|
+
>
|
|
50
|
+
<IconCodeOutline16 size={12} />
|
|
51
|
+
<span>{name}</span>
|
|
52
|
+
</button>
|
|
53
|
+
)
|
|
54
|
+
})}
|
|
55
|
+
{hidden > 0 && <span className={css.producedMore}>+{hidden}</span>}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Register the turn-tail interception (returns the disposer).
|
|
62
|
+
*
|
|
63
|
+
* The slot is a CHILD slot the host's ui-conversation declares in its
|
|
64
|
+
* `conversation.chat.node` children table (kind: chain, scope: session).
|
|
65
|
+
* Registering it directly races the declaration — the ui-slots core's
|
|
66
|
+
* load-time validation throws "not declared (a parent entry's children
|
|
67
|
+
* table must declare it)" when the parent entry is not on the ledger yet.
|
|
68
|
+
* slots.inject waits for the declaration: the callback runs synchronously
|
|
69
|
+
* when the slot is already declared, otherwise it runs inside the declaring
|
|
70
|
+
* register() call once the declaration commits; declaration collapse
|
|
71
|
+
* disposes the entry and a later declaration re-registers it. This mirrors
|
|
72
|
+
* @deepseek-ai/dsh-client-ui-deliverables' registration of the same slot.
|
|
73
|
+
*/
|
|
74
|
+
export function registerTurnTailInterception(ctx: Context, store: SidebarStore): () => void {
|
|
75
|
+
return ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({
|
|
76
|
+
name: 'conversation.chat.turnTail',
|
|
77
|
+
// Decline the takeover while the editor tab type is disabled in the side
|
|
78
|
+
// card settings: the produced-files row falls back to the default
|
|
79
|
+
// deliverables behavior instead of offering chips that cannot open. Also
|
|
80
|
+
// while the sidebar is externally disabled (aionui-panel chosen).
|
|
81
|
+
select: (owner) => {
|
|
82
|
+
if (store.getSuspended()) return null
|
|
83
|
+
if (store.getPrefs().tabsEnabled['editor'] === false) return null
|
|
84
|
+
return selectProducedFiles(owner)
|
|
85
|
+
},
|
|
86
|
+
priority: -1,
|
|
87
|
+
registrant: 'dsh-better-sidebar',
|
|
88
|
+
inject: (sessionId: string) => ({
|
|
89
|
+
openInSidebar: (path: string) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
90
|
+
}),
|
|
91
|
+
}, SidebarProducedFiles))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Register the chat file-open interception: wraps `ctx.workspaces.openPath`
|
|
96
|
+
* — the single funnel every chat-side file open goes through (tool-row path
|
|
97
|
+
* links, the produced-files row, prose mentions) — so opens land in the
|
|
98
|
+
* sidebar editor instead of the Host OS. Gated by BOTH the `interceptOpenPath`
|
|
99
|
+
* pref and the editor tab's enable switch; declined opens fall through to
|
|
100
|
+
* the original method. Returns the disposer restoring the original (HMR-safe).
|
|
101
|
+
*/
|
|
102
|
+
export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
|
|
103
|
+
return wrapOpenPath(ctx.workspaces, {
|
|
104
|
+
takeoverEnabled: () => !store.getSuspended()
|
|
105
|
+
&& store.getPrefs().interceptOpenPath !== false
|
|
106
|
+
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
|
107
|
+
currentSessionId: () => ctx.sessions.list.getSnapshot().current,
|
|
108
|
+
openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
109
|
+
})
|
|
110
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax highlighting for the file editor: extension → CodeMirror language
|
|
3
|
+
* mapping. The key derivation is pure and unit-tested; the factories pull in
|
|
4
|
+
* the CodeMirror language packages (bundled into the client).
|
|
5
|
+
*/
|
|
6
|
+
import { Language, LanguageSupport, StreamLanguage } from '@codemirror/language'
|
|
7
|
+
import { javascript } from '@codemirror/lang-javascript'
|
|
8
|
+
import { json } from '@codemirror/lang-json'
|
|
9
|
+
import { markdown } from '@codemirror/lang-markdown'
|
|
10
|
+
import { python } from '@codemirror/lang-python'
|
|
11
|
+
import { html } from '@codemirror/lang-html'
|
|
12
|
+
import { css } from '@codemirror/lang-css'
|
|
13
|
+
import { xml } from '@codemirror/lang-xml'
|
|
14
|
+
import { yaml } from '@codemirror/lang-yaml'
|
|
15
|
+
import { sql } from '@codemirror/lang-sql'
|
|
16
|
+
import { java } from '@codemirror/lang-java'
|
|
17
|
+
import { cpp } from '@codemirror/lang-cpp'
|
|
18
|
+
import { rust } from '@codemirror/lang-rust'
|
|
19
|
+
import { go } from '@codemirror/lang-go'
|
|
20
|
+
import { php } from '@codemirror/lang-php'
|
|
21
|
+
import { shell } from '@codemirror/legacy-modes/mode/shell'
|
|
22
|
+
import { toml } from '@codemirror/legacy-modes/mode/toml'
|
|
23
|
+
import { nginx } from '@codemirror/legacy-modes/mode/nginx'
|
|
24
|
+
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
|
|
25
|
+
import { properties } from '@codemirror/legacy-modes/mode/properties'
|
|
26
|
+
import { csharp, kotlin } from '@codemirror/legacy-modes/mode/clike'
|
|
27
|
+
import { swift } from '@codemirror/legacy-modes/mode/swift'
|
|
28
|
+
|
|
29
|
+
/** The lowercased file extension of a path ('' when none). */
|
|
30
|
+
export function extOf(path: string): string {
|
|
31
|
+
const at = path.lastIndexOf('.')
|
|
32
|
+
if (at === -1) return ''
|
|
33
|
+
const base = path.slice(at + 1).toLowerCase()
|
|
34
|
+
return base.includes('/') || base.includes('\\') ? '' : base
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Language key for an extension, or null for plain text. Pure (tested). */
|
|
38
|
+
export function languageKeyForExt(ext: string): string | null {
|
|
39
|
+
switch (ext) {
|
|
40
|
+
case 'js': case 'mjs': case 'cjs': return 'js'
|
|
41
|
+
case 'jsx': return 'jsx'
|
|
42
|
+
case 'ts': case 'mts': case 'cts': return 'ts'
|
|
43
|
+
case 'tsx': return 'tsx'
|
|
44
|
+
case 'json': case 'jsonc': return 'json'
|
|
45
|
+
case 'md': case 'markdown': return 'md'
|
|
46
|
+
case 'py': case 'pyw': return 'python'
|
|
47
|
+
case 'html': case 'htm': return 'html'
|
|
48
|
+
case 'css': return 'css'
|
|
49
|
+
case 'xml': case 'xsl': return 'xml'
|
|
50
|
+
case 'yaml': case 'yml': return 'yaml'
|
|
51
|
+
case 'sql': return 'sql'
|
|
52
|
+
case 'java': return 'java'
|
|
53
|
+
case 'cs': return 'csharp'
|
|
54
|
+
case 'kt': case 'kts': return 'kotlin'
|
|
55
|
+
case 'swift': return 'swift'
|
|
56
|
+
case 'c': case 'h': return 'c'
|
|
57
|
+
case 'cc': case 'cpp': case 'cxx': case 'hpp': case 'hh': case 'hxx': return 'cpp'
|
|
58
|
+
case 'rs': return 'rust'
|
|
59
|
+
case 'go': return 'go'
|
|
60
|
+
case 'php': return 'php'
|
|
61
|
+
case 'sh': case 'bash': case 'zsh': return 'shell'
|
|
62
|
+
case 'toml': return 'toml'
|
|
63
|
+
case 'nginx': case 'conf': return 'nginx'
|
|
64
|
+
case 'dockerfile': case 'docker': return 'dockerfile'
|
|
65
|
+
case 'properties': case 'env': return 'properties'
|
|
66
|
+
default: return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const FACTORIES: Record<string, () => Language | LanguageSupport> = {
|
|
71
|
+
js: () => javascript({ jsx: true }),
|
|
72
|
+
jsx: () => javascript({ jsx: true }),
|
|
73
|
+
ts: () => javascript({ typescript: true }),
|
|
74
|
+
tsx: () => javascript({ typescript: true, jsx: true }),
|
|
75
|
+
json: () => json(),
|
|
76
|
+
md: () => markdown(),
|
|
77
|
+
python: () => python(),
|
|
78
|
+
html: () => html(),
|
|
79
|
+
css: () => css(),
|
|
80
|
+
xml: () => xml(),
|
|
81
|
+
yaml: () => yaml(),
|
|
82
|
+
sql: () => sql(),
|
|
83
|
+
java: () => java(),
|
|
84
|
+
csharp: () => StreamLanguage.define(csharp),
|
|
85
|
+
kotlin: () => StreamLanguage.define(kotlin),
|
|
86
|
+
swift: () => StreamLanguage.define(swift),
|
|
87
|
+
c: () => cpp(),
|
|
88
|
+
cpp: () => cpp(),
|
|
89
|
+
rust: () => rust(),
|
|
90
|
+
go: () => go(),
|
|
91
|
+
php: () => php(),
|
|
92
|
+
shell: () => StreamLanguage.define(shell),
|
|
93
|
+
toml: () => StreamLanguage.define(toml),
|
|
94
|
+
nginx: () => StreamLanguage.define(nginx),
|
|
95
|
+
dockerfile: () => StreamLanguage.define(dockerFile),
|
|
96
|
+
properties: () => StreamLanguage.define(properties),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The CodeMirror language support for a path, or null for plain text. */
|
|
100
|
+
export function languageForPath(path: string): Language | LanguageSupport | null {
|
|
101
|
+
const key = languageKeyForExt(extOf(path))
|
|
102
|
+
return key === null ? null : FACTORIES[key]!()
|
|
103
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout push: when a panel is open it OCCUPIES the layout instead of
|
|
3
|
+
* floating over it — the app shell (#root, the AppFrame three-column grid)
|
|
4
|
+
* gives up space. Only the center column is flexible (1fr), so the right
|
|
5
|
+
* panel's width squeeze (margin-right on #root) lands exactly on the
|
|
6
|
+
* conversation output and the input bar, like a VSCode sidebar.
|
|
7
|
+
*
|
|
8
|
+
* The width rides `calc(100% - var(...))` instead of a bare margin on a
|
|
9
|
+
* full-width box: some desktop shells (DSH Desktop, #208) set #root to
|
|
10
|
+
* width:100%, where a margin would overflow the viewport additively —
|
|
11
|
+
* the calc keeps the box at exactly 100% minus the push in every shell.
|
|
12
|
+
* Width and margin transition in lockstep (same variable, same duration
|
|
13
|
+
* and easing), so expand/collapse animates the content width exactly as
|
|
14
|
+
* the bare-margin version did.
|
|
15
|
+
*
|
|
16
|
+
* The bottom panel squeezes ONLY the center column — it must not cover the
|
|
17
|
+
* app's own left sidebar or the right panel. The anchor is the frame's
|
|
18
|
+
* center grid item via its stable data attribute ([data-pane="conversation"]
|
|
19
|
+
* on the centerCol inside [data-dsh-frame]), not a positional path: the
|
|
20
|
+
* frame's child order also contains an overlay layer and drag handles
|
|
21
|
+
* (verified against a live DSH 0.1.x page), so nth-child is brittle. A
|
|
22
|
+
* stretched grid item shrinks by its margins, so the conversation content
|
|
23
|
+
* (output + input bar) lifts without touching the sidebars.
|
|
24
|
+
*
|
|
25
|
+
* The sizes ride CSS variables updated by the Sidebar shell (0 while
|
|
26
|
+
* collapsed); expand/collapse animates both the margins and the panel
|
|
27
|
+
* slides on the same theme duration. Drags disable the transition so the
|
|
28
|
+
* layout tracks the pointer.
|
|
29
|
+
*/
|
|
30
|
+
#root {
|
|
31
|
+
margin-right: var(--dsh-sidebar-width, 0px);
|
|
32
|
+
width: calc(100% - var(--dsh-sidebar-width, 0px));
|
|
33
|
+
transition:
|
|
34
|
+
margin-right var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
35
|
+
width var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The AppFrame's center column, anchored by data attributes (see above).
|
|
39
|
+
Composite selector: DSH 0.1.x versions name the center grid item
|
|
40
|
+
`[data-pane="conversation"]`, while rc.8-era shells put a
|
|
41
|
+
`[data-slot="conversation"]` child inside it — both selectors resolve to
|
|
42
|
+
the SAME element on live pages (verified), and keeping both future-proofs
|
|
43
|
+
the rule against a host rename without touching shell-specific markup. */
|
|
44
|
+
#root [data-dsh-frame] > [data-pane="conversation"],
|
|
45
|
+
#root :has(> [data-slot="conversation"]) {
|
|
46
|
+
margin-bottom: var(--dsh-sidebar-height, 0px);
|
|
47
|
+
transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* When the sidebar is collapsed, the toggle cluster reclaims the top-right
|
|
51
|
+
corner. Push the DSH session header's right padding out so its right-aligned
|
|
52
|
+
utilities (the "Session log" download capsule) yield the corner instead of
|
|
53
|
+
hiding under the cluster. The header default right-pads 28px; the 2-button
|
|
54
|
+
cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on
|
|
55
|
+
the header's slot host wrapper ([data-slot="conversation.session.header"])
|
|
56
|
+
rather than a positional path: DSH 0.1.x nests the header several levels
|
|
57
|
+
under the center column. The Sidebar shell toggles the body attribute with
|
|
58
|
+
the panel open state. */
|
|
59
|
+
body[data-dsh-sidebar-collapsed] [data-slot="conversation.session.header"] > header {
|
|
60
|
+
padding-right: 78px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body[data-dsh-sidebar-dragging] #root,
|
|
64
|
+
body[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane="conversation"],
|
|
65
|
+
body[data-dsh-sidebar-dragging] #root :has(> [data-slot="conversation"]) {
|
|
66
|
+
transition: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* DSH 0.1.x gives external settings sections a generic gear and exposes no
|
|
70
|
+
icon field in the settings.section contract. settings-nav-icon.ts marks
|
|
71
|
+
only this plugin's localized row; render the requested Lucide
|
|
72
|
+
gallery-horizontal-end SVG as a currentColor mask so it follows the native
|
|
73
|
+
nav hover/active colors without changing the shell's 16px icon rhythm. */
|
|
74
|
+
[data-dsh-better-sidebar-settings-nav] > svg:first-child {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[data-dsh-better-sidebar-settings-nav]::before {
|
|
79
|
+
content: '';
|
|
80
|
+
flex: none;
|
|
81
|
+
width: 16px;
|
|
82
|
+
height: 16px;
|
|
83
|
+
background: currentColor;
|
|
84
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
85
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (prefers-reduced-motion: reduce) {
|
|
89
|
+
#root,
|
|
90
|
+
#root [data-dsh-frame] > [data-pane="conversation"],
|
|
91
|
+
#root :has(> [data-slot="conversation"]) {
|
|
92
|
+
transition: none;
|
|
93
|
+
}
|
|
94
|
+
}
|