@dsh-electron/dsh-plugin-multi-root-workspace 0.1.12 → 0.1.14

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 (49) hide show
  1. package/CHANGELOG.en.md +29 -1
  2. package/CHANGELOG.md +29 -1
  3. package/README.en.md +9 -8
  4. package/README.md +9 -8
  5. package/cordis.patch.yml +4 -0
  6. package/detail/DETAIL.en.md +1 -0
  7. package/detail/DETAIL.md +1 -0
  8. package/detail/preview.svg +1 -0
  9. package/detail/preview.zh.svg +1 -0
  10. package/lib/client.js +492 -87
  11. package/lib/client.js.map +1 -1
  12. package/lib/{command-CmLekG1M.js → command-Dcz6nULo.js} +25 -3
  13. package/lib/command.js +1 -1
  14. package/lib/{compat-Bg2HFVMX.js → compat-veDqCOoa.js} +3 -1
  15. package/lib/compat.js +1 -1
  16. package/lib/{fs-CfzJq2wd.js → fs-Uws0ZpmV.js} +38 -0
  17. package/lib/fs.js +1 -1
  18. package/lib/index.js +4 -4
  19. package/lib/{native-win32-2waCRkPI.js → native-win32-CRNXr11U.js} +2 -1
  20. package/lib/{protocol-4zdwV7u4.js → protocol-CnR1KTXy.js} +62 -9
  21. package/lib/{registry-YTUOjmVu.js → registry-DR_sMuvS.js} +425 -125
  22. package/lib/{registry-lease-win32-DVTq9d4D.js → registry-lease-win32-DDO-c7ig.js} +2 -1
  23. package/lib/registry.js +2 -2
  24. package/lib/scope.js +36 -2
  25. package/lib/types/authority/protocol.d.ts +1 -1
  26. package/lib/types/authority/recovery.d.ts +58 -0
  27. package/lib/types/client/RootSwitcher.d.ts +8 -3
  28. package/lib/types/client/auto-review-settings.d.ts +18 -0
  29. package/lib/types/client/files-tree/FilesTree.d.ts +4 -1
  30. package/lib/types/client/files-tree/adapter.d.ts +63 -1
  31. package/lib/types/client/files-tree/locales.d.ts +1 -1
  32. package/lib/types/client/locales.d.ts +12 -0
  33. package/lib/types/client/open-in-app/locales.d.ts +1 -1
  34. package/lib/types/client/skills-settings.d.ts +12 -0
  35. package/lib/types/client/styles.d.ts +1 -1
  36. package/lib/types/compat/auto-review-client.d.ts +58 -0
  37. package/lib/types/compat/connection-rpc.d.ts +12 -0
  38. package/lib/types/compat/dsh-version.d.ts +1 -1
  39. package/lib/types/compat/files-tree.d.ts +36 -0
  40. package/lib/types/compat/fs-patch.d.ts +6 -0
  41. package/lib/types/compat/koffi.d.ts +2 -0
  42. package/lib/types/fs.d.ts +3 -1
  43. package/lib/types/registry.d.ts +98 -0
  44. package/lib/types/scope.d.ts +18 -0
  45. package/locale/en.json +7 -0
  46. package/locale/zh.json +7 -0
  47. package/package.json +89 -82
  48. package/src/auto-review/README.en.md +1 -1
  49. package/src/auto-review/README.md +1 -1
@@ -1,3 +1,4 @@
1
+ import { c as loadHostKoffi } from "./protocol-CnR1KTXy.js";
1
2
  import { realpathSync } from "node:fs";
2
3
  import { createHash } from "node:crypto";
3
4
  import { basename, dirname, join, resolve } from "node:path";
@@ -23,7 +24,7 @@ let bindings;
23
24
  /** Load kernel32 through Koffi the first time a Windows process needs it. */
24
25
  async function kernel32() {
25
26
  if (bindings !== void 0) return bindings;
26
- const library = (await import("koffi")).default.load("kernel32.dll");
27
+ const library = (await loadHostKoffi()).load("kernel32.dll");
27
28
  bindings = {
28
29
  createSemaphoreW: library.func("__stdcall", "CreateSemaphoreW", "intptr", [
29
30
  "void*",
package/lib/registry.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-YTUOjmVu.js";
2
- import { o as nativeHostAvailability } from "./protocol-4zdwV7u4.js";
1
+ import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-DR_sMuvS.js";
2
+ import { o as nativeHostAvailability } from "./protocol-CnR1KTXy.js";
3
3
  export { DEFAULT_MAX_ADDITIONAL_ROOTS, DOMAIN_NAME, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MultiRootRegistry, MultiRootRegistry as default, REGISTRY_CONTENDED_MESSAGE, TABLE_NAME, multiRootDomainSpec, nativeHostAvailability };
package/lib/scope.js CHANGED
@@ -154,6 +154,10 @@ var MultiRootScopeService = class extends Service {
154
154
  /** Additional roots keyed by canonical primary root; empty until roots are registered. */
155
155
  rootsByPrimary = /* @__PURE__ */ new Map();
156
156
  listeners = /* @__PURE__ */ new Set();
157
+ /** Enclosing {@link MultiRootScopeService.batch} depth; inner calls nest. */
158
+ batchDepth = 0;
159
+ /** True when a batched write coalesced its notification into the flush. */
160
+ batchDirty = false;
157
161
  admissionProof;
158
162
  /**
159
163
  * The registry-authority fact the registry row publishes. Defaults to
@@ -183,6 +187,36 @@ var MultiRootScopeService = class extends Service {
183
187
  };
184
188
  }
185
189
  /**
190
+ * Run `body` as one notification round: `setAdditionalRoots()` and
191
+ * `setRegistryAuthority()` apply their state immediately (a same-value
192
+ * authority write still stays silent), but subscriber invocations coalesce
193
+ * into a single flush when the OUTERMOST batch ends — also when `body`
194
+ * throws, because the state already landed. A write a subscriber makes
195
+ * during the flush is not part of the batch and notifies synchronously
196
+ * under the normal rules.
197
+ * @param body - the writes to run as one round.
198
+ * @returns the body's return value.
199
+ */
200
+ batch(body) {
201
+ this.batchDepth++;
202
+ try {
203
+ return body();
204
+ } finally {
205
+ if (--this.batchDepth === 0 && this.batchDirty) {
206
+ this.batchDirty = false;
207
+ for (const listener of this.listeners) listener();
208
+ }
209
+ }
210
+ }
211
+ /** Notify subscribers now, or coalesce into the enclosing batch's flush. */
212
+ notifySubscribers() {
213
+ if (this.batchDepth > 0) {
214
+ this.batchDirty = true;
215
+ return;
216
+ }
217
+ for (const listener of this.listeners) listener();
218
+ }
219
+ /**
186
220
  * The sorted names of the tools `toolAccess` caps — read-only for the
187
221
  * prompt contribution. An invalid config is not enumerated (it caps every
188
222
  * call but is not a listable tool set).
@@ -403,7 +437,7 @@ var MultiRootScopeService = class extends Service {
403
437
  const key = canonicalPath(primaryRoot);
404
438
  if (roots.length === 0) this.rootsByPrimary.delete(key);
405
439
  else this.rootsByPrimary.set(key, [...roots]);
406
- for (const listener of this.listeners) listener();
440
+ this.notifySubscribers();
407
441
  }
408
442
  /**
409
443
  * Publish the registry-authority fact. This is the REGISTRY's write port —
@@ -420,7 +454,7 @@ var MultiRootScopeService = class extends Service {
420
454
  const next = available ? "available" : "unavailable";
421
455
  if (this.registryAuthority === next) return;
422
456
  this.registryAuthority = next;
423
- for (const listener of this.listeners) listener();
457
+ this.notifySubscribers();
424
458
  }
425
459
  };
426
460
  //#endregion
@@ -46,7 +46,7 @@ export declare class AuthorityClient {
46
46
  private closed;
47
47
  epoch: string | undefined;
48
48
  private constructor();
49
- static connect(endpoint: string, store: string, limit: number, onChange: (snapshot: Snapshot) => void, disconnected: () => void): Promise<AuthorityClient>;
49
+ static connect(endpoint: string, store: string, limit: number, onChange: (snapshot: Snapshot) => void, disconnected: () => void, signal?: AbortSignal): Promise<AuthorityClient>;
50
50
  request(method: Method, args: readonly unknown[]): Promise<unknown>;
51
51
  close(): void;
52
52
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Bounded background retry for the registry authority: one pending timer and
3
+ * one in-flight attempt per instance, post-completion backoff, and local
4
+ * cancellation on dispose. The scheduler owns NO policy — the caller supplies
5
+ * `run` (one recovery attempt) and `eligible` (whether a degraded state still
6
+ * wants recovery), so the same object can drive contended, follower
7
+ * resync, and retryable storage failures.
8
+ *
9
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/authority/recovery
10
+ */
11
+ /** Injectable clock so fake-timer specs can drive the scheduler exactly. */
12
+ export interface RecoveryTimers {
13
+ setTimeout: typeof setTimeout;
14
+ clearTimeout: typeof clearTimeout;
15
+ random(): number;
16
+ now(): number;
17
+ }
18
+ /** What the scheduler is doing, for diagnostics surfaces. */
19
+ export type RecoveryPhase = 'idle' | 'waiting' | 'attempting' | 'disabled';
20
+ export declare class RecoveryScheduler {
21
+ private readonly run;
22
+ private readonly eligible;
23
+ private readonly enabled;
24
+ private readonly timers;
25
+ private timer;
26
+ private inFlight;
27
+ private disposed;
28
+ private attempts;
29
+ private retryAt;
30
+ constructor(options: {
31
+ run: () => Promise<void>;
32
+ eligible: () => boolean;
33
+ enabled: boolean;
34
+ timers?: RecoveryTimers;
35
+ });
36
+ /**
37
+ * Ask for a recovery attempt. Called after every `ensureAuthority()` and on
38
+ * every downgrade event. A healthy state (`!eligible()`) resets the backoff;
39
+ * a pending timer or in-flight attempt is never duplicated or reset.
40
+ */
41
+ arm(): void;
42
+ /**
43
+ * Progress event (a follower connection was just adopted): the backoff is
44
+ * earned, so reset it and run the next attempt immediately — still through
45
+ * the single pending timer.
46
+ */
47
+ kick(): void;
48
+ /** Stop scheduling. An in-flight attempt is left to finish on its own. */
49
+ dispose(): void;
50
+ get state(): {
51
+ phase: RecoveryPhase;
52
+ attempts: number;
53
+ nextRetryAt: number | undefined;
54
+ };
55
+ private clearTimer;
56
+ private delay;
57
+ private tick;
58
+ }
@@ -32,13 +32,17 @@
32
32
  * the same element's `closest('[data-dockkit-content]')` — the dockkit
33
33
  * tabHost section — with a generated fallback where the hook is absent.
34
34
  *
35
- * The selected root is component state on purpose: every tab render
36
- * occurrence gets its own, and reopening the pane resets to the primary
37
- * root. There is no cross-tab or persisted selection.
35
+ * The selected root lives in the inject product's session-scoped snapshot,
36
+ * keyed by that dockkit tab id — component state could not survive the
37
+ * remount every file open causes (the opened tab takes the pane, the Files
38
+ * body unmounts with the whole occupant subtree). One selection per tab;
39
+ * session switches mint a fresh inject product, so no selection crosses
40
+ * sessions, and a registry eviction reconciles it back to primary.
38
41
  *
39
42
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/RootSwitcher
40
43
  */
41
44
  import { type ReactNode } from 'react';
45
+ import type { NativeTreeRenderer } from '../compat/files-tree.ts';
42
46
  import { type FilesTreeSurface } from './files-tree/adapter.tsx';
43
47
  import { type OpenInAppAdapter } from './open-in-app/adapter.tsx';
44
48
  import { type WorkspaceFoldersActionProps } from './WorkspaceFoldersAction.tsx';
@@ -58,6 +62,7 @@ export interface RootSwitcherProps extends Omit<WorkspaceFoldersActionProps, 'co
58
62
  * all, which is the same absence upstream's own tree produces.
59
63
  */
60
64
  readonly filesTree?: FilesTreeSurface | undefined;
65
+ readonly nativeTree?: NativeTreeRenderer | undefined;
61
66
  }
62
67
  /** The Files-header switcher: root menu, dialog entry, and the portal tree. */
63
68
  export declare function RootSwitcher(props: RootSwitcherProps): ReactNode;
@@ -0,0 +1,18 @@
1
+ import type { AutoReviewForm } from '../compat/auto-review-client.ts';
2
+ import type { Translate } from './WorkspaceFoldersAction.tsx';
3
+ /**
4
+ * The optional Auto Review row of the plugin settings card.
5
+ *
6
+ * Its switch is the Loader row's own enablement — the same write the Plugin
7
+ * Manager's row switch performs — so the card can never disagree with the row
8
+ * list about whether the companion is on. Feedback is a transient `Toast`
9
+ * banner, never inline text: state-driven messages (a failed read, an entry
10
+ * diagnostic) fire on entry and clear when their source resolves, while write
11
+ * outcomes report once and fade. The row stays hidden entirely on hosts
12
+ * without the `pluginManager` namespace or when the composition does not list
13
+ * the optional entry.
14
+ */
15
+ export declare function AutoReviewSettingsCard({ form, t }: {
16
+ form: AutoReviewForm;
17
+ t: Translate;
18
+ }): import("react").JSX.Element | null;
@@ -3,6 +3,7 @@ import type { RemoteFailure } from '@deepseek-ai/dsh-api-remotes/client';
3
3
  import type { PropsLocale, PropsStore, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit';
5
5
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
6
+ import { type NativeTreeRenderer } from '../../compat/files-tree.ts';
6
7
  import type { WorkspaceDirectoryEntry } from '@deepseek-ai/dsh-api-workspace-files/types';
7
8
  import type { FilesInjected } from './face.ts';
8
9
  import type { createFilesStore } from './store.ts';
@@ -23,6 +24,8 @@ export type FilesTreeProps = PropsStore<ReturnType<typeof createFilesStore>> & P
23
24
  readonly signal: AbortSignal;
24
25
  /** Open one `dsh-resource://file` address (the adapter's sidebarRight route). */
25
26
  readonly onOpen: (address: string) => void;
27
+ /** Optional host-controlled tree Factory, available on the newer Files page. */
28
+ readonly nativeTree?: NativeTreeRenderer | undefined;
26
29
  };
27
30
  /**
28
31
  * Order one level's entries for display: directories first, then everything
@@ -44,4 +47,4 @@ export declare function failureLine(t: TranslateNS<'sidebarFiles'>, failure: Rem
44
47
  * has opened under it — the upstream body's scroll, seed, and render logic
45
48
  * verbatim, with the root arriving as a prop instead of `useSessions(cwd)`.
46
49
  */
47
- export declare function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, }: FilesTreeProps): ReactNode;
50
+ export declare function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, nativeTree, }: FilesTreeProps): ReactNode;
@@ -25,19 +25,72 @@
25
25
  */
26
26
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
27
27
  import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client';
28
- import type { BoundActions } from '@deepseek-ai/dsh-client-store';
28
+ import { type BoundActions, type SnapshotSelectorHook } from '@deepseek-ai/dsh-client-store';
29
+ import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
30
+ import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit';
29
31
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
30
32
  import { type FilesInjected } from './face.ts';
31
33
  import { createFilesStore } from './store.ts';
32
34
  import { filesTreeSurfaceSupported } from './primitives.ts';
33
35
  import type { FilesTreeProps } from './FilesTree.tsx';
36
+ import type { RootView } from '../../contract.ts';
34
37
  import { NS } from './locales.ts';
38
+ /**
39
+ * One session's per-tab additional-root selections, keyed by the pane's
40
+ * dockkit tab id. An absent entry means the primary root.
41
+ */
42
+ export type TreeSelection = Readonly<Record<string, RootView>>;
43
+ /**
44
+ * The lifetimes a selection runs under, keyed by tab like the store bucket:
45
+ * while an entry's signal is live the tree keeps its loaded levels, watches,
46
+ * expansion, and scroll — exactly what upstream's tab-record signal gives the
47
+ * native tree across a body remount (opening a file deactivates this tab,
48
+ * unmounting its body without ending the record).
49
+ */
50
+ export interface FilesTreeTakeover {
51
+ /**
52
+ * The live signal for (tabId, root); reuses the live entry so a remounted
53
+ * body finds its bucket and watches intact. A different root — or an entry
54
+ * already aborted — ends the previous controller before minting the next.
55
+ * @param tabId - the pane's dockkit tab id.
56
+ * @param root - absolute path of the root being browsed.
57
+ * @param section - the dockkit tabHost section the caller mounted under; a
58
+ * live entry keeps an observer on it that ends the takeover whenever the
59
+ * section leaves the document — the record's real end — including when the
60
+ * body that mounted it has long been unmounted itself.
61
+ * @returns the signal the forked tree lives under.
62
+ */
63
+ readonly signal: (tabId: TabId, root: string, section?: Element) => AbortSignal;
64
+ /**
65
+ * Abort and drop one tab's takeover: the reader deselected or switched
66
+ * roots, or the tab record itself went away.
67
+ * @param tabId - the pane's dockkit tab id.
68
+ */
69
+ readonly end: (tabId: TabId) => void;
70
+ }
35
71
  /** The inject face the registration composes onto the occupant's props. */
36
72
  export interface FilesTreeInjected {
37
73
  /** The verbatim files face (list/watch bookkeeping included). */
38
74
  readonly face: FilesInjected;
39
75
  /** Open one `dsh-resource://file` address in the host viewer. */
40
76
  readonly openFile: (address: string) => void;
77
+ /**
78
+ * The reserved inject compartment: the slot renderer binds each source to a
79
+ * `use<Name>` selector hook, so `treeSelection` reaches the component as
80
+ * `useTreeSelection`, never as a prop under its own key.
81
+ */
82
+ readonly hooks: {
83
+ readonly treeSelection: HostObservable<TreeSelection>;
84
+ };
85
+ /**
86
+ * Persist one pane's selected root; `'primary'` drops its entry. The
87
+ * session-scoped snapshot survives the body remount a file open causes.
88
+ * @param tabId - the pane's dockkit tab id.
89
+ * @param root - the selected root, or `'primary'` to browse the session root.
90
+ */
91
+ readonly selectRoot: (tabId: TabId, root: RootView | 'primary') => void;
92
+ /** The forked tree's lifetimes; see {@link FilesTreeTakeover}. */
93
+ readonly takeover: FilesTreeTakeover;
41
94
  }
42
95
  /** Everything `FilesTree` needs that the slot framework does not synthesize. */
43
96
  export interface FilesTreeSurface {
@@ -47,6 +100,12 @@ export interface FilesTreeSurface {
47
100
  readonly face: FilesInjected;
48
101
  readonly openFile: (address: string) => void;
49
102
  readonly t: FilesTreeProps['t'];
103
+ /** Selector hook over this session's per-tab root selections. */
104
+ readonly useTreeSelection: SnapshotSelectorHook<TreeSelection>;
105
+ /** The selection's write side; see {@link FilesTreeInjected.selectRoot}. */
106
+ readonly selectRoot: FilesTreeInjected['selectRoot'];
107
+ /** The forked tree's lifetimes; see {@link FilesTreeTakeover}. */
108
+ readonly takeover: FilesTreeTakeover;
50
109
  }
51
110
  /**
52
111
  * A `ClientRemote` stand-in that resolves the real service per call.
@@ -89,6 +148,9 @@ export declare function createFilesTreeSurface(props: {
89
148
  readonly actions: FilesTreeSurface['actions'];
90
149
  readonly face: FilesInjected | undefined;
91
150
  readonly openFile: ((address: string) => void) | undefined;
151
+ readonly useTreeSelection: FilesTreeSurface['useTreeSelection'] | undefined;
152
+ readonly selectRoot: FilesTreeSurface['selectRoot'] | undefined;
153
+ readonly takeover: FilesTreeSurface['takeover'] | undefined;
92
154
  }, t: FilesTreeProps['t']): FilesTreeSurface | undefined;
93
155
  export { filesTreeSurfaceSupported, NS as FILES_TREE_NS };
94
156
  export { FilesTree } from './FilesTree.tsx';
@@ -1,7 +1,7 @@
1
1
  /** Dictionary namespace owned by upstream's plugin (verbatim). */
2
2
  export declare const NS = "sidebarFiles";
3
3
  /** Key domain of the `sidebarFiles` namespace (verbatim union of upstream's zh keys). */
4
- export type SidebarFilesKey = 'shortcut.noSession' | 'type.label' | 'guide.title' | 'guide.description' | 'loading' | 'empty' | 'truncated' | 'noWorkspace' | 'reload' | 'autoRefresh' | 'autoRefresh.enable' | 'autoRefresh.disable' | 'entry.other' | 'error.notFound' | 'error.outsideWorkspace' | 'error.notDirectory' | 'error.unavailable';
4
+ export type SidebarFilesKey = 'shortcut.noSession' | 'type.label' | 'guide.title' | 'guide.description' | 'loading' | 'empty' | 'truncated' | 'noWorkspace' | 'reload' | 'autoRefresh' | 'autoRefresh.enable' | 'autoRefresh.disable' | 'entry.other' | 'entry.noViewer' | 'navigator.show' | 'navigator.hide' | 'navigator.resize' | 'error.notFound' | 'error.outsideWorkspace' | 'error.notDirectory' | 'error.unavailable';
5
5
  declare module '@deepseek-ai/dsh-client-ui-slots' {
6
6
  interface LocaleNamespaceMap {
7
7
  /** File-tree type name, guide entry, row states, and failure lines. */
@@ -15,6 +15,12 @@ export declare const zh: {
15
15
  'skills.globalEnable': string;
16
16
  'skills.globalHint': string;
17
17
  'skills.settingsFailed': string;
18
+ 'autoReview.title': string;
19
+ 'autoReview.experimental': string;
20
+ 'autoReview.hint': string;
21
+ 'autoReview.loadFailed': string;
22
+ 'autoReview.restartRequired': string;
23
+ 'autoReview.settingsFailed': string;
18
24
  'skills.userOnly': string;
19
25
  'panel.skills': string;
20
26
  'panel.skillsEnabled': string;
@@ -104,6 +110,12 @@ export declare const en: {
104
110
  'skills.globalEnable': string;
105
111
  'skills.globalHint': string;
106
112
  'skills.settingsFailed': string;
113
+ 'autoReview.title': string;
114
+ 'autoReview.experimental': string;
115
+ 'autoReview.hint': string;
116
+ 'autoReview.loadFailed': string;
117
+ 'autoReview.restartRequired': string;
118
+ 'autoReview.settingsFailed': string;
107
119
  'skills.userOnly': string;
108
120
  'panel.skills': string;
109
121
  'panel.skillsEnabled': string;
@@ -1,7 +1,7 @@
1
1
  /** Dictionary namespace owned by upstream's plugin (verbatim). */
2
2
  export declare const NS = "open-in-app";
3
3
  /** Key domain of the `open-in-app` namespace (verbatim union of upstream's zh keys). */
4
- export type OpenInAppKey = 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.opened' | 'path.revealed' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
4
+ export type OpenInAppKey = 'menu.openInSidebar' | 'menu.openInFloat' | 'menu.finder' | 'menu.reveal' | 'menu.defaultApp' | 'menu.external' | 'menu.copyLink' | 'menu.copyPath' | 'menu.copyDirectoryPath' | 'menu.copied' | 'menu.copyFailed' | 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.opened' | 'path.revealed' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
5
5
  declare module '@deepseek-ai/dsh-client-ui-slots' {
6
6
  interface LocaleNamespaceMap {
7
7
  /** Session-header "open workspace in application" copy (upstream declaration). */
@@ -1,10 +1,22 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import { type SkillsForm } from '../compat/skills-client.ts';
3
+ import { type AutoReviewForm } from '../compat/auto-review-client.ts';
3
4
  import type { Translate } from './WorkspaceFoldersAction.tsx';
4
5
  interface CardProps {
5
6
  form: SkillsForm;
6
7
  t: Translate;
7
8
  }
8
9
  export declare function SkillsSettingsCard({ form, t }: CardProps): import("react").JSX.Element;
10
+ /**
11
+ * The bundle's one `plugins.bundle.config` cell: every plugin-level switch the
12
+ * package offers, in display order. Each row owns its own form and write path —
13
+ * Skills through its settings namespace, Auto Review through the Plugin
14
+ * Manager's Loader-row enablement — so one cell never serializes their state.
15
+ */
16
+ export declare function BundleSettingsCard({ form, autoReviewForm, t }: {
17
+ form?: SkillsForm | undefined;
18
+ autoReviewForm?: AutoReviewForm | undefined;
19
+ t: Translate;
20
+ }): import("react").JSX.Element;
9
21
  export declare function installSkillsSettingsCard(ctx: Context, t: Translate): void;
10
22
  export {};
@@ -11,4 +11,4 @@
11
11
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/styles
12
12
  */
13
13
  /** The stylesheet text; every class is prefixed `mrfw-` to stay collision-free. */
14
- export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.2-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n@media (hover: hover) {\n .mrfw-ftOther:hover {\n background: transparent;\n }\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";
14
+ export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* Newer Files pages place the navigator beside the header instead of\n wrapping both in data-files-root. Target the page without hiding the\n portaled replacement or any of its rows. */\n[data-files-page]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) { order: -2; }\n[data-files-page]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) { display: none; }\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-content],\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] { display: none; }\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) { display: none; }\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.2-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n@media (hover: hover) {\n .mrfw-ftOther:hover {\n background: transparent;\n }\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-settingsTitleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Client seam for the optional Auto Review loader row.
3
+ *
4
+ * The row ships `disabled: true` in `cordis.patch.yml`; its only switch is the
5
+ * Loader entry's own enablement, which the Plugin Manager persists as a
6
+ * profile `{ id, disabled }` override through `pluginManager.setPluginEnabled`.
7
+ * This module binds that Remote namespace lazily — hosts without the Plugin
8
+ * Manager simply never attach, and the settings row stays hidden — and mirrors
9
+ * the Plugin Manager's own row semantics: the same entry id, the same
10
+ * `plugin-manager/changed` refresh, and the same `applied` /
11
+ * `restart-required` outcome split.
12
+ *
13
+ * Everything here is structural on purpose: the namespace is an optional
14
+ * service, so no module-level `inject` may name it, and the wire shapes are
15
+ * narrowed to the fields this card reads rather than imported from the
16
+ * generated Remote types.
17
+ *
18
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/auto-review-client
19
+ */
20
+ import type { Context } from '@deepseek-ai/cordis';
21
+ /** The module specifier `cordis.patch.yml` gives the optional row. */
22
+ export declare const AUTO_REVIEW_MODULE = "@dsh-electron/dsh-plugin-multi-root-workspace/auto-review";
23
+ /** The row's live state, narrowed to what the settings card renders. */
24
+ export interface AutoReviewRowView {
25
+ readonly entryId: string;
26
+ readonly enabled: boolean;
27
+ /** Whether `setPluginEnabled` may address this entry (no read-only reason). */
28
+ readonly writable: boolean;
29
+ /** Startup failure text while the entry's fiber is failed. */
30
+ readonly diagnostic?: string;
31
+ }
32
+ export interface AutoReviewSnapshot {
33
+ /**
34
+ * `unavailable` — the `pluginManager` namespace never attached (old host or
35
+ * headless composition): the row renders nothing. `loading` — attached, the
36
+ * first `listPlugins` is in flight. `ready` — `row` is present exactly when
37
+ * the composition lists our module. `error` — the last read failed.
38
+ */
39
+ readonly status: 'unavailable' | 'loading' | 'ready' | 'error';
40
+ readonly row?: AutoReviewRowView;
41
+ }
42
+ /** What one enable/disable write resolved to. */
43
+ export type AutoReviewWriteResult = 'applied' | 'restart-required' | 'failed';
44
+ /** The card's handle: a snapshot source plus the row's one write. */
45
+ export interface AutoReviewForm {
46
+ getSnapshot(): AutoReviewSnapshot;
47
+ subscribe(listener: () => void): () => void;
48
+ setEnabled(enabled: boolean): Promise<AutoReviewWriteResult>;
49
+ }
50
+ /**
51
+ * Create the card's Auto Review handle and attach it when the host's
52
+ * `pluginManager` namespace arrives. The returned form exists immediately with
53
+ * status `unavailable` and publishes on every attach/detach/read, so callers
54
+ * only need `subscribe` — no separate availability callback.
55
+ * @param ctx - the client root context.
56
+ * @returns the stable form the settings row renders.
57
+ */
58
+ export declare function installAutoReviewSettings(ctx: Context): AutoReviewForm;
@@ -0,0 +1,12 @@
1
+ /** Bind Connection's public RPC getter to the fiber that owns the route. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { HostConnectionHandle, HostConnectionRpc } from '@deepseek-ai/dsh-client-connection';
4
+ /**
5
+ * Newer Cordis keeps a service getter's definition site when tracing it, so
6
+ * even reading Connection from the root can make its RPC getter capture the
7
+ * provider's context (which does not inject webServer). Invoke the public
8
+ * accessor with the actual consumer context instead. A data property is used
9
+ * unchanged for simpler Connection implementations. The service is not
10
+ * modified and the route remains owned by the injecting fiber.
11
+ */
12
+ export declare function connectionRpcFor(ctx: Context, connection: HostConnectionHandle): HostConnectionRpc;
@@ -28,7 +28,7 @@
28
28
  * the upgrade smoke passed on that exact version — a green CI run on an
29
29
  * unlisted release is evidence, not authorization (ADR-0009).
30
30
  */
31
- export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3", "0.2.1-rc.4", "0.2.1-rc.5", "0.2.2-alpha.2"];
31
+ export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3", "0.2.1-rc.4", "0.2.1-rc.5", "0.2.2-alpha.2", "0.2.2-alpha.3", "0.2.2-rc.1"];
32
32
  /** One release this plugin claims to support. */
33
33
  export type SupportedDshRelease = typeof SUPPORTED_DSH_RELEASES[number];
34
34
  /**