@jayyuen66/dsh-plugin-shared 0.2.4 → 0.2.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.
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://github.com/JayYuen666/dsh-plugin-shared/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-plugin-shared/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
13
13
  <img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
14
14
  <img src="https://img.shields.io/badge/node-%3E%3D24.9-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
- <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.5-658EFF?style=flat-square" alt="dsh" />
15
+ <img src="https://img.shields.io/badge/dsh-%5E0.2.2--rc.1-658EFF?style=flat-square" alt="dsh" />
16
16
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
17
17
  </p>
18
18
 
@@ -31,18 +31,23 @@
31
31
 
32
32
  ## <img src="https://api.iconify.design/lucide/file-text.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Contents
33
33
 
34
- - [What it does](#what-it-does)
35
- - [Install](#install)
36
- - [Verify it is live](#verify-it-is-live)
37
- - [What it does not provide](#what-it-does-not-provide)
38
- - [Modules](#modules)
39
- - [The config exports](#the-config-exports)
40
- - [Known boundaries](#known-boundaries)
41
- - [Data and privacy](#data-and-privacy)
42
- - [Versioning and compatibility](#versioning-and-compatibility)
43
- - [FAQ](#faq)
44
- - [Development](#development)
45
- - [License](#license)
34
+ - [ Contents](#-contents)
35
+ - [ What it does](#-what-it-does)
36
+ - [What belongs here](#what-belongs-here)
37
+ - [ Install](#-install)
38
+ - [Dependency requirements](#dependency-requirements)
39
+ - [Shared version floor](#shared-version-floor)
40
+ - [What ships](#what-ships)
41
+ - [ Verify it is live](#-verify-it-is-live)
42
+ - [ What it does not provide](#-what-it-does-not-provide)
43
+ - [ Modules](#-modules)
44
+ - [ The config exports](#-the-config-exports)
45
+ - [ Known boundaries](#-known-boundaries)
46
+ - [ Data and privacy](#-data-and-privacy)
47
+ - [ Versioning and compatibility](#-versioning-and-compatibility)
48
+ - [ FAQ](#-faq)
49
+ - [ Development](#-development)
50
+ - [ License](#-license)
46
51
 
47
52
  ## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does
48
53
 
@@ -79,7 +84,7 @@ End users do not install or enable it separately: any consumer package pulls it
79
84
  | Requirement | Value |
80
85
  | --- | --- |
81
86
  | Node.js | `>=24.9.0` — the publish-side floor the plugin family shares, so a package declaring the same `engines.node` never rejects this library |
82
- | dsh | `^0.2.1-rc.5 || ^0.2.2-alpha.3`, declared as an **optional** peer (importing this package does not need a host) |
87
+ | dsh | `^0.2.2-rc.1`, declared as an **optional** peer (importing this package does not need a host) |
83
88
  | Module format | ESM only. `require()` works only where Node supports `require(esm)` |
84
89
  | Tree shaking | `"sideEffects": false` |
85
90
 
@@ -92,8 +97,8 @@ End users do not install or enable it separately: any consumer package pulls it
92
97
  - **`@deepseek-ai/dsh-fs-patch` is the one allowed runtime dependency.** It is a stateless pure parser: it holds no service instance or module-level identity, so a duplicate copy cannot split a `Symbol`. Shipping it makes `apply_patch` syntax resolvable on every host in the supported window. Every other host package stays in peer/dev.
93
98
  - **A host package under `dependencies` disables the consumer's host.** Profiles install with `nodeLinker: hoisted` and `autoInstallPeers: false`, so a listed host package is materialized as a real directory next to the copy the host process already loaded. `@deepseek-ai/dsh-tools` keys its tool-runtime scheduler with a plain `Symbol` (not `Symbol.for`), so symbol identity is per module instance: the host reads the scheduler from the duplicate and gets `undefined`, and every native tool call throws. Build, typecheck and tests pass up to that point; the invariant has to be stated in the manifest.
94
99
  - **`@deepseek-ai/dsh-output-retention` is the one required peer.** It is a value import (`truncateEnd`), provided by the host at runtime, and marked `"optional": false` in `peerDependenciesMeta` rather than left implicit — a peer left out of that table is a hard requirement for npm 7+.
95
- - **The remaining peers are optional.** Except for the bare `@deepseek-ai/dsh` peer, which only declares the host line, they carry type declarations only. `devDependencies` holds the exact development line, including `@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-util-values`, whose types the local PTC declarations name, and `@deepseek-ai/dsh-tool-executor-ptc`, whose types only the parity check reads. Profiles never install peers, so no duplicate appears.
96
- - **Version requirements separate the install gate from the build baseline.** Every `@deepseek-ai/dsh-*` peer carries the same window as `engines.dsh`, and that window names two host lines: a caret's prerelease comparison only covers its own patch tuple, so a single `^0.2.1-rc.5` does not admit `0.2.2-alpha.*` — measured with this workspace's semver, `satisfies('0.2.2-alpha.3','^0.2.1-rc.5')` is false and `{includePrerelease:true}` turns it true, and that flag is exactly what the host's own comparison passes. The cost is measured and belongs here: npm resolves each peer on its own, while the host family pins its own line inside every member (any member of the `0.2.1-rc` line asks cordis `~4.0.5`, the `0.2.2-alpha.2` host asks `~4.0.6-alpha.1`, `0.2.2-alpha.3` asks `~4.0.6-alpha.2`, and `0.2.2-rc.1` asks `~4.1.0`), so a consumer that pins the older host line can still stop at `ERESOLVE`, while the same tree resolves on the newest line. The union also admits later prereleases in the same `0.2.2` tuple, such as `0.2.2-rc.1`; that is range semantics, not a compatibility certification. The verified baseline for this release is `0.2.2-rc.1`; the dev pins and host parity check have moved to that line. pnpm — the installer this workspace supports, and the one behind the host's `dsh plugin add` — resolves both shapes and only prints a peer warning. The loading gate is unaffected either way, because the host compares `engines.dsh` with `includePrerelease: true`. `devDependencies` pin the exact line this repository compiles and tests against (`0.2.2-rc.1`) and must stay uniform: one `dsh-session` version per tree keeps the nominal types recognizable. `@deepseek-ai/cordis` stays exact in `devDependencies` (`4.1.0`) and its peer range `^4.0.5||^4.0.6-alpha.1` covers both host lines, because one package cannot mix lines the way the host family does: alpha.2 requires `~4.0.6-alpha.1`, alpha.3 requires `~4.0.6-alpha.2`, and the range also admits `4.1.0`. The cordis union carries no spaces around `||`; the install-matrix probe reads one specifier per line now, while the dsh-family window keeps spaces. `test/publish-manifest.test.ts` checks each of these separately.
100
+ - **The remaining peers are optional.** Except for the bare `@deepseek-ai/dsh` peer, which only declares the host line, most carry type declarations only. Two subpath-scoped value peers are the exceptions: `react` is value-imported by `./lib/card-ui`, and `@deepseek-ai/dsh-typert-protocol` by `./lib/remote-marker`; both stay optional, so only a consumer of that subpath needs the package present. `devDependencies` holds the exact development line, including `@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-util-values`, whose types the local PTC declarations name, and `@deepseek-ai/dsh-tool-executor-ptc`, whose types only the parity check reads. Profiles never install peers, so no duplicate appears.
101
+ - **Version requirements separate the install gate from the build baseline.** Every `@deepseek-ai/dsh-*` peer carries the same window as `engines.dsh` — `^0.2.2-rc.1`, a single host line. A caret's prerelease comparison only covers its own patch tuple, so `^0.2.2-rc.1` admits `0.2.2-rc.1`, later `0.2.2` prereleases and `0.2.2`/`0.2.x`, but not the older `0.2.1` line or `0.2.2-alpha.*`; the host's own comparison passes `{includePrerelease:true}`, which is what lets same-tuple prereleases participate. The cost is measured and belongs here: npm resolves each peer on its own, while the host family pins its own line inside every member (`0.2.2-rc.1` asks cordis `~4.1.0` and schemastery `~3.19.0`), so a consumer that pins an older host line can still stop at `ERESOLVE`, while the same tree resolves on the supported line. pnpm — the installer this workspace supports, and the one behind the host's `dsh plugin add` — resolves both shapes and only prints a peer warning. The loading gate is unaffected either way, because the host compares `engines.dsh` with `includePrerelease: true`. `devDependencies` pin the exact line this repository compiles and tests against (`0.2.2-rc.2`) and must stay uniform: one `dsh-session` version per tree keeps the nominal types recognizable. `@deepseek-ai/cordis` stays exact in `devDependencies` (`4.1.0`); its peer range `^4.0.5||^4.0.6-alpha.1` is retained unchanged and admits `4.1.0`. The cordis union carries no spaces around `||`; the install-matrix probe reads one specifier per line now. `test/publish-manifest.test.ts` checks each of these separately.
97
102
  - Known upstream limitation: `@deepseek-ai/dsh-llm`'s declarations import `@deepseek-ai/dsh-attachment`, which that package does not declare. It is reached through `dsh-session`, so a consumer running with `skipLibCheck: false` sees `TS2307` from that package, not from this one. The published `./lib/tool-events` declaration also names `@deepseek-ai/dsh-util-values`, which is reached through `dsh-session` / `dsh-tools`; a consumer running with `skipLibCheck: false` must have that package resolvable as well.
98
103
 
99
104
  ### Shared version floor
@@ -107,12 +112,12 @@ A consumer profile can lock this package with a pnpm `overrides` entry. Lock it
107
112
  - `publishConfig` carries `access` and `registry` and nothing else. Entry fields there are a trap: pnpm merges them onto the top level when publishing and npm does not, so one publisher would ship a package whose entry points name files that are not in the tarball.
108
113
  - `build` deletes `dist/` first, then emits the runtime artifacts, the config artifacts, the type declarations and the generated barrel declaration. Both builders minify; `test/dist-size.test.ts` keeps the runtime and config artifacts inside their budgets.
109
114
  - `prepare` runs `build`. It does not run when a consumer installs from the registry; it exists so packing, publishing and workspace links always ship a fresh `dist/`.
110
- - Adding a module means touching five places: the barrel in `lib/index.ts`, `exports`, the `facets` array in `build-host.mjs`, `include` in `tsconfig.build.json`, and a `test/<module>.test.ts`. The consistency checks in Development fail if one is missed.
115
+ - Adding a runtime module means touching the barrel in `lib/index.ts`, `exports`, the `facets` array in `build-host.mjs`, `include` in `tsconfig.build.json`, and a `test/<module>.test.ts`. A subpath-only runtime module (like `remote-marker` or `card-ui`) is registered in `entries` and marked barrel-exempt instead; a build-time module (like `decorator-lowering`) goes into `buildFacets`. The consistency checks in Development fail if one is missed.
111
116
 
112
117
  ## <img src="https://api.iconify.design/lucide/circle-check.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Verify it is live
113
118
 
114
119
  1. **A subpath resolves.** From the consuming package, `node -e 'import("@jayyuen66/dsh-plugin-shared/lib/record").then((m) => console.log(typeof m.isRecord))'` prints `function`. `ERR_MODULE_NOT_FOUND` means the dependency is not installed; `does not provide an export named ...` means the installed copy is older than the subpath you import.
115
- 2. **The barrel resolves.** The same check against the bare package name returns the runtime modules. `canonicalize-region-paths` is the one module deliberately absent from the barrel; import it by subpath when a build script needs it.
120
+ 2. **The barrel resolves.** The same check against the bare package name returns the runtime modules. `canonicalize-region-paths` (build-time), `remote-marker` (value-imports typert), and `card-ui` (client-only) are deliberately absent from the barrel; import them by subpath.
116
121
  3. **A consumer host boots.** Start the profile that declares the plugin. A boot log containing `N entries did not activate` and `does not provide an export named ...` means the profile's `overrides` version is below the version that contains the subpath the consumer imports — raise the version, do not patch the plugin.
117
122
  4. **Local source edits reach the consumer.** Consumers load `dist/`, not `lib/`. Run `pnpm run build` after editing; `test/build-host.test.ts` compares the runtime artifacts on disk against an in-memory build and fails when they are stale. "The code changed but nothing happened" is almost always a stale artifact, not a caching bug.
118
123
 
@@ -130,11 +135,11 @@ The subpath column is the in-package specifier; value imports prefix it with the
130
135
 
131
136
  | Subpath | Provides |
132
137
  | --- | --- |
133
- | `.` | Barrel over the runtime modules below, served from `dist/plugin.js`: the same names plus `name` and a no-op `apply`, so the host loader can also take this package as a row. `canonicalize-region-paths` deliberately stays out, so a runtime consumer does not add an import for it; the client-only `card-ui` stays out for the same reason plus one more: the root barrel must not bring `react` into every consumer. |
138
+ | `.` | Barrel over the runtime modules below, served from `dist/plugin.js`: the same names plus `name` and a no-op `apply`, so the host loader can also take this package as a row. `canonicalize-region-paths` (build-time), `remote-marker` (value-imports typert), and `card-ui` (client-only) deliberately stay out; the root barrel must not pull an unused package into every consumer. |
134
139
  | `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf` handles a single event and already carries normalized `arguments` and `badArguments`. `editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`; the path is `undefined` when it cannot be read. `applyPatchTargetsOf` parses the ordered operations of an `apply_patch` input with the official `dsh-fs-patch`; `editPathsOf` expands every write target, including both sides of a move. Record fields are `readonly`. Re-exports the official `SessionEvent`; `PtcDispatchStartEventData` / `PtcDispatchEventData` are declared in this package and augment `SessionEventMap` locally, because the host moved that augmentation between packages inside the supported window. |
135
140
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`: an apply idempotency guard built on an **own** flag on `globalThis` (a symbol, or `true` written by an outside holder) with `ctx.effect` cleanup. The flag is process-wide, so a second context on the same page is rejected by design. |
136
141
  | `./lib/card-diff` | `diffTouched`: which touched fields really differ from the snapshot, compared by form value. `undefined`, `null` and an absent key are one value; arrays and objects compare element by element; cycles terminate. |
137
- | `./lib/card-ui` | Client-only settings-card kit: `CardForm`, `Section`, `FieldGrid`, `StatusBar`, `Field`, `FieldLabel`, `InfoTip`, `ToggleField`, `NumberField`, `TextField`, `ListField`, `SelectField`, `SaveBar`, `splitHint` and the `CARD_UI_CSS` string. Built with `createElement` only; `react` is an optional peer and the subpath never enters the root barrel. React 18 runtime APIs only (`useId`, `useState`, `useEffect`, refs). |
142
+ | `./lib/card-ui` | Client-only settings-card kit: `CardForm`, `Section`, `FieldGrid`, `StatusBar`, `Field`, `FieldLabel`, `InfoTip`, `ToggleField`, `NumberField`, `TextField`, `ListField`, `SelectField`, `SaveBar`, `splitHint` and the `CARD_UI_CSS` string. Built with `createElement` only; `react` is an optional peer and the subpath never enters the root barrel. The declared peer floor is `^19.3.0`; the kit uses only APIs that already existed in React 18 (`useId`, `useState`, `useEffect`, refs). |
138
143
  | `./client` | Browser client row: the `__ModuleLoader__` bundle the host module graph serves once. Consumers externalize this specifier and enable the row from their own patch with an id-less entry. |
139
144
  | `./client-node` | Node/ESM face of the same client surface, used by tests and type-aware Node consumers. |
140
145
  | `./lib/project-key` | `deriveProjectKey` + `ProjectKeyOptions`: cwd → `last-segment-<first 8 hex of sha256(norm)>`, with `default` for a non-string, an empty string or a normalized root. `realpathSync` resolves symlinks; a failure falls back to the resolved path. Windows `\` and `/` produce the same key. |
@@ -145,9 +150,11 @@ The subpath column is the in-package specifier; value imports prefix it with the
145
150
  | `./lib/errors` | `errorText` follows the official `@deepseek-ai/dsh-llm` `errorChain` semantics (cause chain, `AggregateError` members, empty message → `name`, cross-realm `message`, malicious getters render as a placeholder) in a zero-dependency local implementation. `isSettingsConflict` recognizes the host's settings-conflict pattern from `code` / `name`, so a cross-realm error is still recognized. |
146
151
  | `./lib/jsonl` | `shrinkJsonlTail`: pure decision logic for JSONL tail shrinking. The trigger policy and error handling stay with the caller; the byte budget is the caller's responsibility to keep finite. |
147
152
  | `./lib/job-outcome` | `jobOutcomeOf` + `SettledProcess` + `JobOutcome` map a finished subprocess onto the official job registry's outcome; `killProc` + `KillableProcess` release a child handle and ignore a `kill()` that throws or rejects (for example, on an already-reaped process). |
153
+ | `./lib/remote-marker` | `markRemote(prototype, methodName)`: the programmatic equivalent of the official `@Remote` method decorator. It applies the same decorator function to a hand-built TC39 context, so a source file can avoid decorator syntax and needs no lowering pipeline. It value-imports `@deepseek-ai/dsh-typert-protocol` (an optional peer) and stays out of the root barrel. |
154
+ | `./lib/decorator-lowering` | Build/test helper for standard decorators: `needsDecoratorLowering`, `lowerStandardDecorators`, `rolldownDecorators`, `viteDecorators`. It runs the caller-injected `tsc` entry on one file with `--noResolve`, preserves module specifiers, returns code plus a source map, and (on the Vite side) prunes injected plumbing from coverage mappings. Build-time only, never in the runtime barrel. |
148
155
  | `./lib/canonicalize-region-paths` | Build-time string helper that rewrites rolldown's `//#region <path>` markers into a `process.cwd()`-independent form. Exported as a subpath so build scripts can import it without putting it in the runtime barrel. |
149
156
 
150
- `card-ui` is the one runtime module that is deliberately subpath-only: adding it to the barrel would make every host-side importer resolve `react`.
157
+ `card-ui` (value-imports `react`) and `remote-marker` (value-imports `@deepseek-ai/dsh-typert-protocol`) are the runtime modules that are deliberately subpath-only: putting them in the barrel would make every host-side importer resolve a package it does not use.
151
158
 
152
159
  The client row is enabled by the consuming plugin, not by installing this package alone. The consumer writes `- name: "@jayyuen66/dsh-plugin-shared"` without an `id`; the host still loads it, while the plugin page does not expose it as a separately disableable component. An `id` would add that switch, and switching it off would break every settings card that requires the row's components.
153
160
 
@@ -211,7 +218,7 @@ The package is imported into a host process; the table describes run time.
211
218
  | Should I `dsh plugin add` it? | No. It declares `dsh.client` for the row consumers enable, but carries no `dsh.bundle.patch`, so adding it as a plugin installs no profile layer and enables nothing by itself. Declare it as a dependency of the plugin package, and insert the id-less row in your own `cordis.patch.yml`. |
212
219
  | A consumer boots with `does not provide an export named ...` and `N entries did not activate`. | The profile is serving an older copy of this package than the consumer imports. Raise the profile's `overrides` entry to a version that contains the subpath the consumer imports; check this package's `exports` and CHANGELOG. No per-repository CI run can see this state. |
213
220
  | I changed `lib/*.ts` and the consumer still runs the old behavior. | Consumers load `dist/`. Run `pnpm run build`; `test/build-host.test.ts` fails if the runtime artifact is stale. A workspace link does not bypass the artifact. |
214
- | Why is `@deepseek-ai/dsh-output-retention` a required peer while the rest are optional? | It is the only host package imported for value (`truncateEnd`). The rest are type-only, except the bare `@deepseek-ai/dsh` peer, which only declares the host line. |
221
+ | Why is `@deepseek-ai/dsh-output-retention` a required peer while the rest are optional? | It is the only **required** host package imported for value (`truncateEnd`). `@deepseek-ai/dsh-typert-protocol` is also value-imported, but only by `./lib/remote-marker`, so it stays optional; the remaining dsh peers are type-only. The bare `@deepseek-ai/dsh` peer only declares the host line, and `react` is a value peer for `./lib/card-ui`. |
215
222
  | Why isn't `oxlint` a `peerDependency`? | npm 7+ would try to satisfy it, and `oxlint`'s optional `vite-plus` peer locks `vitest` outside the range this baseline needs, producing `ERESOLVE` for a plain install. The requirement is documented instead. |
216
223
  | Want a helper added here? | First confirm the body repeats verbatim in two or more packages. Domain judgement stays in the plugins. |
217
224
 
@@ -232,7 +239,7 @@ pnpm run build # runtime artifacts, config artifacts, declarations, client row
232
239
  - **Log records**: `test/setup-logs.ts` takes over `console.*`; every `[shared/*]` line must match `test/log-templates.ts` or the run fails. `test/log-templates.test.ts` checks both directions, so adding or deleting a log without updating the record list fails.
233
240
  - **Published shape**: `test/publish-manifest.test.ts` requires every export target to be inside `files`, entry points to be artifacts rather than source, `publishConfig` to carry no entry fields, every peer to be stated in `peerDependenciesMeta`, every `dsh-*` development pin to be exact and uniform, every dsh peer range to equal `engines.dsh`, and the source, docs and artifacts to carry no dates, absolute paths, home-directory references or collaboration-only wording.
234
241
  - Coverage thresholds are **100%** across lines, statements, functions and branches, so a new branch needs a case that reaches it. `test/vitest-base.test.ts` refuses an empty `coverageInclude` because that would make the four thresholds pass over nothing.
235
- - CI runs the checks on Linux, Windows and macOS across a Node 22/24 matrix, with a weekly scheduled run to catch upstream changes. The install matrix packs with both npm and pnpm, installs into a clean directory without peer-resolution flags, imports every subpath and type-checks a small consumer import under both `skipLibCheck` settings.
242
+ - CI runs the checks on Linux, Windows and macOS on Node 24.9, with a weekly scheduled run to catch upstream changes. The install matrix packs with both npm and pnpm, installs into a clean directory without peer-resolution flags, imports every subpath and type-checks a small consumer import under both `skipLibCheck` settings.
236
243
 
237
244
  ## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> License
238
245
 
package/README.zh-CN.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://github.com/JayYuen666/dsh-plugin-shared/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-plugin-shared/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
13
13
  <img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
14
14
  <img src="https://img.shields.io/badge/node-%3E%3D24.9-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
15
- <img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.5-658EFF?style=flat-square" alt="dsh" />
15
+ <img src="https://img.shields.io/badge/dsh-%5E0.2.2--rc.1-658EFF?style=flat-square" alt="dsh" />
16
16
  <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
17
17
  </p>
18
18
 
@@ -31,18 +31,23 @@
31
31
 
32
32
  ## <img src="https://api.iconify.design/lucide/file-text.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 目录
33
33
 
34
- - [它做什么](#它做什么)
35
- - [安装](#安装)
36
- - [确认它在正常工作](#确认它在正常工作)
37
- - [它不提供什么](#它不提供什么)
38
- - [模块清单](#模块清单)
39
- - [config 导出](#config-导出)
40
- - [已知边界](#已知边界)
41
- - [数据与隐私](#数据与隐私)
42
- - [版本与兼容](#版本与兼容)
43
- - [常见问题](#常见问题)
44
- - [开发](#开发)
45
- - [许可](#许可)
34
+ - [ 目录](#-目录)
35
+ - [ 它做什么](#-它做什么)
36
+ - [收录边界](#收录边界)
37
+ - [ 安装](#-安装)
38
+ - [依赖要求](#依赖要求)
39
+ - [shared 的版本下限](#shared-的版本下限)
40
+ - [随包发什么](#随包发什么)
41
+ - [ 确认它在正常工作](#-确认它在正常工作)
42
+ - [ 它不提供什么](#-它不提供什么)
43
+ - [ 模块清单](#-模块清单)
44
+ - [ config 导出](#-config-导出)
45
+ - [ 已知边界](#-已知边界)
46
+ - [ 数据与隐私](#-数据与隐私)
47
+ - [ 版本与兼容](#-版本与兼容)
48
+ - [ 常见问题](#-常见问题)
49
+ - [ 开发](#-开发)
50
+ - [ 许可](#-许可)
46
51
 
47
52
  ## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 它做什么
48
53
 
@@ -74,12 +79,12 @@ pnpm add @jayyuen66/dsh-plugin-shared
74
79
  import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
75
80
  ```
76
81
 
77
- 终端用户不需要单独安装或启用它:任一消费包安装时都会自动包含它。它缺失时的失败是 `ERR_MODULE_NOT_FOUND`(说明符指向 `@jayyuen66/dsh-plugin-shared/...`),不会静默变成其他行为。本包没有 default 导出;请从 barrel 或子路径使用具名导入。
82
+ 终端用户不需要单独安装或启用它:任一消费包安装时都会自动包含它。它缺失时的失败是 `ERR_MODULE_NOT_FOUND`(那条说明符指向 `@jayyuen66/dsh-plugin-shared/...`),不会静默变成其他行为。本包没有 default 导出;请从 barrel 或子路径使用具名导入。
78
83
 
79
84
  | 要求 | 值 |
80
85
  | --- | --- |
81
86
  | Node.js | `>=24.9.0`——插件家族共用的发布侧下限,声明同一个 `engines.node` 的包不会因为这个库而被拒 |
82
- | dsh | `^0.2.1-rc.5 || ^0.2.2-alpha.3`,声明为 **optional** peer(import 本包不需要宿主存在) |
87
+ | dsh | `^0.2.2-rc.1`,声明为 **optional** peer(import 本包不需要宿主存在) |
83
88
  | 模块形态 | 仅 ESM。`require()` 只在支持 `require(esm)` 的 Node 上可用 |
84
89
  | Tree shaking | `"sideEffects": false` |
85
90
 
@@ -92,13 +97,13 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
92
97
  - **`@deepseek-ai/dsh-fs-patch` 是唯一允许的普通依赖。** 它是无状态纯解析器,不承载服务实例或模块级身份,多一份不会分裂 `Symbol`;随包安装是为了让宿主窗口内没有该包的版本也能解析 `apply_patch` 语法。其余宿主包仍然必须留在 peer/dev。
93
98
  - **宿主包写进 `dependencies` 会让消费方的宿主无法工作。** profile 用 `nodeLinker: hoisted` 与 `autoInstallPeers: false` 安装,于是列在 `dependencies` 里的宿主包会被安装成真实目录,与宿主进程已加载的那份并存。`@deepseek-ai/dsh-tools` 的工具运行期调度键是普通 `Symbol`(不是 `Symbol.for`),符号身份按模块实例计算:宿主从副本上读那个键得到 `undefined`,于是每一次原生工具调用都抛错。构建、类型检查与测试在此之前全部通过;这条不变式只能写在 manifest 上。
94
99
  - **`@deepseek-ai/dsh-output-retention` 是唯一必需 peer。** 它是值导入(`truncateEnd`),运行期由宿主提供;在 `peerDependenciesMeta` 里明写成 `"optional": false`,不靠"没写就等于必需",因为没写进那张表对 npm 7+ 就是硬要求。
95
- - **其余 peer 都是 optional。** 除裸 `@deepseek-ai/dsh`(只声明宿主版本线)外,它们只承载类型声明。`devDependencies` 保存精确的开发线,其中包括本包 PTC 声明所引用的 `@deepseek-ai/dsh-llm` 与 `@deepseek-ai/dsh-util-values`,以及只有对照断言会读其类型的 `@deepseek-ai/dsh-tool-executor-ptc`。profile 不装 peer,所以不会产生副本。
96
- - **版本要求把装载门与构建基线分开。** 每条 `@deepseek-ai/dsh-*` peer 与 `engines.dsh` 写同一条窗口,而这条窗口点明两条宿主线:caret 的预发布比较只吃自己那条 patch 元组,单写 `^0.2.1-rc.5` 收不进 `0.2.2-alpha.*`——用本工作区的 semver 实测 `satisfies('0.2.2-alpha.3','^0.2.1-rc.5')` 为 false,带 `{includePrerelease:true}` 才为 true,而宿主自己那道比较正是带这一档。代价是量出来写在这里的:npm 逐条 peer 独立解析,宿主家族每一枚包内部又把自己的那条线精确钉住(`0.2.1-rc` 线上任一成员要求 cordis `~4.0.5`,`0.2.2-alpha.2` 宿主要求 `~4.0.6-alpha.1`,`0.2.2-alpha.3` 要求 `~4.0.6-alpha.2`,`0.2.2-rc.1` 要求 `~4.1.0`),所以消费者把宿主钉在旧那条线时仍可能止步 `ERESOLVE`,同一条树落在最新线上则可解。并集窗口也收得进同一 `0.2.2` 元组里更晚的预发布,例如 `0.2.2-rc.1`;那只是范围语义,不是兼容性取证。本版实际取证的基线是 `0.2.2-rc.1`;dev 钉版与宿主对照断言已同步到这一线。pnpm——本工作区支持、也是宿主 `dsh plugin add` 在用的安装器——两种形状都解得出,只打一条 peer 告警。装载门两边都不受影响。`devDependencies` 精确锁定本仓编译与测试用的那一线(`0.2.2-rc.1`),且必须统一:一棵树里只有一份 `dsh-session`,nominal 类型才互相认得。`@deepseek-ai/cordis` 在 `devDependencies` 里精确锁定 `4.1.0`,peer 范围 `^4.0.5||^4.0.6-alpha.1` 同时覆盖 rc 线与 alpha 线——单独一枚包不存在宿主家族那种混线问题:alpha.2 要求 `~4.0.6-alpha.1`,alpha.3 要求 `~4.0.6-alpha.2`,该范围也收得下 `4.1.0`。cordis 这条并集在 `||` 两侧不带空格;类型面 peer 安装现已按行逐枚读取,dsh 家族窗口保留空格。`test/publish-manifest.test.ts` 分别检查这三件事。
100
+ - **其余 peer 都是 optional。** 除裸 `@deepseek-ai/dsh`(只声明宿主版本线)外,它们大多只承载类型声明;两个按子路径收窄的值导入 peer 是例外:`react` 被 `./lib/card-ui` 按值导入,`@deepseek-ai/dsh-typert-protocol` 被 `./lib/remote-marker` 按值导入。两者仍是 optional,只有取用对应子路径的消费方才需要它们在场。`devDependencies` 保存精确的开发线,其中包括本包 PTC 声明所引用的 `@deepseek-ai/dsh-llm` 与 `@deepseek-ai/dsh-util-values`,以及只有对照断言会读其类型的 `@deepseek-ai/dsh-tool-executor-ptc`。profile 不装 peer,所以不会产生副本。
101
+ - **版本要求把装载门与构建基线分开。** 每条 `@deepseek-ai/dsh-*` peer 与 `engines.dsh` 写同一条窗口——`^0.2.2-rc.1`,单条宿主线。caret 的预发布比较只吃自己那条 patch 元组,所以 `^0.2.2-rc.1` 收得进 `0.2.2-rc.1`、同元组更晚的预发布以及 `0.2.2`/`0.2.x`,收不进更旧的 `0.2.1` 线或 `0.2.2-alpha.*`;宿主自己那道比较带 `{includePrerelease:true}`,正是这一档让同元组预发布参与。代价是量出来写在这里的:npm 逐条 peer 独立解析,宿主家族每一枚包内部又把自己的那条线精确钉住(`0.2.2-rc.1` 要求 cordis `~4.1.0`、schemastery `~3.19.0`),所以消费者把宿主钉在旧线时仍可能止步 `ERESOLVE`,同一条树落在受支持的线上则可解。pnpm——本工作区支持、也是宿主 `dsh plugin add` 在用的安装器——两种形状都解得出,只打一条 peer 告警。装载门两边都不受影响,因为宿主比较 `engines.dsh` 时带 `includePrerelease: true`。`devDependencies` 精确锁定本仓编译与测试用的那一线(`0.2.2-rc.2`),且必须统一:一棵树里只有一份 `dsh-session`,nominal 类型才互相认得。`@deepseek-ai/cordis` 在 `devDependencies` 里精确锁定 `4.1.0`;peer 范围 `^4.0.5||^4.0.6-alpha.1` 保持不变,收得下 `4.1.0`。cordis 这条并集在 `||` 两侧不带空格;install-matrix 探针现已按行逐枚读取。`test/publish-manifest.test.ts` 分别检查这几件事。
97
102
  - 已知上游限制:`@deepseek-ai/dsh-llm` 的声明文件引用了它自己没声明的 `@deepseek-ai/dsh-attachment`。它经 `dsh-session` 传递进来,所以开 `skipLibCheck: false` 的消费方会看到来自那个包的 `TS2307`,不是来自本包。发布出去的 `./lib/tool-events` 声明还引用了 `@deepseek-ai/dsh-util-values`,它经 `dsh-session` / `dsh-tools` 传递进来;开 `skipLibCheck: false` 的消费方同样必须能解析到这个包。
98
103
 
99
104
  ### shared 的版本下限
100
105
 
101
- 消费者的 profile 可以用 pnpm `overrides` 把本包固定在某个版本。固定得低于某个 `./lib/...` 子路径首次出现的版本,宿主启动时就会报 `N entries did not activate` 与 `does not provide an export named ...`:包装得上、永远不加载,而任何单仓 CI 都看不见。要固定版本请先对照本包的 `exports` 与 CHANGELOG。
106
+ 消费者的 profile 可以用 pnpm `overrides` 把本包钉在某个版本。钉得低于某个 `./lib/...` 子路径首次出现的版本,宿主启动时就会报 `N entries did not activate` 与 `does not provide an export named ...`:包装得上、永远不加载,而任何单仓 CI 都看不见。要钉版本请先对照本包的 `exports` 与 CHANGELOG。
102
107
 
103
108
  ### 随包发什么
104
109
 
@@ -107,12 +112,12 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
107
112
  - `publishConfig` 只带 `access` 与 `registry`。把入口字段放在那里容易出错:pnpm 发布时会把它合并到顶层,npm 不会——换一个发布器就会发出一个入口指向 tarball 里不存在文件的包。
108
113
  - `build` 先删除 `dist/`,再产出运行期产物、配置产物、类型声明与生成的 barrel 声明。两个构建脚本都压缩;`test/dist-size.test.ts` 把运行期产物与配置产物各自限制在预算内。
109
114
  - `prepare` 指向 `build`。消费方从 registry 安装时它不执行;它存在是为了让打包、发布与 workspace link 拿到的都是新鲜 `dist/`。
110
- - 新增模块要同时改五处:`lib/index.ts` 的 barrel、`exports`、`build-host.mjs` 的 `facets` 数组、`tsconfig.build.json` 的 `include`,外加一份 `test/<模块>.test.ts`。漏一处会被「开发」一节的一致性检查判定失败。
115
+ - 新增运行期模块要同时改:`lib/index.ts` 的 barrel、`exports`、`build-host.mjs` 的 `facets` 数组、`tsconfig.build.json` 的 `include`,外加一份 `test/<模块>.test.ts`。只走子路径的运行期模块(如 `remote-marker`、`card-ui`)改登记进 `entries` 并在 barrel 检查里豁免;构建期模块(如 `decorator-lowering`)进 `buildFacets`。漏一处会被「开发」一节的一致性检查判定失败。
111
116
 
112
117
  ## <img src="https://api.iconify.design/lucide/circle-check.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 确认它在正常工作
113
118
 
114
119
  1. **子路径能解析。** 在消费包里运行 `node -e 'import("@jayyuen66/dsh-plugin-shared/lib/record").then((m) => console.log(typeof m.isRecord))'`,输出 `function`。`ERR_MODULE_NOT_FOUND` 表示依赖没装上;`does not provide an export named ...` 表示装到的那份比你 import 的子路径旧。
115
- 2. **barrel 能解析。** 换成裸包名做同一件事,应能取到运行期模块。`canonicalize-region-paths` 是唯一刻意不在 barrel 里的模块;构建脚本需要时按子路径取。
120
+ 2. **barrel 能解析。** 换成裸包名做同一件事,应能取到运行期模块。`canonicalize-region-paths`(构建期)、`remote-marker`(值导入 typert)与 `card-ui`(客户端专用)刻意不在 barrel 里;需要时按子路径取。
116
121
  3. **消费方宿主能启动。** 启动声明该插件的 profile。若启动日志出现 `N entries did not activate` 与 `does not provide an export named ...`,说明 profile 的 `overrides` 固定得比包含该子路径的版本低——调高固定版本,不要改插件。
117
122
  4. **本地源码改动能反映到消费方。** 消费方载入的是 `dist/`,不是 `lib/`。改完先运行 `pnpm run build`;`test/build-host.test.ts` 会把磁盘上的运行期产物与内存构建逐字节比对,产物过期即报错。"代码改了却没生效"多半是产物过期,不是缓存问题。
118
123
 
@@ -130,13 +135,13 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
130
135
 
131
136
  | 子路径 | 提供什么 |
132
137
  | --- | --- |
133
- | `.` | 下面列出的运行期模块的 barrel,由 `dist/plugin.js` 提供:同样的导出名,再加 `name` 与一条空操作的 `apply`,宿主 Loader 因此也能把本包当作一行载入。`canonicalize-region-paths` 刻意不进,运行期消费方不必为它增加一次导入;客户端专用的 `card-ui` 同样不进,理由还多一条:根 barrel 不得把 `react` 带进每个消费方 |
138
+ | `.` | 下面列出的运行期模块的 barrel,由 `dist/plugin.js` 提供:同样的导出名,再加 `name` 与一条空操作的 `apply`,宿主 Loader 因此也能把本包当作一行载入。`canonicalize-region-paths`(构建期)、`remote-marker`(值导入 typert)与 `card-ui`(客户端专用)刻意不进;根 barrel 不得把用不到的包带进每个消费方 |
134
139
  | `./lib/tool-events` | `scanToolEvents` 返回 `calls` / `results` 两张表,PTC 子调用一并合并;`toolEventRowsOf` 处理单个事件,交回的记录已带归一后的 `arguments` 与 `badArguments`。`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支;路径取不到时为 `undefined`。`applyPatchTargetsOf` 用官方 `dsh-fs-patch` 解析 `apply_patch` 的有序操作;`editPathsOf` 展开全部写目标(move 的源与目的都算)。记录字段一律 `readonly`。同时再导出官方 `SessionEvent`;`PtcDispatchStartEventData` / `PtcDispatchEventData` 在本包内声明并自行增强 `SessionEventMap`,因为宿主在支持窗口内换过承载这份增强的包 |
135
140
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`:保证 apply 幂等的保护逻辑,建立在 `globalThis` 上的**自有**标记(symbol,或外部写入的 `true`)加 `ctx.effect` 清理。标记是进程级的,所以同一页面的第二个 ctx 会被拒绝——这是有意的设计 |
136
141
  | `./lib/card-diff` | `diffTouched`:touched 层里哪些字段真的与快照不同,按表单值比较。`undefined`、`null` 与键缺失是同一种值;数组与对象逐元素比较;循环引用也能正常结束 |
137
- | `./lib/card-ui` | 客户端专用设置卡组件:`CardForm`、`Section`、`FieldGrid`、`StatusBar`、`Field`、`FieldLabel`、`InfoTip`、`ToggleField`、`NumberField`、`TextField`、`ListField`、`SelectField`、`SaveBar`、`splitHint` 与样式字符串 `CARD_UI_CSS`。只用 `createElement`;`react` 是 optional peer,该子路径永不进根 barrel。只用 React 18 运行时 API(`useId`、`useState`、`useEffect`、refs) |
142
+ | `./lib/card-ui` | 客户端专用设置卡组件:`CardForm`、`Section`、`FieldGrid`、`StatusBar`、`Field`、`FieldLabel`、`InfoTip`、`ToggleField`、`NumberField`、`TextField`、`ListField`、`SelectField`、`SaveBar`、`splitHint` 与样式字符串 `CARD_UI_CSS`。只用 `createElement`;`react` 是 optional peer,该子路径永不进根 barrel。声明的 peer 下限是 `^19.3.0`;组件只用 React 18 就已存在的 API(`useId`、`useState`、`useEffect`、refs) |
138
143
  | `./client` | 浏览器客户端行:宿主模块图只提供一份的 `__ModuleLoader__` bundle。消费者外置这个说明符,并在自己的 patch 里以无 id 条目启用这一行 |
139
- | `./client-node` | 同一客户端面的 Node/ESM 形态,供测试与类型感知的 Node 消费方使用 |
144
+ | `./client-node` | 同一份客户端接口的 Node/ESM 形态,供测试与类型感知的 Node 消费方使用 |
140
145
  | `./lib/project-key` | `deriveProjectKey` + `ProjectKeyOptions`:cwd → `尾目录名-<sha256(norm) 前 8 位 hex>`;非字符串、空串或归一后的根目录归入 `default`。`realpathSync` 会解析软链接,失败则退回解析后的路径。Windows 的 `\` 与 `/` 产生同一个键 |
141
146
  | `./lib/locale` | `Locale`(官方 `BuiltInLocaleId` 的别名)、`DEFAULT_LOCALE`、`resolveLocale`、`resolveLocalePreference`、`messagesFor`、`MessagesCatalog`,加 `LOCALE_SETTINGS_NAMESPACE` 与 `LOCALE_PREFERENCE_FIELD`。只负责宿主侧字典;客户端用官方 locale 包 |
142
147
  | `./lib/lesson-bus` | `settleLessonCall`:同步抛错与异步 rejection 归到同一个 `onFailure` 出口。函数型 thenable 也算可等待;`onFailure` 自己抛错或拒绝时被统一处理,不会分成"抛给调用方"或"未处理拒绝"两种结果 |
@@ -145,9 +150,11 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
145
150
  | `./lib/errors` | `errorText` 对齐官方 `@deepseek-ai/dsh-llm` 的 `errorChain` 语义(cause 链、`AggregateError` 成员、空 message 回退 `name`、跨 realm 取 `message`、恶意 getter 改用占位文本),本地零依赖实现。`isSettingsConflict` 由 `code` / `name` 识别宿主的设置冲突特征,跨 realm 抛来的错误也能识别 |
146
151
  | `./lib/jsonl` | `shrinkJsonlTail`:JSONL 尾部裁剪的纯逻辑模块。触发策略与错误处理留给调用方;字节预算是否有限由调用方保证 |
147
152
  | `./lib/job-outcome` | `jobOutcomeOf` + `SettledProcess` + `JobOutcome` 把已结束的子进程映射成官方作业注册表的结局;`killProc` + `KillableProcess` 释放子进程句柄,对抛错或拒绝的 `kill()` 一律忽略(例如进程已被回收) |
153
+ | `./lib/remote-marker` | `markRemote(prototype, methodName)`:官方 `@Remote` 方法装饰器的程序化等价物。它把同一个装饰器函数作用在手写的 TC39 context 上,源码因此不必写装饰器语法,也不需要降级流水线。按值导入 `@deepseek-ai/dsh-typert-protocol`(optional peer),且不进根 barrel |
154
+ | `./lib/decorator-lowering` | 标准装饰器的构建/测试辅助:`needsDecoratorLowering`、`lowerStandardDecorators`、`rolldownDecorators`、`viteDecorators`。它用调用方注入的 `tsc` 入口单文件编译(`--noResolve`,模块说明符逐字保留),交回代码与源映射;Vite 侧还会把注入样板从覆盖率映射里摘掉。仅构建期使用,绝不进运行期 barrel |
148
155
  | `./lib/canonicalize-region-paths` | 构建期字符串处理:把 rolldown 写下的 `//#region <路径>` 改写成与 `process.cwd()` 无关的形态。只提供子路径,是为了让构建脚本取得到它,同时不把运行期 barrel 增大 |
149
156
 
150
- `card-ui` 是唯一刻意只走子路径的运行期模块:把它加进 barrel 会让每个 host 侧 importer 都去解析 `react`。
157
+ `card-ui`(值导入 `react`)与 `remote-marker`(值导入 `@deepseek-ai/dsh-typert-protocol`)是刻意只走子路径的运行期模块:放进 barrel 会让每个 host 侧 importer 都去解析一枚它用不到的包。
151
158
 
152
159
  客户端行由消费插件启用,不由单独安装本包启用。消费者写 `- name: "@jayyuen66/dsh-plugin-shared"` 且**不写 id**;宿主照常装载它,插件页不会把它暴露成可单独停用的组件。写 id 会多出那个开关,关闭后所有需要该行组件的设置卡都会失效。
153
160
 
@@ -211,7 +218,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
211
218
  | 要不要 `dsh plugin add` 它? | 不要。它声明的是 `dsh.client`(消费者要启用的那一行),但没有 `dsh.bundle.patch`,单独添加不会装出配置层,也启用不了任何东西。正确做法是声明成插件包的依赖,再在自己的 `cordis.patch.yml` 里插入那条不带 id 的行。 |
212
219
  | 消费方启动时报 `does not provide an export named ...` 与 `N entries did not activate`。 | profile 提供的那份本包比消费方 import 的版本旧。把 profile 的 `overrides` 版本调高到包含该子路径的版本;对照本包的 `exports` 与 CHANGELOG。任何单仓 CI 都看不见这个状态。 |
213
220
  | 改了 `lib/*.ts`,消费方还是旧行为。 | 消费方载入的是 `dist/`。先运行 `pnpm run build`;`test/build-host.test.ts` 会在运行期产物过期时判定失败。workspace link 不会绕过产物。 |
214
- | 为什么 `@deepseek-ai/dsh-output-retention` 是必需 peer,其余都是 optional? | 它是唯一按值导入的宿主包(`truncateEnd`)。其余都是类型声明,唯一的例外是裸 `@deepseek-ai/dsh` peer——它只声明宿主版本线。 |
221
+ | 为什么 `@deepseek-ai/dsh-output-retention` 是必需 peer,其余都是 optional? | 它是唯一**必需**且按值导入的宿主包(`truncateEnd`)。`@deepseek-ai/dsh-typert-protocol` 也按值导入,但只被 `./lib/remote-marker` 取用,所以仍是 optional;其余 dsh peer 只承载类型。裸 `@deepseek-ai/dsh` peer 只声明宿主版本线,`react` 则是 `./lib/card-ui` 的值导入 peer。 |
215
222
  | 为什么 `oxlint` 不写成 `peerDependency`? | npm 7+ 会去满足它,而 `oxlint` 的 optional `vite-plus` peer 把 `vitest` 锁定在基线所需范围之外,普通安装会得到 `ERESOLVE`。这条要求因此写在文档里。 |
216
223
  | 想把某个 helper 收录进来? | 先确认那份函数体在两个以上包里逐字重复。带领域判断的部分留在插件里。 |
217
224
 
@@ -232,7 +239,7 @@ pnpm run build # 运行期产物、配置产物、声明文件、客户端行
232
239
  - **日志记录**:`test/setup-logs.ts` 接管 `console.*`;每条 `[shared/*]` 日志都要匹配 `test/log-templates.ts` 里的模板片段,否则测试立即失败。`test/log-templates.test.ts` 双向校验,增删日志却不改记录清单都会失败。
233
240
  - **发布形态**:`test/publish-manifest.test.ts` 要求入口目标都在 `files` 覆盖内、入口指向产物而不是源码、`publishConfig` 不带入口字段、每条 peer 都在 `peerDependenciesMeta` 里明确标注、`dsh-*` 开发钉版精确且统一、dsh peer 范围与 `engines.dsh` 相同,并要求源码、文档与产物里没有日期、绝对路径、家目录指涉或只在协作当时成立的措辞。
234
241
  - 覆盖率四项阈值都是 **100%**(lines / statements / functions / branches),所以新增分支必须有能走到它的用例。`test/vitest-base.test.ts` 拒绝空 `coverageInclude`,因为那会让四项阈值在空统计范围上全部通过。
235
- - CI 在 Linux、Windows 与 macOS 上运行同一组检查,Node 版本矩阵覆盖 22 与 24,并有一周一次的定时运行来发现上游变化。安装矩阵会分别用 npm 与 pnpm 打包,在干净目录里不带 peer 解析选项安装,逐子路径 import,并在两种 `skipLibCheck` 设置下对一小段消费方导入代码做类型检查。
242
+ - CI 在 Linux、Windows 与 macOS 上以 Node 24.9 运行同一组检查,并有一周一次的定时运行来发现上游变化。安装矩阵会分别用 npm 与 pnpm 打包,在干净目录里不带 peer 解析选项安装,逐子路径 import,并在两种 `skipLibCheck` 设置下对一小段消费方导入代码做类型检查。
236
243
 
237
244
  ## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 许可
238
245
 
@@ -1 +1,8 @@
1
- import{randomUUID as e}from"node:crypto";import{realpathSync as t}from"node:fs";import{mkdir as n,readFile as r,rename as i,rm as a,writeFile as o}from"node:fs/promises";import{setTimeout as s}from"node:timers/promises";import c from"node:path";function l(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function u(e){return l(e)?e:{}}async function d(e){let t=c.join(e,`package.json`);try{return JSON.parse(await r(t,`utf8`))}catch(e){throw Error(`读 ${t} 失败:${String(e)}`,{cause:e})}}function f(e){let t=e.indexOf(`/`);if(!e.startsWith(`@`))return t===-1?e:e.slice(0,t);let n=t===-1?-1:e.indexOf(`/`,t+1);return n===-1?e:e.slice(0,n)}function p(e){return e?.type===`chunk`&&e.isEntry===!0&&typeof e.code==`string`&&e.code.trim()!==``}function m(e,t){if(e.length!==1){let n=e.map(e=>`${e.type}:${e.fileName}`).join(`, `);throw Error(`rolldown 为 ${t} 产出了 ${e.length} 项输出(期望恰好一个 entry chunk):${n}`)}let n=e.at(0);if(!p(n))throw Error(`rolldown 为 ${t} 产出的不是单个带正文的 entry chunk`);return n}const h=/* @__PURE__ */ new Set([`EPERM`,`EBUSY`,`EACCES`]),g=[50,100,200];async function _(e,t,n=0){try{await i(e,t)}catch(r){let i=typeof r==`object`&&r&&`code`in r?r.code:void 0;if(typeof i!=`string`||!h.has(i))throw r;let a=g.at(n);if(a===void 0)throw r;await s(a),await _(e,t,n+1)}}async function v(t,r){let i=c.join(c.dirname(t),`.tmp`);await n(i,{recursive:!0});let s=c.join(i,`${c.basename(t)}.${process.pid}.${e()}.tmp`);try{await o(s,r,`utf8`),await _(s,t)}catch(e){try{await a(s,{force:!0})}catch{}throw Error(`写产物失败:${t}(草稿 ${s}):${String(e)}`,{cause:e})}}async function y(e){try{await e.close()}catch(e){console.error(`释放 rolldown bundle 失败(不影响上面那条构建错因):`,e)}}function b(e){let n=process.argv.at(1);if(n===void 0||n.length===0||c.basename(n)!==c.basename(e))return!1;try{return t(n)===t(e)}catch(e){throw Error(`无法解析本脚本自身的路径(argv[1]=${n}):构建未发生,不要当成成功。`,{cause:e})}}export{u as asRecord,y as closeQuietly,b as invokedDirectly,l as isRecord,f as packageNameOf,d as readManifest,m as singleEntryChunk,v as writeArtifact};
1
+ import{randomUUID as e}from"node:crypto";import{realpathSync as t}from"node:fs";import{mkdir as n,readFile as r,rename as i,rm as a,writeFile as o}from"node:fs/promises";import{setTimeout as s}from"node:timers/promises";import c from"node:path";function l(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function u(e){return l(e)?e:{}}async function d(e){let t=c.join(e,`package.json`);try{return JSON.parse(await r(t,`utf8`))}catch(e){throw Error(`读 ${t} 失败:${String(e)}`,{cause:e})}}function f(e){let t=e.indexOf(`/`);if(!e.startsWith(`@`))return t===-1?e:e.slice(0,t);let n=t===-1?-1:e.indexOf(`/`,t+1);return n===-1?e:e.slice(0,n)}function p(e){return e?.type===`chunk`&&e.isEntry===!0&&typeof e.code==`string`&&e.code.trim()!==``}function m(e,t){if(e.length!==1){let n=e.map(e=>`${e.type}:${e.fileName}`).join(`, `);throw Error(`rolldown 为 ${t} 产出了 ${e.length} 项输出(期望恰好一个 entry chunk):${n}`)}let n=e.at(0);if(!p(n))throw Error(`rolldown 为 ${t} 产出的不是单个带正文的 entry chunk`);return n}const h=/* @__PURE__ */ new Set([`EPERM`,`EBUSY`,`EACCES`]),g=[50,100,200];async function _(e,t,n=0){try{await i(e,t)}catch(r){let i=typeof r==`object`&&r&&`code`in r?r.code:void 0;if(typeof i!=`string`||!h.has(i))throw r;let a=g.at(n);if(a===void 0)throw r;await s(a),await _(e,t,n+1)}}async function v(t,r){let i=c.join(c.dirname(t),`.tmp`);await n(i,{recursive:!0});let s=c.join(i,`${c.basename(t)}.${process.pid}.${e()}.tmp`);try{await o(s,r,`utf8`),await _(s,t)}catch(e){try{await a(s,{force:!0})}catch{}throw Error(`写产物失败:${t}(草稿 ${s}):${String(e)}`,{cause:e})}}function y(e,t,n){let r=e.charAt(t);n.push(r);let i=t+1;for(;i<e.length;){let t=e.charAt(i);if(t===`\\`)n.push(e.charAt(i+1)===`
2
+ `?`
3
+ `:` `),i+=2;else if(t===r)return n.push(t),i+1;else n.push(t===`
4
+ `?`
5
+ `:` `),i+=1}return i}function b(e){let t=[],n=0,r=(n,r)=>{for(let i=n;i<r;i+=1)t.push(e.charAt(i)===`
6
+ `?`
7
+ `:` `)};for(;n<e.length;){let i=e.charAt(n);if(i===`"`||i===`'`||i==="`")n=y(e,n,t);else if(e.startsWith(`//`,n)){let t=e.indexOf(`
8
+ `,n),i=t===-1?e.length:t;r(n,i),n=i}else if(e.startsWith(`/*`,n)){let t=e.indexOf(`*/`,n+2),i=t===-1?e.length:t+2;r(n,i),n=i}else t.push(i),n+=1}return t.join(``)}async function x(e){try{await e.close()}catch(e){console.error(`释放 rolldown bundle 失败(不影响上面那条构建错因):`,e)}}function S(e){let n=process.argv.at(1);if(n===void 0||n.length===0||c.basename(n)!==c.basename(e))return!1;try{return t(n)===t(e)}catch(e){throw Error(`无法解析本脚本自身的路径(argv[1]=${n}):构建未发生,不要当成成功。`,{cause:e})}}export{u as asRecord,x as closeQuietly,S as invokedDirectly,l as isRecord,b as maskNonCode,f as packageNameOf,d as readManifest,_ as renameWithRetry,m as singleEntryChunk,v as writeArtifact};
@@ -1,2 +1,2 @@
1
- import{readFile as e,readdir as t,rename as n,rm as r,writeFile as i}from"node:fs/promises";import a from"node:path";import{execFileSync as o}from"node:child_process";const s=/(?<=\bfrom\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,c=/(?<=\bimport\(\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,l=/(?<=\bdeclare\s+module\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,u={cts:`cjs`,mts:`mjs`,ts:`js`},d=/\.(?<extension>mts|cts|ts)(?=["']$)/u;function f(e){return e.replace(d,(e,t)=>`.${u[t]}`)}function p(e){return e.replaceAll(s,f).replaceAll(c,f).replaceAll(l,f)}function m(e){return typeof e==`string`?e:Buffer.isBuffer(e)?e.toString(`utf8`):``}function h(e,t,n){try{o(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`],encoding:`utf8`})}catch(e){let t=(e instanceof Error&&`stdout`in e&&`stderr`in e?[e.stdout,e.stderr]:[]).map(e=>m(e)).join(`
2
- `).trim();throw Error(t===``?String(e):t,{cause:e})}}async function g(e,n){let r=await t(e,{withFileTypes:!0}),i=r.filter(e=>!e.isDirectory()&&e.name.endsWith(`.d.ts`)).map(e=>`${n}${e.name}`),o=await Promise.all(r.filter(e=>e.isDirectory()).map(t=>g(a.join(e,t.name),`${n}${t.name}/`)));return[...i,...o.flat()]}async function _(t){let o=t.scratch??a.join(t.root,`.tmp`,`types`),s=t.outDir??a.join(t.root,`types`),c=t.tsconfig??`tsconfig.types.json`;await r(o,{recursive:!0,force:!0});try{h(process.execPath,[t.tscEntry,`-p`,c,`--outDir`,o],t.root);let l=await g(o,``);return await Promise.all(l.map(async t=>{let n=a.join(o,t),r=await e(n,`utf8`);await i(n,p(r),`utf8`)})),await r(s,{recursive:!0,force:!0}),await n(o,s),l}catch(e){throw await r(o,{recursive:!0,force:!0}),e}}export{_ as buildDeclarations,p as canonicalizeDeclarationSpecifiers,g as listDeclarations,h as runTool,m as textOf};
1
+ import{maskNonCode as e,renameWithRetry as t}from"./build-artifacts.js";import{randomUUID as n}from"node:crypto";import{existsSync as r}from"node:fs";import{readFile as i,readdir as a,rm as o,writeFile as s}from"node:fs/promises";import c from"node:path";import{execFileSync as l}from"node:child_process";const u=/(?<=\bfrom\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,d=/(?<=\bimport\(\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,f=/(?<=\bdeclare\s+module\s*)["']\.{1,2}\/[^"'\n]*?(?<!\.d)\.(?:mts|cts|ts)["']/gu,p={cts:`cjs`,mts:`mjs`,ts:`js`},m=/\.(?<extension>mts|cts|ts)(?=["']$)/u;function h(e){return e.replace(m,(e,t)=>`.${p[t]}`)}function g(t){let n=e(t),r=(e,t)=>n.charAt(t)===`"`||n.charAt(t)===`'`?h(e):e;return t.replaceAll(u,r).replaceAll(d,r).replaceAll(f,r)}function _(e){return typeof e==`string`?e:Buffer.isBuffer(e)?e.toString(`utf8`):``}function v(e,t,n){try{l(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`],encoding:`utf8`})}catch(e){let t=(e instanceof Error&&`stdout`in e&&`stderr`in e?[e.stdout,e.stderr]:[]).map(e=>_(e)).join(`
2
+ `).trim();throw Error(t===``?String(e):t,{cause:e})}}async function y(e,t){let n=await a(e,{withFileTypes:!0}),r=n.filter(e=>!e.isDirectory()&&e.name.endsWith(`.d.ts`)).map(e=>`${t}${e.name}`),i=await Promise.all(n.filter(e=>e.isDirectory()).map(n=>y(c.join(e,n.name),`${t}${n.name}/`)));return[...r,...i.flat()]}async function b(e){let a=e.scratch??c.join(e.root,`.tmp`,`types`),l=e.outDir??c.join(e.root,`types`),u=e.tsconfig??`tsconfig.types.json`;await o(a,{recursive:!0,force:!0});try{v(process.execPath,[e.tscEntry,`-p`,u,`--outDir`,a],e.root);let d=await y(a,``);await Promise.all(d.map(async e=>{let t=c.join(a,e),n=await i(t,`utf8`);await s(t,g(n),`utf8`)}));let f=e.rename??t,p=null;r(l)&&(p=`${l}.backup-${process.pid}-${n()}`,await f(l,p));try{await f(a,l)}catch(e){if(p===null)throw e;try{await f(p,l)}catch(t){throw Error(`声明产物换入失败,且备份目录未能恢复(备份留在 ${p}):${String(e)};恢复失败:${String(t)}`,{cause:t})}throw e}return p!==null&&await o(p,{recursive:!0,force:!0}),d}catch(e){throw await o(a,{recursive:!0,force:!0}),e}}export{b as buildDeclarations,g as canonicalizeDeclarationSpecifiers,y as listDeclarations,v as runTool,_ as textOf};
@@ -0,0 +1,5 @@
1
+ import{maskNonCode as e}from"./build-artifacts.js";import{mkdtempSync as t,readFileSync as n,rmSync as r}from"node:fs";import i from"node:path";import{execFileSync as a}from"node:child_process";import{tmpdir as o}from"node:os";const s=/(?:^|[\s{;(,=:[?!&|>\]})])@[A-Za-z_$][\w$]*/u,c=/\.d\.[cm]?ts$/u,l=/\.[cm]?ts$/u,u=/^\s*if \(_metadata\) Object\.defineProperty\([A-Za-z_$][\w$]*, Symbol\.metadata,/u,d=[`__esDecorate(`,`__runInitializers(`];function f(t,n){return l.test(t)&&!c.test(t)&&s.test(e(n))}function p(e){return e.endsWith(`.mts`)?`.mjs`:e.endsWith(`.cts`)?`.cjs`:`.js`}function m(e,t,n){let r=[],i=[],a=0;for(let o of e.split(`
2
+ `)){let e=n?.[a]??``;if(a+=1,u.test(o)&&!o.trimEnd().endsWith(`);`))throw Error(`dsh-plugin-shared: Symbol.metadata 发布语句不是单行(${o.trim().slice(0,80)}…):按行裁剪会留下残片,产物会是非法 JS。`);let s=u.test(o)||o.startsWith(`//# sourceMappingURL=`),c=d.some(e=>o.includes(e));r.push(s?``:o),i.push(s||t&&c?``:e)}return{code:r.join(`
3
+ `),mapLines:n===null?null:i}}function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function g(e,s,c,u=!1){if(e.length===0)return{code:e,map:null};let d=t(i.join(o(),`dsh-decorators-`)),f=``;try{try{a(process.execPath,[c.tscEntry,`--ignoreConfig`,`--noResolve`,`--target`,`es2024`,`--module`,`preserve`,`--sourceMap`,`--outDir`,d,s],{stdio:[`ignore`,`ignore`,`pipe`],encoding:`utf8`})}catch(e){
4
+ /* v8 ignore next 3 -- 见上:否定支在当前调用方式下不可达 */
5
+ e instanceof Error&&`stderr`in e&&typeof e.stderr==`string`&&(f=e.stderr.trim())}let t=i.join(d,`${i.basename(s).replace(l,``)}${p(s)}`),r;try{r=n(t,`utf8`)}catch(e){let n=f===``?`(tsc 没有输出 stderr)`:f;throw Error(`dsh-plugin-shared: 装饰器降级没有产出 ${i.basename(t)}(${s})。tsc stderr:${n}`,{cause:e})}if(r.length===0)throw Error(`dsh-plugin-shared: 装饰器降级没有产出任何字节(${s})`);let o=null;try{let r=JSON.parse(n(`${t}.map`,`utf8`));o=h(r)?r:null,o!==null&&(o.sources=[s],o.sourcesContent=[e])}catch{}let g=o?.mappings,_=g===void 0?null:g.split(`;`),v=m(r,u,_);return o!==null&&v.mapLines!==null&&(o.mappings=v.mapLines.join(`;`)),{code:v.code,map:o}}finally{r(d,{recursive:!0,force:!0})}}function _(e){return e.includes(`?`)?e.slice(0,e.indexOf(`?`)):e}function v(e){return{name:`dsh-plugin-shared:standard-decorators`,transform(t,n){let r=_(n);return f(r,t)?g(t,r,e):null}}}function y(e){return{name:`dsh-plugin-shared:standard-decorators`,enforce:`pre`,transform(t,n){let r=_(n);return f(r,t)?g(t,r,e,!0):void 0}}}export{g as lowerStandardDecorators,f as needsDecoratorLowering,v as rolldownDecorators,y as viteDecorators};
@@ -43,7 +43,7 @@ import{createElement as e,useCallback as t,useEffect as n,useId as r,useLayoutEf
43
43
  .dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
44
44
  .dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
45
45
  .dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
46
- .dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
46
+ .dsu-menu{position:fixed;position-area:block-end span-inline-start;justify-self:start;margin-top:6px;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
47
47
  .dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
48
48
  .dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
49
49
  .dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
@@ -82,24 +82,24 @@ if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=t.offsetWidth,i=t
82
82
  /* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
83
83
  v.current=null,p.current||m.current||h.current||u(!1)},120))},[y]),w=t(e=>{h.current=e,f(e)},[]);i(()=>{if(!l)return()=>{};b();let e=e=>{let{target:t}=e;t instanceof Node&&_.current?.contains(t)===!0||(h.current?b():x())},t=()=>{b()};return window.addEventListener(`scroll`,e,!0),window.addEventListener(`resize`,t),()=>{window.removeEventListener(`scroll`,e,!0),window.removeEventListener(`resize`,t)}},[l,a.detail,b,x]),n(()=>{if(!l)return()=>{};let e=e=>{e.key===`Escape`&&x()};return document.addEventListener(`keydown`,e),()=>{document.removeEventListener(`keydown`,e)}},[l,x]),n(()=>y,[y]);let T=e(`button`,{ref:g,type:`button`,className:`dsu-helpBtn`,"aria-label":`${a.label} 说明`,"aria-describedby":c,"aria-pressed":d,onClick:()=>{h.current?x():(w(!0),S())}},e(`svg`,{width:12,height:12,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`circle`,{cx:7,cy:7,r:6,fill:`none`,stroke:`currentColor`,strokeWidth:1.2}),e(`path`,{d:`M7 6.2v4`,stroke:`currentColor`,strokeWidth:1.3,strokeLinecap:`round`}),e(`circle`,{cx:7,cy:4.3,r:.8,fill:`currentColor`}))),E=e(`div`,{ref:_,id:c,role:`tooltip`,className:`dsu-tip`,hidden:!l},a.detail),D=a.showLabel===!1?null:e(`span`,{className:`dsu-label${a.compact===!0?` dsu-labelMono`:``}`},a.label);return e(`span`,{className:`dsu-help`,onMouseEnter:()=>{p.current=!0,S()},onMouseLeave:()=>{p.current=!1,C()},onFocus:()=>{m.current=!0,S()},onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||(m.current=!1,C())}},D,T,E)}function h(t){let n=`dsu-label${t.compact===!0?` dsu-labelMono`:``}`,r=t.controlId===void 0?e(`span`,{className:n},t.label):e(`label`,{className:n,htmlFor:t.controlId},t.label);return e(`div`,{className:`dsu-labelRow`},r,t.detail===void 0?null:e(m,{label:t.label,detail:t.detail,showLabel:!1}))}function g(t){let n=t.controlId===void 0?void 0:`${t.controlId}-hint`;return e(`div`,{className:`dsu-field${t.wide===!0?` dsu-fieldWide`:``}`},e(`div`,{className:`dsu-fieldMeta`},e(h,{label:t.label,detail:t.detail,compact:t.compact,controlId:t.controlId}),t.hint===void 0?null:e(`p`,{className:`dsu-hint`,id:n},t.hint)),e(`div`,{className:`dsu-control`},t.children))}function _(t){let n=r(),i=t.hint===void 0?void 0:`${n}-hint`;return e(g,{...t,controlId:n},e(`button`,{type:`button`,role:`switch`,id:n,className:`dsu-switch`,"aria-checked":t.checked,"aria-describedby":i,"data-field":t.field,disabled:t.disabled===!0,onClick:()=>{t.onChange(!t.checked)}},e(`span`,{className:`dsu-knob`})))}function v(t){let i=typeof t.value==`number`&&Number.isFinite(t.value)?String(t.value):``,[a,c]=s(i),[l,u]=s(!1),d=o(void 0),f=r(),p=r(),m=[t.hint===void 0?void 0:`${f}-hint`,l?p:void 0].filter(e=>e!==void 0),h=m.length===0?void 0:m.join(` `);n(()=>{t.value!==d.current&&(d.current=t.value,c(i),u(!1))},[t.value,i]);let _=e=>{let n=t.min??-1/0,r=t.max??1/0;if(!Number.isFinite(e)||e<n||e>r)return!1;let{step:i}=t;if(i===void 0||i<=0)return!0;let a=(e-(t.min??0))/i;return Math.abs(a-Math.round(a))<1e-9},v=e=>{if(c(e),e===``){u(!1),d.current=void 0,t.onChange(void 0);return}let n=Number(e);_(n)?(u(!1),d.current=n,t.onChange(n)):u(!0)},y=()=>{a===``||l||c(String(Number(a)))};return e(g,{...t,controlId:f},e(`div`,{className:`dsu-numberWrap`},e(`div`,{className:`dsu-number`},e(`input`,{className:`dsu-input`,type:`number`,inputMode:`numeric`,id:f,value:a,min:t.min,max:t.max,step:t.step,placeholder:t.placeholder,"aria-invalid":l||void 0,"aria-describedby":h,"data-field":t.field,disabled:t.disabled===!0,onChange:e=>{v(e.target.value)},onBlur:y}),t.unit===void 0?null:e(`span`,{className:`dsu-unit`},t.unit)),l?e(`p`,{className:`dsu-error`,id:p},t.invalidHint):null))}function y(t){let[i,a]=s(t.value),[o,c]=s(!1),l=r(),u=t.hint===void 0?void 0:`${l}-hint`;n(()=>{o||a(t.value)},[t.value,o]);let d={id:l,className:`dsu-input${t.multiline===!0?` dsu-textarea`:``}${t.monospace===!0?` dsu-mono`:``}`,value:i,placeholder:t.placeholder,"aria-describedby":u,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{c(!0)},onBlur:()=>{c(!1)},onChange:e=>{a(e.target.value),t.onChange(e.target.value)}};return e(g,{...t,controlId:l},t.multiline===!0?e(`textarea`,d):e(`input`,d))}function b(t){let i=t.separator??`comma`,a=t.value.join(i===`newline`?`
84
84
  `:`, `),[o,c]=s(a),[l,u]=s(!1),d=r(),f=t.hint===void 0?void 0:`${d}-hint`;n(()=>{l||c(a)},[a,l]);let p=e=>e.split(i===`newline`?`
85
- `:`,`).map(e=>e.trim()).filter(e=>e.length>0),m={id:d,className:`dsu-input${i===`newline`?` dsu-textarea dsu-mono`:``}`,value:o,placeholder:t.placeholder,"aria-describedby":f,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{u(!0)},onBlur:()=>{u(!1)},onChange:e=>{c(e.target.value),t.onChange(p(e.target.value))}};return e(g,{...t,controlId:d},e(i===`newline`?`textarea`:`input`,m))}function x(c){let[l,u]=s(!1),[d,f]=s(0),p=o(null),m=o(null),h=r(),_=r(),v=c.hint===void 0?void 0:`${_}-hint`,{disabled:y,value:b,options:x,onChange:S}=c,C=a(()=>x.map((e,t)=>({option:e,index:t})).filter(e=>e.option.disabled!==!0),[x]),w=x.find(e=>e.value===b)?.label??b,T=x[d],E=T!==void 0&&T.disabled!==!0,D=t(()=>{let e=p.current,t=m.current;
85
+ `:`,`).map(e=>e.trim()).filter(e=>e.length>0),m={id:d,className:`dsu-input${i===`newline`?` dsu-textarea dsu-mono`:``}`,value:o,placeholder:t.placeholder,"aria-describedby":f,"data-field":t.field,disabled:t.disabled===!0,onFocus:()=>{u(!0)},onBlur:()=>{u(!1)},onChange:e=>{c(e.target.value),t.onChange(p(e.target.value))}};return e(g,{...t,controlId:d},e(i===`newline`?`textarea`:`input`,m))}function x(){return`happyDOM`in globalThis?!1:typeof CSS<`u`&&typeof CSS.supports==`function`&&CSS.supports(`position-area: block-end`)}function S(c){let[l,u]=s(!1),[d,f]=s(0),p=o(null),m=o(null),h=r(),_=r(),v=`--dsu-${_.replaceAll(/[^a-zA-Z0-9]/gu,``)}`,y=c.hint===void 0?void 0:`${_}-hint`,{disabled:b,value:S,options:C,onChange:w}=c,T=a(()=>C.map((e,t)=>({option:e,index:t})).filter(e=>e.option.disabled!==!0),[C]),E=C.find(e=>e.value===S)?.label??S,D=C[d],O=D!==void 0&&D.disabled!==!0,k=t(()=>{let e=p.current,t=m.current;
86
86
  /* v8 ignore next 3 -- place 只在 open 的 layout effect 里调用;refs 为空只可能是卸载竞态 */
87
- if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=Math.min(n.width,window.innerWidth-16),{left:i}=n;i+r>window.innerWidth-8&&(i=window.innerWidth-r-8);let a=t.offsetHeight,o=n.bottom+6;o+a>window.innerHeight-8&&(o=n.top-a-6);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-a-8);t.style.width=`${r}px`,t.style.left=`${Math.min(Math.max(8,i),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),O=t(()=>{u(!1)},[]),k=t(()=>{if(y===!0)return;let e=C.find(e=>e.option.value===b)??C.at(0);e!==void 0&&(f(e.index),u(!0))},[y,b,C]),A=t(e=>{
87
+ if(e===null||t===null)return;let n=e.getBoundingClientRect(),r=Math.min(n.width,window.innerWidth-16);if(t.style.width=`${r}px`,x())return;t.style.marginTop=`0px`;let{left:i}=n;i+r>window.innerWidth-8&&(i=window.innerWidth-r-8);let a=t.offsetHeight,o=n.bottom+6;o+a>window.innerHeight-8&&(o=n.top-a-6);let s=Math.max(8,window.innerWidth-r-8),c=Math.max(8,window.innerHeight-a-8);t.style.left=`${Math.min(Math.max(8,i),s)}px`,t.style.top=`${Math.min(Math.max(8,o),c)}px`},[]),A=t(()=>{u(!1)},[]),j=t(()=>{if(b===!0)return;let e=T.find(e=>e.option.value===S)??T.at(0);e!==void 0&&(f(e.index),u(!0))},[b,S,T]),M=t(e=>{
88
88
  /* v8 ignore next 3 -- 浮层只能经由 openMenu 打开;组件禁用时打不开 */
89
- if(y===!0)return;let t=x[e];
90
- /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用面保证 */
91
- t!==void 0&&t.disabled!==!0&&(S(t.value),O(),p.current?.focus())},[y,x,S,O]),j=t(e=>{let t=C.findIndex(e=>e.index===d);
89
+ if(b===!0)return;let t=C[e];
90
+ /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用方保证 */
91
+ t!==void 0&&t.disabled!==!0&&(w(t.value),A(),p.current?.focus())},[b,C,w,A]),N=t(e=>{let t=T.findIndex(e=>e.index===d);
92
92
  /* v8 ignore next 5 -- open 时归一 effect 已保证 enabled 非空且 active 落在可用项上 */
93
- if(t===-1||C.length===0)return;let n=(t+e+C.length)%C.length,r=C[n];
93
+ if(t===-1||T.length===0)return;let n=(t+e+T.length)%T.length,r=T[n];
94
94
  /* v8 ignore next 3 -- next 由取模保证在界内 */
95
- r!==void 0&&f(r.index)},[d,C]);i(()=>{if(!l)return()=>{};D();let e=e=>{let t=e.target;m.current?.contains(t)!==!0&&p.current?.contains(t)!==!0&&O()},t=e=>{let{target:t}=e;t instanceof Node&&m.current?.contains(t)===!0||O()},n=()=>{D()};return document.addEventListener(`pointerdown`,e,!0),window.addEventListener(`scroll`,t,!0),window.addEventListener(`resize`,n),()=>{document.removeEventListener(`pointerdown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,n)}},[l,c.options,D,O]),n(()=>{if(!l)return;if(y===!0||C.length===0){O();return}if(C.some(e=>e.index===d))return;let e=C.find(e=>e.option.value===b)??C.at(0);
95
+ r!==void 0&&f(r.index)},[d,T]);i(()=>{if(!l)return()=>{};k();let e=e=>{let t=e.target;m.current?.contains(t)!==!0&&p.current?.contains(t)!==!0&&A()},t=e=>{let{target:t}=e;t instanceof Node&&m.current?.contains(t)===!0||A()},n=()=>{k()};return document.addEventListener(`pointerdown`,e,!0),window.addEventListener(`scroll`,t,!0),window.addEventListener(`resize`,n),()=>{document.removeEventListener(`pointerdown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,n)}},[l,c.options,k,A]),n(()=>{if(!l)return;if(b===!0||T.length===0){A();return}if(T.some(e=>e.index===d))return;let e=T.find(e=>e.option.value===S)??T.at(0);
96
96
  /* v8 ignore next 3 -- enabled 非空在上一分支已保证 */
97
- e!==void 0&&f(e.index)},[l,y,b,x,d,O,C]),i(()=>{if(!l)return;let e=m.current;
97
+ e!==void 0&&f(e.index)},[l,b,S,C,d,A,T]),i(()=>{if(!l)return;let e=m.current;
98
98
  /* v8 ignore next 2 -- 同上,menu 在 open 的 commit 之后必然已挂上 */
99
99
  if(e===null)return;let t=e.querySelector(`[data-active="true"]`);
100
100
  /* v8 ignore next 2 -- active 已由归一 effect 保证落在可用项上 */
101
- t!==null&&(t.offsetTop<e.scrollTop?e.scrollTop=t.offsetTop:t.offsetTop+t.offsetHeight>e.scrollTop+e.clientHeight&&(e.scrollTop=t.offsetTop+t.offsetHeight-e.clientHeight))},[l,d,x]);let M=e=>{if(e.key===`ArrowDown`||e.key===`ArrowUp`){if(e.preventDefault(),!l){k();return}j(e.key===`ArrowDown`?1:-1)}else if(e.key===`Home`||e.key===`End`){if(e.preventDefault(),l||k(),C.length===0)return;let t=e.key===`Home`?C.at(0):C.at(-1);
101
+ t!==null&&(t.offsetTop<e.scrollTop?e.scrollTop=t.offsetTop:t.offsetTop+t.offsetHeight>e.scrollTop+e.clientHeight&&(e.scrollTop=t.offsetTop+t.offsetHeight-e.clientHeight))},[l,d,C]);let P=e=>{if(e.key===`ArrowDown`||e.key===`ArrowUp`){if(e.preventDefault(),!l){j();return}N(e.key===`ArrowDown`?1:-1)}else if(e.key===`Home`||e.key===`End`){if(e.preventDefault(),l||j(),T.length===0)return;let t=e.key===`Home`?T.at(0):T.at(-1);
102
102
  /* v8 ignore next 3 -- enabled 为空已在上一条返回 */
103
- if(t===void 0)return;f(t.index)}else if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!l){k();return}A(d)}else(e.key===`Tab`||e.key===`Escape`)&&O()},N=[],P;for(let[t,n]of x.entries())n.group!==P&&(n.group!==void 0&&N.push(e(`div`,{key:`group-${t}-${n.group}`,className:`dsu-optionGroup`,role:`presentation`},n.group)),P=n.group),N.push(e(`button`,{key:`option-${n.value}`,type:`button`,role:`option`,id:`${h}-${t}`,className:`dsu-option`,tabIndex:-1,"aria-selected":n.value===c.value,"data-active":t===d,disabled:n.disabled===!0,onMouseDown:e=>{e.preventDefault()},onClick:()=>{A(t)},onMouseEnter:()=>{
103
+ if(t===void 0)return;f(t.index)}else if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!l){j();return}M(d)}else(e.key===`Tab`||e.key===`Escape`)&&A()},F=[],I;for(let[t,n]of C.entries())n.group!==I&&(n.group!==void 0&&F.push(e(`div`,{key:`group-${t}-${n.group}`,className:`dsu-optionGroup`,role:`presentation`},n.group)),I=n.group),F.push(e(`button`,{key:`option-${n.value}`,type:`button`,role:`option`,id:`${h}-${t}`,className:`dsu-option`,tabIndex:-1,"aria-selected":n.value===c.value,"data-active":t===d,disabled:n.disabled===!0,onMouseDown:e=>{e.preventDefault()},onClick:()=>{M(t)},onMouseEnter:()=>{
104
104
  /* v8 ignore next -- happy-dom 对 disabled 元素不派发 mouse 事件;浏览器实现各有差异,保留禁用检查 */
105
- n.disabled!==!0&&f(t)}},e(`span`,null,n.label),e(`svg`,{className:`dsu-check`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M2.5 7.5l3 3 6-7`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))));return e(g,{...c,controlId:_},e(`div`,{className:`dsu-selectWrap`,onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||O()}},e(`button`,{ref:p,type:`button`,id:_,className:`dsu-select dsu-input`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":l,"aria-controls":`${h}-list`,"aria-activedescendant":l&&E?`${h}-${d}`:void 0,"aria-describedby":v,"data-field":c.field,disabled:y===!0||x.length===0,onClick:()=>{l?O():k()},onKeyDown:M},e(`span`,{className:`dsu-selectValue`},w),e(`svg`,{className:`dsu-chevron`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M3 5l4 4 4-4`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))),e(`div`,{ref:m,id:`${h}-list`,role:`listbox`,className:`dsu-menu`,"aria-label":c.label,hidden:!l},...N)))}function S(t){let n=!t.writable||t.busy||!t.dirty,r;return r=t.error===null?t.writable?t.dirty?t.dirtyLabel:t.cleanLabel:t.readOnlyLabel:t.error,e(`div`,{className:`dsu-savebar`,"data-dirty":t.dirty},e(`button`,{type:`button`,className:`dsu-btn dsu-btnPrimary`,"data-field":`save`,disabled:n,onClick:t.onSave},t.busy?t.savingLabel:t.saveLabel),e(`button`,{type:`button`,className:`dsu-btn`,"data-field":`discard`,disabled:n,onClick:t.onDiscard},t.revertLabel),e(`span`,{className:t.error===null?`dsu-status`:`dsu-status dsu-statusError`},r))}export{y as a,h as c,f as d,d as f,c as h,v as i,m as l,l as m,x as n,_ as o,p,b as r,g as s,S as t,u};
105
+ n.disabled!==!0&&f(t)}},e(`span`,null,n.label),e(`svg`,{className:`dsu-check`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M2.5 7.5l3 3 6-7`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))));return e(g,{...c,controlId:_},e(`div`,{className:`dsu-selectWrap`,onBlur:e=>{let t=e.relatedTarget;t instanceof Node&&e.currentTarget.contains(t)||A()}},e(`button`,{ref:p,type:`button`,id:_,style:{anchorName:v},className:`dsu-select dsu-input`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":l,"aria-controls":`${h}-list`,"aria-activedescendant":l&&O?`${h}-${d}`:void 0,"aria-describedby":y,"data-field":c.field,disabled:b===!0||C.length===0,onClick:()=>{l?A():j()},onKeyDown:P},e(`span`,{className:`dsu-selectValue`},E),e(`svg`,{className:`dsu-chevron`,width:14,height:14,viewBox:`0 0 14 14`,"aria-hidden":!0},e(`path`,{d:`M3 5l4 4 4-4`,fill:`none`,stroke:`currentColor`,strokeWidth:1.5,strokeLinecap:`round`,strokeLinejoin:`round`}))),e(`div`,{ref:m,id:`${h}-list`,role:`listbox`,className:`dsu-menu`,style:{positionAnchor:v,positionTryFallbacks:`flip-block, flip-inline`},"aria-label":c.label,hidden:!l},...F)))}function C(t){let n=!t.writable||t.busy||!t.dirty,r;return r=t.error===null?t.writable?t.dirty?t.dirtyLabel:t.cleanLabel:t.readOnlyLabel:t.error,e(`div`,{className:`dsu-savebar`,"data-dirty":t.dirty},e(`button`,{type:`button`,className:`dsu-btn dsu-btnPrimary`,"data-field":`save`,disabled:n,onClick:t.onSave},t.busy?t.savingLabel:t.saveLabel),e(`button`,{type:`button`,className:`dsu-btn`,"data-field":`discard`,disabled:n,onClick:t.onDiscard},t.revertLabel),e(`span`,{className:t.error===null?`dsu-status`:`dsu-status dsu-statusError`},r))}export{y as a,h as c,f as d,d as f,c as h,v as i,m as l,l as m,S as n,_ as o,p,b as r,g as s,C as t,u};
package/dist/card-ui.js CHANGED
@@ -1 +1 @@
1
- export{h as CARD_UI_CSS,u as CardForm,s as Field,d as FieldGrid,c as FieldLabel,l as InfoTip,r as ListField,i as NumberField,t as SaveBar,f as Section,n as SelectField,p as StatusBar,a as TextField,o as ToggleField,m as splitHint}from"./card-ui-C1lrHILm.js";
1
+ export{h as CARD_UI_CSS,u as CardForm,s as Field,d as FieldGrid,c as FieldLabel,l as InfoTip,r as ListField,i as NumberField,t as SaveBar,f as Section,n as SelectField,p as StatusBar,a as TextField,o as ToggleField,m as splitHint}from"./card-ui-D2cuQvLS.js";
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,f as r,h as i,i as a,l as o,m as s,n as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./card-ui-C1lrHILm.js";import{diffTouched as h}from"./card-diff.js";import{claimApply as g}from"./card-apply.js";import{fieldOf as _,isRecord as v}from"./record.js";import{errorText as y}from"./errors.js";const b=`@jayyuen66/dsh-plugin-shared`;function x(){}export{i as CARD_UI_CSS,m as CardForm,f as Field,n as FieldGrid,t as FieldLabel,o as InfoTip,d as ListField,a as NumberField,p as SaveBar,r as Section,c as SelectField,u as StatusBar,e as TextField,l as ToggleField,x as apply,g as claimApply,h as diffTouched,y as errorText,_ as fieldOf,v as isRecord,b as name,s as splitHint};
1
+ import{a as e,c as t,d as n,f as r,h as i,i as a,l as o,m as s,n as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./card-ui-D2cuQvLS.js";import{diffTouched as h}from"./card-diff.js";import{claimApply as g}from"./card-apply.js";import{fieldOf as _,isRecord as v}from"./record.js";import{errorText as y}from"./errors.js";const b=`@jayyuen66/dsh-plugin-shared`;function x(){}export{i as CARD_UI_CSS,m as CardForm,f as Field,n as FieldGrid,t as FieldLabel,o as InfoTip,d as ListField,a as NumberField,p as SaveBar,r as Section,c as SelectField,u as StatusBar,e as TextField,l as ToggleField,x as apply,g as claimApply,h as diffTouched,y as errorText,_ as fieldOf,v as isRecord,b as name,s as splitHint};
package/dist/client.js CHANGED
@@ -54,7 +54,7 @@ const CARD_UI_CSS = `
54
54
  .dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
55
55
  .dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
56
56
  .dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
57
- .dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
57
+ .dsu-menu{position:fixed;position-area:block-end span-inline-start;justify-self:start;margin-top:6px;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
58
58
  .dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
59
59
  .dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
60
60
  .dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
@@ -347,8 +347,8 @@ function NumberField(props) {
347
347
  setInvalid(false);
348
348
  }, [props.value, text]);
349
349
  const accepts = (value) => {
350
- const min = props.min ?? -Infinity;
351
- const max = props.max ?? Infinity;
350
+ const min = props.min ?? -1e400;
351
+ const max = props.max ?? 1e400;
352
352
  if (!Number.isFinite(value) || value < min || value > max) return false;
353
353
  const { step } = props;
354
354
  if (step === void 0 || step <= 0) return true;
@@ -472,6 +472,16 @@ function ListField(props) {
472
472
  }, separator === "newline" ? (0, react.createElement)("textarea", shared) : (0, react.createElement)("input", shared));
473
473
  }
474
474
  // lib/card-ui/select.ts
475
+ /** 浏览器是否支持 CSS anchor 定位(宿主 webview 支持,与宿主 Menu 同一套平台 API)。
476
+ * 支持时浮层位置由 `position-area` 跟随触发器,缩放/平移不漂;happy-dom 等不支持处回退 JS 矩形。
477
+ * 探测的是本组件实际依赖的 `position-area`,不是更早出现的 `anchor-name`:只支持
478
+ * `anchor-name` 的半档实现里浮层不会被真正定位。happy-dom 的 CSS.supports 对 anchor-name
479
+ * 误返 true,但它并不真做 anchor 布局;happy-dom 在 window 上暴露 happyDOM 属性,据此排除
480
+ * (真实浏览器没有该属性)。 */
481
+ function cssAnchorSupported() {
482
+ if ("happyDOM" in globalThis) return false;
483
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("position-area: block-end");
484
+ }
475
485
  /** 自定义下拉:浮层固定定位,键盘可选,点击外部关闭。 */
476
486
  function SelectField(props) {
477
487
  const [open, setOpen] = (0, react.useState)(false);
@@ -480,6 +490,7 @@ function SelectField(props) {
480
490
  const menuRef = (0, react.useRef)(null);
481
491
  const baseId = (0, react.useId)();
482
492
  const controlId = (0, react.useId)();
493
+ const anchorId = `--dsu-${controlId.replaceAll(/[^a-zA-Z0-9]/gu, "")}`;
483
494
  const describedBy = props.hint === void 0 ? void 0 : `${controlId}-hint`;
484
495
  const { disabled, value, options, onChange } = props;
485
496
  const enabled = (0, react.useMemo)(() => options.map((option, index) => ({
@@ -496,6 +507,9 @@ function SelectField(props) {
496
507
  if (trigger === null || menu === null) return;
497
508
  const rect = trigger.getBoundingClientRect();
498
509
  const width = Math.min(rect.width, window.innerWidth - 16);
510
+ menu.style.width = `${width}px`;
511
+ if (cssAnchorSupported()) return;
512
+ menu.style.marginTop = "0px";
499
513
  let { left } = rect;
500
514
  if (left + width > window.innerWidth - 8) left = window.innerWidth - width - 8;
501
515
  const height = menu.offsetHeight;
@@ -503,7 +517,6 @@ function SelectField(props) {
503
517
  if (top + height > window.innerHeight - 8) top = rect.top - height - 6;
504
518
  const maxLeft = Math.max(8, window.innerWidth - width - 8);
505
519
  const maxTop = Math.max(8, window.innerHeight - height - 8);
506
- menu.style.width = `${width}px`;
507
520
  menu.style.left = `${Math.min(Math.max(8, left), maxLeft)}px`;
508
521
  menu.style.top = `${Math.min(Math.max(8, top), maxTop)}px`;
509
522
  }, []);
@@ -525,7 +538,7 @@ function SelectField(props) {
525
538
  /* v8 ignore next 3 -- 浮层只能经由 openMenu 打开;组件禁用时打不开 */
526
539
  if (disabled === true) return;
527
540
  const option = options[index];
528
- /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用面保证 */
541
+ /* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用方保证 */
529
542
  if (option === void 0 || option.disabled === true) return;
530
543
  onChange(option.value);
531
544
  close();
@@ -710,6 +723,7 @@ function SelectField(props) {
710
723
  ref: triggerRef,
711
724
  type: "button",
712
725
  id: controlId,
726
+ style: { anchorName: anchorId },
713
727
  className: "dsu-select dsu-input",
714
728
  role: "combobox",
715
729
  "aria-haspopup": "listbox",
@@ -745,6 +759,10 @@ function SelectField(props) {
745
759
  id: `${baseId}-list`,
746
760
  role: "listbox",
747
761
  className: "dsu-menu",
762
+ style: {
763
+ positionAnchor: anchorId,
764
+ positionTryFallbacks: "flip-block, flip-inline"
765
+ },
748
766
  "aria-label": props.label,
749
767
  hidden: !open
750
768
  }, ...optionNodes)));
@@ -0,0 +1 @@
1
+ import{Remote as e}from"@deepseek-ai/dsh-typert-protocol";function t(t,n){let r={kind:`method`,name:n,static:!1,private:!1,addInitializer:e=>{Reflect.apply(e,Object.create(t),[])}},i=e(n);Reflect.apply(i,void 0,[Reflect.get(t,n),r])}export{t as markRemote};
@@ -15,7 +15,7 @@ export declare function asRecord(raw: unknown): Record<string, unknown>;
15
15
  * 读包根的 package.json,带上下文的失败信息。
16
16
  *
17
17
  * 惰性(调用时才读)而不是在消费方模块顶层读:顶层一次 JSON 语法错误会以未捕获的顶层
18
- * await 错误 + 裸 SyntaxError 收场,读者看不出是哪个文件坏了;而测试 import 构建模块时
18
+ * await 错误 + 不带文件路径的 SyntaxError 收场,读者看不出是哪个文件坏了;而测试 import 构建模块时
19
19
  * 只为拿构建函数,不该连带做一次磁盘 I/O。
20
20
  * @param root - 包根绝对路径
21
21
  * @returns 解析后的 manifest
@@ -51,23 +51,49 @@ export interface OutputItem {
51
51
  export declare function singleEntryChunk(output: readonly OutputItem[], source: string): OutputItem & {
52
52
  code: string;
53
53
  };
54
+ /**
55
+ * rename 的 Windows 退避重试。
56
+ *
57
+ * POSIX 上 rename 换目录项是原子的,失败即真失败;Windows 上目标文件被其它进程持有
58
+ * (Electron / dev server 正 require 产物、杀毒或索引服务短暂占用)时会给 EPERM/EBUSY,
59
+ * 而占用通常在几十毫秒内释放。不重试的话这条失败是**间歇性**的:同一份安装里连着构建两次,
60
+ * 一次失败一次成功。其余错误码照旧立即抛出——真的没权限时重试只是拖慢失败。
61
+ * @param from - 草稿文件
62
+ * @param to - 目标文件
63
+ * @param attempt - 已重试次数
64
+ */
65
+ export declare function renameWithRetry(from: string, to: string, attempt?: number): Promise<void>;
54
66
  /**
55
67
  * 原子写入:目标目录下 `.tmp/` 草稿 + rename;失败时清掉草稿再抛。
56
68
  *
57
69
  * 草稿落在**目标文件所在目录**下的 `.tmp/`,不是 `os.tmpdir()`,也不是产物旁边的裸名:
58
70
  * - 不用 `os.tmpdir()`:rename 只在同一文件系统内原子,跨设备会以 EXDEV 失败,而系统临时
59
71
  * 目录常被挂到另一个分区/卷;
60
- * - 跟随目标目录而不是写死包根:本函数住在公共库里,产物落点由调用方决定;
72
+ * - 跟随目标目录而不是写死包根:本函数定义在公共库里,产物写到哪儿由调用方决定;
61
73
  * - 不用「产物名 + 后缀」直接落在产物旁边:那个名字通常不在 `.gitignore` 里,构建被中断
62
74
  * 时半份产物会以未跟踪文件出现在 `git status`,被误当成源码改动提交。`.tmp/` 是各包
63
75
  * 统一忽略的目录,不需要为构建残留单独扩忽略规则。
64
76
  *
65
- * 失败信息带上两个路径并保留 cause:裸 `ENOENT … open '…/<uuid>.tmp'` 把注意力引到草稿名上,
77
+ * 失败信息带上两个路径并保留 cause:只有 `ENOENT … open '…/<uuid>.tmp'` 把注意力引到草稿名上,
66
78
  * 读者还得自己从中找出真正的产物路径。
67
79
  * @param target - 目标文件绝对路径
68
80
  * @param text - 产物正文
69
81
  */
70
82
  export declare function writeArtifact(target: string, text: string): Promise<void>;
83
+ /**
84
+ * 把注释与字符串内容替换成空格,保留长度与换行;引号/反引号本身保留。
85
+ *
86
+ * 用途是给**形状判断**(装饰器语法、声明里的模块说明符)提供一份与原文逐字符对齐的
87
+ * “只看程序正文”文本:调用方拿同一套下标回到原文取值,既不误伤注释里的示例,也不会
88
+ * 把字面量类型里的 `from "./x.ts"` 当成真实说明符。
89
+ *
90
+ * 字符串里的转义对整体遮蔽;模板字面量的 `${}` 不做二次解析(本包不需要在表达式里
91
+ * 找装饰器或说明符)。正则字面量不做词法识别,里面的 `@` 可能造成一次多余的 tsc 调用
92
+ * ——方向是安全的(只多跑一次编译,不产出错字节)。
93
+ * @param code - 源文本
94
+ * @returns 与入参等长、换行位置不变的遮蔽文本
95
+ */
96
+ export declare function maskNonCode(code: string): string;
71
97
  /** 异常路径上释放 rolldown bundle 的最小接口。 */
72
98
  export interface CloseableBundle {
73
99
  readonly close: () => Promise<void>;
@@ -86,7 +112,7 @@ export interface RolldownBundle extends CloseableBundle {
86
112
  * 调用方注入的 rolldown 构造器。
87
113
  *
88
114
  * 注入而不是由本包声明依赖:rolldown 是各包构建期自己的 devDependency,本包作为库
89
- * 不该为构建工具声明运行期依赖面。参数类型借 rolldown 自己的 InputOptions(仅类型
115
+ * 不该为构建工具声明运行期依赖。参数类型借 rolldown 自己的 InputOptions(仅类型
90
116
  * 导入,产物里没有它)。
91
117
  */
92
118
  export type RolldownFactory = (options: InputOptions) => Promise<RolldownBundle>;
@@ -1,8 +1,8 @@
1
1
  import type { RolldownFactory } from "./build-artifacts.js";
2
2
  /**
3
- * 宿主 seed 表里 react 家族的全部说明符(客户端产物只外置宿主真给得出的模块)。
3
+ * 宿主 seed 表里 react 家族的全部模块说明符——客户端产物只外置宿主真给得出的模块。
4
4
  * 与宿主客户端自己的模块表同源:那张表里 react 家族只有这四条。外置清单更大的包
5
- * (宿主外壳基座扩容后)由调用方在 `externals` 里给全量清单。
5
+ * (宿主 外壳 的 PLATFORM_MODULES,即宿主平台模块表,扩容后)由调用方在 `externals` 里给全量清单。
6
6
  */
7
7
  export declare const REACT_RUNTIME_MODULES: readonly string[];
8
8
  /**
@@ -38,9 +38,9 @@ export declare function collapseStorePaths(code: string): string;
38
38
  * 判断材料在这里。
39
39
  */
40
40
  export interface ClientNameContext {
41
- /** package.json 的 name(外壳 id 的唯一来源)。 */
41
+ /** package.json 的 name(外壳 里 id 的唯一来源)。 */
42
42
  readonly pkgName: string;
43
- /** 改写后的 chunk 正文(未包外壳)。 */
43
+ /** 改写后的 chunk 正文(尚未包进 外壳)。 */
44
44
  readonly body: string;
45
45
  /** 读包根下一份源码文件。 */
46
46
  readonly readSource: (relative: string) => Promise<string>;
@@ -68,9 +68,9 @@ export interface ClientBuildOptions {
68
68
  /**
69
69
  * 客户端外置规则:登记过的精确说明符外置,react 家族里**没登记**的说明符当场失败。
70
70
  *
71
- * 为什么既不静默内联也不前缀放行:外壳注入的 require 只在宿主模块表里查键。前缀放行会
72
- * 得到一条表里没有的 require(加载期 not in the module table,整张卡片静默不渲染);而
73
- * 静默内联会把 node_modules 里那份 React 打进产物、与宿主那份并存,页面里两个 React
71
+ * 为什么既不悄悄内联也不按前缀放行:外壳 注入的 require 只在宿主模块表里查键。按前缀放行会
72
+ * 得到一条表里没有的 require(加载期 not in the module table,整张卡片不渲染也不给提示);而
73
+ * 悄悄内联会把 node_modules 里那份 React 打进产物、与宿主那份并存,页面里两个 React
74
74
  * 实例, hooks 直接报错。两种失败都不在构建期报错,所以这里没有可观察的中间态。
75
75
  * 导出给各包的构建脚本复用与测试直接驱动,与构建走同一份判断。
76
76
  * @param externals - 外置说明符全量清单(精确匹配)
@@ -6,16 +6,22 @@ export interface TypesBuildOptions {
6
6
  readonly tscEntry: string;
7
7
  /** tsconfig 项目名(相对包根)。缺省 `tsconfig.types.json`。 */
8
8
  readonly tsconfig?: string;
9
- /** 草稿目录绝对路径。缺省 `<root>/.tmp/types`(该目录在各包统一被 git 忽略,
9
+ /** scratch 临时目录的绝对路径。缺省 `<root>/.tmp/types`(该目录在各包统一被 git 忽略,
10
10
  * 且与 types/ 同一文件系统,最后一步 rename 原子且不跨设备)。 */
11
11
  readonly scratch?: string;
12
12
  /** 产物目录绝对路径。缺省 `<root>/types`。 */
13
13
  readonly outDir?: string;
14
+ /** 目录换入用的 rename(缺省走 Windows 退避重试)。注入点供测试制造换入失败,
15
+ * 以及让不支持 rename 的宿主替换实现。 */
16
+ readonly rename?: (from: string, to: string) => Promise<void>;
14
17
  }
15
18
  /**
16
- * 把一段声明正文里的相对源码后缀说明符改写成对侧产物后缀。
19
+ * 把一段声明正文里带源码后缀的相对说明符改写成对侧产物后缀。
17
20
  *
18
21
  * 三处 replace 各自收一种句法位置,互不依赖;同一个文件里三种都出现时不漏也不重。
22
+ * 三处正则都会在**注释与字符串内部**命中,所以替换前先用 {@link maskNonCode} 取一份
23
+ * 等长的正文遮蔽文本,只有“说明符起始引号在正文位置上”的命中才改写:JSDoc 示例里的
24
+ * `from "./x.ts"` 与字面量类型 `type P = 'from "./x.ts"'` 因此原样保留。
19
25
  * @param text - 声明文件正文
20
26
  * @returns 改写后的正文
21
27
  */
@@ -1,2 +1,2 @@
1
1
  /** 设置卡统一样式。颜色/圆角全部走宿主主题令牌。 */
2
- export declare const CARD_UI_CSS = "\n.dsu-form{display:flex;flex-direction:column}\n.dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}\n.dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}\n.dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}\n.dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}\n.dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}\n.dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}\n.dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}\n.dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}\n.dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-control{min-width:0;display:flex;align-items:center;gap:8px}\n.dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}\n.dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}\n.dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}\n.dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}\n.dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}\n.dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}\n.dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}\n.dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}\n.dsu-input[aria-invalid=\"true\"]{border-color:var(--dsw-alias-state-error-primary)}\n.dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}\n.dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}\n.dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}\n.dsu-number{display:flex;align-items:center;gap:8px;min-width:0}\n.dsu-number .dsu-input{min-width:0}\n.dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}\n.dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}\n.dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}\n.dsu-switch[aria-checked=\"true\"]{background:var(--dsw-alias-label-primary)}\n.dsu-switch[aria-checked=\"true\"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}\n.dsu-switch:disabled{opacity:.4;cursor:not-allowed}\n.dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dsu-selectWrap{position:relative;width:100%;min-width:0}\n.dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}\n.dsu-select[aria-expanded=\"true\"]{border-color:var(--dsw-alias-state-business-primary)}\n.dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}\n.dsu-select[aria-expanded=\"true\"] .dsu-chevron{transform:rotate(180deg)}\n.dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}\n.dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}\n.dsu-option:hover,.dsu-option[data-active=\"true\"]{background:var(--dsw-alias-bg-layer-3)}\n.dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}\n.dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}\n.dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}\n.dsu-check{opacity:0;flex:none}\n.dsu-option[aria-selected=\"true\"] .dsu-check{opacity:1}\n.dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}\n.dsu-savebar[data-dirty=\"true\"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}\n.dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}\n.dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}\n.dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}\n.dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dsu-btn:disabled{opacity:.4;cursor:not-allowed}\n.dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}\n.dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}\n.dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}\n.dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}\n.dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}\n.dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsu-statusError{color:var(--dsw-alias-state-error-primary)}\n.dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}\n.dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}\n.dsu-grid>.dsu-field .dsu-control{width:100%}\n@media (max-width:640px){\n .dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}\n .dsu-control{width:100%;max-width:720px}\n .dsu-section{margin-top:20px;padding-top:20px}\n}\n@media (prefers-reduced-motion:reduce){\n .dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}\n}\n";
2
+ export declare const CARD_UI_CSS = "\n.dsu-form{display:flex;flex-direction:column}\n.dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}\n.dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}\n.dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}\n.dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}\n.dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}\n.dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}\n.dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}\n.dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}\n.dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}\n.dsu-control{min-width:0;display:flex;align-items:center;gap:8px}\n.dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}\n.dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}\n.dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}\n.dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}\n.dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}\n.dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}\n.dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}\n.dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}\n.dsu-input[aria-invalid=\"true\"]{border-color:var(--dsw-alias-state-error-primary)}\n.dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}\n.dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}\n.dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}\n.dsu-number{display:flex;align-items:center;gap:8px;min-width:0}\n.dsu-number .dsu-input{min-width:0}\n.dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}\n.dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}\n.dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}\n.dsu-switch[aria-checked=\"true\"]{background:var(--dsw-alias-label-primary)}\n.dsu-switch[aria-checked=\"true\"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}\n.dsu-switch:disabled{opacity:.4;cursor:not-allowed}\n.dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dsu-selectWrap{position:relative;width:100%;min-width:0}\n.dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}\n.dsu-select[aria-expanded=\"true\"]{border-color:var(--dsw-alias-state-business-primary)}\n.dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}\n.dsu-select[aria-expanded=\"true\"] .dsu-chevron{transform:rotate(180deg)}\n.dsu-menu{position:fixed;position-area:block-end span-inline-start;justify-self:start;margin-top:6px;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}\n.dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}\n.dsu-option:hover,.dsu-option[data-active=\"true\"]{background:var(--dsw-alias-bg-layer-3)}\n.dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}\n.dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}\n.dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}\n.dsu-check{opacity:0;flex:none}\n.dsu-option[aria-selected=\"true\"] .dsu-check{opacity:1}\n.dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}\n.dsu-savebar[data-dirty=\"true\"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}\n.dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}\n.dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}\n.dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}\n.dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dsu-btn:disabled{opacity:.4;cursor:not-allowed}\n.dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}\n.dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}\n.dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}\n.dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}\n.dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}\n.dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsu-statusError{color:var(--dsw-alias-state-error-primary)}\n.dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}\n.dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}\n.dsu-grid>.dsu-field .dsu-control{width:100%}\n@media (max-width:640px){\n .dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}\n .dsu-control{width:100%;max-width:720px}\n .dsu-section{margin-top:20px;padding-top:20px}\n}\n@media (prefers-reduced-motion:reduce){\n .dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}\n}\n";
@@ -41,7 +41,7 @@ export declare const OFF_JUSTIFICATIONS: Record<string, OffJustification>;
41
41
  * override 层的 off(这个测量方法见本文件头),所以一条规则可以同时是「TS 侧零命中、保持开启」
42
42
  * 与「`scripts/` 的无类型 .mjs 里有命中、按文体收窄」——`eslint/no-continue` 正是这一类,
43
43
  * 它的收窄在 SCRIPTS_OVERRIDES 里,不在这里。`eslint/no-await-in-loop` 则两侧都开着
44
- * (实测探针对 .mjs 照样报),两边都按代码写到位,没有收窄层。
44
+ * (实测探测对 .mjs 照样报),两边都按代码写到位,没有收窄层。
45
45
  */
46
46
  export declare const RETIRED_OFFS: string[];
47
47
  export declare const READONLY_SYNC_ALLOWLIST: string[];
@@ -88,7 +88,7 @@ export declare function resolveBundledJsPlugins(callerPlugins: NonNullable<Oxlin
88
88
  /**
89
89
  * `eslint-plugin-regexp` recommended 里带 `regexp/` 前缀的全部规则(63 条)。
90
90
  *
91
- * 不带它同 preset 的 4 条裸 eslint 规则(no-control-regex / no-misleading-character-class /
91
+ * 不带它同 preset 的 4 条不带插件前缀的 eslint 规则(no-control-regex / no-misleading-character-class /
92
92
  * no-regex-spaces / prefer-regex-literals):oxlint 的内建 eslint 插件已有同名规则,重复挂只会
93
93
  * 把同一个缺陷报两遍。
94
94
  *
@@ -110,10 +110,10 @@ export declare const REGEXP_RULES: RuleTable;
110
110
  * - `no-number-schema-with-int` 5 条(`z.number().int()` → `z.int()`,fixer 可修)。
111
111
  * - `require-error-message` 5 条待按代码补错误文案。
112
112
  * - `consistent-schema-var-name` 71 条**不挂**:本仓的 schema 常量是大写词(`MAX_DENIES_SCHEMA`、
113
- * `STATS_SNAPSHOT_SCHEMA`),规则的默认口径是把 `Schema` 后缀再拼一次,得到
113
+ * `STATS_SNAPSHOT_SCHEMA`),规则的默认做法是把 `Schema` 后缀再拼一次,得到
114
114
  * `MAX_DENIES_SCHEMASchema` 这类坏名字;把它当命名缺陷改 71 处只是风格迁移,不指向缺陷。
115
115
  *
116
- * 该插件只对真的 import zod 的文件生效;本仓九个包都声明了 zod,规则面不会空转。
116
+ * 该插件只对真的 import zod 的文件生效;本仓九个包都声明了 zod,这些规则不会没有实际校验作用。
117
117
  */
118
118
  export declare const ZOD_RULES: RuleTable;
119
119
  /**
@@ -160,7 +160,7 @@ export declare const BASE_RULES: RuleTable;
160
160
  */
161
161
  export declare const TEST_OVERRIDES: OxlintOverride;
162
162
  /**
163
- * 生产代码面的 override:目前只有 `e18e/prefer-static-regex`。
163
+ * 针对生产代码的 override:目前只有 `e18e/prefer-static-regex`。
164
164
  *
165
165
  * 该规则在全仓 1778 条(生产 78、测试 1672、其余 28),没有 fixer;全局 off、这里 on,
166
166
  * 既保住「生产代码里的内联正则提到模块作用域」这条要求,又不把测试断言与构建脚本逼成
@@ -18,7 +18,7 @@ type CoverageOverride = Omit<Partial<CoverageConfig>, "include">;
18
18
  export interface PackageVitestOptions {
19
19
  /** `coverage.include` —— 本包该被统计的源文件(各包只有这里不同);空数组/空串在工厂即抛。 */
20
20
  coverageInclude: string[];
21
- /** 覆盖率覆盖项:逐键合并到公共面。要例外就在这里写,不要在 `test` 里塞 `coverage`。 */
21
+ /** 覆盖率覆盖项:逐键合并到公共配置。要例外就在这里写,不要在 `test` 里塞 `coverage`。 */
22
22
  coverage?: CoverageOverride;
23
23
  /** 追加/覆盖 test 字段(少用;出现即应在包内配置里写明为什么)。 */
24
24
  test?: PackageConfig["test"];
@@ -0,0 +1,45 @@
1
+ /** 预转换的入参。 */
2
+ export interface DecoratorLoweringOptions {
3
+ /** tsc 入口脚本的绝对路径(用 `process.execPath` 跑它,跨平台且不经 PATH / npx)。 */
4
+ readonly tscEntry: string;
5
+ }
6
+ /**
7
+ * 这个文件需要预转换吗(声明文件、后缀与装饰器形状三道筛)。
8
+ * @param id - 模块 id(可能带 query,调用方负责剥)
9
+ * @param code - 源文本
10
+ * @returns 需要预转换时为真
11
+ */
12
+ export declare function needsDecoratorLowering(id: string, code: string): boolean;
13
+ /** 源映射形状(`mappings` 是逐行映射那一串,可选)。 */
14
+ interface SourceMapText {
15
+ sources?: string[];
16
+ sourcesContent?: string[];
17
+ mappings?: string;
18
+ }
19
+ /** tsc 产物与源映射(`map` 为 null 表示源映射缺席,不算致命)。 */
20
+ export interface LoweredDecorators {
21
+ readonly code: string;
22
+ readonly map: SourceMapText | null;
23
+ }
24
+ /**
25
+ * 把一个带装饰器的 TS 文件编译成 JS。
26
+ * @param code - 源文本(只进源映射的 `sourcesContent`;编译交给 tsc 读盘上的原文)
27
+ * @param file - 源文件的**绝对路径**(tsc 按它定位产物名)
28
+ * @param options - tsc 入口
29
+ * @param coverageHints - 把注入的调用从源映射里摘掉(只有测试流水线要,见上)
30
+ * @returns 编译后的代码与源映射
31
+ */
32
+ export declare function lowerStandardDecorators(code: string, file: string, options: DecoratorLoweringOptions, coverageHints?: boolean): LoweredDecorators;
33
+ /** rolldown 插件:在它的解析器看到源码之前把标准装饰器编译掉。 */
34
+ export declare function rolldownDecorators(options: DecoratorLoweringOptions): {
35
+ readonly name: string;
36
+ readonly transform: (code: string, id: string) => LoweredDecorators | null;
37
+ };
38
+ /** Vite 插件(测试流水线):与上面同源,只是挂到 Vite 的钩子上(`enforce: "pre"` 保证在它前面)。
39
+ * 第三个实参 = 覆盖率提示:**只有测试流水线要**(构建产物的字节不该带它,见 pruneInjectedPlumbing)。 */
40
+ export declare function viteDecorators(options: DecoratorLoweringOptions): {
41
+ readonly name: string;
42
+ readonly enforce: "pre";
43
+ readonly transform: (code: string, id: string) => LoweredDecorators | undefined;
44
+ };
45
+ export {};
@@ -6,7 +6,7 @@
6
6
  * - `0` 与负数**照样收缩**到 ≤2 行为止——「0 = 永不截断」是某消费方在**调用前**
7
7
  * `maxBytes > 0` 那道触发策略的承诺,不是本函数的语义(见它自己的 jsonl-fuse 头注)。
8
8
  * 把 0 当成「不裁」会把这个承诺写成一个假象。
9
- * - `NaN` 与 `+Infinity` 让 `字节数 > maxBytes` 恒假 ⇒ 循环一次都不进,整份原样返回,
9
+ * - `NaN` 与 `+Infinity` 让 `字节数 > maxBytes` 永远不成立 ⇒ 循环一次都不进,整份原样返回,
10
10
  * 即**静默关掉收缩**。现存两个消费方都不会把这种值传进来:一个用固定常量作默认实参,
11
11
  另一个在调用前就把非有限值与负数归一成 0。但那道归一在**消费方**,本包并不强制——
12
12
  新增消费方时没有任何东西会提醒它。
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 把 {@link Remote} 的标记程序化地打到 `prototype[methodName]` 上。
3
+ *
4
+ * 官方 `Remote` 的运行时只读 `name` / `private` / `static` 与 `addInitializer`(TC39 的
5
+ * `access` 与 `metadata` 它一概不读),故这里只给这四位到底。初始器只做
6
+ * `Object.getPrototypeOf(this)` 后落标记,所以用原型造一枚替身实例即可。
7
+ *
8
+ * `Reflect.apply` 让装饰器工厂保持未类型化的调用签名 —— `Remote(name)` 的类型是
9
+ * `RemoteMethodDecorator`,它要求第二个参数是真 `ClassMethodDecoratorContext`,而这里喂的是
10
+ * 同形状的手写对象(平台类型不可构造)。
11
+ * @param prototype - 目标类原型(`X.prototype`)
12
+ * @param methodName - 要标记的实例方法名
13
+ */
14
+ export declare function markRemote(prototype: object, methodName: string): void;
@@ -11,11 +11,11 @@ export interface PtcDispatchStartEventData {
11
11
  name: string;
12
12
  arguments: unknown;
13
13
  }
14
- /** 一枚 PTC 子调度**结算**时记下的载荷:结算面沿用 `tool/result` 自己的词表。 */
14
+ /** 一枚 PTC 子调度**结算**时记下的载荷:结算沿用 `tool/result` 自己的词表。 */
15
15
  export interface PtcDispatchEventData extends PtcDispatchStartEventData {
16
16
  isError: boolean;
17
17
  content: ContentBlock[];
18
- /** 可重放的工具侧呈现元数据,与模型可见内容分开记账。 */
18
+ /** 可重放的工具侧呈现元数据,与模型可见内容分开记录。 */
19
19
  meta?: JsonValue;
20
20
  /** 可选的失败身份与原始用户可见细节,不在模型可见内容里。 */
21
21
  error?: {
@@ -48,7 +48,7 @@ export interface ToolCallRecord {
48
48
  * 只读判定、检查侧的 EDIT_TOOLS 与 refSearchTools 名单——而 editPathOf 的回退分支会把任何
49
49
  * 非 str_replace_editor 的调用一律判成 write。无名调用一旦进了记录,只要它的 arguments
50
50
  * 里有 file_path 就算成写操作。现在是靠三个消费方各自记得先判一次才没出事——那是同一条
51
- * 不变式的三份手抄,漏一份就出事。
51
+ * 不变式的三份抄本,漏一份就出事。
52
52
  *
53
53
  * 与 callId 的差别是**为什么一个必选、一个可空**:callId 由 usableCallId 回退,且它有
54
54
  * `nc:<seq>` 回退值(消费方的 noCallId 组专门收那种行),缺了不影响归类;name 一旦缺了
@@ -117,7 +117,7 @@ export type EditTarget = {
117
117
  };
118
118
  /**
119
119
  * 一条 `apply_patch` 补丁操作的目标。`moveTo` 只出现在 update 上;源路径与目的路径
120
- * 都是文件效应,调用方需要分别记账。
120
+ * 都是文件效应,调用方需要分别记录。
121
121
  */
122
122
  export interface ApplyPatchTarget {
123
123
  readonly kind: "add" | "update" | "delete";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayyuen66/dsh-plugin-shared",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + 卡片 apply/差异骨架 + 统一设置卡 UI 组件 + 官方作业结局映射 + 项目键派生 + 代理对安全截断。宿主半由各插件 import;客户端半以 ./client 共享行由插件 patch 启用,不由用户单独停用",
5
5
  "keywords": [
6
6
  "cordis",
@@ -92,6 +92,10 @@
92
92
  "types": "./dist/types/job-outcome.d.ts",
93
93
  "default": "./dist/job-outcome.js"
94
94
  },
95
+ "./lib/remote-marker": {
96
+ "types": "./dist/types/remote-marker.d.ts",
97
+ "default": "./dist/remote-marker.js"
98
+ },
95
99
  "./lib/canonicalize-region-paths": {
96
100
  "types": "./dist/types/canonicalize-region-paths.d.ts",
97
101
  "default": "./dist/canonicalize-region-paths.js"
@@ -112,6 +116,10 @@
112
116
  "types": "./dist/types/build-types.d.ts",
113
117
  "default": "./dist/build/build-types.js"
114
118
  },
119
+ "./lib/decorator-lowering": {
120
+ "types": "./dist/types/decorator-lowering.d.ts",
121
+ "default": "./dist/build/decorator-lowering.js"
122
+ },
115
123
  "./config/oxlint": {
116
124
  "types": "./dist/types/config/oxlint.base.d.ts",
117
125
  "default": "./dist/config/oxlint.base.js"
@@ -129,21 +137,23 @@
129
137
  "registry": "https://registry.npmjs.org"
130
138
  },
131
139
  "dependencies": {
132
- "@deepseek-ai/dsh-fs-patch": "0.2.2-rc.1"
140
+ "@deepseek-ai/dsh-fs-patch": "0.2.2-rc.2"
133
141
  },
134
142
  "devDependencies": {
135
143
  "@deepseek-ai/cordis": "4.1.0",
136
- "@deepseek-ai/dsh-brand": "0.2.2-rc.1",
137
- "@deepseek-ai/dsh-client-locale": "0.2.2-rc.1",
138
- "@deepseek-ai/dsh-jobs": "0.2.2-rc.1",
139
- "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
140
- "@deepseek-ai/dsh-output-retention": "0.2.2-rc.1",
141
- "@deepseek-ai/dsh-scope": "0.2.2-rc.1",
142
- "@deepseek-ai/dsh-session": "0.2.2-rc.1",
143
- "@deepseek-ai/dsh-shell": "0.2.2-rc.1",
144
- "@deepseek-ai/dsh-tool-executor-ptc": "0.2.2-rc.1",
145
- "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
146
- "@deepseek-ai/dsh-util-values": "0.2.2-rc.1",
144
+ "@deepseek-ai/dsh-brand": "0.2.2-rc.2",
145
+ "@deepseek-ai/dsh-client-locale": "0.2.2-rc.2",
146
+ "@deepseek-ai/dsh-error": "0.2.2-rc.2",
147
+ "@deepseek-ai/dsh-jobs": "0.2.2-rc.2",
148
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.2",
149
+ "@deepseek-ai/dsh-output-retention": "0.2.2-rc.2",
150
+ "@deepseek-ai/dsh-scope": "0.2.2-rc.2",
151
+ "@deepseek-ai/dsh-session": "0.2.2-rc.2",
152
+ "@deepseek-ai/dsh-shell": "0.2.2-rc.2",
153
+ "@deepseek-ai/dsh-tool-executor-ptc": "0.2.2-rc.2",
154
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.2",
155
+ "@deepseek-ai/dsh-typert-protocol": "0.2.2-rc.2",
156
+ "@deepseek-ai/dsh-util-values": "0.2.2-rc.2",
147
157
  "@e18e/eslint-plugin": "0.8.1",
148
158
  "@types/node": "26.6.6",
149
159
  "@types/react": "19.3.0",
@@ -154,25 +164,26 @@
154
164
  "eslint-plugin-zod": "5.2.2",
155
165
  "fast-check": "4.10.2",
156
166
  "happy-dom": "21.0.4",
157
- "oxfmt": "0.76.0",
158
- "oxlint": "1.91.0",
159
- "oxlint-tsgolint": "7.0.2005",
167
+ "oxfmt": "0.78.0",
168
+ "oxlint": "1.93.0",
169
+ "oxlint-tsgolint": "7.0.2006",
160
170
  "react": "^19.3.0",
161
171
  "react-dom": "^19.3.1",
162
- "rolldown": "1.2.16",
172
+ "rolldown": "1.3.2",
163
173
  "typescript": "7.0.2",
164
- "vite": "8.3.6",
174
+ "vite": "8.3.8",
165
175
  "vitest": "5.1.0"
166
176
  },
167
177
  "peerDependencies": {
168
178
  "@deepseek-ai/cordis": "^4.0.5||^4.0.6-alpha.1",
169
- "@deepseek-ai/dsh": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
170
- "@deepseek-ai/dsh-client-locale": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
171
- "@deepseek-ai/dsh-jobs": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
172
- "@deepseek-ai/dsh-output-retention": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
173
- "@deepseek-ai/dsh-session": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
174
- "@deepseek-ai/dsh-shell": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
175
- "@deepseek-ai/dsh-tools": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
179
+ "@deepseek-ai/dsh": "^0.2.2-rc.1",
180
+ "@deepseek-ai/dsh-client-locale": "^0.2.2-rc.1",
181
+ "@deepseek-ai/dsh-jobs": "^0.2.2-rc.1",
182
+ "@deepseek-ai/dsh-output-retention": "^0.2.2-rc.1",
183
+ "@deepseek-ai/dsh-session": "^0.2.2-rc.1",
184
+ "@deepseek-ai/dsh-shell": "^0.2.2-rc.1",
185
+ "@deepseek-ai/dsh-tools": "^0.2.2-rc.1",
186
+ "@deepseek-ai/dsh-typert-protocol": "^0.2.2-rc.1",
176
187
  "react": "^19.3.0"
177
188
  },
178
189
  "peerDependenciesMeta": {
@@ -200,13 +211,16 @@
200
211
  "@deepseek-ai/dsh-tools": {
201
212
  "optional": true
202
213
  },
214
+ "@deepseek-ai/dsh-typert-protocol": {
215
+ "optional": true
216
+ },
203
217
  "react": {
204
218
  "optional": true
205
219
  }
206
220
  },
207
221
  "icon": "icon.svg",
208
222
  "engines": {
209
- "dsh": "^0.2.1-rc.5 || ^0.2.2-alpha.3",
223
+ "dsh": "^0.2.2-rc.1",
210
224
  "node": ">=24.9.0"
211
225
  },
212
226
  "dsh": {