@chatcode/cco-better-sidebar 0.18.0-rc.6 → 0.18.0-rc.7

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.
@@ -40,6 +40,10 @@ export declare const zh: {
40
40
  pluginMarketChooseProject: string;
41
41
  pluginMarketProjectHint: string;
42
42
  pluginMarketChoosingDirectory: string;
43
+ pluginMarketAwaitingConfirmation: string;
44
+ pluginMarketConfirmProjectInstall: string;
45
+ pluginMarketConfirmProjectDescription: string;
46
+ pluginMarketConfirmInstall: string;
43
47
  pluginMarketInstalledAt: string;
44
48
  pluginMarketInstallPath: string;
45
49
  pluginMarketDirectoryPath: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatcode/cco-better-sidebar",
3
- "version": "0.18.0-rc.6",
3
+ "version": "0.18.0-rc.7",
4
4
  "description": "ChatCode CLI Web plugin: a VS Code-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes a service for other plugins to register sidebar tabs and file viewers.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -9,6 +9,7 @@
9
9
  .projectRow > button { flex-shrink: 0; }
10
10
  .installPath { margin: 4px 0; overflow-wrap: anywhere; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
11
11
  .projectPicker { width: min(620px, calc(100vw - 32px)); }
12
+ .confirmPath { margin: 0; padding: 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; white-space: pre-wrap; }
12
13
  .directoryPath { display: flex; align-items: center; gap: 8px; }
13
14
  .directoryPath > span { flex: 1; min-width: 0; }
14
15
  .directoryCrumbs { display: flex; flex-wrap: wrap; gap: 4px; margin: 12px 0; }
@@ -1,6 +1,6 @@
1
1
  /** Two catalog levels with project/user actions directly on plugin cards. */
2
2
  import { useEffect, useRef, useState, useSyncExternalStore } from 'react'
3
- import { Button, Input, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
3
+ import { Button, Input, Modal, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
4
4
  import { api, type SessionScope } from './api.ts'
5
5
  import { t } from './locales.ts'
6
6
  import { IconChevronLeftOutline14, IconChevronRightOutline14, IconRefreshOutline14 } from './icons.tsx'
@@ -9,6 +9,8 @@ import type { InstalledMarketplacePlugin, MarketplacePlugin, PluginMarketScope }
9
9
  import css from './PluginMarketView.module.css'
10
10
  import { PluginProjectPicker, type PluginProjectRequest } from './PluginProjectPicker.tsx'
11
11
 
12
+ interface ProjectInstallConfirmation { name: string; path: string; finish: (confirmed: boolean) => void }
13
+
12
14
  export function PluginMarketView({ scope, visible, catalog }: { scope: SessionScope; visible: boolean; catalog: PluginMarketCatalog }): JSX.Element {
13
15
  const snapshot = useSyncExternalStore(catalog.subscribe, catalog.getSnapshot)
14
16
  const [marketId, setMarketId] = useState<string | null>(null)
@@ -16,7 +18,9 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
16
18
  const [query, setQuery] = useState('')
17
19
  const [recordState, setRecordState] = useState<{ key: string; records: InstalledMarketplacePlugin[]; ready: boolean; error: string }>()
18
20
  const [message, setMessage] = useState<{ text: string; error: boolean }>()
19
- const [working, setWorking] = useState<{ id: string; name: string; scope: PluginMarketScope; uninstall: boolean; choosing?: boolean }>()
21
+ const [working, setWorking] = useState<{ id: string; name: string; scope: PluginMarketScope; uninstall: boolean; confirming?: boolean }>()
22
+ const [confirmation, setConfirmation] = useState<ProjectInstallConfirmation>()
23
+ const confirmationRef = useRef<ProjectInstallConfirmation>()
20
24
  const [selection, setSelection] = useState<{ key: string; path: string }>()
21
25
  const [picker, setPicker] = useState<PluginProjectRequest>()
22
26
  const pickerRef = useRef<PluginProjectRequest>()
@@ -37,7 +41,7 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
37
41
  const market = markets.find(item => item.id === marketId)
38
42
  const catalogEntry = tab === 'markets' && marketId ? snapshot.plugins[marketId] : snapshot.markets
39
43
  const loading = snapshot.refresh?.status === 'loading' || (tab === 'markets' && (!catalogEntry || catalogEntry.status === 'loading'))
40
- const operating = !!working && !working.choosing
44
+ const operating = !!working && !working.confirming
41
45
  const blocked = loading || snapshot.refresh?.status === 'error' || !!working
42
46
 
43
47
  useEffect(() => {
@@ -49,9 +53,15 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
49
53
  useEffect(() => { if (focusBack.current) { breadcrumbNode.current?.querySelector('button')?.focus(); focusBack.current = false } }, [marketId])
50
54
 
51
55
  useEffect(() => { setMessage(undefined) }, [scopeKey])
56
+ useEffect(() => {
57
+ confirmationRef.current?.finish(false)
58
+ confirmationRef.current = undefined
59
+ setConfirmation(undefined)
60
+ }, [scopeKey, visible])
52
61
  useEffect(() => () => {
53
62
  pickerRef.current?.controller.abort()
54
63
  pickerRef.current?.finish(null)
64
+ confirmationRef.current?.finish(false)
55
65
  }, [])
56
66
  useEffect(() => {
57
67
  if (!visible) return
@@ -88,6 +98,17 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
88
98
  pickerRef.current = undefined
89
99
  setPicker(undefined)
90
100
  }
101
+ const confirmProjectInstall = (name: string, path: string): Promise<boolean> => new Promise(finish => {
102
+ const request = { name, path, finish }
103
+ confirmationRef.current = request
104
+ setConfirmation(request)
105
+ })
106
+ const finishConfirmation = (confirmed: boolean): void => {
107
+ const request = confirmationRef.current
108
+ confirmationRef.current = undefined
109
+ setConfirmation(undefined)
110
+ request?.finish(confirmed)
111
+ }
91
112
  const chooseProject = async (): Promise<void> => {
92
113
  if (pending.current || blocked) return
93
114
  pending.current = true
@@ -106,18 +127,20 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
106
127
  const key = scopeKey
107
128
  const record = installed.find(item => item.id === plugin.id && item.scope === installScope)
108
129
  const present = !!record
130
+ if (installScope === 'project' && !present && !projectPath) {
131
+ setMessage({ text: t('pluginMarketProjectHint'), error: true })
132
+ return
133
+ }
109
134
  pending.current = true
110
135
  mutationVersion.current++
111
136
  const operation = { id: plugin.id, name: plugin.name, scope: installScope, uninstall: present }
112
- setWorking({ ...operation, choosing: installScope === 'project' && !present })
137
+ setWorking({ ...operation, confirming: installScope === 'project' && !present })
113
138
  setMessage(undefined)
114
139
  try {
115
140
  let target = record?.scope === 'project' ? record.projectPath : projectPath
116
141
  if (installScope === 'project' && !present) {
117
- const chosen = await chooseDirectory()
118
- if (!chosen || latestScope.current !== key) return
119
- target = chosen
120
- setSelection({ key, path: target })
142
+ const confirmed = await confirmProjectInstall(plugin.name, target!)
143
+ if (!confirmed || latestScope.current !== key) return
121
144
  setWorking(operation)
122
145
  }
123
146
  let installedPath = ''
@@ -203,7 +226,7 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
203
226
  const busy = working?.id === item.id && working.scope === installScope
204
227
  return <Button key={installScope} size="sm" variant={installScope === 'project' && !present ? 'primary' : 'outline'} className={installScope === 'project' && !present ? css.projectInstall : undefined} disabled={blocked || !!working || !recordsReady} onClick={() => { void perform(item, installScope) }}>
205
228
  {busy && operating ? <span className={css.spinning} aria-hidden="true"><IconRefreshOutline14 /></span> : null}
206
- {t(busy ? working?.choosing ? 'pluginMarketChoosingDirectory' : working?.uninstall ? 'pluginMarketUninstalling' : 'pluginMarketInstalling' : present ? 'pluginMarketUninstall' : 'pluginMarketInstall')} · {t(installScope === 'project' ? 'pluginMarketProject' : 'pluginMarketUser')}
229
+ {t(busy ? working?.confirming ? 'pluginMarketAwaitingConfirmation' : working?.uninstall ? 'pluginMarketUninstalling' : 'pluginMarketInstalling' : present ? 'pluginMarketUninstall' : 'pluginMarketInstall')} · {t(installScope === 'project' ? 'pluginMarketProject' : 'pluginMarketUser')}
207
230
  </Button>
208
231
  })}
209
232
  </div>
@@ -211,6 +234,10 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
211
234
  })}
212
235
  </div>
213
236
  {!loading && hasData && (showMarkets ? filteredMarkets : filteredPlugins).length === 0 ? <p className={css.state}>{t('pluginMarketEmpty')}</p> : null}
237
+ {confirmation ? <Modal open title={t('pluginMarketConfirmProjectInstall')} description={t('pluginMarketConfirmProjectDescription', { name: confirmation.name })} closeLabel={t('cancel')} onClose={() => finishConfirmation(false)} className={css.projectPicker}
238
+ footer={<><Button variant="outline" data-modal-autofocus onClick={() => finishConfirmation(false)}>{t('cancel')}</Button><Button variant="primary" onClick={() => finishConfirmation(true)}>{t('pluginMarketConfirmInstall')}</Button></>}>
239
+ <p className={css.confirmPath}>{confirmation.path}</p>
240
+ </Modal> : null}
214
241
  {picker ? <PluginProjectPicker request={picker} initialPath={projectPath} onPicked={finishPicker} onCancel={() => finishPicker(null)} onError={text => { setMessage({ text, error: true }); finishPicker(null) }} /> : null}
215
242
  </section>
216
243
  }
@@ -34,6 +34,10 @@ export const ja: Record<string, string> = {
34
34
  pluginMarketChooseProject: 'プロジェクトディレクトリを選択',
35
35
  pluginMarketProjectHint: 'プロジェクトにインストールする場合は対象フォルダーを選択してください。',
36
36
  pluginMarketChoosingDirectory: 'ディレクトリを選択中…',
37
+ pluginMarketAwaitingConfirmation: '確認待ち…',
38
+ pluginMarketConfirmProjectInstall: 'プロジェクトへのインストールを確認',
39
+ pluginMarketConfirmProjectDescription: '{name} を以下のプロジェクトディレクトリにインストールします。変更する場合はキャンセルし、ページ上部でディレクトリを選択してください。',
40
+ pluginMarketConfirmInstall: 'インストールを確認',
37
41
  pluginMarketInstalledAt: 'プラグインのインストール先:{path}',
38
42
  pluginMarketInstallPath: '{scope}のインストール先:{path}',
39
43
  pluginMarketDirectoryPath: 'ディレクトリパス',
@@ -41,6 +41,10 @@ export const zh = {
41
41
  pluginMarketChooseProject: '选择项目目录',
42
42
  pluginMarketProjectHint: '安装到项目时,请先选择目标项目文件夹。',
43
43
  pluginMarketChoosingDirectory: '正在选择目录…',
44
+ pluginMarketAwaitingConfirmation: '等待确认…',
45
+ pluginMarketConfirmProjectInstall: '确认项目级安装',
46
+ pluginMarketConfirmProjectDescription: '将把 {name} 安装到以下项目目录。如需更改,请取消后在顶部选择项目目录。',
47
+ pluginMarketConfirmInstall: '确认安装',
44
48
  pluginMarketInstalledAt: '插件已安装到:{path}',
45
49
  pluginMarketInstallPath: '{scope}安装目录:{path}',
46
50
  pluginMarketDirectoryPath: '目录路径',
@@ -547,6 +551,10 @@ export const en: Record<keyof typeof zh, string> = {
547
551
  pluginMarketChooseProject: 'Choose project directory',
548
552
  pluginMarketProjectHint: 'Choose a target project folder when installing for a project.',
549
553
  pluginMarketChoosingDirectory: 'Choosing directory…',
554
+ pluginMarketAwaitingConfirmation: 'Awaiting confirmation…',
555
+ pluginMarketConfirmProjectInstall: 'Confirm project installation',
556
+ pluginMarketConfirmProjectDescription: '{name} will be installed in the project directory below. To change it, cancel and choose a project directory at the top of the page.',
557
+ pluginMarketConfirmInstall: 'Confirm installation',
550
558
  pluginMarketInstalledAt: 'Plugin installed at: {path}',
551
559
  pluginMarketInstallPath: '{scope} installation directory: {path}',
552
560
  pluginMarketDirectoryPath: 'Directory path',
@@ -1083,6 +1091,10 @@ const accountAndModelFallback = {
1083
1091
  pluginMarketChooseProject: en.pluginMarketChooseProject,
1084
1092
  pluginMarketProjectHint: en.pluginMarketProjectHint,
1085
1093
  pluginMarketChoosingDirectory: en.pluginMarketChoosingDirectory,
1094
+ pluginMarketAwaitingConfirmation: en.pluginMarketAwaitingConfirmation,
1095
+ pluginMarketConfirmProjectInstall: en.pluginMarketConfirmProjectInstall,
1096
+ pluginMarketConfirmProjectDescription: en.pluginMarketConfirmProjectDescription,
1097
+ pluginMarketConfirmInstall: en.pluginMarketConfirmInstall,
1086
1098
  pluginMarketInstalledAt: en.pluginMarketInstalledAt,
1087
1099
  pluginMarketInstallPath: en.pluginMarketInstallPath,
1088
1100
  pluginMarketDirectoryPath: en.pluginMarketDirectoryPath,