@linxin666/dsh-client-ui-skin-center 0.3.4 → 0.3.6
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.i18n.yaml +2 -2
- package/README.md +2 -2
- package/README.zh.md +1 -1
- package/contracts/README.md +3 -10
- package/contracts/performance-guidelines-v1.md +107 -0
- package/contracts/semantic-attrs-v1.md +2 -1
- package/contracts/skin-manifest-v2.schema.json +2 -2
- package/lib/client.js +244 -66
- package/lib/client.js.map +1 -1
- package/lib/index.js +550 -15
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 6c66e64a68379e434045dd573fae29d2fbdf75f3
|
|
6
|
+
README.zh.md: 278218950a5310342387b1a3c19c4b91013d43d3
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ dsh plugin --profile web add @linxin666/dsh-client-ui-skin-center
|
|
|
22
22
|
# From the repo (dev): dsh plugin --profile web add link:$(pwd)/packages/skins/skin-center
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`$(pwd)` is your clone of the dsh-web monorepo. Only the default skin (blue-fantasy) ships inside this package; every other skin installs on demand from dsh-market.com into `$DSH_HOME/skins/<id>/`, and community skins are plain directories in the same place (no install command, no restart — reopen the card or reload to pick them up). A fresh install activates blue-fantasy by default (host seed); an update whose active skin is no longer available falls back to the stock look. The package ships `skins/blue-fantasy` only; the remaining skins stay in the repository under `skins/` as the market-build
|
|
25
|
+
`$(pwd)` is your clone of the dsh-web monorepo. Only the default skin (blue-fantasy) ships inside this package; every other skin installs on demand from dsh-market.com into `$DSH_HOME/skins/<id>/`, and community skins are plain directories in the same place (no install command, no restart — reopen the card or reload to pick them up). A fresh install activates blue-fantasy by default (host seed); an update whose active skin is no longer available falls back to the stock look. The package ships `skins/blue-fantasy` only; the remaining skins stay in the repository under `skins/` as the market-build source and are never published in the package.
|
|
26
26
|
|
|
27
27
|
skin-center is a self-contained bundle meeting the official DSH plugin standard (`dsh.bundle.patch` points to `cordis.patch.yml`); it can also be installed via git: `dsh plugin --profile web add github:<org>/dsh-web#<sha>` (the `prepare` script builds `lib/` in place). pnpm ≥10 requires authorizing `allowBuilds` before installing a git dependency; a local `link:` install has no such requirement.
|
|
28
28
|
|
|
@@ -40,7 +40,7 @@ skin-center is a self-contained bundle meeting the official DSH plugin standard
|
|
|
40
40
|
- All `/api/skin-center/*` routes are same-origin only: writes reject cross-site requests (Sec-Fetch-Site / Origin fence), and asset reads are contained inside each skin directory (path escapes fail closed).
|
|
41
41
|
- Skin CSS is sanitized (whitelist) before serving; `patches.css` (L3) is arbitrary CSS by design and disclosed as such — it runs with full page styling power and is not a security boundary.
|
|
42
42
|
- The custom-theme editor emits only fixed declarations from `CUSTOM_THEME_ALLOWED_TOKENS`, each verified against the official token registry. User input is normalized color/contrast data and never becomes a selector, URL or free-form CSS payload.
|
|
43
|
-
- `hooks.mjs` is trusted code that shares this repository's review and release; it is served same-origin only and its import/apply errors can never take the static skin down. Hooks run for built-in skins
|
|
43
|
+
- `hooks.mjs` is trusted code that shares this repository's review and release; it is served same-origin only and its import/apply errors can never take the static skin down. Hooks run for built-in skins and for user-directory skins whose executable identity is byte-verified as reviewed official-market content: current Workshop installs use `dsh-market.provenance.json`, while legacy pre-provenance installs must match the generated `src/reviewed-hooks.generated.ts` identity for id, declared entry, complete `skin.json`, and hooks bytes (verified by `src/provenance.ts`, issue #1073). The fallback is read-only and offline; any modified, renamed, hand-dropped, or tampered directory keeps the hooks facet refused while the declarative parts still load.
|
|
44
44
|
|
|
45
45
|
## Known limitations
|
|
46
46
|
|
package/README.zh.md
CHANGED
|
@@ -40,7 +40,7 @@ dsh plugin --profile web add @linxin666/dsh-client-ui-skin-center
|
|
|
40
40
|
- 所有 `/api/skin-center/*` 路由仅接受同源请求:写操作拒绝跨站请求(Sec-Fetch-Site / Origin 围栏),资产读取限定在各皮肤目录之内(路径逃逸 fail-closed)。
|
|
41
41
|
- 皮肤 CSS 在服务前经白名单净化;`patches.css`(L3)按设计就是任意 CSS 并如实公示——它拥有完整页面样式能力,不构成安全边界。
|
|
42
42
|
- 自定义主题编辑器只会从 `CUSTOM_THEME_ALLOWED_TOKENS` 生成固定声明,且每个 token 都对照官方 token 注册表校验。用户输入只作为规范化后的颜色/对比度数据,不会成为选择器、URL 或自由 CSS 载荷。
|
|
43
|
-
- `hooks.mjs` 是与本仓库同审同发的受信代码,仅同源 serve,其 import/apply 错误永远不会拖垮静态皮肤。hooks
|
|
43
|
+
- `hooks.mjs` 是与本仓库同审同发的受信代码,仅同源 serve,其 import/apply 错误永远不会拖垮静态皮肤。hooks 对内置皮肤及可按字节验证为官方市场已审查内容的用户目录皮肤放行:当前 Workshop 安装使用 `dsh-market.provenance.json`,而早于 provenance 的历史安装必须同时匹配生成的 `src/reviewed-hooks.generated.ts` 身份中的 id、声明入口、完整 `skin.json` 与 hooks 字节(由 `src/provenance.ts` 校验,issue #1073)。回退只读且离线;任何被修改、改名、手工投放或篡改的目录都会继续拒绝 hooks facet,声明式部分仍正常加载。
|
|
44
44
|
|
|
45
45
|
## 已知限制
|
|
46
46
|
|
package/contracts/README.md
CHANGED
|
@@ -44,16 +44,9 @@ and release. It is served verbatim over `GET /skins/<id>/hooks.mjs` only for:
|
|
|
44
44
|
|
|
45
45
|
- **built-in skins** — shipped inside the skin-center npm package, same
|
|
46
46
|
review and release by definition;
|
|
47
|
-
- **official-market installs** — user-directory skins
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
content is built from this repository, so a hash match means the bytes
|
|
51
|
-
are the reviewed ones). Verification lives in `src/provenance.ts` and
|
|
52
|
-
fails closed: any missing, foreign-source or mismatched provenance keeps
|
|
53
|
-
the hooks facet refused with a catalog warning while the declarative
|
|
54
|
-
parts (skin.css / patches.css / assets) still load (issue #1073).
|
|
55
|
-
|
|
56
|
-
Locally dropped or third-party skin directories never run hooks.
|
|
47
|
+
- **official-market installs** — user-directory skins whose executable identity is byte-verified as reviewed content. Current Workshop installs carry a `dsh-market.provenance.json` whose sha256 pins the on-disk `skin.json` and hooks entry to the bytes the official DSH Market served. Historical installs created before provenance existed may recover only when id, declared entry, complete `skin.json`, and hooks bytes match one identity in generated `src/reviewed-hooks.generated.ts`. The registry is generated from this repository's market skin sources by `scripts/skin-hooks-registry.mjs`; `--check` is part of `skin-center:check`, so any reviewed manifest or hook change must regenerate it. Verification lives in `src/provenance.ts`, is read-only and offline, and fails closed: a foreign source, unknown identity, renamed skin, or an executable-identity mismatch keeps the hooks facet refused with a catalog warning while the declarative parts (skin.css / patches.css / assets) still load (issue #1073).
|
|
48
|
+
|
|
49
|
+
Locally dropped or third-party skin directories never run hooks unless their executable identity is byte-identical to a reviewed official-market skin. Declarative files outside that identity may be customized without granting additional executable capability.
|
|
57
50
|
|
|
58
51
|
## Loader-side rules (pinned here, enforced in M2)
|
|
59
52
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Skin Runtime Performance Guidelines (v1)
|
|
2
|
+
|
|
3
|
+
Contract owner: skin-center (single owner, conflict resolution does not rely
|
|
4
|
+
on load order). Version: `performance-guidelines/v1` (2026-02, authored after
|
|
5
|
+
the orca-link session-switch latency investigation).
|
|
6
|
+
|
|
7
|
+
These are the runtime-performance rules for skin **hooks** (`hooks.mjs`,
|
|
8
|
+
`facets.client`) and their stylesheets. They exist because skins share the
|
|
9
|
+
host page with the DSH shell and every plugin: a skin that watches the whole
|
|
10
|
+
document or keeps GPU layers alive degrades everyone. The rules are advice
|
|
11
|
+
with teeth — a code review hook check and a human review of the case study
|
|
12
|
+
([orca-link hooks](../../../packages/skins/skin-center/skins/orca-link/hooks.mjs),
|
|
13
|
+
[patches](../../../packages/skins/skin-center/skins/orca-link/patches.css))
|
|
14
|
+
should reject their violation.
|
|
15
|
+
|
|
16
|
+
## R1 — DOM observation must be scoped and short-circuited
|
|
17
|
+
|
|
18
|
+
Never broadcast a full-tree scan from a `MutationObserver` callback:
|
|
19
|
+
|
|
20
|
+
- Watch the narrowest possible scope. Observing `document.body` with
|
|
21
|
+
`subtree: true` is only acceptable when every callback answers "did my
|
|
22
|
+
anchor actually change?" in O(1) before doing any work.
|
|
23
|
+
- Cache the anchor. A conversation/session root is rebuilt only on session
|
|
24
|
+
switches; between switches the phase lives on the same node, so a
|
|
25
|
+
`let memo = null; memo = memo?.isConnected ? memo : locate()` cache is
|
|
26
|
+
exact (a rebuilt root is a different node, `isConnected` flips on the old
|
|
27
|
+
one, and the next callback re-locates).
|
|
28
|
+
- Skip fully mounted state. For per-seat work, record the mount phase or the
|
|
29
|
+
exact element references on the binding and return early when nothing
|
|
30
|
+
changed; only re-run the "may need rebuild" path when an element or phase
|
|
31
|
+
actually changed.
|
|
32
|
+
- High-churn regions (terminals, streaming input backdrops) should be
|
|
33
|
+
excluded from work with a fast `closest()` check before any scanning.
|
|
34
|
+
|
|
35
|
+
## R2 — Host-glyph fingerprint matching must be one regex pre-scan
|
|
36
|
+
|
|
37
|
+
When a skin redraws host SVG glyphs by matching path fingerprints:
|
|
38
|
+
|
|
39
|
+
- Compile the fingerprint table once into a single alternation regex and use
|
|
40
|
+
`regex.test(html)` as the boolean gate. Per-svg `includes()` loops over
|
|
41
|
+
N fingerprints turn a session-loading DOM insert into O(svgs × N × len)
|
|
42
|
+
string work; one regex test makes it O(len).
|
|
43
|
+
- Keep the ordered key table for the *decision*: "first key in the table that
|
|
44
|
+
appears" is the precedence contract, and the pre-scan regex only answers
|
|
45
|
+
the boolean. Do not reorder or deduplicate keys to let the regex decide.
|
|
46
|
+
- Never clone a fresh svg. A svg that has no skin art yet carries only host
|
|
47
|
+
markup — read `svg.innerHTML` directly. The clone/remove-art dance is only
|
|
48
|
+
needed on an already-reconciled svg.
|
|
49
|
+
- Preserve idempotency: re-reconciling an already-art'd svg must not stack a
|
|
50
|
+
second art group (guard on the art attribute / recorded name).
|
|
51
|
+
|
|
52
|
+
## R3 — Frame loops and infinite animations must pause when hidden
|
|
53
|
+
|
|
54
|
+
- A `setTimeout` frame chain (character sprite atlases, per-frame CSS-variable
|
|
55
|
+
writes) must subscribe to `visibilitychange`: hidden = clear the pending
|
|
56
|
+
timeout; visible = reschedule from the current sequence index (never reset
|
|
57
|
+
the animation state).
|
|
58
|
+
- Mirror the hidden state onto an attribute the stylesheet can read
|
|
59
|
+
(e.g. `body[data-*tab-hidden]`) and set `animation-play-state: paused` for
|
|
60
|
+
the skin's own infinite `@keyframes` loops. The stylesheet pause is what
|
|
61
|
+
actually stops the compositor work.
|
|
62
|
+
- Do not write CSS custom properties that nothing consumes. Each
|
|
63
|
+
`style.setProperty` is a style-recalc surface; a consumed-by-nobody
|
|
64
|
+
variable written 60+ times per second is pure waste.
|
|
65
|
+
- Animate with composited properties (`transform`, `opacity`). Animating
|
|
66
|
+
`background-position` repaints per frame: it rasterizes instead of
|
|
67
|
+
compositing and shows up in long-task traces.
|
|
68
|
+
|
|
69
|
+
## R4 — `will-change` must not be parked on full-screen or idle layers
|
|
70
|
+
|
|
71
|
+
- `will-change: opacity, transform, filter` keeps a GPU compositing layer
|
|
72
|
+
alive **forever**, even when nothing is animating. A static 0px filter
|
|
73
|
+
(`blur()`) is a no-op; the layer cost is not.
|
|
74
|
+
- Only keep `will-change` on elements in a *sustained* animation (e.g. an
|
|
75
|
+
infinite loop already covered by R3); never on full-viewport fixed
|
|
76
|
+
backgrounds that merely crossfade — a .64s transition promotes the layer
|
|
77
|
+
automatically for its duration and releases it afterwards.
|
|
78
|
+
- Prefer visibility/display switches (with the opposite theme hidden) over
|
|
79
|
+
layering both scene sets, and keep the visible scene count at one.
|
|
80
|
+
- `backdrop-filter` is realtime and expensive: keep its covered area to the
|
|
81
|
+
smallest surface that needs the effect, avoid animating it, and test with
|
|
82
|
+
scrolling under the blurred surface.
|
|
83
|
+
|
|
84
|
+
## R5 — Never interleave layout reads and writes in mutation callbacks
|
|
85
|
+
|
|
86
|
+
- `getBoundingClientRect()` / `offset*` reads force layout. In a mutation
|
|
87
|
+
storm (large DOM inserts), a read-write-read-write pattern in one callback
|
|
88
|
+
is layout thrashing: batch all reads first, then all writes.
|
|
89
|
+
- Cache measurements on the binding; recompute only when the referenced
|
|
90
|
+
element changes. A restore-button re-anchor on an unchanged seat is the
|
|
91
|
+
classic offender — skip it via the R1 short-circuit.
|
|
92
|
+
|
|
93
|
+
## R6 — Delegation of cleanup (existing contract, restated)
|
|
94
|
+
|
|
95
|
+
Every observer, listener (`visibilitychange`, `pointermove`, `click`,
|
|
96
|
+
`focusin`...), timer and interval created in `apply()` must be registered
|
|
97
|
+
through `ctx.onCleanup` and torn down in reverse order, including new
|
|
98
|
+
global-body attributes. A skin that leaks observers doubles the cost of every
|
|
99
|
+
future session switch.
|
|
100
|
+
|
|
101
|
+
## Verification
|
|
102
|
+
|
|
103
|
+
Measure before and after with the DevTools Performance panel during session
|
|
104
|
+
load and session switch (the heaviest DOM operations a skin hooks into).
|
|
105
|
+
Long tasks with an aggregate > 100ms, or frames on the main busied line
|
|
106
|
+
during those operations, indicate a violation of the rules above. Per-skin
|
|
107
|
+
tracking numbers serve as regression baselines; include them in the PR body.
|
|
@@ -78,6 +78,7 @@ family / 插件区域:
|
|
|
78
78
|
| `dialog` | git-graph | 图对话框;`[data-gitgraph-dialog]` |
|
|
79
79
|
| `graph-row` | git-graph | 提交行;dialog 内行容器 |
|
|
80
80
|
| `ref` | git-graph | 分支徽标;`[data-gitgraph-ref]` |
|
|
81
|
+
| `worktree-create` / `worktree-manage` | git-graph | worktree 创建/管理入口按钮;分支弹层 footer 内裸值按钮 |
|
|
81
82
|
| `sprite` | pet | 精灵;`[data-dsh-pet-root]` 子树 float 容器 |
|
|
82
83
|
| `bubble` | pet | 气泡容器 |
|
|
83
84
|
| `panel` | pet | 交互面板;`[data-placement]` |
|
|
@@ -120,7 +121,7 @@ family / 插件区域:
|
|
|
120
121
|
| `remote-web-ui` | dsh-remote-web-ui | slot entry id `remote-web-ui` |
|
|
121
122
|
| `web-ui-settings` | dsh-web-settings | settings.section id `web-ui-plugins` |
|
|
122
123
|
| `skill-explorer` | dsh-skill-explorer | `[data-dsh-skill-explorer-view]` / `[data-dsh-skill-explorer-entry]` |
|
|
123
|
-
| `doctor` | dsh-doctor | web-ui.plugin.item 槽 entry id `doctor`(设置 → Web
|
|
124
|
+
| `doctor` | dsh-doctor | web-ui.plugin.item 槽 entry id `doctor`(设置 → Web 插件 → Doctor 卡片);卡片内 `[data-dsh-plugin="doctor"]` |
|
|
124
125
|
| `aionui-panel` | dsh-aionui-panel(停更) | dock entry id `aionui-*` |
|
|
125
126
|
| `dsh-web-ui-market` | dsh-market | 创意工坊商店一级页(settings.section id `dsh-web-ui-market`),商店卡与目录条目容器 |
|
|
126
127
|
| `skin-center` | skins/skin-center | 一级设置分区 settings.section id `skin-center`(列已安装皮肤,属内置源时显式标记) |
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"attribution": {
|
|
82
82
|
"type": "string",
|
|
83
|
-
"description": "Optional attribution line shown in the
|
|
83
|
+
"description": "Optional attribution line shown in the shop."
|
|
84
84
|
},
|
|
85
85
|
"preview": {
|
|
86
86
|
"type": "object",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
},
|
|
145
145
|
"patches": {
|
|
146
146
|
"$ref": "#/$defs/relPath",
|
|
147
|
-
"description": "Optional L3 free-selector patches. High sensitivity; disclosed in UI and
|
|
147
|
+
"description": "Optional L3 free-selector patches. High sensitivity; disclosed in the Skin Center UI and the shop."
|
|
148
148
|
},
|
|
149
149
|
"backgroundMedia": {
|
|
150
150
|
"type": "object",
|