dsh-claude-desktop-theme 0.2.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 (76) hide show
  1. package/LICENSE +54 -0
  2. package/README.md +197 -0
  3. package/cordis.patch.yml +5 -0
  4. package/lib/client.js +5492 -0
  5. package/lib/index.js +666 -0
  6. package/lib/types/client/apply.d.ts +25 -0
  7. package/lib/types/client/compat/account-menu.d.ts +52 -0
  8. package/lib/types/client/compat/adapter.d.ts +17 -0
  9. package/lib/types/client/compat/blank-session-rows.d.ts +74 -0
  10. package/lib/types/client/compat/composer-menus.d.ts +11 -0
  11. package/lib/types/client/compat/composer-placeholder.d.ts +3 -0
  12. package/lib/types/client/compat/composer-status-order.d.ts +56 -0
  13. package/lib/types/client/compat/conversation-fade.d.ts +37 -0
  14. package/lib/types/client/compat/dom.d.ts +2 -0
  15. package/lib/types/client/compat/header-labels.d.ts +24 -0
  16. package/lib/types/client/compat/host-builds.d.ts +101 -0
  17. package/lib/types/client/compat/host-dom.d.ts +247 -0
  18. package/lib/types/client/compat/open-target.d.ts +9 -0
  19. package/lib/types/client/compat/pet-anchor.d.ts +30 -0
  20. package/lib/types/client/compat/plugin-manager.d.ts +32 -0
  21. package/lib/types/client/compat/session-menu.d.ts +47 -0
  22. package/lib/types/client/compat/slots.d.ts +21 -0
  23. package/lib/types/client/compat/stats-values.d.ts +20 -0
  24. package/lib/types/client/compat/view-switch.d.ts +14 -0
  25. package/lib/types/client/compat/workspace-menu.d.ts +15 -0
  26. package/lib/types/client/contracts/feature.d.ts +22 -0
  27. package/lib/types/client/contracts/ports.d.ts +169 -0
  28. package/lib/types/client/core/cleanup.d.ts +10 -0
  29. package/lib/types/client/core/mount-features.d.ts +3 -0
  30. package/lib/types/client/features/composer-pet/BlankPet.d.ts +17 -0
  31. package/lib/types/client/features/composer-pet/ComposerPet.d.ts +7 -0
  32. package/lib/types/client/features/composer-pet/Whale.d.ts +18 -0
  33. package/lib/types/client/features/composer-pet/mount.d.ts +9 -0
  34. package/lib/types/client/features/composer-pet/reactions.d.ts +15 -0
  35. package/lib/types/client/features/conversation/header-actions/HeaderActionButton.d.ts +25 -0
  36. package/lib/types/client/features/conversation/header-actions/entries.d.ts +101 -0
  37. package/lib/types/client/features/conversation/header-actions/mount.d.ts +28 -0
  38. package/lib/types/client/features/conversation/header-actions/sidebar-view.d.ts +21 -0
  39. package/lib/types/client/features/conversation/index.d.ts +16 -0
  40. package/lib/types/client/features/model-controls/EffortPanel.d.ts +14 -0
  41. package/lib/types/client/features/model-controls/ModelControls.d.ts +11 -0
  42. package/lib/types/client/features/model-controls/mount.d.ts +5 -0
  43. package/lib/types/client/features/model-controls/pixels.d.ts +4 -0
  44. package/lib/types/client/features/model-controls/popup.d.ts +10 -0
  45. package/lib/types/client/features/model-controls/selection.d.ts +66 -0
  46. package/lib/types/client/features/new-session/index.d.ts +8 -0
  47. package/lib/types/client/features/settings/ConfigPage.d.ts +28 -0
  48. package/lib/types/client/features/settings/FontMenu.d.ts +37 -0
  49. package/lib/types/client/features/settings/edits.d.ts +39 -0
  50. package/lib/types/client/features/settings/fields.d.ts +83 -0
  51. package/lib/types/client/features/settings/fonts.d.ts +40 -0
  52. package/lib/types/client/features/settings/locales.d.ts +61 -0
  53. package/lib/types/client/features/settings/mount.d.ts +17 -0
  54. package/lib/types/client/features/settings/register.d.ts +17 -0
  55. package/lib/types/client/features/shell/index.d.ts +8 -0
  56. package/lib/types/client/features/sidebar/index.d.ts +14 -0
  57. package/lib/types/client/features/statistics/books.d.ts +34 -0
  58. package/lib/types/client/features/statistics/card.d.ts +143 -0
  59. package/lib/types/client/features/statistics/format.d.ts +36 -0
  60. package/lib/types/client/features/statistics/index.d.ts +8 -0
  61. package/lib/types/client/features/statistics/mount.d.ts +12 -0
  62. package/lib/types/client/features/statistics/source.d.ts +35 -0
  63. package/lib/types/client/features/statistics/view.d.ts +95 -0
  64. package/lib/types/client/features/tool-calls/index.d.ts +2 -0
  65. package/lib/types/client/index.d.ts +1 -0
  66. package/lib/types/client/theme/mount.d.ts +15 -0
  67. package/lib/types/client/theme/palette.d.ts +80 -0
  68. package/lib/types/client/theme/tokens.d.ts +27 -0
  69. package/lib/types/host/index.d.ts +106 -0
  70. package/lib/types/host/stats/aggregate.d.ts +38 -0
  71. package/lib/types/host/stats/service.d.ts +18 -0
  72. package/lib/types/host/stats/unit.d.ts +115 -0
  73. package/lib/types/shared/config.d.ts +61 -0
  74. package/lib/types/shared/identity.d.ts +5 -0
  75. package/lib/types/shared/stats.d.ts +133 -0
  76. package/package.json +116 -0
@@ -0,0 +1,30 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Where the frame-wide seat is drawn, in viewport coordinates. */
3
+ export interface PetAnchor {
4
+ readonly left: number;
5
+ readonly top: number;
6
+ }
7
+ /** One observed, disposable source of the seat's position. */
8
+ export interface PetAnchorSource {
9
+ /** Current position, or null while the seat has no card to sit on. */
10
+ getSnapshot(): PetAnchor | null;
11
+ /** Observe position changes; returns the releaser. */
12
+ subscribe(listener: () => void): Disposer;
13
+ /** Release every observer and listener this source installed. */
14
+ dispose(): void;
15
+ }
16
+ /** The artwork's box: a 32 × 24 pixel grid drawn at 1:1, as `Whale.tsx` renders it. */
17
+ export declare const PET_BOX: Readonly<{
18
+ width: 32;
19
+ height: 24;
20
+ }>;
21
+ /** Inset from the Composer card's trailing edge; `pet.css` draws the seat with
22
+ the measured numbers, and the test asserts the two agree. */
23
+ export declare const PET_INSET = 12;
24
+ /**
25
+ * Publish the top trailing corner of the hero Composer card in viewport
26
+ * coordinates for shell.overlay. The DSH conversation root's data-phase=hero
27
+ * selects new sessions; hidden, missing or unlaid-out cards publish null.
28
+ * @returns position source whose dispose releases all listeners and observers.
29
+ */
30
+ export declare function createPetAnchor(document: Document, report: (error: unknown) => void): PetAnchorSource;
@@ -0,0 +1,32 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ReactElement } from 'react';
3
+ import type { Disposer } from '../contracts/ports.ts';
4
+ /**
5
+ * The Plugins panel's row configuration slot.
6
+ *
7
+ * `@deepseek-ai/dsh-client-ui-plugin-manager` declares it in the child table of
8
+ * its `main` panel registration as `{ kind: 'keyed', scope: 'root' }` and keys
9
+ * it by `<package>#<row id>`; the row then grows a configuration page. The
10
+ * package is not part of this plugin's type baseline, so the two members used
11
+ * here are declared structurally and the real service object is passed through
12
+ * unchanged — the same treatment `contracts/ports.ts` gives the other
13
+ * out-of-baseline providers. Registration waits for the declaration, so a
14
+ * deployment without that panel simply keeps the page unmounted.
15
+ */
16
+ export declare const ROW_CONFIG_SLOT = "plugins.row.config";
17
+ /** Props the panel renders a row configuration page with. */
18
+ export interface RowConfigPageProps {
19
+ /** `summary` beside a row title without a description, `page` for the form. */
20
+ readonly view?: string;
21
+ /** Locale seat synthesized from the registration's namespace. */
22
+ readonly t?: (key: string, params?: Record<string, unknown>) => string;
23
+ }
24
+ /**
25
+ * Register one row configuration page and wait for the slot to exist.
26
+ * @param ctx - context carrying the slots service.
27
+ * @param key - `<package>#<row id>` of the row whose page this is.
28
+ * @param locale - dictionary namespace for the page's `t` seat, when available.
29
+ * @param component - the page component.
30
+ * @returns the injection's disposer.
31
+ */
32
+ export declare function mountRowConfigPage(ctx: Context, key: string, locale: string | undefined, component: (props: RowConfigPageProps) => ReactElement | null): Disposer;
@@ -0,0 +1,47 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /**
3
+ * The Session header's own ⋮ menu, extended with one row.
4
+ *
5
+ * `session-log-export` owns that menu: a `ui-primitives` `Menu` whose rows are
6
+ * data (`下载 Session 日志`, `反馈`) and whose list is rendered in place, as the
7
+ * second child of the menu's own root span beside its trigger. There is no slot
8
+ * a third-party row could register into, so this module adds one to the list the
9
+ * host drew — its first row, ahead of the host's own — cloned from a native row,
10
+ * which is what makes the addition look native without pinning a single menu
11
+ * class: the clone carries the host's own classes, so whatever draws that menu
12
+ * draws this row too (today `theme/menus.css`), and the primitive's keyboard
13
+ * walk (`Menu` walks `button:not(:disabled)` inside the list) and its
14
+ * post-selection focus return reach it without any shared state.
15
+ *
16
+ * The row exists only while it is wanted: `wanted()` answers from the document,
17
+ * and the plugin retires the shipped re-entry control this row replaces
18
+ * (`ui-sidebar-right`'s `ExpandButton`, shadowed in the header's corner cell by
19
+ * an entry that renders nothing), so the two never stand side by side.
20
+ *
21
+ * Closing the menu stays the owner's decision, exactly as it is for the host's
22
+ * own data rows: the trigger is clicked again, which is what its own
23
+ * `setOpen(value => !value)` handler does for a second click. Nothing is
24
+ * dispatched at the document — a synthetic Escape or outside pointerdown would
25
+ * be a gesture the reader never made.
26
+ */
27
+ /** Marker the added row carries, so a later pass finds its own node. */
28
+ export declare const MENU_ROW_ATTRIBUTE = "data-ccd-menu-row";
29
+ /** One row this module adds to the Session header's menu. */
30
+ export interface SessionMenuRow {
31
+ /** Localised row label, written over the cloned row's own copy. */
32
+ readonly label: string;
33
+ /** Whether the row belongs in the open menu right now. */
34
+ readonly wanted: () => boolean;
35
+ /** Runs when the row is chosen, before the menu is dismissed. */
36
+ readonly act: () => void;
37
+ }
38
+ /**
39
+ * Keep one row in the Session header's menu for as long as it is wanted.
40
+ *
41
+ * @param document - the renderer document whose header menu is extended.
42
+ * @param row - the label, the predicate that decides whether the row exists,
43
+ * and the action it runs.
44
+ * @param report - receives a failure from the action or from one observer pass.
45
+ * @returns disposer that removes the row and stops the observer.
46
+ */
47
+ export declare function mountSessionMenuRow(document: Document, row: SessionMenuRow, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,21 @@
1
+ /** Verified names; ownership and props must be read from the SDK before registration. */
2
+ export declare const DSH_SLOTS: Readonly<{
3
+ readonly sidebar: 'sidebar';
4
+ readonly workspaces: 'sidebar.workspaces';
5
+ readonly settings: 'sidebar.settings';
6
+ readonly main: 'main';
7
+ readonly conversation: 'main.conversation';
8
+ readonly header: 'conversation.header';
9
+ readonly sessionHeader: 'conversation.session.header';
10
+ /** Trailing header seats: `utilities` is a list the native actions occupy too. */
11
+ readonly sessionHeaderUtilities: 'conversation.session.header.utilities';
12
+ readonly sessionHeaderCorner: 'conversation.session.header.corner';
13
+ readonly composerBar: 'conversation.composer.bar';
14
+ readonly inputDock: 'conversation.input.dock';
15
+ readonly composerDock: 'conversation.composer.dock';
16
+ readonly toolView: 'tool.call.toolview';
17
+ readonly leading: 'shell.leading';
18
+ /** Frame-wide floating layer, click-through until an entry opts back in. */
19
+ readonly shellOverlay: 'shell.overlay';
20
+ }>;
21
+ export declare const OVERRIDE_PRIORITY = -10;
@@ -0,0 +1,20 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Short readout mirrored from native text for wide columns only. */
3
+ export declare const STAT_VALUE_PROPERTY = "--ccd-stat-value";
4
+ /** Custom property carrying the width of the Composer's trailing control group. */
5
+ export declare const TRAILING_WIDTH_PROPERTY = "--ccd-trailing-width";
6
+ /** Remaining model-button space after leading controls, statistics and gaps. */
7
+ export declare const MODEL_MAX_WIDTH_PROPERTY = "--ccd-model-max-width";
8
+ /** The one gap the trailing cluster uses, read to budget the model button. */
9
+ export declare const CLUSTER_GAP_PROPERTY = "--ccd-cluster-gap";
10
+ /**
11
+ * The responsive statistics cluster sits before the model selector. Its dock is
12
+ * a sibling of the card rather than a row child, so its wide-layout offset needs
13
+ * the trailing group's live width. Readouts and their detail dialogs stay owned
14
+ * by the host; wide columns mirror one short segment without changing the host label.
15
+ *
16
+ * @param document - renderer document carrying the Composer.
17
+ * @param report - sink for observer failures; the native readouts stay.
18
+ * @returns disposer that disconnects the observers and clears every property.
19
+ */
20
+ export declare function mountComposerStats(document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,14 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Custom property carrying the active View segment's offset inside the tablist. */
3
+ export declare const VIEW_OFFSET_PROPERTY = "--ccd-view-x";
4
+ /** Custom property carrying the active View segment's width. */
5
+ export declare const VIEW_WIDTH_PROPERTY = "--ccd-view-w";
6
+ /** Animate selection changes; layout corrections must be immediate. */
7
+ export declare const VIEW_DURATION_PROPERTY = "--ccd-view-duration";
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.
13
+ */
14
+ export declare function mountViewSwitch(document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,15 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /**
3
+ * Print the workspace picker's "add workspace" row with spaced dots.
4
+ *
5
+ * The card is watched only while the picker is open, and every write is undone
6
+ * on release, so a disabled plugin leaves the host's own label on screen. The
7
+ * observer runs synchronously from the mutation batch (never on an animation
8
+ * frame, which an occluded window throttles), so the row is rewritten in the
9
+ * commit that mounts it and never shows the host's glyph first.
10
+ *
11
+ * @param document - renderer document carrying the hero chip and the portal.
12
+ * @param report - sink for observer failures; the native label stays untouched.
13
+ * @returns disposer that disconnects every observer and restores each label.
14
+ */
15
+ export declare function mountWorkspaceMenu(document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,22 @@
1
+ import type { FeatureId, StyleConfig } from '../../shared/config.ts';
2
+ import type { CleanupScope } from '../core/cleanup.ts';
3
+ import type { DomPort, HostServices, Logger } from './ports.ts';
4
+ export interface FeatureEnvironment {
5
+ readonly config: StyleConfig;
6
+ readonly host: HostServices;
7
+ readonly dom: DomPort;
8
+ readonly logger: Logger;
9
+ }
10
+ interface FeatureMetadata {
11
+ readonly id: FeatureId;
12
+ readonly task: string;
13
+ }
14
+ export interface PlannedFeature extends FeatureMetadata {
15
+ readonly status: 'planned';
16
+ }
17
+ export interface ImplementedFeature extends FeatureMetadata {
18
+ readonly status: 'implemented';
19
+ mount(environment: FeatureEnvironment, scope: CleanupScope): void;
20
+ }
21
+ export type FeatureDefinition = PlannedFeature | ImplementedFeature;
22
+ export {};
@@ -0,0 +1,169 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ThemePreference, ThemeRuntime } from '@deepseek-ai/dsh-client-ui-theme/client';
3
+ import type { UiWorkspace } from '@deepseek-ai/dsh-client-ui-workspace/client';
4
+ export type { ThemePreference };
5
+ export type Disposer = () => void;
6
+ /**
7
+ * One settings-path operation, the shape `remote.settings.mutate` accepts on
8
+ * the wire. A nested field is addressed by its path segments — `set()` only
9
+ * ever sends a one-segment path — and `unset` removes the profile override so
10
+ * the schema default takes over again.
11
+ * Source: packages/client/ui-settings/src/client/config-forms.ts and
12
+ * packages/api/settings-controller (`SettingsPathOp`).
13
+ */
14
+ export type SettingsPathOp = {
15
+ readonly op: 'set';
16
+ readonly path: readonly string[];
17
+ readonly value: unknown;
18
+ } | {
19
+ readonly op: 'unset';
20
+ readonly path: readonly string[];
21
+ };
22
+ /** Read side of one namespace's form; only the members this plugin uses. */
23
+ export interface ConfigFormSnapshot {
24
+ /** `loading` until the describe view arrives, `ready` once a valid value is served, `unavailable` without one. */
25
+ readonly status: string;
26
+ /** The effective section, projected to the volatile fields; undefined until `ready`. */
27
+ readonly value: unknown;
28
+ /** Host revision of this namespace, the fence every write carries. */
29
+ readonly revision?: number;
30
+ /** Whether the Host will persist a write from this page. */
31
+ readonly writable?: boolean;
32
+ }
33
+ /**
34
+ * One namespace's configuration form, structurally matching
35
+ * `@deepseek-ai/dsh-client-ui-settings`' `ConfigForm` — the service every
36
+ * settings-backed client plugin reads its own section through. Only the
37
+ * members this plugin uses are declared, and the real service object is passed
38
+ * through unchanged, so no second definition of its behaviour lives here.
39
+ * Source: packages/client/ui-settings/src/client/config-forms.ts.
40
+ */
41
+ export interface ConfigFormPort {
42
+ /** Current section snapshot; `value` is the effective section until it is served. */
43
+ getSnapshot(): ConfigFormSnapshot;
44
+ /** Observe snapshot replacements. */
45
+ subscribe(listener: () => void): Disposer;
46
+ /** Queue one scalar write into the section; resolves with the Host's verdict. */
47
+ set(field: string, value: unknown): Promise<boolean>;
48
+ /** Queue one field clear, restoring the inherited or default value. */
49
+ unset(field: string): Promise<boolean>;
50
+ /** Queue one atomic namespace mutation, fenced by the revision it was read at. */
51
+ mutate(ops: readonly SettingsPathOp[], expectedRevision?: number): Promise<boolean>;
52
+ }
53
+ /** Namespace-addressed access to the settings transport. */
54
+ export interface ConfigFormsPort {
55
+ /** @param namespace - the loader entry id whose section is wanted. */
56
+ get(namespace: string): ConfigFormPort;
57
+ }
58
+ /**
59
+ * One right-Sidebar tab as the cross-plugin face reports it: membership only,
60
+ * independent of visible panes.
61
+ * Source: packages/client/ui-sidebar-right/src/client/persistence.ts.
62
+ */
63
+ export interface SidebarTabRecord {
64
+ readonly sessionId: string;
65
+ readonly tabId: string;
66
+ readonly kind: string;
67
+ readonly contentId?: string;
68
+ }
69
+ /**
70
+ * The cross-plugin right-Sidebar face the official panel plugins publish as
71
+ * `ctx.sidebarRight`, a reflect-provided service
72
+ * (`dsh-client-ui-sidebar-right`, "Cross-plugin right-Sidebar face"). That
73
+ * package is not part of this plugin's type baseline, so only the members the
74
+ * header uses are declared here and the real object is passed through
75
+ * unchanged; a partial service means no header views rather than a dead button.
76
+ */
77
+ export interface SidebarRightPort {
78
+ /** Open one page kind for the on-screen Session; an unregistered kind throws. */
79
+ openTab(kind: string, options?: {
80
+ readonly params?: Record<string, string>;
81
+ }): void;
82
+ /** Focus a tab and the pane holding it; a missing tab is left alone. */
83
+ focus(tabId: string): void;
84
+ /** Open tab metadata across adopted Sessions. */
85
+ readonly openTabs: {
86
+ getSnapshot(): readonly SidebarTabRecord[];
87
+ };
88
+ /**
89
+ * Collapse the column, or expand it and focus its active dock pane.
90
+ *
91
+ * The header's shipped re-entry control (`ExpandButton`) is shadowed by this
92
+ * plugin's corner entry, so the Session menu's own row is what reveals a
93
+ * collapsed column again; a build whose face does not carry this member
94
+ * keeps that row out rather than offering one that cannot act.
95
+ */
96
+ toggleExpanded?(): void;
97
+ }
98
+ /**
99
+ * The tab-type registry that face publishes as `ctx.sidebarRightTabs`. A header
100
+ * view is only offered while its kind is registered, so a build without the
101
+ * terminal or browser plugin keeps no dead button. Membership is read by
102
+ * presence and observed through the registry's own low-frequency signal.
103
+ * Source: packages/client/ui-sidebar-right/src/client/tab-registry.ts.
104
+ */
105
+ export interface SidebarRightTabsPort {
106
+ /** @param kind - page type the panel can place; undefined when unregistered. */
107
+ get(kind: string): unknown;
108
+ /** @param listener - synchronous invalidation callback. */
109
+ subscribe(listener: () => void): Disposer;
110
+ }
111
+ /**
112
+ * The host's provisional New Sessions, as the sidebar column reads them.
113
+ *
114
+ * DSH creates the Session the moment its New Session entry is used and keeps
115
+ * the row in the list while `SessionSummary.blank` is true — the host's own
116
+ * fact for "created, no message yet". The Workspace browser shows the selected
117
+ * blank entry; the CCD column keeps every blank row out of the list, so the
118
+ * entry arrives with the first sent message. Filtering is presentation: the
119
+ * plugin writes no session state and hides nothing the host would call durable.
120
+ */
121
+ export interface BlankSessionsPort {
122
+ /** Session ids the host still presents as a provisional New Session. */
123
+ ids(): ReadonlySet<string>;
124
+ /** Observe list replacements and row-state changes; returns the releaser. */
125
+ subscribe(listener: () => void): Disposer;
126
+ }
127
+ /** Actual SDK types, never a second definition of DSH component props. */
128
+ export interface HostServices {
129
+ readonly slots: Context['slots'];
130
+ readonly theme: Pick<ThemeRuntime, 'overrideTokens'>;
131
+ /**
132
+ * The native light/dark/system preference as the configuration page sees it,
133
+ * or null on a build whose theme service does not publish it. DSH owns the
134
+ * value (the user-settings document owns the durable preference and
135
+ * `prefers-color-scheme` answers for `system`), so the page reads and writes
136
+ * it instead of holding a copy.
137
+ */
138
+ readonly themePreference: ThemePreferencePort | null;
139
+ readonly workspace: Pick<UiWorkspace, 'openSession' | 'openWorkspace' | 'startSession'>;
140
+ /**
141
+ * Provisional New Sessions, or null on a build that does not publish the
142
+ * session list; a null port leaves the native rows exactly as they are.
143
+ */
144
+ readonly blankSessions: BlankSessionsPort | null;
145
+ /** The settings transport carrying this plugin's own configuration section. */
146
+ readonly configForms: ConfigFormsPort;
147
+ }
148
+ /**
149
+ * Read/write face of the theme service's preference: the same members the
150
+ * official Appearance row uses (`getTheme`/`setTheme`/`theme/change`), so a
151
+ * second control cannot disagree with the first one.
152
+ */
153
+ export interface ThemePreferencePort {
154
+ /** The persisted preference — never the resolved active theme. */
155
+ preference(): ThemePreference;
156
+ /** Switch the preference through the theme service's only write entry. */
157
+ set(preference: ThemePreference): void;
158
+ /** Observe preference, registry and OS-scheme changes; returns the releaser. */
159
+ subscribe(listener: () => void): Disposer;
160
+ }
161
+ export interface DomPort {
162
+ activate(): Disposer;
163
+ mountStyles(css: string): Disposer;
164
+ }
165
+ export interface Logger {
166
+ debug(message: string): void;
167
+ /** `error` carries a cause when there is one; a report without one is fine. */
168
+ error(message: string, error?: unknown): void;
169
+ }
@@ -0,0 +1,10 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Own resources per activation; release in reverse acquisition order. */
3
+ export declare class CleanupScope {
4
+ private readonly cleanups;
5
+ private disposed;
6
+ private readonly onError;
7
+ constructor(onError?: (error: unknown) => void);
8
+ add(cleanup: Disposer): Disposer;
9
+ dispose(): void;
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { FeatureDefinition, FeatureEnvironment } from '../contracts/feature.ts';
2
+ import { CleanupScope } from './cleanup.ts';
3
+ export declare function mountFeatures(features: readonly FeatureDefinition[], environment: FeatureEnvironment, parent: CleanupScope): void;
@@ -0,0 +1,17 @@
1
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { PetAnchor } from '../../compat/pet-anchor.ts';
3
+ /** Business face the frame overlay injects: the measured new-session corner. */
4
+ export interface BlankPetFace {
5
+ /** Selector hook over the compat layer's measurement. */
6
+ readonly useAnchor: () => PetAnchor | null;
7
+ }
8
+ export type BlankPetProps = PropsRuntime<'shell.overlay'> & BlankPetFace;
9
+ /**
10
+ * The whale's only seat, drawn from the frame-wide overlay at the measured
11
+ * corner of the new-session Composer card. Renders nothing until the anchor
12
+ * finds a card — and the anchor only finds one on a page the host marks
13
+ * `data-phase="hero"`, so a conversation never shows the pet.
14
+ * @param props - injected measurement.
15
+ * @returns the positioned whale, or null.
16
+ */
17
+ export declare function BlankPet({ useAnchor }: BlankPetProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The hero-page whale. Accepted clicks choose one of three local reactions;
3
+ * busy clicks do nothing, and hiding or unmounting cancels all active frames.
4
+ * It reads no session state and does not trigger any Composer action.
5
+ * @returns the whale mark.
6
+ */
7
+ export declare function ComposerPet(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Hand-drawn 32 × 24 pixel interpretation of the DeepSeek whale.
3
+ *
4
+ * Brand reference: deepseek-ai/DeepSeek-V2/figures/logo.svg (blue body, pale
5
+ * belly, light eye, swept pectoral fin and raised split tail). The mark is
6
+ * drawn at 1:1 with the grid and rendered at 32 × 24 CSS px, so one artwork
7
+ * pixel is one CSS pixel (two device pixels on a Retina display) and
8
+ * `crispEdges` keeps every step on a whole device pixel. The eye sits near the
9
+ * tail, as in the mark; the head is the blunt left end.
10
+ *
11
+ * ComposerPet owns the local click reactions. The spray is offset three grid
12
+ * pixels towards the middle of the head, matching the approved preview.
13
+ *
14
+ * The parts are classes, not fixed fills: `pet.css` paints the body from
15
+ * `--ccd-pet-blue` and the belly/eye from `--ccd-pet-light`, so the mark
16
+ * follows the palette in both schemes.
17
+ */
18
+ export declare function Whale(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
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
+ * Mount the new-session whale in shell.overlay at the measured Composer corner.
6
+ * pet-anchor selects the DSH conversation root's data-phase=hero state.
7
+ * @param cleanup - scope owning the styles, slot registration and anchor.
8
+ */
9
+ export declare function mountComposerPet(ctx: Context, environment: FeatureEnvironment, cleanup: CleanupScope): void;
@@ -0,0 +1,15 @@
1
+ /** Local reactions only: the pet does not read or change a session. */
2
+ export type PetReaction = 'spout' | 'hop' | 'wag';
3
+ /** Equal thirds, sampled once per accepted click. */
4
+ export declare function pickPetReaction(random?: () => number): PetReaction;
5
+ export interface PetPlayback {
6
+ /** Resolves on completion or cancellation, including component disposal. */
7
+ readonly finished: Promise<void>;
8
+ cancel(): void;
9
+ }
10
+ /**
11
+ * Play the approved preview's poses on the existing 32 × 24 artwork. Per-frame
12
+ * easing keeps pixel swaps discrete without quantizing the whole timeline.
13
+ * No filled final frames: cancellation and completion restore the idle art.
14
+ */
15
+ export declare function playPetReaction(art: SVGSVGElement, reaction: PetReaction, reducedMotion: boolean): PetPlayback;
@@ -0,0 +1,25 @@
1
+ import type { ReactElement } from 'react';
2
+ /** Props the registration layer derives; the component never sees `ctx`. */
3
+ export interface HeaderActionProps {
4
+ /** Localised accessible name, also used as the native tooltip. */
5
+ readonly label: string;
6
+ /** Which right-panel view this button opens; selects the glyph in CSS. */
7
+ readonly kind: 'terminal' | 'browser' | 'files';
8
+ /** Opens or focuses the native right-panel view. */
9
+ readonly open: () => void;
10
+ }
11
+ /**
12
+ * One view button for the conversation header.
13
+ *
14
+ * All three sit in the utilities group beside the native actions, led by the
15
+ * project folder; the corner seat past them holds nothing (the panel's shipped
16
+ * expand control is retired there and its action moves into the Session menu).
17
+ * All three are the same control: the box size, hover surface and spacing
18
+ * belong to the seat's own stylesheet
19
+ * (`features/conversation/conversation.css`) so every control in the row shares
20
+ * one rhythm; this component only carries the glyph, the name and the click.
21
+ *
22
+ * @param props - Derived label, view kind and callback.
23
+ * @returns The header button.
24
+ */
25
+ export declare function HeaderActionButton({ label, kind, open }: HeaderActionProps): ReactElement;
@@ -0,0 +1,101 @@
1
+ import type { SessionMenuRow } from '../../../compat/session-menu.ts';
2
+ import type { SidebarRightPort } from '../../../contracts/ports.ts';
3
+ import type { HeaderActionProps } from './HeaderActionButton.tsx';
4
+ /**
5
+ * What this plugin contributes to the conversation header.
6
+ *
7
+ * The mounts in `mount.ts` are wiring: they resolve the panel's face, keep one
8
+ * registration per seat, and hand it a component. *What* is offered — which
9
+ * slot, in what order, at what shadowing rank, under which condition — is
10
+ * decided here, so the contract can be read and tested without a renderer.
11
+ */
12
+ /**
13
+ * The right panel's own file list (`ui-sidebar-files`): the Session's project
14
+ * directory, as a tree, in the same panel the terminal and browser views open
15
+ * into.
16
+ */
17
+ export declare const FILES_KIND = "files";
18
+ /**
19
+ * List orders inside `conversation.session.header.utilities`.
20
+ *
21
+ * The project folder leads the row, so it carries the lowest order of every
22
+ * entry there is — below the retired open-in-app control (-10) and the native
23
+ * scheduled-tasks entry (-5), not only below this plugin's own views: the
24
+ * reference draws the folder as the cluster's first control, whatever a Session
25
+ * happens to carry. The two views then land directly before the native session
26
+ * menu at 0 and keep it as the cluster's closing entry.
27
+ */
28
+ export declare const FOLDER_ORDER = -11;
29
+ export declare const TERMINAL_ORDER = -2;
30
+ export declare const BROWSER_ORDER = -1;
31
+ /** The views this plugin exposes in the utilities group, in cluster order. */
32
+ export declare const VIEWS: readonly [{
33
+ readonly id: 'ccd-folder';
34
+ readonly kind: "files";
35
+ readonly order: -11;
36
+ }, {
37
+ readonly id: 'ccd-terminal';
38
+ readonly kind: 'terminal';
39
+ readonly order: -2;
40
+ }, {
41
+ readonly id: 'ccd-browser';
42
+ readonly kind: 'browser';
43
+ readonly order: -1;
44
+ }];
45
+ /**
46
+ * Registration key of the corner retirement. The seat is a *single* cell, so
47
+ * the entry carries no id of its own — this key names it in the mount's own
48
+ * bookkeeping, where a registration is replaced at most once.
49
+ */
50
+ export declare const CORNER_ENTRY = "header-corner";
51
+ /** Opens or focuses one right-panel view for the Session whose header was used. */
52
+ export type OpenViewKind = (kind: string, sessionId: string) => void;
53
+ /**
54
+ * The utilities entry for one right-panel view.
55
+ *
56
+ * `id` and `order` are what make the entry a *list* cell: the panel's existing
57
+ * entries keep their own seats, and this one takes the position its order names.
58
+ *
59
+ * @param view - the entry's identity and the page kind it opens.
60
+ * @param labels - localised copy, by view kind.
61
+ * @param open - opens the kind for the slot's own Session id.
62
+ * @returns the options the registry is called with.
63
+ */
64
+ export declare function utilitiesEntry(view: (typeof VIEWS)[number], labels: Record<(typeof VIEWS)[number]['kind'] | typeof FILES_KIND, string>, open: OpenViewKind): {
65
+ name: "conversation.session.header.utilities";
66
+ id: "ccd-browser" | "ccd-folder" | "ccd-terminal";
67
+ order: -11 | -2 | -1;
68
+ inject: (sessionId: string) => HeaderActionProps;
69
+ };
70
+ /**
71
+ * The corner seat's retirement: the cell the reference leaves empty.
72
+ *
73
+ * That seat's shipped occupant is `ui-sidebar-right`'s collapsed-panel expand
74
+ * control, and this interface does not want it: hovering it drew a control the
75
+ * reference has no room for, and the action it carries lives in the Session menu
76
+ * instead ({@link sidebarExpandRow}). Registering below the occupant's rank
77
+ * ({@link OVERRIDE_PRIORITY}) takes the cell, and the entry's component renders
78
+ * nothing — a structural retirement rather than a control hidden in place, so
79
+ * nothing of the displaced button stays behind: no hover surface, no tooltip and
80
+ * no hit area.
81
+ *
82
+ * @returns the options the registry is called with.
83
+ */
84
+ export declare function cornerRetirement(): {
85
+ name: "conversation.session.header.corner";
86
+ priority: number;
87
+ };
88
+ /**
89
+ * The Session menu's row that puts a collapsed right panel back on screen.
90
+ *
91
+ * Two facts decide it, and both are read fresh: the frame carries the host's own
92
+ * `data-rightbar-collapsed` (the panel's column has no width) and the panel's
93
+ * face can expand it again. The action is the panel's own method rather than a
94
+ * click on a hidden control, so the column's state stays entirely the panel's.
95
+ *
96
+ * @param sidebar - the right-Sidebar face the header was built over.
97
+ * @param document - the renderer document whose frame reports the panel's state.
98
+ * @param label - localised row copy.
99
+ * @returns the row the menu module keeps in the header's ⋮ list while it holds.
100
+ */
101
+ export declare function sidebarExpandRow(sidebar: SidebarRightPort, document: Document, label: string): SessionMenuRow;
@@ -0,0 +1,28 @@
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
+ * Add the right panel's views to the conversation header — the project folder
6
+ * first, then terminal and browser, all in the utilities group — and move the
7
+ * collapsed panel's re-entry action into the Session menu.
8
+ *
9
+ * The header's trailing group is a native list slot — `ui-open-in-app`,
10
+ * `ui-schedule` and `session-log-export` all register into `utilities` — so the
11
+ * plugin takes three more entries rather than replacing or reordering anything.
12
+ * Each entry appears only while its tab kind is registered in the panel's own
13
+ * registry, so a build without that view keeps the native cluster intact and
14
+ * never shows a button that cannot open anything.
15
+ *
16
+ * The corner seat is different, and deliberately so. It is a *single* cell whose
17
+ * shipped occupant is `ui-sidebar-right`'s collapsed-panel expand control; the
18
+ * reference draws nothing there, so the plugin shadows that occupant with an
19
+ * entry that renders nothing and puts the expand action into the Session menu's
20
+ * own ⋮ list (`compat/session-menu.ts`), where it appears only while the panel
21
+ * is collapsed. Nothing about the panel's own state changes hands: the menu row
22
+ * calls the panel's own service method.
23
+ *
24
+ * @param ctx - client context carrying the injected services.
25
+ * @param environment - feature environment for logging and style mounting.
26
+ * @param cleanup - scope owning the stylesheet, the registrations and their Fiber.
27
+ */
28
+ export declare function mountHeaderActions(ctx: Context, environment: FeatureEnvironment, cleanup: CleanupScope): void;
@@ -0,0 +1,21 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { SidebarRightPort, SidebarRightTabsPort } from '../../../contracts/ports.ts';
3
+ /** Read the cross-plugin right-Sidebar face, or nothing when this build lacks it. */
4
+ export declare function resolveSidebarRight(scope: Context): SidebarRightPort | undefined;
5
+ /** Read the tab-type registry that decides which views exist in this build. */
6
+ export declare function resolveSidebarTabs(scope: Context): SidebarRightTabsPort | undefined;
7
+ /**
8
+ * Focus the Session's existing view of this kind, or open a new one.
9
+ *
10
+ * `openTab` places a page at the address the panel records pages under, which
11
+ * would front an already-open view for kinds that allow one instance; the
12
+ * inventory snapshot lets the header focus that tab instead of stacking a
13
+ * second one. Terminal views may legitimately be multiple, so the first match
14
+ * wins — the panel's own guide still offers a new one. A build whose face does
15
+ * not carry the inventory falls back to plain `openTab`.
16
+ *
17
+ * @param sidebar - the right-Sidebar face.
18
+ * @param kind - page type to reveal.
19
+ * @param sessionId - Session whose header was clicked.
20
+ */
21
+ export declare function openView(sidebar: SidebarRightPort, kind: string, sessionId: string): void;