@chatcode/cco-better-sidebar 0.18.0-alpha.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 +554 -0
- package/README_EN.md +563 -0
- package/assets/logo.svg +1 -0
- package/cordis.patch.yml +79 -0
- package/lib/client-editor.js +55744 -0
- package/lib/client-mermaid.js +208256 -0
- package/lib/client-registry.js +30378 -0
- package/lib/client-terminal.js +16894 -0
- package/lib/client.js +30378 -0
- package/lib/index.js +6897 -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/chatcode-update.d.ts +47 -0
- package/lib/types/client/AccountMenu.d.ts +1 -0
- package/lib/types/client/AiRatioSection.d.ts +3 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/ChatcodeGate.d.ts +6 -0
- package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -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/FenceErrorNotice.d.ts +5 -0
- package/lib/types/client/FileTree.d.ts +41 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +12 -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/ReleaseNotesDialog.d.ts +11 -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/Sidebar.d.ts +8 -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 +27 -0
- package/lib/types/client/UpdateDialog.d.ts +10 -0
- package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
- package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +345 -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/chat-presentation.d.ts +13 -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/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +66 -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 +44 -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 +571 -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 +21 -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 +52 -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 +445 -0
- package/lib/types/client/session-pins.d.ts +14 -0
- package/lib/types/client/settings-nav-icon.d.ts +27 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/split-pane.d.ts +38 -0
- package/lib/types/client/state.d.ts +480 -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/ui-tweaks/appicons.d.ts +16 -0
- package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
- package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
- package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
- package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
- package/lib/types/client/ui-tweaks/index.d.ts +279 -0
- package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
- package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
- package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/useChatcodeAccount.d.ts +10 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/codingplan.d.ts +37 -0
- package/lib/types/config.d.ts +95 -0
- package/lib/types/context-types.d.ts +540 -0
- package/lib/types/fs-operations.d.ts +30 -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/login-route.d.ts +2 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +26 -0
- package/lib/types/prefs-shared.d.ts +257 -0
- package/lib/types/pty-deps.d.ts +79 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/ui-tweaks/archive.d.ts +71 -0
- package/lib/types/ui-tweaks/config.d.ts +186 -0
- package/lib/types/ui-tweaks/git-web.d.ts +43 -0
- package/lib/types/ui-tweaks/git.d.ts +311 -0
- package/lib/types/ui-tweaks/index.d.ts +17 -0
- package/lib/types/ui-tweaks/mcp.d.ts +103 -0
- package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
- package/lib/types/ui-tweaks/web.d.ts +55 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +213 -0
- package/scripts/install.ps1 +310 -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/chatcode-update.ts +193 -0
- package/src/client/AccountMenu.tsx +177 -0
- package/src/client/AiRatioSection.module.css +329 -0
- package/src/client/AiRatioSection.tsx +292 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/ChatcodeGate.tsx +35 -0
- package/src/client/CodingPlanModelsSettings.module.css +118 -0
- package/src/client/CodingPlanModelsSettings.tsx +101 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +563 -0
- package/src/client/FenceErrorNotice.tsx +40 -0
- package/src/client/FileTree.tsx +701 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +722 -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/ReleaseNotesDialog.tsx +91 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +932 -0
- package/src/client/SideCardSection.tsx +1169 -0
- package/src/client/Sidebar.tsx +1857 -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 +279 -0
- package/src/client/UpdateDialog.tsx +186 -0
- package/src/client/UpdateSettingsPanel.tsx +29 -0
- package/src/client/UpdateSettingsSection.tsx +22 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/UsageHeatmapSection.module.css +440 -0
- package/src/client/UsageHeatmapSection.tsx +301 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +433 -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 +275 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chat-presentation.ts +95 -0
- package/src/client/chunk-loader.ts +350 -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 +170 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +501 -0
- package/src/client/intercept.tsx +172 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +37 -0
- package/src/client/layout.css +156 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +387 -0
- package/src/client/locales-de.ts +372 -0
- package/src/client/locales-fr.ts +379 -0
- package/src/client/locales-hi.ts +386 -0
- package/src/client/locales-id.ts +384 -0
- package/src/client/locales-it.ts +377 -0
- package/src/client/locales-ja.ts +486 -0
- package/src/client/locales-ko.ts +378 -0
- package/src/client/locales-nl.ts +384 -0
- package/src/client/locales-pl.ts +388 -0
- package/src/client/locales-pt.ts +369 -0
- package/src/client/locales-ru.ts +384 -0
- package/src/client/locales-sv.ts +369 -0
- package/src/client/locales-th.ts +386 -0
- package/src/client/locales-tr.ts +386 -0
- package/src/client/locales-vi.ts +386 -0
- package/src/client/locales-zh-HK.ts +401 -0
- package/src/client/locales-zh-MO.ts +401 -0
- package/src/client/locales-zh-TW.ts +401 -0
- package/src/client/locales.ts +1229 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +31 -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 +79 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +184 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +153 -0
- package/src/client/plugins-viewers.ts +44 -0
- package/src/client/prefs.ts +216 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +873 -0
- package/src/client/session-pins.tsx +334 -0
- package/src/client/settings-nav-icon.ts +55 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +3607 -0
- package/src/client/split-pane.tsx +333 -0
- package/src/client/state.ts +1646 -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/ui-tweaks/appicons.ts +24 -0
- package/src/client/ui-tweaks/archive.tsx +281 -0
- package/src/client/ui-tweaks/branding.tsx +232 -0
- package/src/client/ui-tweaks/cachehit.tsx +122 -0
- package/src/client/ui-tweaks/gitbar.tsx +2559 -0
- package/src/client/ui-tweaks/index.tsx +1696 -0
- package/src/client/ui-tweaks/mcp.tsx +582 -0
- package/src/client/ui-tweaks/notifier.ts +514 -0
- package/src/client/ui-tweaks/whale.tsx +171 -0
- package/src/client/upload.ts +187 -0
- package/src/client/useChatcodeAccount.ts +103 -0
- package/src/client/wco.ts +120 -0
- package/src/codingplan.ts +146 -0
- package/src/config.ts +207 -0
- package/src/context-types.ts +559 -0
- package/src/fs-operations.ts +97 -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 +1524 -0
- package/src/invariant.ts +32 -0
- package/src/login-route.ts +26 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +81 -0
- package/src/prefs-shared.ts +303 -0
- package/src/pty-deps.ts +241 -0
- package/src/pty-manager.ts +376 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/ui-tweaks/archive.ts +293 -0
- package/src/ui-tweaks/config.ts +257 -0
- package/src/ui-tweaks/git-web.ts +421 -0
- package/src/ui-tweaks/git.ts +1524 -0
- package/src/ui-tweaks/index.ts +61 -0
- package/src/ui-tweaks/mcp.ts +535 -0
- package/src/ui-tweaks/turn-outcome.ts +105 -0
- package/src/ui-tweaks/web.ts +225 -0
- package/src/wire.ts +110 -0
- package/vendor/addon-fit.js +2 -0
- package/vendor/xterm.css +218 -0
- package/vendor/xterm.js +5 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A non-invasive extension of DSH's native Workspace browser.
|
|
3
|
+
*
|
|
4
|
+
* `sidebar.workspaces` is a single-owner host slot, so replacing its occupant
|
|
5
|
+
* would also mean reimplementing search, workspace creation, ordering and all
|
|
6
|
+
* of the native dialogs. Instead this module mounts one small React island as
|
|
7
|
+
* a PEER of that slot and augments the existing row menu. The native browser
|
|
8
|
+
* therefore continues to own every pre-existing action.
|
|
9
|
+
*/
|
|
10
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
import { useEffect, useState, useSyncExternalStore } from 'react'
|
|
12
|
+
import type { Context, SidebarSessionSummary } from '../context-types.ts'
|
|
13
|
+
import type { SidebarStore } from './state.ts'
|
|
14
|
+
import { api } from './api.ts'
|
|
15
|
+
import { parsePrefs } from './prefs.ts'
|
|
16
|
+
import { t } from './locales.ts'
|
|
17
|
+
import { IconPinOutline16 } from './icons.tsx'
|
|
18
|
+
import css from './sidebar.module.css'
|
|
19
|
+
|
|
20
|
+
const PIN_SETTINGS_KEY = 'session-pins'
|
|
21
|
+
|
|
22
|
+
/** Read and sanitize the plugin-owned durable pin order. */
|
|
23
|
+
export function pinnedSessionIdsOf(value: { pluginSettings: Record<string, Record<string, unknown>> }): string[] {
|
|
24
|
+
const raw = value.pluginSettings[PIN_SETTINGS_KEY]?.ids
|
|
25
|
+
if (!Array.isArray(raw)) return []
|
|
26
|
+
const seen = new Set<string>()
|
|
27
|
+
return raw.filter((id): id is string => {
|
|
28
|
+
if (typeof id !== 'string' || id === '' || seen.has(id)) return false
|
|
29
|
+
seen.add(id)
|
|
30
|
+
return true
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pinSettings(ids: readonly string[]): Record<string, unknown> {
|
|
35
|
+
return { ids: [...ids] }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A small current-color pin icon for the menu row injected into DSH's menu. */
|
|
39
|
+
function pinIconMarkup(): string {
|
|
40
|
+
return '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M9.5 1.5 14.5 6.5 12.5 8.5 10 6 5.5 10.5 6 12 4.5 13.5 2.5 11.5 4 10 5.5 10.5 10 6 7.5 8.5 6.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isSessionRow(element: Element | null): element is HTMLElement {
|
|
44
|
+
return element instanceof HTMLElement && element.matches('[class*="_sessionRow"]')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The native row deliberately has no session-id DOM attribute. React retains
|
|
49
|
+
* the row component's `node` prop on its fiber, however; read that identity
|
|
50
|
+
* rather than guessing from the title (automatic titles can be duplicated).
|
|
51
|
+
*/
|
|
52
|
+
function sessionIdFromReactRow(row: HTMLElement): string | undefined {
|
|
53
|
+
const record = row as unknown as Record<string, unknown>
|
|
54
|
+
const fiberKey = Object.keys(record).find(key => key.startsWith('__reactFiber$'))
|
|
55
|
+
let fiber = fiberKey === undefined ? undefined : record[fiberKey] as Record<string, unknown> | undefined
|
|
56
|
+
const seen = new Set<object>()
|
|
57
|
+
while (fiber !== undefined && !seen.has(fiber)) {
|
|
58
|
+
seen.add(fiber)
|
|
59
|
+
const props = fiber.memoizedProps as Record<string, unknown> | undefined
|
|
60
|
+
const node = props?.node as Record<string, unknown> | undefined
|
|
61
|
+
if (typeof node?.id === 'string') return node.id
|
|
62
|
+
fiber = fiber.return as Record<string, unknown> | undefined
|
|
63
|
+
}
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Keep native Workspace rows hidden only while their session is pinned. */
|
|
68
|
+
function syncNativePinnedRows(ids: readonly string[]): void {
|
|
69
|
+
const pinned = new Set(ids)
|
|
70
|
+
for (const row of document.querySelectorAll<HTMLElement>('[data-slot="sidebar.workspaces"] [class*="_sessionRow"]')) {
|
|
71
|
+
const sessionId = sessionIdFromReactRow(row)
|
|
72
|
+
if (sessionId !== undefined && pinned.has(sessionId)) row.dataset.dshSessionPinned = ''
|
|
73
|
+
else delete row.dataset.dshSessionPinned
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function menuNear(anchor: HTMLElement): HTMLElement | undefined {
|
|
78
|
+
const rect = anchor.getBoundingClientRect()
|
|
79
|
+
return [...document.querySelectorAll<HTMLElement>('[role="menu"]')].find((menu) => {
|
|
80
|
+
if (menu.dataset.dshSessionPinMenu === 'true') return false
|
|
81
|
+
const items = menu.querySelectorAll(':scope button[role="menuitem"]')
|
|
82
|
+
if (items.length !== 3 || menu.getClientRects().length === 0) return false
|
|
83
|
+
const menuRect = menu.getBoundingClientRect()
|
|
84
|
+
return Math.abs(menuRect.top - rect.top) < 260 && Math.abs(menuRect.left - rect.right) < 360
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Mount the separate pinned group and append a fourth item to DSH's native
|
|
90
|
+
* three-item row menu. The group is inserted beside (not inside) the host
|
|
91
|
+
* Workspace slot, so it is a top-level left-sidebar section. Both DOM seams
|
|
92
|
+
* are explicitly scoped to the current alpha host's stable slot and row class
|
|
93
|
+
* suffixes; failure is fail-open and leaves the native Workspace UI untouched.
|
|
94
|
+
*/
|
|
95
|
+
export function installSessionPins(ctx: Context, store: SidebarStore): () => void {
|
|
96
|
+
let root: Root | undefined
|
|
97
|
+
let mount: HTMLDivElement | undefined
|
|
98
|
+
let observer: MutationObserver | undefined
|
|
99
|
+
let write = Promise.resolve()
|
|
100
|
+
let rowSyncFrame: number | undefined
|
|
101
|
+
|
|
102
|
+
const scheduleNativeRowSync = (): void => {
|
|
103
|
+
if (rowSyncFrame !== undefined) return
|
|
104
|
+
rowSyncFrame = requestAnimationFrame(() => {
|
|
105
|
+
rowSyncFrame = undefined
|
|
106
|
+
syncNativePinnedRows(pinnedSessionIdsOf(store.getPrefs()))
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const updatePins = (nextIds: readonly string[]): void => {
|
|
111
|
+
const before = store.getPrefs()
|
|
112
|
+
const next = {
|
|
113
|
+
...before,
|
|
114
|
+
pluginSettings: {
|
|
115
|
+
...before.pluginSettings,
|
|
116
|
+
[PIN_SETTINGS_KEY]: pinSettings(nextIds),
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
store.setPrefs(next)
|
|
120
|
+
write = write.catch(() => undefined).then(async () => {
|
|
121
|
+
const current = store.getPrefs()
|
|
122
|
+
const view = await api.settingsUpdate({
|
|
123
|
+
pluginSettings: {
|
|
124
|
+
...current.pluginSettings,
|
|
125
|
+
[PIN_SETTINGS_KEY]: pinSettings(nextIds),
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
store.setPrefs(parsePrefs(view.value))
|
|
129
|
+
}).catch((error: unknown) => {
|
|
130
|
+
// Do not roll back a later toggle if this older write fails.
|
|
131
|
+
if (pinnedSessionIdsOf(store.getPrefs()).join('\0') === nextIds.join('\0')) store.setPrefs(before)
|
|
132
|
+
console.warn('[dsh-better-sidebar] persist session pins failed:', error)
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sessionForRow = (row: HTMLElement): SidebarSessionSummary | undefined => {
|
|
137
|
+
const id = sessionIdFromReactRow(row)
|
|
138
|
+
if (id === undefined) return undefined
|
|
139
|
+
const list = ctx.sessions.list.getSnapshot()
|
|
140
|
+
const session = list.byId[id]
|
|
141
|
+
return session?.origin === 'subagent' ? undefined : session
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const decorateMenu = (anchor: HTMLElement, sessionId: string): void => {
|
|
145
|
+
const menu = menuNear(anchor)
|
|
146
|
+
if (menu === undefined) return
|
|
147
|
+
const template = menu.querySelector<HTMLButtonElement>(':scope button[role="menuitem"]')
|
|
148
|
+
if (template === null || template === undefined) return
|
|
149
|
+
menu.dataset.dshSessionPinMenu = 'true'
|
|
150
|
+
const item = template.cloneNode(true) as HTMLButtonElement
|
|
151
|
+
item.dataset.dshSessionPinAction = sessionId
|
|
152
|
+
const pinned = pinnedSessionIdsOf(store.getPrefs()).includes(sessionId)
|
|
153
|
+
const label = item.querySelector<HTMLElement>('span[class*="_itemLabel"]')
|
|
154
|
+
if (label !== null) label.textContent = pinned ? t('unpinSession') : t('pinSession')
|
|
155
|
+
else item.textContent = pinned ? t('unpinSession') : t('pinSession')
|
|
156
|
+
const icon = item.querySelector<HTMLElement>('span[class*="_itemIcon"]')
|
|
157
|
+
if (icon !== null) icon.innerHTML = pinIconMarkup()
|
|
158
|
+
item.addEventListener('click', (event) => {
|
|
159
|
+
event.preventDefault()
|
|
160
|
+
event.stopPropagation()
|
|
161
|
+
const ids = pinnedSessionIdsOf(store.getPrefs())
|
|
162
|
+
updatePins(ids.includes(sessionId) ? ids.filter(id => id !== sessionId) : [...ids, sessionId])
|
|
163
|
+
// The portal is owned by DSH React. Never remove it ourselves: doing
|
|
164
|
+
// so leaves React attempting to unmount an already-detached node and
|
|
165
|
+
// can take the whole Workspace browser down on its next render. Its
|
|
166
|
+
// standard outside-pointer listener closes the menu safely.
|
|
167
|
+
document.body.dispatchEvent(new Event('pointerdown', { bubbles: true }))
|
|
168
|
+
})
|
|
169
|
+
menu.appendChild(item)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const onDocumentClick = (event: MouseEvent): void => {
|
|
173
|
+
const button = (event.target as Element | null)?.closest('button')
|
|
174
|
+
const row = button?.closest('[class*="_sessionRow"]')
|
|
175
|
+
if (row === null || row === undefined || !isSessionRow(row) || button === null || button === undefined) return
|
|
176
|
+
const session = sessionForRow(row)
|
|
177
|
+
if (session === undefined) return
|
|
178
|
+
// DSH creates its portalled menu during this click's React update.
|
|
179
|
+
requestAnimationFrame(() => { decorateMenu(button, session.id) })
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const attach = (): void => {
|
|
183
|
+
const slot = document.querySelector<HTMLElement>('[data-slot="sidebar.workspaces"]')
|
|
184
|
+
const region = slot?.parentElement
|
|
185
|
+
if (slot === null || slot === undefined || !(region instanceof HTMLElement) || mount?.parentElement === region) return
|
|
186
|
+
root?.unmount()
|
|
187
|
+
mount?.remove()
|
|
188
|
+
mount = document.createElement('div')
|
|
189
|
+
mount.dataset.dshPinnedSessions = ''
|
|
190
|
+
// The slot wrapper is `display: contents`; placing this mount immediately
|
|
191
|
+
// before it makes the pin group a peer of the Workspace browser in the
|
|
192
|
+
// sidebar's flex column, not a child of the browser's session list.
|
|
193
|
+
region.insertBefore(mount, slot)
|
|
194
|
+
root = createRoot(mount)
|
|
195
|
+
root.render(<PinnedSessions ctx={ctx} store={store} onChange={updatePins} />)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
observer = new MutationObserver(() => {
|
|
199
|
+
// Conversation streaming mutates the page continuously. Once our island
|
|
200
|
+
// is attached there is nothing to scan; only a host re-render that removes
|
|
201
|
+
// it needs a fresh lookup.
|
|
202
|
+
if (!mount?.isConnected) attach()
|
|
203
|
+
if (pinnedSessionIdsOf(store.getPrefs()).length > 0) scheduleNativeRowSync()
|
|
204
|
+
})
|
|
205
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
206
|
+
attach()
|
|
207
|
+
document.addEventListener('click', onDocumentClick, true)
|
|
208
|
+
return () => {
|
|
209
|
+
document.removeEventListener('click', onDocumentClick, true)
|
|
210
|
+
observer?.disconnect()
|
|
211
|
+
if (rowSyncFrame !== undefined) cancelAnimationFrame(rowSyncFrame)
|
|
212
|
+
syncNativePinnedRows([])
|
|
213
|
+
root?.unmount()
|
|
214
|
+
mount?.remove()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function PinnedSessions({ ctx, store, onChange }: {
|
|
219
|
+
ctx: Context
|
|
220
|
+
store: SidebarStore
|
|
221
|
+
onChange: (ids: readonly string[]) => void
|
|
222
|
+
}) {
|
|
223
|
+
const list = useSyncExternalStore(ctx.sessions.list.subscribe, ctx.sessions.list.getSnapshot, ctx.sessions.list.getSnapshot)
|
|
224
|
+
const prefs = useSyncExternalStore(store.subscribe.bind(store), store.getSnapshot.bind(store), store.getSnapshot.bind(store)).prefs
|
|
225
|
+
const ids = pinnedSessionIdsOf(prefs)
|
|
226
|
+
const pinKey = ids.join('\0')
|
|
227
|
+
const sessions = ids.flatMap((id) => {
|
|
228
|
+
const session = list.byId[id]
|
|
229
|
+
return session === undefined || session.origin === 'subagent' ? [] : [session]
|
|
230
|
+
})
|
|
231
|
+
const [menuFor, setMenuFor] = useState<string | undefined>()
|
|
232
|
+
|
|
233
|
+
// Purge only definitively vanished rows; a temporarily warming list must
|
|
234
|
+
// never erase a durable pin just after startup.
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (sessions.length === ids.length || Object.keys(list.byId).length === 0) return
|
|
237
|
+
onChange(sessions.map(session => session.id))
|
|
238
|
+
}, [ids.length, list.byId, onChange, sessions])
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
syncNativePinnedRows(ids)
|
|
242
|
+
return () => { syncNativePinnedRows([]) }
|
|
243
|
+
}, [pinKey])
|
|
244
|
+
|
|
245
|
+
const closeMenu = (): void => { setMenuFor(undefined) }
|
|
246
|
+
|
|
247
|
+
const renameSession = (session: SidebarSessionSummary): void => {
|
|
248
|
+
const title = window.prompt(t('pinnedSessionRenamePrompt'), session.displayTitle)?.trim()
|
|
249
|
+
if (title === undefined || title === '') return
|
|
250
|
+
closeMenu()
|
|
251
|
+
void ctx.sessions.binding?.(session.id)?.session.rename(title).catch((error: unknown) => {
|
|
252
|
+
console.warn('[dsh-better-sidebar] rename pinned session failed:', error)
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const forkSession = (sessionId: string): void => {
|
|
257
|
+
closeMenu()
|
|
258
|
+
void ctx.sessions.fork?.({ sessionId, increaseTitle: true }).then((childId) => {
|
|
259
|
+
ctx.sessions.open?.(childId)
|
|
260
|
+
}).catch((error: unknown) => {
|
|
261
|
+
console.warn('[dsh-better-sidebar] fork pinned session failed:', error)
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const archiveSession = (sessionId: string): void => {
|
|
266
|
+
closeMenu()
|
|
267
|
+
// `archiveSession` is a native Workspace browser verb. It is intentionally
|
|
268
|
+
// structural here: this island reuses the host action rather than owning
|
|
269
|
+
// a parallel archive implementation.
|
|
270
|
+
const archive = (ctx.workspaces as typeof ctx.workspaces & {
|
|
271
|
+
archiveSession?: (id: string) => Promise<void>
|
|
272
|
+
}).archiveSession
|
|
273
|
+
if (archive === undefined) return
|
|
274
|
+
void archive(sessionId).then(() => {
|
|
275
|
+
onChange(ids.filter(id => id !== sessionId))
|
|
276
|
+
}).catch((error: unknown) => {
|
|
277
|
+
console.warn('[dsh-better-sidebar] archive pinned session failed:', error)
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (sessions.length === 0) return null
|
|
282
|
+
return (
|
|
283
|
+
<section className={css.pinnedSessions} aria-label={t('pinnedSessions')}>
|
|
284
|
+
<div className={css.pinnedSessionsHeading}>
|
|
285
|
+
<span>{t('pinnedSessions')}</span>
|
|
286
|
+
</div>
|
|
287
|
+
<div className={css.pinnedSessionsList} role="list">
|
|
288
|
+
{sessions.map(session => (
|
|
289
|
+
<div className={css.pinnedSessionRow} key={session.id} role="listitem">
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
className={css.pinnedSessionOpen}
|
|
293
|
+
aria-current={session.id === list.current ? 'page' : undefined}
|
|
294
|
+
onClick={() => { ctx.sessions.open?.(session.id) }}
|
|
295
|
+
>
|
|
296
|
+
<span className={css.pinnedSessionDot} data-running={session.running || undefined} aria-hidden="true" />
|
|
297
|
+
<span className={css.pinnedSessionTitle}>{session.displayTitle}</span>
|
|
298
|
+
</button>
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
className={css.pinnedSessionMore}
|
|
302
|
+
aria-label={t('unpinSession')}
|
|
303
|
+
aria-expanded={menuFor === session.id}
|
|
304
|
+
onClick={() => { setMenuFor(menuFor === session.id ? undefined : session.id) }}
|
|
305
|
+
>
|
|
306
|
+
<span aria-hidden="true">•••</span>
|
|
307
|
+
</button>
|
|
308
|
+
{menuFor === session.id && (
|
|
309
|
+
<div className={css.pinnedSessionMenu} role="menu">
|
|
310
|
+
<button type="button" role="menuitem" onClick={() => { renameSession(session) }}>
|
|
311
|
+
{t('pinnedSessionRename')}
|
|
312
|
+
</button>
|
|
313
|
+
<button type="button" role="menuitem" onClick={() => { forkSession(session.id) }}>
|
|
314
|
+
{t('pinnedSessionFork')}
|
|
315
|
+
</button>
|
|
316
|
+
<button type="button" role="menuitem" onClick={() => { archiveSession(session.id) }}>
|
|
317
|
+
{t('pinnedSessionArchive')}
|
|
318
|
+
</button>
|
|
319
|
+
<div className={css.pinnedSessionMenuDivider} role="separator" />
|
|
320
|
+
<button type="button" role="menuitem" onClick={() => {
|
|
321
|
+
onChange(ids.filter(id => id !== session.id))
|
|
322
|
+
closeMenu()
|
|
323
|
+
}}>
|
|
324
|
+
<IconPinOutline16 size={16} />
|
|
325
|
+
{t('unpinSession')}
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
332
|
+
</section>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark this plugin's row in the DSH settings navigation so its bundled CSS
|
|
3
|
+
* can replace the shell's fallback gear with the Side card glyph.
|
|
4
|
+
*
|
|
5
|
+
* DSH 0.1.x projects only `id`, `order`, and `label` from a
|
|
6
|
+
* `settings.section` registration, then chooses icons inside the settings
|
|
7
|
+
* shell from a closed list of built-in ids. Until that public contract grows
|
|
8
|
+
* an icon field, the plugin identifies only its own localized row after the
|
|
9
|
+
* dialog mounts. The marker owns no shell structure and is removed on fiber
|
|
10
|
+
* disposal, so the adaptation remains HMR-safe.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const SETTINGS_NAV_MARKER = 'data-dsh-better-sidebar-settings-nav'
|
|
14
|
+
|
|
15
|
+
/** Marker of the "Token 用量" (usage heatmap) settings nav row. */
|
|
16
|
+
export const USAGE_NAV_MARKER = 'data-dsh-better-sidebar-usage-nav'
|
|
17
|
+
export const AI_RATIO_NAV_MARKER = 'data-dsh-better-sidebar-ai-ratio-nav'
|
|
18
|
+
|
|
19
|
+
/** Marker of the "更新" (update) settings nav row. */
|
|
20
|
+
export const UPDATE_NAV_MARKER = 'data-dsh-better-sidebar-update-nav'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Keep the marker on the settings-nav button whose visible text is this
|
|
24
|
+
* plugin's current localized section label.
|
|
25
|
+
* @param label - locale-aware label resolver used by the section registration.
|
|
26
|
+
* @param marker - the marker attribute this registration owns (each icon gets
|
|
27
|
+
* its own so several sections can mark their rows without stripping each
|
|
28
|
+
* other's markers).
|
|
29
|
+
* @returns disposer that disconnects observation and removes owned markers.
|
|
30
|
+
*/
|
|
31
|
+
export function registerSettingsNavIcon(label: () => string, marker: string = SETTINGS_NAV_MARKER): () => void {
|
|
32
|
+
let disposed = false
|
|
33
|
+
|
|
34
|
+
const sync = (): void => {
|
|
35
|
+
if (disposed) return
|
|
36
|
+
const currentLabel = label().trim()
|
|
37
|
+
const buttons = document.querySelectorAll<HTMLButtonElement>('[role="dialog"] nav button')
|
|
38
|
+
for (const button of buttons) {
|
|
39
|
+
const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel
|
|
40
|
+
if (matches) button.setAttribute(marker, '')
|
|
41
|
+
else button.removeAttribute(marker)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sync()
|
|
46
|
+
const observer = new MutationObserver(sync)
|
|
47
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true })
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
disposed = true
|
|
51
|
+
observer.disconnect()
|
|
52
|
+
document.querySelectorAll(`[${marker}]`)
|
|
53
|
+
.forEach(element => { element.removeAttribute(marker) })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in desktop-shell presets for the "位置兼容模式" secondary setting
|
|
3
|
+
* (scheme `preset`). DATA-DRIVEN by design: each shell's adaptation lives
|
|
4
|
+
* in one entry (strip values / optional CSS), the core applies it through
|
|
5
|
+
* the SAME generic strip variable and CSS-injection mechanism — adding a
|
|
6
|
+
* shell is adding data, never a code path.
|
|
7
|
+
*
|
|
8
|
+
* Inclusion rule (maintained in docs/external-plugin-guide.md §12): only shells that (a) appear
|
|
9
|
+
* in this repo's issues/PRs (a user actually hit a problem) and (b) have
|
|
10
|
+
* 100+ GitHub stars (a real user base). The mechanism is opt-in: auto
|
|
11
|
+
* detection never applies a preset — the settings badge only SUGGESTS it.
|
|
12
|
+
*/
|
|
13
|
+
import type { DesktopEnv } from './desktop-env.ts'
|
|
14
|
+
|
|
15
|
+
export interface ShellPreset {
|
|
16
|
+
/** Stable preset id (persisted in `titleBarPresetId`). */
|
|
17
|
+
readonly id: string
|
|
18
|
+
/** User-facing name of the shell. */
|
|
19
|
+
readonly title: string
|
|
20
|
+
/** One-line description shown in the settings popup. */
|
|
21
|
+
readonly desc: string
|
|
22
|
+
/**
|
|
23
|
+
* The top strip (px) this shell reserves over web content, per
|
|
24
|
+
* environment — the fallback used when neither the standard WCO API nor
|
|
25
|
+
* the `dsh-desktop-titlebar-inset` contract parameter is available.
|
|
26
|
+
* Return undefined when the shell needs no strip in that environment.
|
|
27
|
+
* MUST be pure (called during render).
|
|
28
|
+
*/
|
|
29
|
+
readonly stripFor?: (env: DesktopEnv) => number | undefined
|
|
30
|
+
/**
|
|
31
|
+
* Extra CSS applied while the preset is enabled (injected last, after the
|
|
32
|
+
* plugin's own styles). Targets the plugin's stable data attributes and
|
|
33
|
+
* shell-declared body/URL markers only — never other shells' class names.
|
|
34
|
+
* Empty for presets fully covered by the strip variable.
|
|
35
|
+
*/
|
|
36
|
+
readonly css?: string
|
|
37
|
+
/**
|
|
38
|
+
* Whether this shell's marker is currently visible (URL stamps / preload
|
|
39
|
+
* markers — see desktop-env.ts). Used ONLY for the settings "已检测"
|
|
40
|
+
* badge; never auto-applies anything. MUST be pure.
|
|
41
|
+
*/
|
|
42
|
+
readonly detect?: (env: DesktopEnv) => boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* DeepSeek Harness Desktop (anywhere-labs, Electron, 16k+ stars — the most
|
|
47
|
+
* reported shell in this repo's issues/PRs). Advanced mode: macOS reserves
|
|
48
|
+
* a 20px caption row (traffic lights top-left), win32 draws the native
|
|
49
|
+
* window controls in a ~32px overlay (WCO reports the real height when
|
|
50
|
+
* available, which the auto scheme already consumes; the 32 is the
|
|
51
|
+
* no-WCO fallback). Compatibility mode keeps the native frame — nothing.
|
|
52
|
+
*/
|
|
53
|
+
const DSH_DESKTOP: ShellPreset = {
|
|
54
|
+
id: 'dsh-desktop',
|
|
55
|
+
title: 'DeepSeek Harness Desktop',
|
|
56
|
+
desc: 'Electron 高级模式(无边框):macOS 顶栏 20px、Windows 无 WCO 时 32px 标题栏让位',
|
|
57
|
+
stripFor: (env) => {
|
|
58
|
+
if (env.mode !== 'advanced') return undefined
|
|
59
|
+
if (env.platform === 'darwin') return 20
|
|
60
|
+
if (env.platform === 'win32') return 32
|
|
61
|
+
return undefined
|
|
62
|
+
},
|
|
63
|
+
detect: (env) => env.mode === 'advanced',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const PRESETS: readonly ShellPreset[] = [
|
|
67
|
+
DSH_DESKTOP,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
/** All built-in shell presets (registration order = settings list order). */
|
|
71
|
+
export function getShellPresets(): readonly ShellPreset[] {
|
|
72
|
+
return PRESETS
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One preset by id, or undefined for an unknown/empty id. */
|
|
76
|
+
export function getShellPreset(id: string): ShellPreset | undefined {
|
|
77
|
+
return PRESETS.find(preset => preset.id === id)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The strip the active preset contributes for the given environment. */
|
|
81
|
+
export function presetStripFor(preset: ShellPreset | undefined, env: DesktopEnv): number | undefined {
|
|
82
|
+
return preset?.stripFor?.(env)
|
|
83
|
+
}
|