dsh-claude-desktop-theme 0.4.3 → 0.4.5

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 (37) hide show
  1. package/README.md +95 -140
  2. package/lib/client.js +9036 -812
  3. package/lib/index.js +56 -19
  4. package/lib/types/client/compat/browser-header.d.ts +1 -2
  5. package/lib/types/client/compat/frame.d.ts +29 -0
  6. package/lib/types/client/compat/hero-greeting.d.ts +6 -0
  7. package/lib/types/client/compat/host-dom.d.ts +8 -0
  8. package/lib/types/client/compat/mutation-scope.d.ts +43 -0
  9. package/lib/types/client/compat/pane-chrome.d.ts +2 -0
  10. package/lib/types/client/compat/permission-confirmation.d.ts +5 -0
  11. package/lib/types/client/compat/permission-menu.d.ts +4 -1
  12. package/lib/types/client/compat/pet-anchor.d.ts +5 -1
  13. package/lib/types/client/compat/sidebar-search.d.ts +18 -0
  14. package/lib/types/client/compat/view-switch.d.ts +1 -1
  15. package/lib/types/client/contracts/ports.d.ts +2 -0
  16. package/lib/types/client/features/composer-pet/BlankPet.d.ts +4 -1
  17. package/lib/types/client/features/composer-pet/ClaudePet.d.ts +7 -0
  18. package/lib/types/client/features/composer-pet/claude-frames.d.ts +12 -0
  19. package/lib/types/client/features/composer-pet/claude-motion.d.ts +3 -0
  20. package/lib/types/client/features/conversation/header-actions/HeaderActionButton.d.ts +2 -2
  21. package/lib/types/client/features/conversation/header-actions/entries.d.ts +11 -15
  22. package/lib/types/client/features/conversation/view-switch-icons.d.ts +2 -2
  23. package/lib/types/client/features/model-controls/EffortPanel.d.ts +2 -1
  24. package/lib/types/client/features/model-controls/ModelControls.d.ts +11 -1
  25. package/lib/types/client/features/model-controls/model-icon-data.d.ts +31 -0
  26. package/lib/types/client/features/model-controls/model-icon-match.d.ts +20 -0
  27. package/lib/types/client/features/model-controls/model-icons.d.ts +13 -0
  28. package/lib/types/client/features/model-controls/popup.d.ts +20 -0
  29. package/lib/types/client/features/model-controls/selection.d.ts +2 -0
  30. package/lib/types/client/features/new-session/ripples.d.ts +2 -2
  31. package/lib/types/client/features/settings/ChoiceMenu.d.ts +15 -0
  32. package/lib/types/client/features/settings/fields.d.ts +18 -0
  33. package/lib/types/client/features/settings/locales.d.ts +16 -1
  34. package/lib/types/client/features/statistics/source.d.ts +2 -1
  35. package/lib/types/host/index.d.ts +30 -0
  36. package/lib/types/shared/config.d.ts +11 -2
  37. package/package.json +2 -1
package/lib/index.js CHANGED
@@ -2,6 +2,11 @@
2
2
  import z from "@deepseek-ai/schemastery";
3
3
 
4
4
  // src/shared/config.ts
5
+ var DEFAULT_PERSONALIZATION = Object.freeze({
6
+ petStyle: "whale",
7
+ greetingStyle: "explore",
8
+ greetingName: ""
9
+ });
5
10
  var DEFAULT_FEATURES = Object.freeze({
6
11
  shell: true,
7
12
  sidebar: true,
@@ -11,7 +16,8 @@ var DEFAULT_FEATURES = Object.freeze({
11
16
  statistics: false,
12
17
  "composer-pet": true,
13
18
  "composer-stats": false,
14
- "context-panel": true
19
+ "context-panel": true,
20
+ "hero-ripples": true
15
21
  });
16
22
  var DEFAULT_APPEARANCE = Object.freeze({
17
23
  canvas: "",
@@ -26,7 +32,9 @@ var DEFAULT_VIEW = Object.freeze({
26
32
  showEmptyGroups: false,
27
33
  groupByStatus: true,
28
34
  showHeaderSubagents: false,
29
- showHeaderTeam: false
35
+ showHeaderTeam: false,
36
+ showSidebarBrand: false,
37
+ showModelIcons: true
30
38
  });
31
39
  var FONT_NAME_MAX_LENGTH = 64;
32
40
 
@@ -431,12 +439,14 @@ var UsageAggregator = class {
431
439
  ctx;
432
440
  warn;
433
441
  values = /* @__PURE__ */ new Map();
434
- queued = [];
442
+ queued = /* @__PURE__ */ new Map();
435
443
  folding = /* @__PURE__ */ new Set();
436
444
  failures = /* @__PURE__ */ new Map();
437
445
  running = 0;
438
446
  refreshRun = null;
439
447
  snapshot = null;
448
+ dirty = true;
449
+ listed = /* @__PURE__ */ new Set();
440
450
  disposed = false;
441
451
  constructor(ctx, warn) {
442
452
  this.ctx = ctx;
@@ -444,7 +454,15 @@ var UsageAggregator = class {
444
454
  }
445
455
  /** The most recent snapshot; an empty one before the first refresh. */
446
456
  get current() {
447
- return this.snapshot ?? aggregateUsage([], { now: Date.now(), pending: 0, missed: 0 });
457
+ if (this.snapshot === null || this.dirty) {
458
+ this.snapshot = aggregateUsage(this.values.values(), {
459
+ now: Date.now(),
460
+ pending: this.queued.size + this.running,
461
+ missed: this.failures.size
462
+ });
463
+ this.dirty = false;
464
+ }
465
+ return this.snapshot;
448
466
  }
449
467
  /**
450
468
  * Bring the snapshot up to date.
@@ -456,6 +474,7 @@ var UsageAggregator = class {
456
474
  * @returns the snapshot as of this pass.
457
475
  */
458
476
  refresh() {
477
+ if (this.disposed) return Promise.resolve(this.current);
459
478
  const running = this.refreshRun;
460
479
  if (running !== null) return running;
461
480
  const run = this.pass().finally(() => {
@@ -467,24 +486,34 @@ var UsageAggregator = class {
467
486
  /** Stop folding; in-flight reads settle but no longer publish. */
468
487
  dispose() {
469
488
  this.disposed = true;
470
- this.queued.length = 0;
489
+ this.queued.clear();
490
+ this.values.clear();
491
+ this.failures.clear();
492
+ this.listed.clear();
493
+ this.snapshot = null;
471
494
  }
472
495
  async pass() {
473
496
  try {
474
497
  const candidates = await this.list();
498
+ if (this.disposed) return this.current;
475
499
  const listed = new Set(candidates.map((candidate) => candidate.header.id));
500
+ this.listed = listed;
476
501
  for (const id of [...this.values.keys()]) if (!listed.has(id)) this.values.delete(id);
477
502
  for (const id of [...this.failures.keys()]) if (!listed.has(id)) this.failures.delete(id);
503
+ for (const id of this.queued.keys()) if (!listed.has(id)) this.queued.delete(id);
478
504
  for (const candidate of candidates) {
479
505
  if (this.disposed) break;
480
506
  const value = this.readSafely(candidate);
481
507
  if (value === void 0) this.enqueue(candidate);
482
- else this.values.set(candidate.header.id, value);
508
+ else {
509
+ this.queued.delete(candidate.header.id);
510
+ this.values.set(candidate.header.id, value);
511
+ }
483
512
  }
484
513
  } catch (error) {
485
514
  this.warn("session listing failed; the card keeps the last snapshot", error);
486
515
  }
487
- this.publish();
516
+ this.invalidate();
488
517
  return this.current;
489
518
  }
490
519
  /** Every stored session the card counts, newest first. */
@@ -530,24 +559,25 @@ var UsageAggregator = class {
530
559
  /** Queue one cold fold unless it is already queued, running, or cooling down. */
531
560
  enqueue(candidate) {
532
561
  const id = candidate.header.id;
533
- if (this.folding.has(id) || this.queued.some((entry) => entry.header.id === id)) return;
562
+ if (this.disposed || this.folding.has(id) || this.queued.has(id)) return;
534
563
  const failedAt = this.failures.get(id);
535
564
  if (failedAt !== void 0 && Date.now() - failedAt < RETRY_COOLDOWN_MS) return;
536
- this.queued.push(candidate);
565
+ this.queued.set(id, candidate);
537
566
  this.pump();
538
567
  }
539
568
  /** Keep at most {@link FOLD_CONCURRENCY} folds in flight. */
540
569
  pump() {
541
570
  while (!this.disposed && this.running < FOLD_CONCURRENCY) {
542
- const candidate = this.queued.shift();
571
+ const candidate = this.queued.values().next().value;
543
572
  if (candidate === void 0) return;
544
573
  const id = candidate.header.id;
574
+ this.queued.delete(id);
545
575
  this.folding.add(id);
546
576
  this.running += 1;
547
577
  void this.fold(candidate).finally(() => {
548
578
  this.folding.delete(id);
549
579
  this.running -= 1;
550
- this.publish();
580
+ this.invalidate();
551
581
  this.pump();
552
582
  });
553
583
  }
@@ -560,7 +590,9 @@ var UsageAggregator = class {
560
590
  let handle;
561
591
  try {
562
592
  handle = await persistence.open(candidate.header.id, "read");
593
+ if (this.disposed || !this.listed.has(candidate.header.id)) return;
563
594
  const { events } = await handle.read(0);
595
+ if (this.disposed || !this.listed.has(candidate.header.id)) return;
564
596
  const block = cache.coldSnapshot(handle.header, handle.inheritedEventCount, events);
565
597
  const value = block.values[USAGE_KEY];
566
598
  if (this.disposed) return;
@@ -571,6 +603,7 @@ var UsageAggregator = class {
571
603
  this.values.set(candidate.header.id, value);
572
604
  this.failures.delete(candidate.header.id);
573
605
  } catch (error) {
606
+ if (this.disposed || !this.listed.has(candidate.header.id)) return;
574
607
  this.failures.set(candidate.header.id, Date.now());
575
608
  this.warn(`fold failed for session ${candidate.header.id}`, error);
576
609
  } finally {
@@ -582,14 +615,10 @@ var UsageAggregator = class {
582
615
  }
583
616
  }
584
617
  }
585
- /** Recompute the snapshot from the values read so far. */
586
- publish() {
618
+ /** Fold completions invalidate totals; only a route read pays for aggregation. */
619
+ invalidate() {
587
620
  if (this.disposed) return;
588
- this.snapshot = aggregateUsage(this.values.values(), {
589
- now: Date.now(),
590
- pending: this.queued.length + this.running,
591
- missed: this.failures.size
592
- });
621
+ this.dirty = true;
593
622
  }
594
623
  };
595
624
  function mountStatistics(ctx, isEnabled) {
@@ -995,7 +1024,8 @@ var Config = z.object({
995
1024
  statistics: z.boolean().default(DEFAULT_FEATURES.statistics),
996
1025
  "composer-pet": z.boolean().default(DEFAULT_FEATURES["composer-pet"]),
997
1026
  "composer-stats": z.boolean().default(DEFAULT_FEATURES["composer-stats"]),
998
- "context-panel": z.boolean().default(DEFAULT_FEATURES["context-panel"])
1027
+ "context-panel": z.boolean().default(DEFAULT_FEATURES["context-panel"]),
1028
+ "hero-ripples": z.boolean().default(DEFAULT_FEATURES["hero-ripples"])
999
1029
  }).default({ ...DEFAULT_FEATURES }).description("\u6309\u6A21\u5757\u542F\u7528").volatile(),
1000
1030
  /* One volatile node per section, exactly like `features`: Cordis rejects a
1001
1031
  volatile field nested inside another volatile field ("volatile fields
@@ -1010,7 +1040,14 @@ var Config = z.object({
1010
1040
  uiCjk: z.string().default(DEFAULT_FONTS.uiCjk).description("\u754C\u9762\u4E2D\u6587\u5B57\u4F53\u540D\uFF08\u7559\u7A7A\u7528\u7CFB\u7EDF\u6808\uFF09").pattern(FONT_NAME),
1011
1041
  code: z.string().default(DEFAULT_FONTS.code).description("\u4EE3\u7801\u4E0E\u7B49\u5BBD\u5B57\u4F53\u540D\uFF08\u7559\u7A7A\u7528\u7CFB\u7EDF\u6808\uFF09").pattern(FONT_NAME)
1012
1042
  }).default({ ...DEFAULT_FONTS }).description("\u5B57\u4F53").volatile(),
1043
+ personalization: z.object({
1044
+ petStyle: z.union(["whale", "claude"]).default(DEFAULT_PERSONALIZATION.petStyle).description("\u8F93\u5165\u6846\u5BA0\u7269\u6837\u5F0F"),
1045
+ greetingStyle: z.union(["explore", "claude"]).default(DEFAULT_PERSONALIZATION.greetingStyle).description("\u9996\u9875\u95EE\u5019\u8BED\u4E0E\u56FE\u6807\u6837\u5F0F"),
1046
+ greetingName: z.string().default("").pattern(/^.{0,64}$/u).description("\u95EE\u5019\u8BED\u4E2D\u7684\u79F0\u547C\uFF08\u53EF\u7559\u7A7A\uFF09")
1047
+ }).default({ ...DEFAULT_PERSONALIZATION }).description("\u4E2A\u6027\u5316").volatile(),
1013
1048
  view: z.object({
1049
+ showModelIcons: z.boolean().default(DEFAULT_VIEW.showModelIcons).description("\u663E\u793A\u6A21\u578B\u9009\u5355\u4E2D\u7684\u524D\u7F6E\u56FE\u6807\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09"),
1050
+ showSidebarBrand: z.boolean().default(DEFAULT_VIEW.showSidebarBrand).description("\u663E\u793A\u4FA7\u680F DeepSeek Harness \u54C1\u724C\u6761\u76EE\uFF08\u9ED8\u8BA4\u5173\u95ED\uFF09"),
1014
1051
  showHeaderSubagents: z.boolean().default(DEFAULT_VIEW.showHeaderSubagents).description("\u663E\u793A\u9876\u680F\u5B50\u4EE3\u7406\u5165\u53E3\uFF08\u4EC5\u63A7\u5236\u5165\u53E3\u663E\u793A\uFF09"),
1015
1052
  showHeaderTeam: z.boolean().default(DEFAULT_VIEW.showHeaderTeam).description("\u663E\u793A\u9876\u680F\u56E2\u961F\u5165\u53E3\uFF08\u4EC5\u63A7\u5236\u5165\u53E3\u663E\u793A\uFF09"),
1016
1053
  groupByStatus: z.boolean().default(DEFAULT_VIEW.groupByStatus).description("\u6309\u4F1A\u8BDD\u72B6\u6001\u5206\u7EC4\uFF08\u4FDD\u7559\u539F\u751F\u9879\u76EE\u89C6\u56FE\u9009\u9879\uFF09"),
@@ -2,10 +2,9 @@ import type { Disposer } from '../contracts/ports.ts';
2
2
  /**
3
3
  * The name the browsing region's own title line draws.
4
4
  * @param text - the host's word, exactly as it rendered it.
5
- * @param noHistory - whether the plugin's no-history prompt is on screen.
6
5
  * @returns the reference's name, or null to leave the host's own word.
7
6
  */
8
- export declare function browserHeaderWord(text: string, noHistory: boolean): string | null;
7
+ export declare function browserHeaderWord(text: string): string | null;
9
8
  /**
10
9
  * Name the browsing region's separate toolbar.
11
10
  *
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Coalesce DOM work into one paint frame.
3
+ *
4
+ * A mutation observer delivers one batch per microtask, and several documents
5
+ * (a panel drag, a streaming transcript, a slider writing its own inline
6
+ * style) can produce many batches between two paints. Doing a full geometry
7
+ * pass per batch multiplies the layout reads by the batch count; doing it once
8
+ * per frame is what the screen can actually use.
9
+ *
10
+ * The fallbacks are deliberate: an occluded Electron window throttles animation
11
+ * frames, and a frame without `requestAnimationFrame` at all (some test hosts)
12
+ * must still settle. In both cases the work runs on a timeout instead, so the
13
+ * published values are never left stale.
14
+ */
15
+ /** One coalescing scheduler; `schedule` runs `task` once before the next paint. */
16
+ export interface FrameScheduler {
17
+ /** Queue the task; repeated calls before it runs collapse into one. */
18
+ schedule(): void;
19
+ /** Drop a queued task without running it. */
20
+ cancel(): void;
21
+ }
22
+ /**
23
+ * Build a scheduler over one task.
24
+ *
25
+ * @param task - the read/write pass to run at most once per frame.
26
+ * @param win - the window supplying animation frames; defaults to the global.
27
+ * @returns the scheduler; its task never runs after `cancel`.
28
+ */
29
+ export declare function frameScheduler(task: () => void, win?: Window | null): FrameScheduler;
@@ -0,0 +1,6 @@
1
+ import type { PersonalizationConfig } from '../../shared/config.ts';
2
+ import type { Disposer } from '../contracts/ports.ts';
3
+ /** The greeting follows the interface language and never includes an account name. */
4
+ export declare function claudeGreeting(language?: string): string;
5
+ /** Add a reversible presentation beside the native heading without taking its React nodes. */
6
+ export declare function mountHeroGreeting(document: Document, config: PersonalizationConfig): Disposer;
@@ -42,6 +42,8 @@
42
42
  * - `packages/client/ui-settings/src/client/SettingsRoot.module.css` (`Dws9Sa_`)
43
43
  * - `packages/client/ui-settings-account/src/client/AccountMenu.module.css` (`ZogL4G_`)
44
44
  * - `packages/client/ui-primitives/lib/Menu.module.css` (`_4ub78_`, `_ri079_`)
45
+ * - `packages/client/ui-primitives/lib/Modal.module.css` (`_o6lrb_`) and
46
+ * `RiskConfirmation.module.css` (`_cjd8z_`), shared static frontend styles
45
47
  * - `packages/client/ui-primitives/lib/Menu.js` (`MenuItemButton`: the icon seat
46
48
  * `account-menu.css` scales; its `[role="menuitem"] > span` structure)
47
49
  * - `packages/client/ui-chat/src/client/chat/AssistantMarkdown.module.css` (`gKv1-q_`)
@@ -127,6 +129,7 @@ export declare const HOST: Readonly<{
127
129
  readonly filesBody: '[data-files-body]';
128
130
  readonly filesEntry: '[data-files-entry]';
129
131
  readonly sidebarRoot: '._3WPZCG_root';
132
+ readonly sidebarCollapsed: '._3WPZCG_collapsed';
130
133
  readonly sidebarLogoRow: '._3WPZCG_logoRow';
131
134
  readonly sidebarBrand: '._3WPZCG_brand';
132
135
  readonly sidebarBrandName: '._3WPZCG_brandName';
@@ -326,6 +329,11 @@ export declare const HOST: Readonly<{
326
329
  children, so the statistics card is appended here and positioned against
327
330
  the greeting. */
328
331
  readonly heroComposerStack: '.ST7X_W_composerStack';
332
+ /** Shared frontend RiskConfirmation DOM, identical in both desktop builds. */
333
+ readonly riskConfirmation: '._confirmation_cjd8z_1[role="dialog"]';
334
+ readonly riskAcknowledgement: '._acknowledgement_cjd8z_38';
335
+ readonly riskConfirmAction: '._confirmAction_cjd8z_71';
336
+ readonly riskCancelAction: '._modalAction_cjd8z_67';
329
337
  /** Every menu surface the `Menu` primitive renders, portal or in place. */
330
338
  readonly menu: '[role="menu"]';
331
339
  readonly menuItem: '[role="menuitem"]';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Whether a mutation batch can concern one region of the page.
3
+ *
4
+ * Several modules here observe the whole body because the host can move or
5
+ * rebuild their anchor at any time, then pay a geometry pass on every batch.
6
+ * Most batches, though, come from somewhere else — a drag in the right pane, a
7
+ * streaming transcript on another page, another plugin writing its own markup.
8
+ * This predicate lets a module skip those batches without narrowing what it
9
+ * observes, so an anchor that is replaced still reaches it.
10
+ *
11
+ * The answer is deliberately conservative: a region that is missing, detached
12
+ * or cannot answer `contains` makes every batch relevant, so a module that
13
+ * cannot yet measure is never starved of the mutation that would let it.
14
+ */
15
+ /**
16
+ * Whether any record in a batch can move something inside `region`.
17
+ *
18
+ * `region` is the element the module measures — the composer, the scroller,
19
+ * the conversation root. A change inside it, on it, or on any of its ancestors
20
+ * counts, because those are the writes that can change its box. A `null`
21
+ * region, or one the host has already detached, makes the batch relevant:
22
+ * that is exactly the state in which the module has to look again.
23
+ *
24
+ * @param records - the observer's batch.
25
+ * @param region - the element being measured, or null while it is unknown.
26
+ * @returns true when the batch is worth a pass.
27
+ */
28
+ export declare function recordsTouch(records: readonly MutationRecord[], region: Element | null): boolean;
29
+ /**
30
+ * Whether a batch inserted a subtree the selector names.
31
+ *
32
+ * For modules that watch the whole body only because their anchor can be
33
+ * replaced: an insertion that matches the selector is the one structural event
34
+ * worth a pass, and it costs one `matches`/`querySelector` per inserted element
35
+ * rather than a scan of the document. The predicate is also conservative: a
36
+ * node from another realm, or one without the selector helpers, counts as a
37
+ * match.
38
+ *
39
+ * @param records - the observer's batch.
40
+ * @param selector - the marker whose arrival matters.
41
+ * @returns true when an inserted element is, or contains, a match.
42
+ */
43
+ export declare function recordsIntroduce(records: readonly MutationRecord[], selector: string): boolean;
@@ -7,6 +7,7 @@ export interface PaneChromeOptions {
7
7
  readonly onNew: () => void;
8
8
  readonly onSelect: (id: string) => void;
9
9
  readonly onClose: (id: string) => void;
10
+ readonly onCloseGroup?: () => void;
10
11
  readonly closeTabs: readonly string[];
11
12
  readonly terminals: readonly {
12
13
  id: string;
@@ -16,6 +17,7 @@ export interface PaneChromeOptions {
16
17
  readonly files: readonly {
17
18
  id: string;
18
19
  title: string;
20
+ path: string;
19
21
  selected: boolean;
20
22
  }[];
21
23
  readonly panel: HTMLElement;
@@ -0,0 +1,5 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Remove the redundant acknowledgement step from permission confirmations.
3
+ * The native handler still updates React; only the user can press Enable.
4
+ * Other RiskConfirmation callers retain their acknowledgement controls. */
5
+ export declare function mountPermissionConfirmation(document: Document, report: (error: unknown) => void): Disposer;
@@ -36,6 +36,9 @@ export interface PermissionCopy {
36
36
  * @returns Chinese copy for a Chinese document, English otherwise.
37
37
  */
38
38
  export declare function permissionCopy(document: Document): PermissionCopy;
39
+ /** Short summaries of DSH's four built-in Agent presets. */
40
+ export declare function agentPresetCopy(document: Document): PermissionCopy;
41
+ export declare function mountAgentPresetMenu(document: Document, report: (error: unknown) => void): Disposer;
39
42
  /**
40
43
  * The permission picker's card, drawn the way the reference draws it.
41
44
  *
@@ -54,7 +57,7 @@ export declare function permissionCopy(document: Document): PermissionCopy;
54
57
  * `permissionCopy` above. It is kept in one table per document language, so a
55
58
  * wording change is one edit in one place.
56
59
  *
57
- * Three facts decide how the rows are reached, and each is worth stating:
60
+ * Three facts decide how the permission rows are reached, and each is worth stating:
58
61
  *
59
62
  * - **A row is recognised by its name.** `Menu` rows carry no identity of their
60
63
  * own — no id, no data attribute, and the preset's own glyph is a thing this
@@ -27,4 +27,8 @@ export declare const PET_INSET = 12;
27
27
  * selects new sessions; hidden, missing or unlaid-out cards publish null.
28
28
  * @returns position source whose dispose releases all listeners and observers.
29
29
  */
30
- export declare function createPetAnchor(document: Document, report: (error: unknown) => void): PetAnchorSource;
30
+ export declare function createPetAnchor(document: Document, report: (error: unknown) => void, box?: {
31
+ readonly width: number;
32
+ readonly height: number;
33
+ readonly inset?: number;
34
+ }): PetAnchorSource;
@@ -0,0 +1,18 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Marks the sidebar column whose field is currently parked above New Session. */
3
+ export declare const SIDEBAR_SEARCH_PLACED_ATTRIBUTE = "data-ccd-sidebar-search-placed";
4
+ /**
5
+ * Park the browsing region's search field above New Session.
6
+ *
7
+ * The field belongs to `ui-workspace`'s `WorkspaceBrowser`, and React removes
8
+ * it from the section header whenever the sidebar collapses to its rail.
9
+ * Moving the React-owned node would make that removal target the wrong parent,
10
+ * so the node stays where React put it and is positioned over the gap that New
11
+ * Session opens above itself. That gap sits after the brand row when the brand
12
+ * is visible and directly below the window strip when it is not.
13
+ *
14
+ * The section header keeps its own row. With the search slot out of flow, its
15
+ * label takes the leading edge and the native View options trigger stays on the
16
+ * trailing edge.
17
+ */
18
+ export declare function mountSidebarSearch(document: Document, report: (error: unknown) => void): Disposer;
@@ -21,7 +21,7 @@ export declare const VIEW_GLYPH_ATTRIBUTE = "data-ccd-view-glyph";
21
21
  /**
22
22
  * The parked pill's inset from the sidebar column's right edge, in px. Measured
23
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).
24
+ * pill's right edge (DESIGN.md §6.5).
25
25
  */
26
26
  export declare const VIEW_DOCK_INSET = 8;
27
27
  /** Bundled artwork, one per shipped View; see features/conversation/view-switch-icons.ts. */
@@ -82,6 +82,8 @@ export interface SidebarRightPort {
82
82
  }): void;
83
83
  /** Focus a tab and the pane holding it; a missing tab is left alone. */
84
84
  focus(tabId: string): void;
85
+ /** Close a tab in the on-screen Session through the host's cleanup path. */
86
+ close?(tabId: string): void;
85
87
  /**
86
88
  * Open tab metadata across adopted Sessions — and, in the shipped face, the
87
89
  * snapshot store behind it: the plugin's pane layer re-reads the layout
@@ -1,7 +1,10 @@
1
1
  import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { PersonalizationConfig } from '../../../shared/config.ts';
2
3
  import type { PetAnchor } from '../../compat/pet-anchor.ts';
3
4
  /** Business face the frame overlay injects: the measured new-session corner. */
4
5
  export interface BlankPetFace {
6
+ /** Visual variant chosen in settings. */
7
+ readonly petStyle: PersonalizationConfig['petStyle'];
5
8
  /** Selector hook over the compat layer's measurement. */
6
9
  readonly useAnchor: () => PetAnchor | null;
7
10
  }
@@ -14,4 +17,4 @@ export type BlankPetProps = PropsRuntime<'shell.overlay'> & BlankPetFace;
14
17
  * @param props - injected measurement.
15
18
  * @returns the positioned whale, or null.
16
19
  */
17
- export declare function BlankPet({ useAnchor }: BlankPetProps): import("react/jsx-runtime").JSX.Element | null;
20
+ export declare function BlankPet({ useAnchor, petStyle }: BlankPetProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,7 @@
1
+ /** Reference pixel companion: take out a laptop, type, then return to standing. */
2
+ export declare function ClaudePet(): import("react/jsx-runtime").JSX.Element;
3
+ /** One shared drawing path for idle and recorded poses, also used by previews. */
4
+ export declare function ClaudeArt({ elapsed, reducedMotion }: {
5
+ readonly elapsed?: number | null;
6
+ readonly reducedMotion?: boolean;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ /** Geometric reconstruction of the original click sequence, sampled at 20fps.
2
+ * Recording antialiasing/compression fringes are not artwork: there is no outline
3
+ * layer. All feet have a constant 3px width; their roots follow the belly while
4
+ * their toes share a ground line. Only the projected feet have a deliberate bend.
5
+ */
6
+ export type ClaudeFrame = readonly [body: string, shade: string, eyes: string, laptop: string];
7
+ export declare const CLAUDE_GROUND_Y = 23.5;
8
+ export declare const CLAUDE_IDLE: ClaudeFrame;
9
+ /** Pose order and hold times follow the recording; identical clean silhouettes
10
+ * are reused so compression noise cannot animate otherwise stationary edges. */
11
+ export declare const CLAUDE_FRAMES: readonly ClaudeFrame[];
12
+ export declare const CLAUDE_TRACK: readonly number[];
@@ -0,0 +1,3 @@
1
+ export declare const CLAUDE_REACTION_MS: number;
2
+ /** Each recorded pose lasts 50ms; null restores the static mark after playback. */
3
+ export declare function claudePose(elapsed: number, reducedMotion?: boolean): readonly string[] | null;
@@ -11,8 +11,8 @@ export interface HeaderActionProps {
11
11
  /**
12
12
  * One view button for the conversation header.
13
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
14
+ * All three sit in the utilities group beside the native actions, in terminal,
15
+ * browser, project folder order; the corner seat past them holds nothing (the panel's shipped
16
16
  * expand control is retired there and its action moves into the Session menu).
17
17
  * All three are the same control: the box size, hover surface and spacing
18
18
  * belong to the seat's own stylesheet
@@ -18,28 +18,24 @@ export declare const FILES_KIND = "files";
18
18
  /**
19
19
  * List orders inside `conversation.session.header.utilities`.
20
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.
21
+ * Terminal, browser and project folder sit directly before the native session
22
+ * menu at 0, in that order.
27
23
  */
28
- export declare const FOLDER_ORDER = -11;
29
- export declare const TERMINAL_ORDER = -2;
30
- export declare const BROWSER_ORDER = -1;
24
+ export declare const TERMINAL_ORDER = -3;
25
+ export declare const BROWSER_ORDER = -2;
26
+ export declare const FOLDER_ORDER = -1;
31
27
  /** The views this plugin exposes in the utilities group, in cluster order. */
32
28
  export declare const VIEWS: readonly [{
33
- readonly id: 'ccd-folder';
34
- readonly kind: "files";
35
- readonly order: -11;
36
- }, {
37
29
  readonly id: 'ccd-terminal';
38
30
  readonly kind: 'terminal';
39
- readonly order: -2;
31
+ readonly order: -3;
40
32
  }, {
41
33
  readonly id: 'ccd-browser';
42
34
  readonly kind: 'browser';
35
+ readonly order: -2;
36
+ }, {
37
+ readonly id: 'ccd-folder';
38
+ readonly kind: "files";
43
39
  readonly order: -1;
44
40
  }];
45
41
  /**
@@ -64,7 +60,7 @@ export type OpenViewKind = (kind: string, sessionId: string) => void;
64
60
  export declare function utilitiesEntry(view: (typeof VIEWS)[number], labels: Record<(typeof VIEWS)[number]['kind'] | typeof FILES_KIND, string>, open: OpenViewKind): {
65
61
  name: "conversation.session.header.utilities";
66
62
  id: "ccd-browser" | "ccd-folder" | "ccd-terminal";
67
- order: -11 | -2 | -1;
63
+ order: -3 | -2 | -1;
68
64
  inject: (sessionId: string) => HeaderActionProps;
69
65
  };
70
66
  /**
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The two View switcher glyphs, bundled as trusted static markup.
3
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
4
+ * Drawn to the reference's own segments (DESIGN.md §6.5, @2x): a pair of
5
+ * speech bubbles for the chat View and the `</>` pair for the
6
6
  * trajectory View, both on the CCD 16 grid — 1–1.2px stroke (each glyph's own
7
7
  * weight read off the reference), round caps and joins, `currentColor` ink,
8
8
  * ink box ≈ 12.5 × 11px inside the 16px artwork.
@@ -4,6 +4,7 @@ export interface EffortPanelProps {
4
4
  readonly anchor: RefObject<HTMLButtonElement>;
5
5
  readonly choices: readonly EffortChoice[];
6
6
  readonly selected: number;
7
+ readonly defaultIndex: number;
7
8
  readonly disabled: boolean;
8
9
  readonly busy: boolean;
9
10
  readonly error: string | null;
@@ -11,4 +12,4 @@ export interface EffortPanelProps {
11
12
  readonly onClose: () => void;
12
13
  }
13
14
  /** Presentation-only slider: continuous local preview, one supported choice on release. */
14
- export declare function EffortPanel({ anchor, choices, selected, disabled, busy, error, onCommit, onClose }: EffortPanelProps): import("react/jsx-runtime").JSX.Element | null;
15
+ export declare function EffortPanel({ anchor, choices, selected, defaultIndex, disabled, busy, error, onCommit, onClose }: EffortPanelProps): import("react/jsx-runtime").JSX.Element | null;
@@ -3,9 +3,19 @@ import type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selec
3
3
  import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
5
5
  export type ModelControlsProps = Omit<ModelSelectInjected, 'directory'> & PropsLocale<'model'> & {
6
+ readonly showModelIcons: boolean;
6
7
  readonly locked: boolean;
7
8
  readonly useDirectory: () => ModelDirectoryState;
8
9
  readonly selectEffort: (route: ModelSelection, effort: string | undefined) => void;
9
10
  };
10
11
  /** SDK-derived values/hooks and callbacks only; DSH retains all business state. */
11
- export declare function ModelControls({ locked, available, useDirectory, load, select, selectEffort, t }: ModelControlsProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export declare function ModelControls({ showModelIcons, locked, available, useDirectory, load, select, selectEffort, t }: ModelControlsProps): import("react/jsx-runtime").JSX.Element | null;
13
+ export declare function ModelPanel({ showModelIcons, anchor, state, disabled, load, t, onClose, onSelect }: PropsLocale<'model'> & {
14
+ readonly showModelIcons: boolean;
15
+ readonly anchor: React.RefObject<HTMLButtonElement>;
16
+ readonly state: ModelDirectoryState;
17
+ readonly disabled: boolean;
18
+ readonly load: () => void;
19
+ readonly onClose: () => void;
20
+ readonly onSelect: (selection: ModelSelection) => void;
21
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,31 @@
1
+ export interface ModelIconShape {
2
+ readonly kind: 'path' | 'circle' | 'ellipse' | 'rect';
3
+ readonly d?: string;
4
+ readonly cx?: string;
5
+ readonly cy?: string;
6
+ readonly r?: string;
7
+ readonly rx?: string;
8
+ readonly ry?: string;
9
+ readonly x?: string;
10
+ readonly y?: string;
11
+ readonly width?: string;
12
+ readonly height?: string;
13
+ readonly fillOpacity?: string;
14
+ readonly fillRule?: string;
15
+ readonly clipRule?: string;
16
+ readonly opacity?: string;
17
+ readonly transform?: string;
18
+ }
19
+ export interface ModelIconMark {
20
+ readonly title: string;
21
+ readonly viewBox: string;
22
+ readonly shapes: readonly ModelIconShape[];
23
+ readonly color?: ModelIconColor;
24
+ }
25
+ export interface ModelIconColor {
26
+ readonly viewBox: string;
27
+ readonly fill?: string;
28
+ readonly fillRule?: string;
29
+ readonly markup: string;
30
+ }
31
+ export declare const MODEL_ICONS: Readonly<Record<string, ModelIconMark>>;
@@ -0,0 +1,20 @@
1
+ import type { ModelIconMark } from './model-icon-data.ts';
2
+ export interface ModelIconTarget {
3
+ readonly id: string;
4
+ readonly name: string;
5
+ }
6
+ export interface ModelIconMatch {
7
+ readonly slug: string;
8
+ readonly mark: ModelIconMark;
9
+ }
10
+ /**
11
+ * Resolve one row's mark, preferring the model over its routing group.
12
+ *
13
+ * The provider is a fallback because a group such as `Magpie` can carry
14
+ * several brands; matching the group first made every row in it share one
15
+ * initial. The model row is the closest statement of which family a route
16
+ * actually serves.
17
+ */
18
+ export declare function resolveModelIcon(provider: ModelIconTarget, model: ModelIconTarget): ModelIconMatch | null;
19
+ /** The fallback seat: the model's own initial, not its routing group's. */
20
+ export declare function modelIconInitial(provider: ModelIconTarget, model: ModelIconTarget): string;
@@ -0,0 +1,13 @@
1
+ interface ModelIconTarget {
2
+ readonly id: string;
3
+ readonly name: string;
4
+ }
5
+ /**
6
+ * One row's leading glyph: the model family's mark when the index knows it,
7
+ * otherwise the model's initial. The seat is always drawn so rows align.
8
+ */
9
+ export declare function ModelIcon({ provider, model }: {
10
+ readonly provider: ModelIconTarget;
11
+ readonly model: ModelIconTarget;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export {};