dsh-claude-desktop-theme 0.2.0 → 0.4.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.
Files changed (50) hide show
  1. package/README.md +76 -54
  2. package/lib/client.js +9392 -2579
  3. package/lib/index.js +387 -20
  4. package/lib/types/client/compat/account-menu.d.ts +4 -4
  5. package/lib/types/client/compat/account-more.d.ts +3 -0
  6. package/lib/types/client/compat/account-theme.d.ts +3 -0
  7. package/lib/types/client/compat/adapter.d.ts +22 -1
  8. package/lib/types/client/compat/browser-header.d.ts +57 -0
  9. package/lib/types/client/compat/browser-menu.d.ts +16 -0
  10. package/lib/types/client/compat/composer-menus.d.ts +6 -3
  11. package/lib/types/client/compat/context-panel.d.ts +47 -0
  12. package/lib/types/client/compat/empty-groups.d.ts +46 -0
  13. package/lib/types/client/compat/files-pane.d.ts +78 -0
  14. package/lib/types/client/compat/header-menu-actions.d.ts +14 -0
  15. package/lib/types/client/compat/header-menus.d.ts +4 -0
  16. package/lib/types/client/compat/host-dom.d.ts +180 -0
  17. package/lib/types/client/compat/pane-chrome.d.ts +29 -0
  18. package/lib/types/client/compat/pane-icons.d.ts +23 -0
  19. package/lib/types/client/compat/pane-tiles.d.ts +37 -0
  20. package/lib/types/client/compat/permission-menu.d.ts +88 -0
  21. package/lib/types/client/compat/relative-file-path.d.ts +2 -0
  22. package/lib/types/client/compat/right-panes.d.ts +16 -0
  23. package/lib/types/client/compat/scroll-pill.d.ts +70 -0
  24. package/lib/types/client/compat/sidebar-navigation.d.ts +11 -0
  25. package/lib/types/client/compat/stats-values.d.ts +18 -1
  26. package/lib/types/client/compat/view-options.d.ts +58 -0
  27. package/lib/types/client/compat/view-switch.d.ts +46 -5
  28. package/lib/types/client/contracts/ports.d.ts +176 -1
  29. package/lib/types/client/features/context-panel/ContextPanel.d.ts +12 -0
  30. package/lib/types/client/features/context-panel/mount.d.ts +20 -0
  31. package/lib/types/client/features/conversation/index.d.ts +5 -3
  32. package/lib/types/client/features/conversation/view-switch-icons.d.ts +13 -0
  33. package/lib/types/client/features/settings/locales.d.ts +7 -0
  34. package/lib/types/client/features/shell/index.d.ts +9 -3
  35. package/lib/types/client/features/sidebar/index.d.ts +1 -1
  36. package/lib/types/client/features/sidebar/navigation-icons.d.ts +6 -0
  37. package/lib/types/client/features/statistics/books.d.ts +1 -2
  38. package/lib/types/client/features/statistics/format.d.ts +4 -4
  39. package/lib/types/client/features/view-options/ViewOptionsCard.d.ts +30 -0
  40. package/lib/types/client/features/view-options/mount.d.ts +25 -0
  41. package/lib/types/client/theme/mount.d.ts +2 -2
  42. package/lib/types/client/theme/palette.d.ts +1 -1
  43. package/lib/types/host/context/compaction.d.ts +26 -0
  44. package/lib/types/host/context/pricing.d.ts +99 -0
  45. package/lib/types/host/context/service.d.ts +24 -0
  46. package/lib/types/host/context/unit.d.ts +136 -0
  47. package/lib/types/host/index.d.ts +18 -0
  48. package/lib/types/shared/config.d.ts +26 -1
  49. package/lib/types/shared/context.d.ts +243 -0
  50. package/package.json +6 -3
@@ -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,2 @@
1
+ /** Display a host file relative to the session's working directory on either platform. */
2
+ export declare function relativeFilePath(path: string, cwd: string): string;
@@ -0,0 +1,16 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Disposer } from '../contracts/ports.ts';
3
+ /** DockLayout can only render two horizontal dock panes. Its floating leaves
4
+ * have no such limit: retain those host-rendered bodies and controls, and give
5
+ * them session-owned column geometry. No React-owned node is reparented. */
6
+ export declare const PANES_ATTRIBUTE = "data-ccd-panes";
7
+ export declare const PANE_ATTRIBUTE = "data-ccd-pane";
8
+ export declare const SEAM_ATTRIBUTE = "data-ccd-pane-seam";
9
+ export declare const SEAM_AXIS_ATTRIBUTE = "data-ccd-pane-axis";
10
+ export declare const SEAM_INDEX_ATTRIBUTE = "data-ccd-pane-seam-index";
11
+ export declare const DRAGGING_ATTRIBUTE = "data-ccd-pane-dragging";
12
+ export declare const PANE_LEFT_PROPERTY = "--ccd-pane-left";
13
+ export declare const PANE_TOP_PROPERTY = "--ccd-pane-top";
14
+ export declare const PANE_WIDTH_PROPERTY = "--ccd-pane-width";
15
+ export declare const PANE_HEIGHT_PROPERTY = "--ccd-pane-height";
16
+ export declare function mountRightPanes(ctx: Context, document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,70 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /**
3
+ * The window's scrollbars: thin, dark, and there only while something moves
4
+ * (DESIGN.md §三).
5
+ *
6
+ * Two mechanisms, one look.
7
+ *
8
+ * **The ink.** Every scroller inside `theme/scrollbars.css`'s scope keeps the
9
+ * host's own track, thumb box and navigation, and this module publishes the
10
+ * moment one is moving as {@link SCROLLING_ATTRIBUTE}; the sheet keeps the ink
11
+ * switched off until it sees that attribute, so no surface in the window
12
+ * carries a bar at rest. A listener on the document's capture phase sees every
13
+ * element scroll in one place — scroll events do not bubble, but they do travel
14
+ * down — and clears the mark again once the surface has been still for
15
+ * {@link IDLE_MS}.
16
+ *
17
+ * **The pill.** The five page-level surfaces — the Conversation page, the
18
+ * transcript column, the sidebar's tree, the Files pane and the agent-team panel
19
+ * — go one step further and keep no ink at all: {@link PILL_SURFACE_ATTRIBUTE}
20
+ * takes their track's thumb out of the painting, and a floating pill
21
+ * ({@link PILL_ATTRIBUTE}, all of them in one fixed layer) follows the scroll
22
+ * position instead. That pill is the short capsule the reference draws. The
23
+ * painted thumb cannot be it: its length is the content's share of the track, so
24
+ * a surface that only just overflows — a short conversation, a panel with one
25
+ * page of content — paints a bar the height of its own viewport. The pill is
26
+ * capped at {@link PILL_MAX} instead, and never shorter than {@link PILL_MIN}.
27
+ *
28
+ * The pill is centred on the painted thumb it stands in for, and sits inside the
29
+ * track's own strip at the edge, so it is where the pointer expects: the host's
30
+ * thumb — invisible, but still laid out and still the thing that drags — is
31
+ * directly underneath it, and a drag that starts anywhere on the pill grabs
32
+ * that bar.
33
+ *
34
+ * Everything here is written on elements the host owns, so the disposer puts
35
+ * each mark back: attributes off, layer gone, listeners removed.
36
+ */
37
+ /** Marks a surface whose bar is the floating pill rather than the track's ink. */
38
+ export declare const PILL_SURFACE_ATTRIBUTE = "data-ccd-scroll-pill";
39
+ /** Marks a surface in motion — and, on a pill, the pill that belongs to it. */
40
+ export declare const SCROLLING_ATTRIBUTE = "data-ccd-scrolling";
41
+ /** The one fixed layer every pill is painted in. */
42
+ export declare const PILL_LAYER_ATTRIBUTE = "data-ccd-scroll-layer";
43
+ /** Identifies one floating pill. */
44
+ export declare const PILL_ATTRIBUTE = "data-ccd-scroll-thumb";
45
+ /**
46
+ * How long a mark survives after the last scroll event. The reference's own
47
+ * overlay scroller fades out about this long after the wheel stops; long enough
48
+ * to read the position between two flicks, short enough that a resting window
49
+ * carries no ink.
50
+ */
51
+ export declare const IDLE_MS = 800;
52
+ /**
53
+ * The surfaces the pill replaces the ink on. All five are page-level chrome the
54
+ * reader scrolls by the screenful; the host's popup cards and this plugin's own
55
+ * panels keep the painted bar (it appears while they scroll, like every other
56
+ * surface in the sheet's scope), because they are short-lived and their tracks
57
+ * are narrower.
58
+ * @param document - renderer document carrying the interface.
59
+ * @returns the selectors, resolved for the build on screen.
60
+ */
61
+ export declare function pillSurfaces(document: Document): readonly string[];
62
+ /**
63
+ * Follow the interface's five page-level scrollers with a floating pill each,
64
+ * and mark every other scroller while it moves.
65
+ *
66
+ * @param document - renderer document carrying the interface.
67
+ * @param report - sink for observer failures; the host's own bars stay usable.
68
+ * @returns disposer that removes every pill, mark and listener.
69
+ */
70
+ export declare function mountScrollPills(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;
@@ -6,9 +6,50 @@ export declare const VIEW_WIDTH_PROPERTY = "--ccd-view-w";
6
6
  /** Animate selection changes; layout corrections must be immediate. */
7
7
  export declare const VIEW_DURATION_PROPERTY = "--ccd-view-duration";
8
8
  /**
9
- * Measure the selected ui-conversation tab and publish slider geometry.
10
- * MutationObserver handles tablist replacement and layout changes synchronously,
11
- * including occluded windows where animation frames are throttled.
12
- * @returns disposer that clears the observer and published properties.
9
+ * Marker on the tablist while it is parked in the sidebar's window-control row.
10
+ * `conversation.css` draws the fixed-position state from it; its absence means
11
+ * the host's own row (the header, or whatever the platform still offers) keeps
12
+ * the switcher.
13
13
  */
14
- export declare function mountViewSwitch(document: Document, report: (error: unknown) => void): Disposer;
14
+ export declare const VIEW_DOCK_ATTRIBUTE = "data-ccd-view-dock";
15
+ /** Custom property carrying the parked tablist's inset from the window's right edge. */
16
+ export declare const VIEW_DOCK_RIGHT_PROPERTY = "--ccd-view-dock-right";
17
+ /** Marker on a tab whose label is drawn as artwork, naming the View it selects. */
18
+ export declare const VIEW_TAB_ATTRIBUTE = "data-ccd-view";
19
+ /** Marker on the artwork seat injected into a decorated tab. */
20
+ export declare const VIEW_GLYPH_ATTRIBUTE = "data-ccd-view-glyph";
21
+ /**
22
+ * The parked pill's inset from the sidebar column's right edge, in px. Measured
23
+ * at @2x on the reference: the column's divider sits 16 device px right of the
24
+ * pill's right edge (docs/issues/img/todo-sidebar-sessions-target.webp).
25
+ */
26
+ export declare const VIEW_DOCK_INSET = 8;
27
+ /** Bundled artwork, one per shipped View; see features/conversation/view-switch-icons.ts. */
28
+ export interface ViewSwitchIcons {
29
+ readonly chat: string;
30
+ readonly trajectory: string;
31
+ }
32
+ /**
33
+ * Shape the host's View switcher, and park it where the reference has it.
34
+ *
35
+ * Three jobs, one observer:
36
+ * - Publish the selected segment's geometry for the sliding thumb
37
+ * (`--ccd-view-x` / `-w` / `-duration`), measured against the tablist.
38
+ * - Replace each shipped tab's text label with bundled artwork (the host draws
39
+ * the labels itself; a tab has no icon of its own), reversibly.
40
+ * - Park the tablist in the sidebar column's top-right corner — the reference
41
+ * keeps the View switcher on the window-control row — by measuring the
42
+ * column and publishing its inset (`data-ccd-view-dock` +
43
+ * `--ccd-view-dock-right`). The node stays where React put it: only CSS moves
44
+ * it, exactly as the macOS sidebar toggle is parked.
45
+ *
46
+ * The mutation observer covers tablist replacement, selection, frame drags
47
+ * (inline grid tracks) and the collapse flag, synchronously, including occluded
48
+ * windows where animation frames are throttled. A ResizeObserver on the sidebar
49
+ * column covers what no attribute says: the column's own width while the grid
50
+ * track animates between 0 and its settled value.
51
+ *
52
+ * @returns disposer that clears the observers, every injected node, every
53
+ * written attribute and every published property.
54
+ */
55
+ export declare function mountViewSwitch(document: Document, icons: ViewSwitchIcons, report: (error: unknown) => void): Disposer;
@@ -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
  /**
@@ -81,9 +82,16 @@ export interface SidebarRightPort {
81
82
  }): void;
82
83
  /** Focus a tab and the pane holding it; a missing tab is left alone. */
83
84
  focus(tabId: string): void;
84
- /** Open tab metadata across adopted Sessions. */
85
+ /**
86
+ * Open tab metadata across adopted Sessions — and, in the shipped face, the
87
+ * snapshot store behind it: the plugin's pane layer re-reads the layout
88
+ * whenever that list changes, which covers every way a tab opens, closes or
89
+ * changes pane. A build whose source cannot be observed is read once per
90
+ * mutation instead.
91
+ */
85
92
  readonly openTabs: {
86
93
  getSnapshot(): readonly SidebarTabRecord[];
94
+ subscribe?(listener: () => void): Disposer;
87
95
  };
88
96
  /**
89
97
  * Collapse the column, or expand it and focus its active dock pane.
@@ -94,6 +102,27 @@ export interface SidebarRightPort {
94
102
  * keeps that row out rather than offering one that cannot act.
95
103
  */
96
104
  toggleExpanded?(): void;
105
+ /**
106
+ * Take one docked tab out into a floating pane, and put one back.
107
+ *
108
+ * The panel's own renderer draws at most one pane or two horizontal panes —
109
+ * its layout component throws on anything else — so a vertical split or a
110
+ * third pane cannot be asked of it. A floating pane has no such limit (the
111
+ * layout stores a rectangle per float and the package allows any number), so
112
+ * `compat/right-panes.ts` builds the panel's panes out of these two calls and
113
+ * draws their boxes itself. A missing `float` means a build that cannot host
114
+ * panes at all: the panel keeps its tabs, exactly as it does today.
115
+ * Source: packages/client/ui-sidebar-right/src/client/controller.ts
116
+ * (`float(tabId, rect)`, `dock(paneId)`).
117
+ */
118
+ float?(tabId: string, rect?: {
119
+ x: number;
120
+ y: number;
121
+ width: number;
122
+ height: number;
123
+ }): void;
124
+ /** Return one floating pane's tab to the panel's docked column. */
125
+ dock?(paneId: string): void;
97
126
  }
98
127
  /**
99
128
  * The tab-type registry that face publishes as `ctx.sidebarRightTabs`. A header
@@ -124,6 +153,146 @@ export interface BlankSessionsPort {
124
153
  /** Observe list replacements and row-state changes; returns the releaser. */
125
154
  subscribe(listener: () => void): Disposer;
126
155
  }
156
+ /** One group of the host's view-options menu, as the host drew it. */
157
+ export interface ViewOptionsGroupRead {
158
+ /** The heading the host drew above this group, in the document's language. */
159
+ readonly label: string;
160
+ /** Every row's own label, in the host's order. */
161
+ readonly labels: readonly string[];
162
+ /** Index of the row the host marks as current, or −1 when none is marked. */
163
+ readonly selected: number;
164
+ }
165
+ /** The host's view-options menu, read out of its own card. */
166
+ export interface ViewOptionsRead {
167
+ /** The three groups in the host's own order: group by, order by, archived filter. */
168
+ readonly groups: readonly ViewOptionsGroupRead[];
169
+ }
170
+ /** Which of the host's three groups a choice belongs to. */
171
+ export type ViewOptionsGroup = 0 | 1 | 2;
172
+ /** What the card is drawn from: whether it is up, what it hangs from, and the
173
+ host's three values as they were read when it opened. */
174
+ export interface ViewOptionsSnapshot {
175
+ readonly open: boolean;
176
+ readonly anchor: Element | null;
177
+ /** Null before the first open, and after the host's card could not be read. */
178
+ readonly values: ViewOptionsRead | null;
179
+ }
180
+ /**
181
+ * The sidebar's view-options menu, as this plugin's card needs it.
182
+ *
183
+ * DSH keeps the three view settings in the browser and publishes no way to read
184
+ * or write them, so the only handle is the host's own menu: opening this card
185
+ * reads it, and `choose` clicks one of its rows. `getSnapshot`/`subscribe`
186
+ * carry the whole of what the DOM side owns — the trigger, whether the card is
187
+ * up, and the values read for it.
188
+ */
189
+ export interface ViewOptionsPort {
190
+ /** The current state; the object identity changes only when the state does. */
191
+ getSnapshot(): ViewOptionsSnapshot;
192
+ /** Observe opening, closing, and the trigger being replaced. */
193
+ subscribe(listener: () => void): Disposer;
194
+ /** Put the card away; the trigger's own state follows. */
195
+ close(): void;
196
+ /** Choose one option by driving the host's own row; false when it could not be reached. */
197
+ choose(group: ViewOptionsGroup, index: number): Promise<boolean>;
198
+ }
199
+ /** Native view preferences also used by the sidebar's empty-group policy. */
200
+ export interface ViewOptionsSettingsPort {
201
+ getSnapshot(): ViewOptionsRead | null;
202
+ subscribe(listener: () => void): Disposer;
203
+ }
204
+ /** Authoritative membership, including sessions omitted from collapsed DOM. */
205
+ export interface SidebarSessionsPort {
206
+ /** Durable history across all groups and filters; null while loading. */
207
+ hasHistory(): boolean | null;
208
+ /** Null while either catalog is still arriving; null filter keeps all history. */
209
+ populatedGroups(filter: number | null, nested: boolean): ReadonlySet<string> | null;
210
+ subscribe(listener: () => void): Disposer;
211
+ }
212
+ /**
213
+ * One session's context readings, as the client session list holds them.
214
+ *
215
+ * The host computes all of this itself: `contextPressure` is the ring's own
216
+ * occupancy and `contextBreakdown` its three-way composition, and both travel
217
+ * to the browser as wire projections on the session list. The panel reads them
218
+ * rather than recomputing them, so its header can never disagree with the ring
219
+ * beside it.
220
+ */
221
+ export interface ContextProjectionRead {
222
+ /** `contextPressure.contextWindow`. */
223
+ readonly window: number;
224
+ /** Occupancy: `projectedTokens ?? pressureTokens`. */
225
+ readonly used: number;
226
+ /** `contextBreakdown.systemTokens`. */
227
+ readonly systemTokens: number;
228
+ /** `contextBreakdown.toolsTokens`. */
229
+ readonly toolsTokens: number;
230
+ /** `contextBreakdown.messageTokens`. */
231
+ readonly messageTokens: number;
232
+ /** This plugin's own `ccdContext` value, or null while it is not there. */
233
+ readonly breakdown: ContextBreakdownView | null;
234
+ }
235
+ /**
236
+ * The host's context projections, read off the client session list.
237
+ *
238
+ * `ctx.sessions.list` publishes every registered wire value per session
239
+ * (`SessionListState.projectionsBySession`) and carries rows for sessions that
240
+ * were never opened, so this is a plain snapshot read with no I/O. A build that
241
+ * does not publish the list yields null and the ring keeps DSH's own panel.
242
+ */
243
+ export interface ContextProjectionPort {
244
+ /** One session's readings, or null when the list has nothing for it yet. */
245
+ read(sessionId: string): ContextProjectionRead | null;
246
+ /** Observe list replacements and live projection frames. */
247
+ subscribe(listener: () => void): Disposer;
248
+ }
249
+ /** One entry of a drill-down group, as the panel draws it. */
250
+ export interface ContextDetailGroup {
251
+ /** The group's own name, in the document's language. */
252
+ readonly label: string;
253
+ /** The group's total, which is the authoritative number above it. */
254
+ readonly tokens: number;
255
+ /** The rows, largest first. */
256
+ readonly rows: readonly ContextEntry[];
257
+ }
258
+ /** What the context panel is drawn from. */
259
+ export interface ContextPanelSnapshot {
260
+ readonly open: boolean;
261
+ /** The ring trigger the panel hangs from; null before the shell renders it. */
262
+ readonly anchor: Element | null;
263
+ /** The whole reading, or null while the ring has no numbers for this session. */
264
+ readonly breakdown: ContextBreakdown | null;
265
+ /** `mcp__` tools, largest first. */
266
+ readonly mcp: readonly ContextEntry[];
267
+ /** Built-in tools, largest first. */
268
+ readonly tools: readonly ContextEntry[];
269
+ /** Retained instruction files, largest first. */
270
+ readonly files: readonly ContextEntry[];
271
+ /** Retained skill-catalog entries, largest first. */
272
+ readonly skills: readonly ContextEntry[];
273
+ /** The panel's own expansion; it lives only while the panel is open. */
274
+ readonly expanded: boolean;
275
+ }
276
+ /**
277
+ * The context ring's breakdown panel, as this plugin's card needs it.
278
+ *
279
+ * DSH opens its own 264px panel from the ring's click, and that panel is the
280
+ * whole of its own affordance — no public face reads or drives it. So this port
281
+ * owns the interaction instead: the driver intercepts the trigger's click
282
+ * (DSH's handler is React-delegated at the root, so stopping the event on the
283
+ * button keeps the host's panel from ever opening), holds the open and expanded
284
+ * state, and publishes the host's own numbers beside this plugin's rows.
285
+ */
286
+ export interface ContextPanelPort {
287
+ /** The current state; the object identity changes only when the state does. */
288
+ getSnapshot(): ContextPanelSnapshot;
289
+ /** Observe opening, closing, expanding, and the trigger being replaced. */
290
+ subscribe(listener: () => void): Disposer;
291
+ /** Put the panel away; the trigger's own state follows. */
292
+ close(): void;
293
+ /** Flip the panel between its collapsed and expanded states. */
294
+ toggleExpanded(): void;
295
+ }
127
296
  /** Actual SDK types, never a second definition of DSH component props. */
128
297
  export interface HostServices {
129
298
  readonly slots: Context['slots'];
@@ -142,6 +311,12 @@ export interface HostServices {
142
311
  * session list; a null port leaves the native rows exactly as they are.
143
312
  */
144
313
  readonly blankSessions: BlankSessionsPort | null;
314
+ readonly sidebarSessions: SidebarSessionsPort | null;
315
+ /**
316
+ * The host's context projections, or null on a build that does not publish
317
+ * the session list; a null port leaves the ring's own panel in charge.
318
+ */
319
+ readonly contextProjections: ContextProjectionPort | null;
145
320
  /** The settings transport carrying this plugin's own configuration section. */
146
321
  readonly configForms: ConfigFormsPort;
147
322
  }
@@ -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;
@@ -5,9 +5,11 @@ import type { ImplementedFeature } from '../../contracts/feature.ts';
5
5
  * adapts the header chrome, the transcript column and the message surfaces.
6
6
  *
7
7
  * The header keeps DSH's own View switcher: the host still owns which View is
8
- * selected and how it is rendered, and this module only moves the tablist onto
9
- * the title line and publishes the selected segment's geometry so the
10
- * stylesheet can slide the thumb onto it.
8
+ * selected and how it is rendered. This module swaps the two shipped Views'
9
+ * text labels for bundled artwork, publishes the selected segment's geometry so
10
+ * the stylesheet can slide the thumb onto it, and parks the tablist in the
11
+ * sidebar's window-control row while that column has a corner to hold it
12
+ * (`compat/view-switch.ts`).
11
13
  *
12
14
  * The transcript's own edge fade is published the same way: the host owns the
13
15
  * scroll position, `compat/conversation-fade.ts` turns it into two attributes,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The two View switcher glyphs, bundled as trusted static markup.
3
+ *
4
+ * Drawn to the reference's own segments (docs/issues/img/todo-sidebar-sessions-target.webp,
5
+ * @2x): a pair of speech bubbles for the chat View and the `</>` pair for the
6
+ * trajectory View, both on the CCD 16 grid — 1–1.2px stroke (each glyph's own
7
+ * weight read off the reference), round caps and joins, `currentColor` ink,
8
+ * ink box ≈ 12.5 × 11px inside the 16px artwork.
9
+ */
10
+ export declare const VIEW_SWITCH_ICONS: Readonly<{
11
+ chat: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M10.4 8.3a2.6 2.6 0 0 1-2.6 2.6H5.7L3.3 12.5V5.9a2.6 2.6 0 0 1 2.6-2.6h1.9a2.6 2.6 0 0 1 2.6 2.6Z\"/>\n <path d=\"M10.3 5.9H12.1a2.1 2.1 0 0 1 2.1 2.1V10.4a2.1 2.1 0 0 1-2.1 2.1H10.25L9.1 13.1\"/>\n</svg>";
12
+ trajectory: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M5.3 5.8 3.05 8l2.25 2.2\"/>\n <path d=\"M9.1 3.8 6.9 12.2\"/>\n <path d=\"M10.7 5.8 12.95 8l-2.25 2.2\"/>\n</svg>";
13
+ }>;
@@ -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. */