dsh-claude-desktop-theme 0.2.0 → 0.3.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.
@@ -56,6 +56,7 @@ export declare const HOST: Readonly<{
56
56
  /** SidebarRoot.module.css: expanded macOS window-chrome toggle. */
57
57
  readonly sidebarToggle: '._3WPZCG_toggle';
58
58
  readonly sidebarNewSession: '._3WPZCG_newSession';
59
+ readonly sidebarNewSessionContent: '._3WPZCG_newSessionContent';
59
60
  readonly sidebarNewSessionLabel: '._3WPZCG_newSessionLabel';
60
61
  readonly sidebarNewSessionShortcut: '._3WPZCG_newSessionShortcut';
61
62
  readonly sidebarPanelList: '._3WPZCG_panelList';
@@ -70,6 +71,22 @@ export declare const HOST: Readonly<{
70
71
  readonly browserSectionHeader: '._7514NG_sectionHeader';
71
72
  readonly browserSectionLabel: '._7514NG_sectionLabel';
72
73
  readonly browserListArea: '._7514NG_listArea';
74
+ /**
75
+ * WorkspaceBrowser.module.css: the section header's view-options trigger —
76
+ * the one control there that is an `iconButton` *and* carries `wide` (its
77
+ * neighbours are `searchButton` and a plain `iconButton`), and the list class
78
+ * its card puts on the `Menu` surface. `compat/view-options.ts` reads the
79
+ * values out of that card and clicks its rows; the card itself is kept off
80
+ * screen while it does.
81
+ */
82
+ readonly viewOptionsTrigger: '._7514NG_sectionHeader ._7514NG_iconButton._7514NG_wide';
83
+ readonly viewOptionsMenu: '._7514NG_viewOptionsMenu';
84
+ /**
85
+ * WorkspaceBrowser.module.css: one Workspace's row container — the box that
86
+ * holds its Workspace row, its nested groups and its Session rows, and the
87
+ * box `compat/empty-groups.ts` hides when the group has nothing to show.
88
+ */
89
+ readonly workspaceGroup: '._7514NG_groupSection';
73
90
  readonly settingsTrigger: '.Dws9Sa_trigger';
74
91
  readonly settingsTriggerRow: '.Dws9Sa_triggerRow';
75
92
  readonly accountTrigger: '.ZogL4G_trigger';
@@ -104,6 +121,12 @@ export declare const HOST: Readonly<{
104
121
  readonly assistantMarkdown: '.gKv1-q_root';
105
122
  readonly markdownBody: '._markdown_1ypvv_5:not([data-markdown-variant="compact"])';
106
123
  readonly markdownFileMention: '._fileMention_1ypvv_85';
124
+ /**
125
+ * MarkdownText.module.css: the category glyph the host seats inside a URL
126
+ * anchor (and inside file mentions and URL-promoted inline code). `conversation.css`
127
+ * hides the anchor's copy; the reference draws link text alone.
128
+ */
129
+ readonly markdownLinkIcon: '._linkIcon_1ypvv_94';
107
130
  readonly conversationScroll: '.ST7X_W_scrollBody';
108
131
  /** ChatView.module.css: the transcript's own scroll column, inside the page's
109
132
  scroll viewport, and the two shells of the floating jump-to-bottom control
@@ -156,6 +179,17 @@ export declare const HOST: Readonly<{
156
179
  readonly modelSelectEffort: '.cl2Rlq_triggerEffort';
157
180
  /** PermissionSelect.module.css: compact permission text, keeping its button. */
158
181
  readonly permissionSelectLabel: '.wXeviG_triggerLabel';
182
+ /** PermissionSelect.module.css: the control itself, in the status bar and in
183
+ the new-session card alike — `compat/permission-menu.ts` reads the mode's
184
+ name off it, and writes the card's own wording back. */
185
+ readonly permissionSelectTrigger: '.wXeviG_trigger';
186
+ /** PermissionSelect.module.css: the one preset badge DSH marks (`EXP`), on
187
+ the trigger as well as on the option row it belongs to. */
188
+ readonly permissionSelectBadge: '.wXeviG_badge';
189
+ /** PermissionSelect.module.css: the badge row's own name, told apart from the
190
+ badge beside it — the row label carries both, and only this one is the
191
+ preset's name. */
192
+ readonly permissionSelectOptionLabelText: '.wXeviG_optionLabelText';
159
193
  /** TeamAction.module.css: native team dialog entry, retaining its label at every width. */
160
194
  readonly teamActionTrigger: '._2tNPVa_trigger';
161
195
  readonly teamActionLabel: '._2tNPVa_triggerLabel';
@@ -169,6 +203,15 @@ export declare const HOST: Readonly<{
169
203
  readonly statsSeparator: '.OpZ85W_sep';
170
204
  readonly statsPill: '.OpZ85W_pill';
171
205
  readonly contextMeterTrigger: '.y0jqnG_trigger';
206
+ /**
207
+ * ContextMeter.module.css: the host's own click-open breakdown panel. It is a
208
+ * `position: fixed` portal on `document.body` at `z-index: 1100` rather than a
209
+ * native popover, so this plugin's panel — a top-layer popover — paints over
210
+ * it; the marker the driver writes is the belt to that braces, and it keeps
211
+ * the host's panel out of the paint if a click ever reaches DSH's own
212
+ * handler. `compat/context-panel.ts` is the only reader.
213
+ */
214
+ readonly contextMeterPanel: '.y0jqnG_panel';
172
215
  readonly heroRoot: '.bocITq_root';
173
216
  readonly heroHeadline: '.bocITq_headline';
174
217
  readonly heroWorkspaceChip: '.bocITq_workspace';
@@ -180,6 +223,20 @@ export declare const HOST: Readonly<{
180
223
  /** Every menu surface the `Menu` primitive renders, portal or in place. */
181
224
  readonly menu: '[role="menu"]';
182
225
  readonly menuItem: '[role="menuitem"]';
226
+ /** Menu.module.css: the row's own label seat, the one box this plugin puts a
227
+ second line after (`compat/permission-menu.ts`). The `_4ub78_` prefix is
228
+ the static web frontend's bundle, identical in both installers, so it needs
229
+ no entry in `host-builds.ts` — the same fact `tokens.css` records for the
230
+ primitive's check mark. */
231
+ readonly menuItemLabel: '._itemLabel_4ub78_190';
232
+ /** Menu.module.css: the trailing mark a *selected* row draws — `Menu` renders
233
+ it only for the row whose id is in `selectedIds`, so its presence is how
234
+ `compat/view-options.ts` reads which option is current. */
235
+ readonly menuItemCheck: '._check_4ub78_179';
236
+ /** Menu.module.css: the scrolling row group inside the card — where the
237
+ permission card's heading is written, above the rows and outside the
238
+ keyboard's walk (that walk collects `button`s only). */
239
+ readonly menuViewport: '._viewport_4ub78_19';
183
240
  }>;
184
241
  /**
185
242
  * Anchors that must exist before a feature may touch the host DOM. They are
@@ -220,6 +277,13 @@ export declare const ANCHOR: Readonly<{
220
277
  * with a Session, so this is the seat the no-session pet is measured from.
221
278
  */
222
279
  readonly composerCardHero: ".ST7X_W_root[data-phase=\"hero\"] [data-composer-card]";
280
+ /**
281
+ * ConversationRoot.tsx: the conversation body, carrying the Session identity
282
+ * the context panel reads its projections by. The Composer seat — and so the
283
+ * context ring — is a descendant, which is what makes `closest()` from the
284
+ * trigger enough. The host's own stable hook rather than the hashed class.
285
+ */
286
+ readonly conversationSession: '[data-conversation-session]';
223
287
  }>;
224
288
  /** One document's selector table, resolved for the build that document runs. */
225
289
  export type HostSelectors = Readonly<Record<keyof typeof HOST, string>>;
@@ -0,0 +1,88 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Names the preset a tagged row switches to; `theme/menus.css` keys the two-line cell off it. */
3
+ export declare const PERMISSION_MODE_ATTRIBUTE = "data-ccd-permission-mode";
4
+ /** Tags the card those rows sit in, so the stylesheet can reach the card itself. */
5
+ export declare const PERMISSION_MENU_ATTRIBUTE = "data-ccd-permission-menu";
6
+ /** One preset as the card draws it. */
7
+ export interface PermissionPresetCopy {
8
+ /** DSH's machine value for the preset (`read-only`, `auto`, …): the row's identity, never shown. */
9
+ readonly value: string;
10
+ /** The name DSH renders, which is what a row is recognised by. */
11
+ readonly name: string;
12
+ /** The name to draw — the same wording wherever the host's is already the one to use. */
13
+ readonly label: string;
14
+ /** The preset's trailing badge: the word DSH prints, and the word to print instead. */
15
+ readonly badge?: {
16
+ readonly host: string;
17
+ readonly text: string;
18
+ };
19
+ /** The row's second line, saying what the mode does. */
20
+ readonly description: string;
21
+ }
22
+ /** The card's copy in one language. */
23
+ export interface PermissionCopy {
24
+ /** The group's name, above the rows — the one thing the reference writes that the host does not. */
25
+ readonly heading: string;
26
+ readonly presets: readonly PermissionPresetCopy[];
27
+ }
28
+ /**
29
+ * The card's copy for the document on screen.
30
+ *
31
+ * DSH localises its own strings through `ctx.locale`; a third-party plugin may
32
+ * not borrow another package's namespace, so the second line follows the
33
+ * document language instead — the same rule `compat/header-labels.ts` follows.
34
+ *
35
+ * @param document - renderer document whose language DSH sets from the locale.
36
+ * @returns Chinese copy for a Chinese document, English otherwise.
37
+ */
38
+ export declare function permissionCopy(document: Document): PermissionCopy;
39
+ /**
40
+ * The permission picker's card, drawn the way the reference draws it.
41
+ *
42
+ * DSH's card is four flat rows of a preset's name — `仅可查看` / `工作区内修改`
43
+ * / `完全权限` / `Auto review` — one 20px line each, and the only thing that
44
+ * tells them apart is the name itself. The reference draws the same four
45
+ * choices as two-line cells: the mode's name over a sentence saying what the
46
+ * mode does, with the one flagged preset carrying a badge beside its name. The
47
+ * names are the host's and stay the host's; what this module adds is the second
48
+ * line, and its home is a node the host never renders — `optionDescription`
49
+ * exists in the permission catalog, but the picker passes it to the trigger's
50
+ * `title` and prints nothing in the row.
51
+ *
52
+ * So this is the one compat module that *writes* copy rather than rewriting
53
+ * what the host rendered, and the copy is this plugin's own: see
54
+ * `permissionCopy` above. It is kept in one table per document language, so a
55
+ * wording change is one edit in one place.
56
+ *
57
+ * Three facts decide how the rows are reached, and each is worth stating:
58
+ *
59
+ * - **A row is recognised by its name.** `Menu` rows carry no identity of their
60
+ * own — no id, no data attribute, and the preset's own glyph is a thing this
61
+ * plugin removes (`theme/menus.css` hides the icon seat) — so the name the
62
+ * host localised is the only handle there is. That is the same fact
63
+ * `compat/workspace-menu.ts` reads for its own row, and it fails safe: a
64
+ * deployment that configured its own preset names matches nothing, and its
65
+ * card keeps the host's single-line rows exactly as they are.
66
+ * - **The card is recognised by its rows.** The picker's trigger carries no
67
+ * `aria-expanded` (unlike the account trigger and the hero chips), so the
68
+ * portalled card cannot be tied back to the control that opened it; the card
69
+ * a recognised row sits in is therefore the card this module tags. A card
70
+ * none of whose rows is recognised is left whole — its heading with it, since
71
+ * a heading over four unrecognised rows names nothing.
72
+ * - **Nothing here reorders a row.** The second line is inserted as the label's
73
+ * next sibling and the heading as the row group's first child, so the
74
+ * primitive's own keyboard walk — which collects `button`s out of the group,
75
+ * not its children — steps over both, and every click target, focus order and
76
+ * accessible name the host built is left where it was. The row keeps its own
77
+ * name too: the inserted line widens what a screen reader reads out of the
78
+ * row, which is the point of the sentence.
79
+ *
80
+ * The host mounts the card afresh on every open, so every pass is idempotent
81
+ * and every write is undone on release: a disabled plugin leaves DSH's own card
82
+ * on screen.
83
+ *
84
+ * @param document - renderer document carrying the status bar and the portal.
85
+ * @param report - sink for observer failures; the native card stays untouched.
86
+ * @returns disposer that disconnects the observers and takes every write back.
87
+ */
88
+ export declare function mountPermissionMenu(document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,11 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ export interface SidebarNavigationIcons {
3
+ readonly newSession: string;
4
+ readonly plugins: string;
5
+ readonly routines: string;
6
+ }
7
+ export declare const SIDEBAR_NAV_ATTRIBUTE = "data-ccd-nav";
8
+ /** Rename and decorate the native buttons without replacing React-owned nodes
9
+ * or handlers. Every write is conditional and reversible, including delayed
10
+ * mounts, locale changes, wide/rail rerenders and the native tooltip portals. */
11
+ export declare function mountSidebarNavigation(document: Document, icons: SidebarNavigationIcons, report: (error: unknown) => void): Disposer;
@@ -7,14 +7,31 @@ export declare const TRAILING_WIDTH_PROPERTY = "--ccd-trailing-width";
7
7
  export declare const MODEL_MAX_WIDTH_PROPERTY = "--ccd-model-max-width";
8
8
  /** The one gap the trailing cluster uses, read to budget the model button. */
9
9
  export declare const CLUSTER_GAP_PROPERTY = "--ccd-cluster-gap";
10
+ /**
11
+ * Root attribute carrying whether the readouts are drawn, whose values are
12
+ * `on` and `off`.
13
+ *
14
+ * Both states are published rather than only the off one: the stylesheet that
15
+ * hides the cluster keys on `off`, so a document where this module never ran —
16
+ * a frame the observers could not attach to — keeps the host's own readouts
17
+ * instead of losing them to a stylesheet default it cannot correct.
18
+ */
19
+ export declare const STATS_ATTRIBUTE = "data-ccd-composer-stats";
10
20
  /**
11
21
  * The responsive statistics cluster sits before the model selector. Its dock is
12
22
  * a sibling of the card rather than a row child, so its wide-layout offset needs
13
23
  * the trailing group's live width. Readouts and their detail dialogs stay owned
14
24
  * by the host; wide columns mirror one short segment without changing the host label.
15
25
  *
26
+ * The cluster is the configuration's to keep or drop (`features.composer-stats`,
27
+ * off by default): its state is published on the root for the stylesheet, which
28
+ * draws it only while the readouts are on. The width measurements below are
29
+ * outside that switch — they budget the row whether or not the readouts are
30
+ * drawn, and a hidden cluster simply hands its width back to the model button.
31
+ *
16
32
  * @param document - renderer document carrying the Composer.
17
33
  * @param report - sink for observer failures; the native readouts stay.
34
+ * @param readouts - whether the configuration asks for the plugin's readouts.
18
35
  * @returns disposer that disconnects the observers and clears every property.
19
36
  */
20
- export declare function mountComposerStats(document: Document, report: (error: unknown) => void): Disposer;
37
+ export declare function mountComposerStats(document: Document, report: (error: unknown) => void, readouts: boolean): Disposer;
@@ -0,0 +1,58 @@
1
+ import type { ViewOptionsPort, ViewOptionsSettingsPort } from '../contracts/ports.ts';
2
+ /**
3
+ * The sidebar's view-options menu, driven instead of replaced.
4
+ *
5
+ * DSH's card is a `ui-primitives` `Menu` holding three labelled option groups —
6
+ * grouping mode, ordering, archived visibility — with the current option marked
7
+ * by the primitive's trailing check. The three values live in the workspace
8
+ * browser's own browser-local store, and nothing on the public face writes them:
9
+ * `UiWorkspace` (`dsh-client-ui-workspace/client` `navigation.d.ts`) publishes
10
+ * navigation, archive and directory operations only, and the store it keeps
11
+ * (`stores.d.ts`) exports its factory alone, so a second instance would hold a
12
+ * copy rather than the browser's state. The one handle that *does* write them is
13
+ * the host's own row: `WorkspaceBrowser`'s `onSelect` calls `setOpen(false)`
14
+ * after it applies the choice.
15
+ *
16
+ * So this module treats that card as the state API it effectively is. Opening
17
+ * the card reads it — the host's own menu is opened through the trigger's click
18
+ * with `DRIVING_ATTRIBUTE` keeping the sheet from drawing it — and choosing an
19
+ * option clicks the host's own row. Nothing is shown, nothing is synthesised
20
+ * beyond the clicks the host's control already answers, and the card the reader
21
+ * sees is the one `features/view-options` draws.
22
+ *
23
+ * Three details decide how the drive is written:
24
+ *
25
+ * - **The trigger's click is intercepted.** The host opens its card from the
26
+ * trigger's `onClick`; a listener on the button that stops propagation keeps
27
+ * React's delegated handler from running, so the card opens only when this
28
+ * module drives it. The one flag `driving` is what lets our own clicks
29
+ * through.
30
+ * - **React flushes a discrete event synchronously**, so the card is in the
31
+ * document by the time the driving click returns; `settle` still waits a
32
+ * microtask and then polls, because a build that mounts the card later must
33
+ * not turn into a failed read — the card is off screen either way.
34
+ * - **A card that is not the pinned shape hands the click back.** `readCard`
35
+ * refuses anything that is not three groups of at least two rows with exactly
36
+ * one check each, and the click that opened it is then passed to DSH so its
37
+ * own menu opens. A later build keeps its own interface instead of a card
38
+ * with nothing in it.
39
+ */
40
+ /** Marks the host popup this module is driving; the sheet keeps it off screen. */
41
+ export declare const DRIVING_ATTRIBUTE = "data-ccd-view-options-driving";
42
+ /** Marks the trigger while this plugin's card is the one on screen. */
43
+ export declare const OPEN_ATTRIBUTE = "data-ccd-view-options-open";
44
+ /** The driver's own face, plus the release the mounting scope holds. */
45
+ export interface ViewOptionsDriver {
46
+ readonly port: ViewOptionsPort;
47
+ readonly settings: ViewOptionsSettingsPort;
48
+ /** Read persisted native preferences without showing either menu. */
49
+ inspect(): Promise<void>;
50
+ dispose(): void;
51
+ }
52
+ /**
53
+ * Watch the sidebar's view-options trigger and drive the host's card for it.
54
+ * @param document - renderer document carrying the sidebar and the portal.
55
+ * @param report - sink for observer failures; the native menu stays in charge.
56
+ * @returns the port the card reads through, and the release of every observer.
57
+ */
58
+ export declare function createViewOptionsDriver(document: Document, report: (error: unknown) => void): ViewOptionsDriver;
@@ -1,6 +1,7 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { ThemePreference, ThemeRuntime } from '@deepseek-ai/dsh-client-ui-theme/client';
3
3
  import type { UiWorkspace } from '@deepseek-ai/dsh-client-ui-workspace/client';
4
+ import type { ContextBreakdown, ContextBreakdownView, ContextEntry } from '../../shared/context.ts';
4
5
  export type { ThemePreference };
5
6
  export type Disposer = () => void;
6
7
  /**
@@ -124,6 +125,146 @@ export interface BlankSessionsPort {
124
125
  /** Observe list replacements and row-state changes; returns the releaser. */
125
126
  subscribe(listener: () => void): Disposer;
126
127
  }
128
+ /** One group of the host's view-options menu, as the host drew it. */
129
+ export interface ViewOptionsGroupRead {
130
+ /** The heading the host drew above this group, in the document's language. */
131
+ readonly label: string;
132
+ /** Every row's own label, in the host's order. */
133
+ readonly labels: readonly string[];
134
+ /** Index of the row the host marks as current, or −1 when none is marked. */
135
+ readonly selected: number;
136
+ }
137
+ /** The host's view-options menu, read out of its own card. */
138
+ export interface ViewOptionsRead {
139
+ /** The three groups in the host's own order: group by, order by, archived filter. */
140
+ readonly groups: readonly ViewOptionsGroupRead[];
141
+ }
142
+ /** Which of the host's three groups a choice belongs to. */
143
+ export type ViewOptionsGroup = 0 | 1 | 2;
144
+ /** What the card is drawn from: whether it is up, what it hangs from, and the
145
+ host's three values as they were read when it opened. */
146
+ export interface ViewOptionsSnapshot {
147
+ readonly open: boolean;
148
+ readonly anchor: Element | null;
149
+ /** Null before the first open, and after the host's card could not be read. */
150
+ readonly values: ViewOptionsRead | null;
151
+ }
152
+ /**
153
+ * The sidebar's view-options menu, as this plugin's card needs it.
154
+ *
155
+ * DSH keeps the three view settings in the browser and publishes no way to read
156
+ * or write them, so the only handle is the host's own menu: opening this card
157
+ * reads it, and `choose` clicks one of its rows. `getSnapshot`/`subscribe`
158
+ * carry the whole of what the DOM side owns — the trigger, whether the card is
159
+ * up, and the values read for it.
160
+ */
161
+ export interface ViewOptionsPort {
162
+ /** The current state; the object identity changes only when the state does. */
163
+ getSnapshot(): ViewOptionsSnapshot;
164
+ /** Observe opening, closing, and the trigger being replaced. */
165
+ subscribe(listener: () => void): Disposer;
166
+ /** Put the card away; the trigger's own state follows. */
167
+ close(): void;
168
+ /** Choose one option by driving the host's own row; false when it could not be reached. */
169
+ choose(group: ViewOptionsGroup, index: number): Promise<boolean>;
170
+ }
171
+ /** Native view preferences also used by the sidebar's empty-group policy. */
172
+ export interface ViewOptionsSettingsPort {
173
+ getSnapshot(): ViewOptionsRead | null;
174
+ subscribe(listener: () => void): Disposer;
175
+ }
176
+ /** Authoritative membership, including sessions omitted from collapsed DOM. */
177
+ export interface SidebarSessionsPort {
178
+ /** Durable history across all groups and filters; null while loading. */
179
+ hasHistory(): boolean | null;
180
+ /** Null while either catalog is still arriving; null filter keeps all history. */
181
+ populatedGroups(filter: number | null, nested: boolean): ReadonlySet<string> | null;
182
+ subscribe(listener: () => void): Disposer;
183
+ }
184
+ /**
185
+ * One session's context readings, as the client session list holds them.
186
+ *
187
+ * The host computes all of this itself: `contextPressure` is the ring's own
188
+ * occupancy and `contextBreakdown` its three-way composition, and both travel
189
+ * to the browser as wire projections on the session list. The panel reads them
190
+ * rather than recomputing them, so its header can never disagree with the ring
191
+ * beside it.
192
+ */
193
+ export interface ContextProjectionRead {
194
+ /** `contextPressure.contextWindow`. */
195
+ readonly window: number;
196
+ /** Occupancy: `projectedTokens ?? pressureTokens`. */
197
+ readonly used: number;
198
+ /** `contextBreakdown.systemTokens`. */
199
+ readonly systemTokens: number;
200
+ /** `contextBreakdown.toolsTokens`. */
201
+ readonly toolsTokens: number;
202
+ /** `contextBreakdown.messageTokens`. */
203
+ readonly messageTokens: number;
204
+ /** This plugin's own `ccdContext` value, or null while it is not there. */
205
+ readonly breakdown: ContextBreakdownView | null;
206
+ }
207
+ /**
208
+ * The host's context projections, read off the client session list.
209
+ *
210
+ * `ctx.sessions.list` publishes every registered wire value per session
211
+ * (`SessionListState.projectionsBySession`) and carries rows for sessions that
212
+ * were never opened, so this is a plain snapshot read with no I/O. A build that
213
+ * does not publish the list yields null and the ring keeps DSH's own panel.
214
+ */
215
+ export interface ContextProjectionPort {
216
+ /** One session's readings, or null when the list has nothing for it yet. */
217
+ read(sessionId: string): ContextProjectionRead | null;
218
+ /** Observe list replacements and live projection frames. */
219
+ subscribe(listener: () => void): Disposer;
220
+ }
221
+ /** One entry of a drill-down group, as the panel draws it. */
222
+ export interface ContextDetailGroup {
223
+ /** The group's own name, in the document's language. */
224
+ readonly label: string;
225
+ /** The group's total, which is the authoritative number above it. */
226
+ readonly tokens: number;
227
+ /** The rows, largest first. */
228
+ readonly rows: readonly ContextEntry[];
229
+ }
230
+ /** What the context panel is drawn from. */
231
+ export interface ContextPanelSnapshot {
232
+ readonly open: boolean;
233
+ /** The ring trigger the panel hangs from; null before the shell renders it. */
234
+ readonly anchor: Element | null;
235
+ /** The whole reading, or null while the ring has no numbers for this session. */
236
+ readonly breakdown: ContextBreakdown | null;
237
+ /** `mcp__` tools, largest first. */
238
+ readonly mcp: readonly ContextEntry[];
239
+ /** Built-in tools, largest first. */
240
+ readonly tools: readonly ContextEntry[];
241
+ /** Retained instruction files, largest first. */
242
+ readonly files: readonly ContextEntry[];
243
+ /** Retained skill-catalog entries, largest first. */
244
+ readonly skills: readonly ContextEntry[];
245
+ /** The panel's own expansion; it lives only while the panel is open. */
246
+ readonly expanded: boolean;
247
+ }
248
+ /**
249
+ * The context ring's breakdown panel, as this plugin's card needs it.
250
+ *
251
+ * DSH opens its own 264px panel from the ring's click, and that panel is the
252
+ * whole of its own affordance — no public face reads or drives it. So this port
253
+ * owns the interaction instead: the driver intercepts the trigger's click
254
+ * (DSH's handler is React-delegated at the root, so stopping the event on the
255
+ * button keeps the host's panel from ever opening), holds the open and expanded
256
+ * state, and publishes the host's own numbers beside this plugin's rows.
257
+ */
258
+ export interface ContextPanelPort {
259
+ /** The current state; the object identity changes only when the state does. */
260
+ getSnapshot(): ContextPanelSnapshot;
261
+ /** Observe opening, closing, expanding, and the trigger being replaced. */
262
+ subscribe(listener: () => void): Disposer;
263
+ /** Put the panel away; the trigger's own state follows. */
264
+ close(): void;
265
+ /** Flip the panel between its collapsed and expanded states. */
266
+ toggleExpanded(): void;
267
+ }
127
268
  /** Actual SDK types, never a second definition of DSH component props. */
128
269
  export interface HostServices {
129
270
  readonly slots: Context['slots'];
@@ -142,6 +283,12 @@ export interface HostServices {
142
283
  * session list; a null port leaves the native rows exactly as they are.
143
284
  */
144
285
  readonly blankSessions: BlankSessionsPort | null;
286
+ readonly sidebarSessions: SidebarSessionsPort | null;
287
+ /**
288
+ * The host's context projections, or null on a build that does not publish
289
+ * the session list; a null port leaves the ring's own panel in charge.
290
+ */
291
+ readonly contextProjections: ContextProjectionPort | null;
145
292
  /** The settings transport carrying this plugin's own configuration section. */
146
293
  readonly configForms: ConfigFormsPort;
147
294
  }
@@ -0,0 +1,12 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ContextPanelPort } from '../../contracts/ports.ts';
3
+ export interface ContextPanelProps {
4
+ readonly port: ContextPanelPort;
5
+ }
6
+ /**
7
+ * The panel, in whichever of its two states the port says.
8
+ *
9
+ * @param props - the driver's port.
10
+ * @returns the card, or null while it is closed.
11
+ */
12
+ export declare function ContextPanel({ port }: ContextPanelProps): ReactElement | null;
@@ -0,0 +1,20 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { FeatureEnvironment } from '../../contracts/feature.ts';
3
+ import type { CleanupScope } from '../../core/cleanup.ts';
4
+ /**
5
+ * Put the context ring's breakdown panel in the frame-wide overlay.
6
+ *
7
+ * The panel is this plugin's own surface, so it is registered like one: a React
8
+ * entry in `shell.overlay`, holding the driver that watches the ring's trigger
9
+ * and takes its click (`compat/context-panel.ts`). Nothing here writes session
10
+ * state — the numbers are the host's own projections, read off the session list
11
+ * — so a write can never re-mount the plugin behind the panel.
12
+ *
13
+ * A build that does not publish the session list yields no port, and the ring
14
+ * keeps DSH's own panel: the feature loses a richer reading, not the control.
15
+ *
16
+ * @param ctx - client root context carrying the slot service.
17
+ * @param environment - configuration, ports and logging for this activation.
18
+ * @param cleanup - scope owning the styles, the driver and the registration.
19
+ */
20
+ export declare function mountContextPanel(ctx: Context, environment: FeatureEnvironment, cleanup: CleanupScope): void;
@@ -14,6 +14,9 @@ export declare const zh: {
14
14
  readonly 'section.features': '模块';
15
15
  readonly 'section.appearance': '外观';
16
16
  readonly 'section.fonts': '字体';
17
+ readonly 'section.view': '视图';
18
+ readonly 'field.showEmptyGroups': '显示空分组';
19
+ readonly 'hint.showEmptyGroups': '关掉时没有会话的工作区不再显示;视图选项菜单里的同一项';
17
20
  readonly 'field.enabled': '启用 CCD 风格界面';
18
21
  readonly 'field.theme': '主题';
19
22
  readonly 'theme.system': '跟随系统';
@@ -49,6 +52,10 @@ export declare const zh: {
49
52
  readonly 'feature.tool-calls': '工具调用展示';
50
53
  readonly 'feature.statistics': '统计卡片';
51
54
  readonly 'feature.composer-pet': '输入框小鲸鱼';
55
+ readonly 'feature.composer-stats': '状态栏统计读数';
56
+ readonly 'hint.composer-stats': '输出速度与缓存命中率,关掉时这两项不显示(默认)';
57
+ readonly 'feature.context-panel': '上下文分解面板';
58
+ readonly 'hint.context-panel': '点环打开的分段面板;关掉时环点开的是 DSH 原生面板';
52
59
  };
53
60
  export type SettingsKey = keyof typeof zh;
54
61
  /** English dictionary; every Chinese key has a counterpart. */
@@ -0,0 +1,6 @@
1
+ /** Approved Claude sidebar SVG artwork, bundled as trusted static markup. */
2
+ export declare const SIDEBAR_NAVIGATION_ICONS: Readonly<{
3
+ newSession: "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"claude-sidebar-icon\" data-icon=\"new\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle class=\"icon-disc\" cx=\"12\" cy=\"12\" r=\"12\" fill=\"currentColor\" fill-opacity=\"0.09\"/>\n <path d=\"M12 7V17M7 12H17\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/>\n</svg>";
4
+ plugins: "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"claude-sidebar-icon\" data-icon=\"customize\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <g class=\"customize-case\" stroke=\"currentColor\" stroke-width=\"1.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M8 7V5.2C8 4.54 8.54 4 9.2 4H14.8C15.46 4 16 4.54 16 5.2V7\"/>\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"12.5\" rx=\"1.5\"/>\n <path d=\"M3 13.5H7M9.5 13.5H14.5M17 13.5H21\"/>\n </g>\n</svg>";
5
+ routines: "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"claude-sidebar-icon\" data-icon=\"routines\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <g stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\n <path class=\"routine-long-hand\" d=\"M12 12V7.4\"/>\n <path d=\"M12 12L14.8 13.75\"/>\n </g>\n</svg>";
6
+ }>;
@@ -0,0 +1,30 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ViewOptionsPort } from '../../contracts/ports.ts';
3
+ export interface ViewOptionsCardProps {
4
+ readonly port: ViewOptionsPort;
5
+ /** `view.showEmptyGroups`: whether empty Workspaces are kept on screen. */
6
+ readonly showEmptyGroups: boolean;
7
+ /** Write the switch back through the settings transport. */
8
+ readonly setShowEmptyGroups: (value: boolean) => void;
9
+ }
10
+ /**
11
+ * The sidebar's view-options card, drawn in this plugin's own words and geometry.
12
+ *
13
+ * DSH's own menu is a flat list of eight options under three grey headings. The
14
+ * reference draws the same settings as *value rows* — the dimension on the left,
15
+ * the current choice on the right, a disclosure mark at the end — with the
16
+ * options one level down in a card of their own, and the switch and the action
17
+ * that belong to the family at the bottom. So this card is that shape, and the
18
+ * host's menu is only ever read (`port.read`) and clicked (`port.choose`): the
19
+ * three values are the host's, its rows are what writes them, and the words of
20
+ * two of the three rows are the host's own — the card borrows the labels it read
21
+ * out of the host's card and renames only the archived-visibility triple, which
22
+ * is the one set the reference names differently (活跃 / 已归档 / 全部).
23
+ *
24
+ * Only `显示空分组` is this plugin's own state: it writes
25
+ * `view.showEmptyGroups` through the settings transport, and a write re-mounts
26
+ * the plugin, so the card closes behind it. `清除筛选` puts the filter rows back
27
+ * to their defaults and exists only while one of them is off its default, which
28
+ * is what the reference does with `Clear filters`.
29
+ */
30
+ export declare function ViewOptionsCard({ port, showEmptyGroups, setShowEmptyGroups, }: ViewOptionsCardProps): ReactElement | null;
@@ -0,0 +1,25 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { FeatureEnvironment } from '../../contracts/feature.ts';
3
+ import type { CleanupScope } from '../../core/cleanup.ts';
4
+ import type { ViewOptionsSettingsPort } from '../../contracts/ports.ts';
5
+ /**
6
+ * Put the sidebar's view-options card in the frame-wide overlay.
7
+ *
8
+ * The card is this plugin's own surface, so it is registered like one: a React
9
+ * entry in `shell.overlay`, holding the driver that watches the host's trigger
10
+ * and drives the host's own menu (`compat/view-options.ts`). The one value the
11
+ * card writes is `view.showEmptyGroups`, and it goes through the settings
12
+ * transport the same way the configuration page writes its fields — a write
13
+ * re-mounts the plugin, which is why the card closes behind the switch.
14
+ *
15
+ * Nothing here fails the interface if the trigger is missing: the driver reports
16
+ * a card it cannot read and hands the click back to DSH, whose own menu opens.
17
+ *
18
+ * @param ctx - client root context carrying the slot service.
19
+ * @param environment - configuration, ports and logging for this activation.
20
+ * @param cleanup - scope owning the styles, the driver and the registration.
21
+ */
22
+ export declare function mountViewOptions(ctx: Context, environment: FeatureEnvironment, cleanup: CleanupScope): {
23
+ settings: ViewOptionsSettingsPort;
24
+ showAllSessions(): void;
25
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The autocompaction policy this deployment actually runs.
3
+ *
4
+ * The panel's last row says how much of the window the compaction policy is
5
+ * holding back, and that number belongs to another plugin's configuration. DSH
6
+ * publishes no read face for it, so the loader's own entry tree is the source:
7
+ * an entry that is enabled *and* declares the policy's own fields
8
+ * (`thresholdRatio` / `headroomTokens`) is the backend that will compact this
9
+ * session, and an enabled `compaction-basic` with an empty config is that
10
+ * backend running on its defaults.
11
+ *
12
+ * A deployment with no such entry — which is this plugin's own machine, where
13
+ * `compaction-basic` is `disabled: true` — yields null, and the panel leaves
14
+ * the row out rather than drawing a threshold nobody enforces.
15
+ *
16
+ * @module
17
+ */
18
+ import type { Context } from '@deepseek-ai/cordis';
19
+ import type { CompactionPolicy } from './unit.ts';
20
+ /**
21
+ * The policy that will compact a session on this deployment.
22
+ *
23
+ * @param ctx - host plugin context carrying the loader.
24
+ * @returns the policy, or null when no enabled backend declares one.
25
+ */
26
+ export declare function readCompactionPolicy(ctx: Context): CompactionPolicy | null;