@chatcode/cco-better-sidebar 0.18.0-alpha.2 → 0.18.0-alpha.3

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.
@@ -4,6 +4,8 @@ import type { SidebarStore } from './state.ts';
4
4
  export declare function pinnedSessionIdsOf(value: {
5
5
  pluginSettings: Record<string, Record<string, unknown>>;
6
6
  }): string[];
7
+ /** Open through the Host's navigation policy, not the pure Session catalog. */
8
+ export declare function openPinnedSession(ctx: Context, sessionId: string): void;
7
9
  /**
8
10
  * Mount the separate pinned group and append a fourth item to DSH's native
9
11
  * three-item row menu. The group is inserted beside (not inside) the host
@@ -15,6 +15,11 @@ export declare const USAGE_NAV_MARKER = "data-dsh-better-sidebar-usage-nav";
15
15
  export declare const AI_RATIO_NAV_MARKER = "data-dsh-better-sidebar-ai-ratio-nav";
16
16
  /** Marker of the "更新" (update) settings nav row. */
17
17
  export declare const UPDATE_NAV_MARKER = "data-dsh-better-sidebar-update-nav";
18
+ /** Fallback markers for host/plugin settings rows whose icons mount late. */
19
+ export declare const GENERAL_NAV_MARKER = "data-dsh-better-sidebar-general-nav";
20
+ export declare const PLUGIN_MARKET_NAV_MARKER = "data-dsh-better-sidebar-plugin-market-nav";
21
+ export declare const UI_TWEAKS_NAV_MARKER = "data-dsh-better-sidebar-ui-tweaks-nav";
22
+ export declare const SKILL_MARKET_NAV_MARKER = "data-dsh-better-sidebar-skill-market-nav";
18
23
  /**
19
24
  * Keep the marker on the settings-nav button whose visible text is this
20
25
  * plugin's current localized section label.
@@ -24,4 +29,4 @@ export declare const UPDATE_NAV_MARKER = "data-dsh-better-sidebar-update-nav";
24
29
  * other's markers).
25
30
  * @returns disposer that disconnects observation and removes owned markers.
26
31
  */
27
- export declare function registerSettingsNavIcon(label: () => string, marker?: string): () => void;
32
+ export declare function registerSettingsNavIcon(label: () => string | readonly string[], marker?: string): () => void;
@@ -40,6 +40,6 @@ export declare function SidebarProjectLogo({ size }: {
40
40
  }): ReactNode;
41
41
  /** Expanded-sidebar wordmark supplied through the host's dedicated slot. */
42
42
  export declare function SidebarProjectName(): ReactNode;
43
- export declare const BRANDING_CSS = "\n/* Keep the light-theme type hierarchy in ChatCode's restrained red family,\n while leaving every surface and interaction state (including workspace /\n sidebar hover and selected fills, menus, the composer and shadows) owned\n by the host theme. */\nbody:not([data-ds-dark-theme]){\n --dsw-alias-label-primary:#301111;\n --dsw-alias-label-secondary:#754745;\n --dsw-alias-label-tertiary:#a77471;\n}\n/* Saturated brand red remains exclusive to explicitly marked primary actions. */\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action{background:linear-gradient(135deg,#9B0B09,#b71916) !important;border-color:#9B0B09 !important;color:#fff !important;box-shadow:0 7px 18px rgba(155,11,9,.24) !important}\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action:hover{background:linear-gradient(135deg,#b71916,#cf302c) !important;border-color:#b71916 !important;color:#fff !important;box-shadow:0 10px 24px rgba(155,11,9,.30) !important}\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action:active{transform:translateY(1px);box-shadow:0 4px 11px rgba(155,11,9,.22) !important}\n/* In dark mode, alter only the resting background of primary buttons. Once\n hovered, the host owns the complete interaction treatment again. */\nbody[data-ds-dark-theme] .dut-unicom-primary-action:not(:hover){background:#9B0B09 !important}\n.dut-project-logo{display:block;object-fit:contain}\n.dut-left-logo{width:44px !important;height:44px !important}\n.dut-brand-title{font-size:18px !important;font-weight:600 !important}\n.dut-sidebar-logo{width:44px !important;height:44px !important}\n/* The alpha sidebar keeps its slotted identity at 24px and clips the brand\n button. Give only our expanded identity the full artwork height so the\n 44px knot is not cropped by that host-owned overflow boundary. */\n[class*=\"brandIdentity\"]:has(.dut-sidebar-logo){height:44px !important}\n.dut-sidebar-brand-name{font-size:18px !important;line-height:22px !important;font-weight:600 !important;letter-spacing:0 !important;white-space:nowrap}\n.dut-brand-version{display:none !important}\n.dut-hero-preview{display:none !important}\n.dut-hero-logo{width:60px !important;height:60px !important}\n[class*=\"headline\"]:has(.dut-hero-logo){grid-template-columns:60px auto !important;font-size:26px !important;line-height:34px !important}\n.dut-settings-logo{width:19px;height:19px;object-fit:contain}\n";
43
+ export declare const BRANDING_CSS = "\n/* ChatCode CLI branding is deliberately limited to primary actions. Every\n other surface and interaction state (including workspace/sidebar rows,\n menus, the composer, text and shadows) remains owned by the host theme. */\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action{background:linear-gradient(135deg,#9B0B09,#b71916) !important;border-color:#9B0B09 !important;color:#fff !important;box-shadow:0 7px 18px rgba(155,11,9,.24) !important}\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action:hover{background:linear-gradient(135deg,#b71916,#cf302c) !important;border-color:#b71916 !important;color:#fff !important;box-shadow:0 10px 24px rgba(155,11,9,.30) !important}\nbody:not([data-ds-dark-theme]) .dut-unicom-primary-action:active{transform:translateY(1px);box-shadow:0 4px 11px rgba(155,11,9,.22) !important}\n/* In dark mode, alter only the resting background of primary buttons. Once\n hovered, the host owns the complete interaction treatment again. */\nbody[data-ds-dark-theme] .dut-unicom-primary-action:not(:hover){background:#9B0B09 !important}\n.dut-project-logo{display:block;object-fit:contain}\n.dut-left-logo{width:44px !important;height:44px !important}\n.dut-brand-title{font-size:18px !important;font-weight:600 !important}\n.dut-sidebar-logo{width:44px !important;height:44px !important}\n/* The alpha sidebar keeps its slotted identity at 24px and clips the brand\n button. Give only our expanded identity the full artwork height so the\n 44px knot is not cropped by that host-owned overflow boundary. */\n[class*=\"brandIdentity\"]:has(.dut-sidebar-logo){height:44px !important}\n.dut-sidebar-brand-name{font-size:18px !important;line-height:22px !important;font-weight:600 !important;letter-spacing:0 !important;white-space:nowrap}\n.dut-brand-version{display:none !important}\n.dut-hero-preview{display:none !important}\n.dut-hero-logo{width:60px !important;height:60px !important}\n.dut-hero-title{font-size:30px !important;line-height:40px !important;font-weight:600 !important;letter-spacing:.06em !important;white-space:nowrap}\n[class*=\"headline\"]:has(.dut-hero-logo){grid-template-columns:60px auto !important;font-size:30px !important;line-height:40px !important}\n.dut-settings-logo{width:19px;height:19px;object-fit:contain}\n";
44
44
  /** Install the brand theme and retain it across host navigation/rerenders. */
45
45
  export declare function installBranding(): () => void;
@@ -307,11 +307,6 @@ export interface SidebarSessionsService {
307
307
  getSnapshot(): SidebarSessionList;
308
308
  subscribe(fn: () => void): () => void;
309
309
  };
310
- /**
311
- * Select a listed session as current (mirror of the runtime ISessions.open)
312
- * — used to jump back to the main agent from the topology root node.
313
- */
314
- open?(id: string): void;
315
310
  /**
316
311
  * Fork a session from a completed-turn prefix of the source and resolve
317
312
  * the child session id (mirror of the runtime ISessions.fork — throws on
@@ -413,6 +408,15 @@ export interface SidebarWorkspacesService {
413
408
  /** Open a filesystem path with the Host operating system's default application. */
414
409
  openPath(path: string): Promise<void>;
415
410
  }
411
+ /**
412
+ * The client Workspace navigation face. Session selection is a UI navigation
413
+ * operation in current DSH builds: it retains the target for the main view,
414
+ * updates the scoped Session adapter, and reveals the conversation panel.
415
+ */
416
+ export interface SidebarUiWorkspaceService {
417
+ /** Select a Session and show its Conversation, including persisted history. */
418
+ openSession(sessionId: string): void;
419
+ }
416
420
  /**
417
421
  * The invariant service face (mirror of @deepseek-ai/dsh-invariants'
418
422
  * InvariantRegistry). The upstream augmentation does not reach this Context
@@ -495,6 +499,8 @@ export interface SidebarContextShape {
495
499
  slots: SidebarSlotsService;
496
500
  /** The client workspaces service face (file-open funnel). */
497
501
  workspaces: SidebarWorkspacesService;
502
+ /** The client Workspace navigation service (session-open funnel). */
503
+ uiWorkspace: SidebarUiWorkspaceService;
498
504
  /** The settings service face (prefs persistence + namespace reads). */
499
505
  settings: SidebarSettingsService;
500
506
  /** The invariant registry face. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatcode/cco-better-sidebar",
3
- "version": "0.18.0-alpha.2",
3
+ "version": "0.18.0-alpha.3",
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": {
@@ -22,7 +22,16 @@ import { RenderBoundary } from './RenderBoundary.tsx'
22
22
  import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
23
23
  import { registerLinkInterception } from './link-intercept.ts'
24
24
  import { registerImeGuard } from './ime-guard.ts'
25
- import { AI_RATIO_NAV_MARKER, registerSettingsNavIcon, UPDATE_NAV_MARKER, USAGE_NAV_MARKER } from './settings-nav-icon.ts'
25
+ import {
26
+ AI_RATIO_NAV_MARKER,
27
+ GENERAL_NAV_MARKER,
28
+ PLUGIN_MARKET_NAV_MARKER,
29
+ registerSettingsNavIcon,
30
+ SKILL_MARKET_NAV_MARKER,
31
+ UI_TWEAKS_NAV_MARKER,
32
+ UPDATE_NAV_MARKER,
33
+ USAGE_NAV_MARKER,
34
+ } from './settings-nav-icon.ts'
26
35
  import { installSessionPins } from './session-pins.tsx'
27
36
  import { registerChatPresentationCompatibility } from './chat-presentation.ts'
28
37
  import { loadExternalDisable, loadPrefs } from './prefs.ts'
@@ -43,7 +52,7 @@ import { apply as applyUiTweaksClient } from './ui-tweaks/index.tsx'
43
52
  * locale service backs the sidebar's copy — see locales.ts). `modules`
44
53
  * (rc.8+) is the client module system the chunk loader resolves its
45
54
  * externals through — Cordis guards service access without inject. */
46
- export const inject = ['slots', 'sessions', 'uiSession', 'workspaces', 'locale', 'modules', 'commandUi', 'conversation']
55
+ export const inject = ['slots', 'sessions', 'uiSession', 'uiWorkspace', 'workspaces', 'locale', 'modules', 'commandUi', 'conversation']
47
56
 
48
57
  /**
49
58
  * Error boundary over the sidebar tree (root scope): a render error in the
@@ -455,6 +464,22 @@ export function apply(ctx: Context): void {
455
464
  () => registerSettingsNavIcon(() => t('updateNav'), UPDATE_NAV_MARKER),
456
465
  'dsh-better-sidebar: update settings navigation icon',
457
466
  )
467
+ ctx.effect(
468
+ () => registerSettingsNavIcon(() => ['通用设置', 'General'], GENERAL_NAV_MARKER),
469
+ 'dsh-better-sidebar: general settings navigation icon fallback',
470
+ )
471
+ ctx.effect(
472
+ () => registerSettingsNavIcon(() => ['插件市场', 'Plugin marketplace', 'Plugin Market'], PLUGIN_MARKET_NAV_MARKER),
473
+ 'dsh-better-sidebar: plugin market navigation icon fallback',
474
+ )
475
+ ctx.effect(
476
+ () => registerSettingsNavIcon(() => ['界面调整', 'UI Tweaks'], UI_TWEAKS_NAV_MARKER),
477
+ 'dsh-better-sidebar: UI tweaks navigation icon fallback',
478
+ )
479
+ ctx.effect(
480
+ () => registerSettingsNavIcon(() => ['技能市场', 'Skill marketplace', 'Skill Market'], SKILL_MARKET_NAV_MARKER),
481
+ 'dsh-better-sidebar: skill market navigation icon fallback',
482
+ )
458
483
 
459
484
  // The "Side card" settings section: appears in the DSH Settings shell
460
485
  // once the shell's declaration is on the ledger (slots.inject waits for
@@ -94,6 +94,48 @@ body[data-dsh-sidebar-dragging] #root :has(> [data-slot="conversation"]) {
94
94
  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;
95
95
  }
96
96
 
97
+ /* Some host/plugin settings rows receive their SVG only after the global
98
+ plugin group is expanded. Paint stable fallbacks immediately so the nav
99
+ never opens with blank icon slots; a late host SVG is hidden to avoid a
100
+ duplicate. */
101
+ [data-dsh-better-sidebar-general-nav] > svg:first-child,
102
+ [data-dsh-better-sidebar-plugin-market-nav] > svg:first-child,
103
+ [data-dsh-better-sidebar-ui-tweaks-nav] > svg:first-child,
104
+ [data-dsh-better-sidebar-skill-market-nav] > svg:first-child {
105
+ display: none;
106
+ }
107
+
108
+ [data-dsh-better-sidebar-general-nav]::before,
109
+ [data-dsh-better-sidebar-plugin-market-nav]::before,
110
+ [data-dsh-better-sidebar-ui-tweaks-nav]::before,
111
+ [data-dsh-better-sidebar-skill-market-nav]::before {
112
+ content: '';
113
+ flex: none;
114
+ width: 16px;
115
+ height: 16px;
116
+ background: currentColor;
117
+ }
118
+
119
+ [data-dsh-better-sidebar-general-nav]::before {
120
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9 7 7M17 17l2.1 2.1M19.1 4.9 17 7M7 17l-2.1 2.1'/%3E%3C/svg%3E") center / contain no-repeat;
121
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9 7 7M17 17l2.1 2.1M19.1 4.9 17 7M7 17l-2.1 2.1'/%3E%3C/svg%3E") center / contain no-repeat;
122
+ }
123
+
124
+ [data-dsh-better-sidebar-plugin-market-nav]::before {
125
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7' rx='1'/%3E%3Crect x='14' y='3' width='7' height='7' rx='1'/%3E%3Crect x='3' y='14' width='7' height='7' rx='1'/%3E%3Crect x='14' y='14' width='7' height='7' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
126
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7' rx='1'/%3E%3Crect x='14' y='3' width='7' height='7' rx='1'/%3E%3Crect x='3' y='14' width='7' height='7' rx='1'/%3E%3Crect x='14' y='14' width='7' height='7' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
127
+ }
128
+
129
+ [data-dsh-better-sidebar-ui-tweaks-nav]::before {
130
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6'/%3E%3C/svg%3E") center / contain no-repeat;
131
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6'/%3E%3C/svg%3E") center / contain no-repeat;
132
+ }
133
+
134
+ [data-dsh-better-sidebar-skill-market-nav]::before {
135
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 3-1.7 4.3L6 9l4.3 1.7L12 15l1.7-4.3L18 9l-4.3-1.7Z'/%3E%3Cpath d='m5 15-.8 2.2L2 18l2.2.8L5 21l.8-2.2L8 18l-2.2-.8Z'/%3E%3Cpath d='m19 14-1 2.5-2.5 1L18 18.5l1 2.5 1-2.5 2.5-1-2.5-1Z'/%3E%3C/svg%3E") center / contain no-repeat;
136
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 3-1.7 4.3L6 9l4.3 1.7L12 15l1.7-4.3L18 9l-4.3-1.7Z'/%3E%3Cpath d='m5 15-.8 2.2L2 18l2.2.8L5 21l.8-2.2L8 18l-2.2-.8Z'/%3E%3Cpath d='m19 14-1 2.5-2.5 1L18 18.5l1 2.5 1-2.5 2.5-1-2.5-1Z'/%3E%3C/svg%3E") center / contain no-repeat;
137
+ }
138
+
97
139
  /* The "Token 用量" heatmap section's nav row: same marker scheme as above,
98
140
  painting a calendar glyph (Lucide calendar-days) — a month of days is the
99
141
  section's whole subject. */
@@ -24,7 +24,7 @@ export const ja: Record<string, string> = {
24
24
  updateSettingsDesc: 'ChatCode の CLI 版を npm の最新版へ更新します。',
25
25
  updateChecking: 'バージョン更新を確認しています…',
26
26
  updatePerforming: '最新バージョンへ更新しています…',
27
- updateUpToDate: 'すべて最新です。',
27
+ updateUpToDate: 'すでに最新です。',
28
28
  updateNow: '今すぐ更新',
29
29
  updateConfirm: 'これらのパッケージを最新版へ更新します。反映には再起動が必要です。',
30
30
  updateRestartHint: '更新が完了しました。ChatCode CLI を再起動してください。',
@@ -31,7 +31,7 @@ export const zh = {
31
31
  updateSettingsDesc: '将 ChatCode CLI 端升级到 npm 最新版。',
32
32
  updateChecking: '正在检查版本更新…',
33
33
  updatePerforming: '正在升级到最新版本…',
34
- updateUpToDate: '全部已是最新版本。',
34
+ updateUpToDate: '已是最新版本。',
35
35
  updateNow: '立即更新',
36
36
  updateConfirm: '将把这些包升级到最新版本,完成后需要重启才能生效。',
37
37
  updateRestartHint: '更新成功,请重启 ChatCode CLI 以使用新版本。',
@@ -502,7 +502,7 @@ export const en: Record<keyof typeof zh, string> = {
502
502
  updateSettingsDesc: 'Upgrade the ChatCode CLI to the latest npm version.',
503
503
  updateChecking: 'Checking for version updates…',
504
504
  updatePerforming: 'Upgrading to the latest versions…',
505
- updateUpToDate: 'Everything is already up to date.',
505
+ updateUpToDate: 'Already up to date.',
506
506
  updateNow: 'Update now',
507
507
  updateConfirm: 'These packages will be upgraded to the latest versions. A restart is required to apply them.',
508
508
  updateRestartHint: 'Update complete. Restart ChatCode CLI to use the new version.',
@@ -36,6 +36,11 @@ function pinSettings(ids: readonly string[]): Record<string, unknown> {
36
36
  return { ids: [...ids] }
37
37
  }
38
38
 
39
+ /** Open through the Host's navigation policy, not the pure Session catalog. */
40
+ export function openPinnedSession(ctx: Context, sessionId: string): void {
41
+ ctx.uiWorkspace.openSession(sessionId)
42
+ }
43
+
39
44
  /** A small current-color pin icon for the menu row injected into DSH's menu. */
40
45
  function pinIconMarkup(): string {
41
46
  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>'
@@ -258,7 +263,7 @@ function PinnedSessions({ ctx, store, onChange }: {
258
263
  const forkSession = (sessionId: string): void => {
259
264
  closeMenu()
260
265
  void ctx.sessions.fork?.({ sessionId, increaseTitle: true }).then((childId) => {
261
- ctx.sessions.open?.(childId)
266
+ openPinnedSession(ctx, childId)
262
267
  }).catch((error: unknown) => {
263
268
  console.warn('[dsh-better-sidebar] fork pinned session failed:', error)
264
269
  })
@@ -293,7 +298,7 @@ function PinnedSessions({ ctx, store, onChange }: {
293
298
  type="button"
294
299
  className={css.pinnedSessionOpen}
295
300
  aria-current={session.id === currentSessionId ? 'page' : undefined}
296
- onClick={() => { ctx.sessions.open?.(session.id) }}
301
+ onClick={() => { openPinnedSession(ctx, session.id) }}
297
302
  >
298
303
  <span className={css.pinnedSessionDot} data-running={session.running || undefined} aria-hidden="true" />
299
304
  <span className={css.pinnedSessionTitle}>{session.displayTitle}</span>
@@ -19,6 +19,12 @@ export const AI_RATIO_NAV_MARKER = 'data-dsh-better-sidebar-ai-ratio-nav'
19
19
  /** Marker of the "更新" (update) settings nav row. */
20
20
  export const UPDATE_NAV_MARKER = 'data-dsh-better-sidebar-update-nav'
21
21
 
22
+ /** Fallback markers for host/plugin settings rows whose icons mount late. */
23
+ export const GENERAL_NAV_MARKER = 'data-dsh-better-sidebar-general-nav'
24
+ export const PLUGIN_MARKET_NAV_MARKER = 'data-dsh-better-sidebar-plugin-market-nav'
25
+ export const UI_TWEAKS_NAV_MARKER = 'data-dsh-better-sidebar-ui-tweaks-nav'
26
+ export const SKILL_MARKET_NAV_MARKER = 'data-dsh-better-sidebar-skill-market-nav'
27
+
22
28
  /**
23
29
  * Keep the marker on the settings-nav button whose visible text is this
24
30
  * plugin's current localized section label.
@@ -28,15 +34,16 @@ export const UPDATE_NAV_MARKER = 'data-dsh-better-sidebar-update-nav'
28
34
  * other's markers).
29
35
  * @returns disposer that disconnects observation and removes owned markers.
30
36
  */
31
- export function registerSettingsNavIcon(label: () => string, marker: string = SETTINGS_NAV_MARKER): () => void {
37
+ export function registerSettingsNavIcon(label: () => string | readonly string[], marker: string = SETTINGS_NAV_MARKER): () => void {
32
38
  let disposed = false
33
39
 
34
40
  const sync = (): void => {
35
41
  if (disposed) return
36
- const currentLabel = label().trim()
42
+ const resolved = label()
43
+ const currentLabels = new Set((typeof resolved === 'string' ? [resolved] : resolved).map(value => value.trim()).filter(Boolean))
37
44
  const buttons = document.querySelectorAll<HTMLButtonElement>('[role="dialog"] nav button')
38
45
  for (const button of buttons) {
39
- const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel
46
+ const matches = currentLabels.has(button.textContent?.trim() ?? '')
40
47
  if (matches) button.setAttribute(marker, '')
41
48
  else button.removeAttribute(marker)
42
49
  }
@@ -27,6 +27,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
27
27
  /** Browser URL for the project-owned `assets/logo.svg` file. */
28
28
  export const PROJECT_LOGO_URL = '/_dsh/ui-tweaks/assets/logo.svg'
29
29
  const PROJECT_NAME = 'ChatCode CLI'
30
+ const HERO_TITLE = '与世界,深一度对话'
30
31
  const LEGACY_HOST_BRAND_TEXT = /DSH\s*(?:Local\s*Build|本地构建)|ChatCode\s*CLI/giu
31
32
  const HOST_BRAND_LABEL_TEXT = /^(?:DSH\s*(?:Local\s*Build|本地构建)|ChatCode\s*CLI)$/iu
32
33
  const HOST_BUILD_VERSION_TEXT = /^v?\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)*$/iu
@@ -57,16 +58,9 @@ export function SidebarProjectName(): ReactNode {
57
58
  }
58
59
 
59
60
  export const BRANDING_CSS = `
60
- /* Keep the light-theme type hierarchy in ChatCode's restrained red family,
61
- while leaving every surface and interaction state (including workspace /
62
- sidebar hover and selected fills, menus, the composer and shadows) owned
63
- by the host theme. */
64
- body:not([data-ds-dark-theme]){
65
- --dsw-alias-label-primary:#301111;
66
- --dsw-alias-label-secondary:#754745;
67
- --dsw-alias-label-tertiary:#a77471;
68
- }
69
- /* Saturated brand red remains exclusive to explicitly marked primary actions. */
61
+ /* ChatCode CLI branding is deliberately limited to primary actions. Every
62
+ other surface and interaction state (including workspace/sidebar rows,
63
+ menus, the composer, text and shadows) remains owned by the host theme. */
70
64
  body:not([data-ds-dark-theme]) .dut-unicom-primary-action{background:linear-gradient(135deg,#9B0B09,#b71916) !important;border-color:#9B0B09 !important;color:#fff !important;box-shadow:0 7px 18px rgba(155,11,9,.24) !important}
71
65
  body:not([data-ds-dark-theme]) .dut-unicom-primary-action:hover{background:linear-gradient(135deg,#b71916,#cf302c) !important;border-color:#b71916 !important;color:#fff !important;box-shadow:0 10px 24px rgba(155,11,9,.30) !important}
72
66
  body:not([data-ds-dark-theme]) .dut-unicom-primary-action:active{transform:translateY(1px);box-shadow:0 4px 11px rgba(155,11,9,.22) !important}
@@ -85,7 +79,8 @@ body[data-ds-dark-theme] .dut-unicom-primary-action:not(:hover){background:#9B0B
85
79
  .dut-brand-version{display:none !important}
86
80
  .dut-hero-preview{display:none !important}
87
81
  .dut-hero-logo{width:60px !important;height:60px !important}
88
- [class*="headline"]:has(.dut-hero-logo){grid-template-columns:60px auto !important;font-size:26px !important;line-height:34px !important}
82
+ .dut-hero-title{font-size:30px !important;line-height:40px !important;font-weight:600 !important;letter-spacing:.06em !important;white-space:nowrap}
83
+ [class*="headline"]:has(.dut-hero-logo){grid-template-columns:60px auto !important;font-size:30px !important;line-height:40px !important}
89
84
  .dut-settings-logo{width:19px;height:19px;object-fit:contain}
90
85
  `
91
86
 
@@ -176,6 +171,17 @@ function syncBranding(): void {
176
171
  if (HERO_HEADLINE.test(node.data)) node.data = node.data.replace(HERO_HEADLINE, PROJECT_NAME)
177
172
  }
178
173
 
174
+ // Keep the product name in the sidebar, but use a more expressive headline
175
+ // above the new-session composer.
176
+ document.querySelectorAll<HTMLElement>('.dut-hero-logo').forEach((logo) => {
177
+ const headline = logo.closest<HTMLElement>('[class*="headline"]')
178
+ const title = Array.from(headline?.querySelectorAll<HTMLElement>('*') ?? [])
179
+ .find(element => element.childElementCount === 0 && element.textContent?.trim() === PROJECT_NAME)
180
+ if (title === undefined) return
181
+ title.textContent = HERO_TITLE
182
+ title.classList.add('dut-hero-title')
183
+ })
184
+
179
185
  // Older host builds expose a brand-mark wrapper but no text-bearing sibling.
180
186
  document.querySelectorAll<HTMLElement>('[class*="brandMark"]').forEach((host) => {
181
187
  const svg = host.matches('svg') ? host : host.querySelector('svg')
@@ -325,11 +325,6 @@ export interface SidebarSessionsService {
325
325
  getSnapshot(): SidebarSessionList
326
326
  subscribe(fn: () => void): () => void
327
327
  }
328
- /**
329
- * Select a listed session as current (mirror of the runtime ISessions.open)
330
- * — used to jump back to the main agent from the topology root node.
331
- */
332
- open?(id: string): void
333
328
  /**
334
329
  * Fork a session from a completed-turn prefix of the source and resolve
335
330
  * the child session id (mirror of the runtime ISessions.fork — throws on
@@ -427,6 +422,16 @@ export interface SidebarWorkspacesService {
427
422
  openPath(path: string): Promise<void>
428
423
  }
429
424
 
425
+ /**
426
+ * The client Workspace navigation face. Session selection is a UI navigation
427
+ * operation in current DSH builds: it retains the target for the main view,
428
+ * updates the scoped Session adapter, and reveals the conversation panel.
429
+ */
430
+ export interface SidebarUiWorkspaceService {
431
+ /** Select a Session and show its Conversation, including persisted history. */
432
+ openSession(sessionId: string): void
433
+ }
434
+
430
435
  /**
431
436
  * The invariant service face (mirror of @deepseek-ai/dsh-invariants'
432
437
  * InvariantRegistry). The upstream augmentation does not reach this Context
@@ -513,6 +518,8 @@ export interface SidebarContextShape {
513
518
  slots: SidebarSlotsService
514
519
  /** The client workspaces service face (file-open funnel). */
515
520
  workspaces: SidebarWorkspacesService
521
+ /** The client Workspace navigation service (session-open funnel). */
522
+ uiWorkspace: SidebarUiWorkspaceService
516
523
  /** The settings service face (prefs persistence + namespace reads). */
517
524
  settings: SidebarSettingsService
518
525
  /** The invariant registry face. */