@chatcode/cco-better-sidebar 0.18.0-alpha.1 → 0.18.0-alpha.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/lib/client-editor.js +258 -258
- package/lib/client-mermaid.js +258 -258
- package/lib/client-registry.js +419 -385
- package/lib/client-terminal.js +258 -258
- package/lib/client.js +423 -389
- package/lib/types/client/chatcode-login-route.d.ts +4 -0
- package/lib/types/client/current-session.d.ts +11 -0
- package/lib/types/context-types.d.ts +15 -1
- package/package.json +1 -1
- package/src/client/AccountMenu.tsx +10 -4
- package/src/client/ChatcodeGate.tsx +2 -2
- package/src/client/Sidebar.tsx +3 -1
- package/src/client/chatcode-login-route.ts +11 -0
- package/src/client/current-session.ts +22 -0
- package/src/client/index.tsx +1 -1
- package/src/client/intercept.tsx +2 -1
- package/src/client/session-pins.tsx +3 -1
- package/src/context-types.ts +14 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Build the local ChatCode login gate URL while preserving the current page. */
|
|
2
|
+
export declare function chatcodeLoginRoute(location: Pick<Location, 'pathname' | 'search' | 'hash'>): string;
|
|
3
|
+
/** Enter the existing local login flow after account state cannot be read. */
|
|
4
|
+
export declare function redirectToChatcodeLogin(location?: Location): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from '../context-types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Read the session selected by the host's scoped-session adapter.
|
|
4
|
+
*
|
|
5
|
+
* Newer hosts deliberately removed `sessions.list.current`: selection is UI
|
|
6
|
+
* scope state, not catalog state. The adapter is authoritative on both old
|
|
7
|
+
* and new hosts and tracks main-view retain/release transitions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function currentSessionId(ctx: Context): string | undefined;
|
|
10
|
+
/** React subscription to the same authoritative current-session source. */
|
|
11
|
+
export declare function useCurrentSessionId(ctx: Context): string | undefined;
|
|
@@ -289,7 +289,8 @@ export interface SidebarSessionPersistenceService {
|
|
|
289
289
|
}
|
|
290
290
|
/** The client session list snapshot the sidebar subscribes to. */
|
|
291
291
|
export interface SidebarSessionList {
|
|
292
|
-
current
|
|
292
|
+
/** @deprecated Selection moved to `uiSession.adapter.current`; old hosts may still expose this field. */
|
|
293
|
+
current?: string;
|
|
293
294
|
byId: Record<string, SidebarSessionSummary>;
|
|
294
295
|
/** Direct durable catalogs keyed by their selected parent address. */
|
|
295
296
|
subagentsByParent?: Readonly<Record<string, SidebarSubagentCatalog>>;
|
|
@@ -356,6 +357,17 @@ export interface SidebarSessionsService {
|
|
|
356
357
|
*/
|
|
357
358
|
refreshSubagents?(parentSessionId: string): Promise<void>;
|
|
358
359
|
}
|
|
360
|
+
/** The host's authoritative current Session scope binding. */
|
|
361
|
+
export interface SidebarUiSessionService {
|
|
362
|
+
adapter: {
|
|
363
|
+
current: {
|
|
364
|
+
getSnapshot(): {
|
|
365
|
+
readonly key: string | undefined;
|
|
366
|
+
};
|
|
367
|
+
subscribe(fn: () => void): () => void;
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
}
|
|
359
371
|
/**
|
|
360
372
|
* The client locale service face (mirror of @deepseek-ai/dsh-client-locale's
|
|
361
373
|
* LocaleRuntime — only the slices the sidebar touches). The sidebar follows
|
|
@@ -475,6 +487,8 @@ export interface SidebarContextShape {
|
|
|
475
487
|
webServer: SidebarWebServer;
|
|
476
488
|
/** The session store (host `.get`) and the client list feed (`.list`) faces. */
|
|
477
489
|
sessions: SidebarSessionStore & SidebarSessionsService;
|
|
490
|
+
/** Current UI Session scope; selection no longer belongs to the Session list catalog. */
|
|
491
|
+
uiSession: SidebarUiSessionService;
|
|
478
492
|
/** The web runtime trust list (bind-derived). */
|
|
479
493
|
webRuntime: SidebarWebRuntime;
|
|
480
494
|
/** The client slot registry (register/inject). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatcode/cco-better-sidebar",
|
|
3
|
-
"version": "0.18.0-alpha.
|
|
3
|
+
"version": "0.18.0-alpha.2",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { useEffect, useRef, useState } from 'react'
|
|
11
11
|
import { api } from './api.ts'
|
|
12
12
|
import { useChatcodeAccount } from './useChatcodeAccount.ts'
|
|
13
|
+
import { redirectToChatcodeLogin } from './chatcode-login-route.ts'
|
|
13
14
|
import { t } from './locales.ts'
|
|
14
15
|
import css from './sidebar.module.css'
|
|
15
16
|
|
|
@@ -93,6 +94,10 @@ export function AccountMenu(): JSX.Element {
|
|
|
93
94
|
return () => { document.removeEventListener('pointerdown', onPointerDown) }
|
|
94
95
|
}, [])
|
|
95
96
|
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (error === 'status') redirectToChatcodeLogin()
|
|
99
|
+
}, [error])
|
|
100
|
+
|
|
96
101
|
// The host has no slot for replacing this one closed-shell control. Keep
|
|
97
102
|
// the original fully recoverable: its inline style is restored on disposal
|
|
98
103
|
// and it remains the target invoked by our Settings menu item.
|
|
@@ -127,7 +132,8 @@ export function AccountMenu(): JSX.Element {
|
|
|
127
132
|
const signedIn = chatcode?.validation === 'valid' && chatcode.expired !== true
|
|
128
133
|
// A saved credential remains usable for the next check when the company
|
|
129
134
|
// network is unavailable. Do not turn that transient state into a login CTA.
|
|
130
|
-
const
|
|
135
|
+
const hasChatcodeAccount = chatcode?.available === true && chatcode.configured === true
|
|
136
|
+
const canStartChatcodeLogin = chatcode?.available === true && !hasChatcodeAccount
|
|
131
137
|
&& !(chatcode.configured === true && chatcode.validation === 'unavailable')
|
|
132
138
|
|
|
133
139
|
return (
|
|
@@ -144,15 +150,15 @@ export function AccountMenu(): JSX.Element {
|
|
|
144
150
|
<SettingsIcon />
|
|
145
151
|
{t('accountMenuSettings')}
|
|
146
152
|
</button>
|
|
147
|
-
{error && <div role="alert" className={css.accountItem}>{t(
|
|
153
|
+
{error === 'operation' && <div role="alert" className={css.accountItem}>{t('accountMenuChatcodeError')}</div>}
|
|
148
154
|
{(chatcode?.login?.state === 'failed' || chatcode?.login?.state === 'timed-out') && <div role="alert" className={css.accountItem}>{t('accountMenuChatcodeRetry')}</div>}
|
|
149
155
|
{loginUrl && <a data-dsh-external="true" className={css.accountItem} href={loginUrl} target="_blank" rel="noopener noreferrer" onClick={event => { event.stopPropagation() }}>{t('accountMenuChatcodeOpen')}</a>}
|
|
150
156
|
{chatcode?.available === true && <>
|
|
151
157
|
{canStartChatcodeLogin && <button type="button" role="menuitem" className={css.accountItem} disabled={busy} onClick={startLogin}>
|
|
152
158
|
<LogoutIcon />{busy ? t('accountMenuChatcodeStarting') : t('accountMenuChatcodeLogin')}
|
|
153
159
|
</button>}
|
|
154
|
-
{
|
|
155
|
-
<LogoutIcon />{t('
|
|
160
|
+
{hasChatcodeAccount && <button type="button" role="menuitem" className={css.accountItem} disabled={busy} onClick={logoutChatcode}>
|
|
161
|
+
<LogoutIcon />{t('accountMenuLogout')}
|
|
156
162
|
</button>}
|
|
157
163
|
</>}
|
|
158
164
|
{chatcode?.available === false && <button type="button" role="menuitem" className={css.accountItem} onClick={logout}>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useEffect, useRef } from 'react'
|
|
3
3
|
import type { ChatCodeAuthState } from './api.ts'
|
|
4
4
|
import { useChatcodeAccount } from './useChatcodeAccount.ts'
|
|
5
|
+
import { redirectToChatcodeLogin } from './chatcode-login-route.ts'
|
|
5
6
|
|
|
6
7
|
type GateDecision = 'allow' | 'login' | 'wait'
|
|
7
8
|
|
|
@@ -28,8 +29,7 @@ export function ChatcodeGate(): JSX.Element | null {
|
|
|
28
29
|
// this visit has passed that check, a later company-network outage does
|
|
29
30
|
// not interrupt ongoing work.
|
|
30
31
|
if (chatcodeGateDecision(chatcode, verifiedThisVisit.current) !== 'login') return
|
|
31
|
-
|
|
32
|
-
window.location.replace(`/sidebar/login?return=${encodeURIComponent(back)}`)
|
|
32
|
+
redirectToChatcodeLogin()
|
|
33
33
|
}, [chatcode])
|
|
34
34
|
return null
|
|
35
35
|
}
|
package/src/client/Sidebar.tsx
CHANGED
|
@@ -64,6 +64,7 @@ import { OrphanedTab } from './OrphanedTab.tsx'
|
|
|
64
64
|
import { RenderBoundary } from './RenderBoundary.tsx'
|
|
65
65
|
import { tabContentCompare, type TabContentMemoKey } from './tab-content-memo.ts'
|
|
66
66
|
import { t } from './locales.ts'
|
|
67
|
+
import { useCurrentSessionId } from './current-session.ts'
|
|
67
68
|
import { api, type SessionScope } from './api.ts'
|
|
68
69
|
import { AccountMenu } from './AccountMenu.tsx'
|
|
69
70
|
import css from './sidebar.module.css'
|
|
@@ -322,7 +323,7 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
|
|
322
323
|
useMemo(() => (callback: () => void) => ctx.sessions.list.subscribe(callback), [ctx]),
|
|
323
324
|
useCallback(() => ctx.sessions.list.getSnapshot(), [ctx]),
|
|
324
325
|
)
|
|
325
|
-
const current =
|
|
326
|
+
const current = useCurrentSessionId(ctx)
|
|
326
327
|
|
|
327
328
|
// Per-session sidebar state.
|
|
328
329
|
const snapshot = useSyncExternalStore(
|
|
@@ -1385,6 +1386,7 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
|
|
1385
1386
|
// focus is the only way the existing Tooltip can explain what is missing.
|
|
1386
1387
|
return (
|
|
1387
1388
|
<div data-dsh-panel-host {...osFileDragShield}>
|
|
1389
|
+
<AccountMenu />
|
|
1388
1390
|
{nativeToolbarHost !== null ? createPortal(
|
|
1389
1391
|
<div className={clsx(css.toggleCluster, css.toggleClusterNative)} data-dsh-toggle-cluster data-dsh-native-toolbar-cluster>
|
|
1390
1392
|
{!narrow && (
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Build the local ChatCode login gate URL while preserving the current page. */
|
|
2
|
+
export function chatcodeLoginRoute(location: Pick<Location, 'pathname' | 'search' | 'hash'>): string {
|
|
3
|
+
const back = `${location.pathname}${location.search}${location.hash}`
|
|
4
|
+
return `/sidebar/login?return=${encodeURIComponent(back)}`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Enter the existing local login flow after account state cannot be read. */
|
|
8
|
+
export function redirectToChatcodeLogin(location: Location = window.location): void {
|
|
9
|
+
if (location.pathname === '/sidebar/login') return
|
|
10
|
+
location.replace(chatcodeLoginRoute(location))
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react'
|
|
2
|
+
import type { Context } from '../context-types.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read the session selected by the host's scoped-session adapter.
|
|
6
|
+
*
|
|
7
|
+
* Newer hosts deliberately removed `sessions.list.current`: selection is UI
|
|
8
|
+
* scope state, not catalog state. The adapter is authoritative on both old
|
|
9
|
+
* and new hosts and tracks main-view retain/release transitions.
|
|
10
|
+
*/
|
|
11
|
+
export function currentSessionId(ctx: Context): string | undefined {
|
|
12
|
+
return ctx.uiSession.adapter.current.getSnapshot().key
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** React subscription to the same authoritative current-session source. */
|
|
16
|
+
export function useCurrentSessionId(ctx: Context): string | undefined {
|
|
17
|
+
const source = ctx.uiSession.adapter.current
|
|
18
|
+
return useSyncExternalStore(
|
|
19
|
+
useCallback(listener => source.subscribe(listener), [source]),
|
|
20
|
+
useCallback(() => source.getSnapshot().key, [source]),
|
|
21
|
+
)
|
|
22
|
+
}
|
package/src/client/index.tsx
CHANGED
|
@@ -43,7 +43,7 @@ import { apply as applyUiTweaksClient } from './ui-tweaks/index.tsx'
|
|
|
43
43
|
* locale service backs the sidebar's copy — see locales.ts). `modules`
|
|
44
44
|
* (rc.8+) is the client module system the chunk loader resolves its
|
|
45
45
|
* externals through — Cordis guards service access without inject. */
|
|
46
|
-
export const inject = ['slots', 'sessions', 'workspaces', 'locale', 'modules', 'commandUi', 'conversation']
|
|
46
|
+
export const inject = ['slots', 'sessions', 'uiSession', 'workspaces', 'locale', 'modules', 'commandUi', 'conversation']
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Error boundary over the sidebar tree (root scope): a render error in the
|
package/src/client/intercept.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { firstLeaf, revealPaths, togglePanel, type SidebarStore } from './state.
|
|
|
11
11
|
import { t } from './locales.ts'
|
|
12
12
|
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
|
13
13
|
import { wrapOpenPath } from './openpath-intercept.ts'
|
|
14
|
+
import { currentSessionId } from './current-session.ts'
|
|
14
15
|
import css from './sidebar.module.css'
|
|
15
16
|
|
|
16
17
|
/** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
|
|
@@ -190,7 +191,7 @@ export function registerOpenPathInterception(ctx: Context, store: SidebarStore):
|
|
|
190
191
|
takeoverEnabled: () => !store.getSuspended()
|
|
191
192
|
&& store.getPrefs().interceptOpenPath !== false
|
|
192
193
|
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
|
193
|
-
currentSessionId: () => ctx
|
|
194
|
+
currentSessionId: () => currentSessionId(ctx),
|
|
194
195
|
openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
195
196
|
revealInExplorer: (_path, sessionId) => { revealInExplorer(ctx, store, sessionId, lastProduced) },
|
|
196
197
|
})
|
|
@@ -16,6 +16,7 @@ import { parsePrefs } from './prefs.ts'
|
|
|
16
16
|
import { t } from './locales.ts'
|
|
17
17
|
import { IconPinOutline16 } from './icons.tsx'
|
|
18
18
|
import css from './sidebar.module.css'
|
|
19
|
+
import { useCurrentSessionId } from './current-session.ts'
|
|
19
20
|
|
|
20
21
|
const PIN_SETTINGS_KEY = 'session-pins'
|
|
21
22
|
|
|
@@ -221,6 +222,7 @@ function PinnedSessions({ ctx, store, onChange }: {
|
|
|
221
222
|
onChange: (ids: readonly string[]) => void
|
|
222
223
|
}) {
|
|
223
224
|
const list = useSyncExternalStore(ctx.sessions.list.subscribe, ctx.sessions.list.getSnapshot, ctx.sessions.list.getSnapshot)
|
|
225
|
+
const currentSessionId = useCurrentSessionId(ctx)
|
|
224
226
|
const prefs = useSyncExternalStore(store.subscribe.bind(store), store.getSnapshot.bind(store), store.getSnapshot.bind(store)).prefs
|
|
225
227
|
const ids = pinnedSessionIdsOf(prefs)
|
|
226
228
|
const pinKey = ids.join('\0')
|
|
@@ -290,7 +292,7 @@ function PinnedSessions({ ctx, store, onChange }: {
|
|
|
290
292
|
<button
|
|
291
293
|
type="button"
|
|
292
294
|
className={css.pinnedSessionOpen}
|
|
293
|
-
aria-current={session.id ===
|
|
295
|
+
aria-current={session.id === currentSessionId ? 'page' : undefined}
|
|
294
296
|
onClick={() => { ctx.sessions.open?.(session.id) }}
|
|
295
297
|
>
|
|
296
298
|
<span className={css.pinnedSessionDot} data-running={session.running || undefined} aria-hidden="true" />
|
package/src/context-types.ts
CHANGED
|
@@ -306,7 +306,8 @@ export interface SidebarSessionPersistenceService {
|
|
|
306
306
|
|
|
307
307
|
/** The client session list snapshot the sidebar subscribes to. */
|
|
308
308
|
export interface SidebarSessionList {
|
|
309
|
-
current
|
|
309
|
+
/** @deprecated Selection moved to `uiSession.adapter.current`; old hosts may still expose this field. */
|
|
310
|
+
current?: string
|
|
310
311
|
byId: Record<string, SidebarSessionSummary>
|
|
311
312
|
/** Direct durable catalogs keyed by their selected parent address. */
|
|
312
313
|
subagentsByParent?: Readonly<Record<string, SidebarSubagentCatalog>>
|
|
@@ -371,6 +372,16 @@ export interface SidebarSessionsService {
|
|
|
371
372
|
refreshSubagents?(parentSessionId: string): Promise<void>
|
|
372
373
|
}
|
|
373
374
|
|
|
375
|
+
/** The host's authoritative current Session scope binding. */
|
|
376
|
+
export interface SidebarUiSessionService {
|
|
377
|
+
adapter: {
|
|
378
|
+
current: {
|
|
379
|
+
getSnapshot(): { readonly key: string | undefined }
|
|
380
|
+
subscribe(fn: () => void): () => void
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
374
385
|
/**
|
|
375
386
|
* The client locale service face (mirror of @deepseek-ai/dsh-client-locale's
|
|
376
387
|
* LocaleRuntime — only the slices the sidebar touches). The sidebar follows
|
|
@@ -494,6 +505,8 @@ export interface SidebarContextShape {
|
|
|
494
505
|
webServer: SidebarWebServer
|
|
495
506
|
/** The session store (host `.get`) and the client list feed (`.list`) faces. */
|
|
496
507
|
sessions: SidebarSessionStore & SidebarSessionsService
|
|
508
|
+
/** Current UI Session scope; selection no longer belongs to the Session list catalog. */
|
|
509
|
+
uiSession: SidebarUiSessionService
|
|
497
510
|
/** The web runtime trust list (bind-derived). */
|
|
498
511
|
webRuntime: SidebarWebRuntime
|
|
499
512
|
/** The client slot registry (register/inject). */
|