# 开发 / Development
[中文](#中文) | [English](#english)
中文
本仓库无构建步骤:`lib/*.js` 是直接发布的产物,浏览器半是手写的 `__ModuleLoader__` 包。
## 命令
```bash
npm run check # 语法检查
npm test # 三个离线套件
npm run test:process # 进程级回归:用 --patch overlay 装进真实 dsh web 并探测端点
npm run verify # 上面三个依次跑(CI 用的就是它)
npm run pack:check # npm pack --dry-run,核对发布载荷
```
`npm test` 不需要 dsh,也不需要网络。`test-process.mjs` 需要 `dsh` 在 PATH 上,否则整体跳过(退出码 0),因此可以安全地在无 dsh 的环境里运行。
## 测试套件
| 套件 | 覆盖 |
| --- | --- |
| `scripts/test-pricing.mjs` | 价目表校验、匹配优先级、峰谷判定、时段切换倒计时、费用换算 |
| `scripts/test-host.mjs` | `apply` 的注册行为、投影折叠(含替换与重试)、价目表文件读写、端点鉴权与 Promise 归属 |
| `scripts/test-client.mjs` | 模块工厂装配、`apply` 的 slot 注册、真实 React 渲染(含胶囊上的计费模式),以及 host / 浏览器两侧费用与时段判定的一致性 |
| `scripts/test-process.mjs` | 经 `--patch` overlay 装进真实 `dsh web`:端点契约、异常路径,以及**端点出错后进程仍然存活** |
`test-client.mjs` 会从 `/profiles/node_modules` 解析真实 React;找不到时退回替身并跳过渲染用例。
`test-client.mjs` 里有三类「冻结约定」的断言,它们守的是本仓库最容易悄悄退化的地方:
- **两侧一致性**:host 与浏览器的费用换算、时段判定、倒计时逐项对齐;
- **设计 token 白名单**:引用的每个 `--dsw-*` 都经过核对,并断言「画了 `--dsw-specific-menu` 就要配毛玻璃」「`position:sticky` 要用不透明底色」;
- **胶囊结构**:分隔符必须落在 `.tf_text` 内、不写死字色、不带尾随空格。
## 代码结构
| 文件 | 职责 |
| --- | --- |
| `lib/pricing.js` | 纯定价原语:内置表、命名调度、分层合并、条目匹配、时段判定与倒计时、费用换算 |
| `lib/index.js` | host 半:`tokenFee` 会话投影 + 价目表读写端点 |
| `lib/client.js` | 浏览器半:费用胶囊(含计费模式与倒计时)、明细面板、价格编辑器、设置页 |
| `data/cn-holidays.json` | 大陆法定节假日数据;host 在装载期同步读一次,年度更新只改这个文件 |
| `cordis.patch.yml` | bundle 层 patch,挂载 host 半 |
| `scripts/install.mjs` | 无法使用 `dsh plugin` 时的备选安装器 |
`lib/client.js` 是一个手写的 CommonJS 风格包体,`lib/index.js` 与 `lib/pricing.js` 是普通 ESM。三者都不经过打包器,改完直接生效(浏览器侧刷新页面即可)。
## 文档
| 文件 | 内容 |
| --- | --- |
| [`configuration.md`](configuration.md) | 价目表与峰谷规则的完整参考、编辑器行为 |
| [`internals.md`](internals.md) | 投影与时段归属、两侧换算、端点防线 |
| [`dsh-integration.md`](dsh-integration.md) | 与 DSH 的集成约束、跨版本兼容矩阵 |
| [`release-notes/`](release-notes/) | 每个版本的发版说明 |
English
This repository has no build step: `lib/*.js` is the shipped artifact, and the browser half is a hand-written `__ModuleLoader__` bundle.
## Commands
```bash
npm run check # syntax check
npm test # the three offline suites
npm run test:process # process-level regression: install into a real dsh web via a --patch overlay and probe the endpoints
npm run verify # the three above, in order (what CI runs)
npm run pack:check # npm pack --dry-run, to check the published payload
```
`npm test` needs neither dsh nor the network. `test-process.mjs` needs `dsh` on PATH and otherwise skips as a whole (exit code 0), so it is safe to run where dsh is absent.
## Test suites
| Suite | Coverage |
| --- | --- |
| `scripts/test-pricing.mjs` | Pricing validation, matching precedence, the tariff decision, the switch countdown, cost conversion |
| `scripts/test-host.mjs` | `apply` registration, projection folding (including replacement and retry), pricing-file reads and writes, endpoint authorization and promise ownership |
| `scripts/test-client.mjs` | Module-factory assembly, `apply` slot registration, real React rendering (including the billing mode on the pill), and host/browser agreement on cost and period decisions |
| `scripts/test-process.mjs` | Installed into a real `dsh web` via a `--patch` overlay: endpoint contract, failure paths, and **the process staying alive after an endpoint error** |
`test-client.mjs` resolves real React from `/profiles/node_modules`; when it is missing, the suite falls back to a stub and skips the rendering cases.
`test-client.mjs` carries three kinds of "frozen convention" assertions, guarding the places this repository most easily degrades:
- **Two-side agreement**: host and browser cost conversion, tariff decisions and countdowns aligned item by item;
- **A design-token allowlist**: every `--dsw-*` referenced was checked, plus assertions that a `--dsw-specific-menu` fill carries the backdrop filter and that `position:sticky` uses an opaque fill;
- **Pill structure**: separators must sit inside `.tf_text`, must not pin a colour, and must carry no trailing space.
## Code structure
| File | Responsibility |
| --- | --- |
| `lib/pricing.js` | Pure pricing primitives: the built-in table, named schedules, layer merging, entry matching, tariff decisions and countdown, cost conversion |
| `lib/index.js` | Host half: the `tokenFee` session projection plus the pricing read/write endpoints |
| `lib/client.js` | Browser half: the cost pill (with billing mode and countdown), the breakdown panel, the price editor, the settings page |
| `data/cn-holidays.json` | Mainland China holiday data; the host reads it once at load, and the yearly update touches only this file |
| `cordis.patch.yml` | Bundle-layer patch mounting the host half |
| `scripts/install.mjs` | Fallback installer for when `dsh plugin` is unavailable |
`lib/client.js` is a hand-written CommonJS-style bundle body; `lib/index.js` and `lib/pricing.js` are plain ESM. None of them goes through a bundler, so edits take effect directly (refresh the page for the browser side).
## Documentation
| File | Content |
| --- | --- |
| [`configuration.md`](configuration.md) | Complete reference for the pricing table and peak/off-peak schedules, plus editor behaviour |
| [`internals.md`](internals.md) | Projection and period attribution, two-side conversion, endpoint defenses |
| [`dsh-integration.md`](dsh-integration.md) | DSH integration constraints and the cross-version compatibility matrix |
| [`release-notes/`](release-notes/) | Release notes per version |