@linxin666/dsh-client-ui-skin-center 0.3.0 → 0.3.2

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.
@@ -12,6 +12,7 @@ every official-DSH coupling behind them.
12
12
  | `skin-manifest-v2.schema.json` | skin.json v2 structure. Editors/IDEs may point at it; validators must use this local copy and never fetch the `$id` URL. |
13
13
  | `hooks-api.d.ts` | The `facets.client` escape-hatch API (`x-org.linxin666.skin-center/v1alpha1`): `SkinHooksContext`, `defineSkinHooks()`. |
14
14
  | `semantic-attrs-v1.md` | The L2 semantic-attribute enumeration (`data-dsh-surface` / `data-dsh-part` / `data-dsh-plugin`) with owner, version and meaning per value. |
15
+ | `primary-action-tokens-v1.md` | The filled-primary-button token set (fill / hover / dimmed / foreground), the skin author rules, the loader completion matrix and the warning-only audit. |
15
16
 
16
17
  The runtime validator (`src/core/manifest-v2/validate.ts`) is the
17
18
  authoritative fail-closed check; the JSON Schema mirrors it for editors and
@@ -0,0 +1,70 @@
1
+ # Primary-Action Token Contract (v1) — issue #506 follow-up
2
+
3
+ The filled primary button (the "one-click install" CTA, every plugin's
4
+ primary action) renders from **one matched set** in every theme. This file is
5
+ the authoritative statement of that set, of what a skin must declare, and of
6
+ what the skin-center proves when a skin loads.
7
+
8
+ ## The set
9
+
10
+ | Token | Role |
11
+ | --- | --- |
12
+ | `--dsw-alias-button-primary-fill` | Button background |
13
+ | `--dsw-alias-button-primary-hover` | Button background while hovered |
14
+ | `--dsw-alias-button-primary-dimmed` | Disabled-button tint |
15
+ | `--dsw-alias-label-primary-foreground` | Text on the fill |
16
+
17
+ The official shell declares all four (fill and dimmed derive from
18
+ `brand-primary` / statics). `--dsw-alias-brand-primary` and
19
+ `--dsw-alias-brand-primary-invert` are **accent and foreground tokens, not a
20
+ button pair**: in the current official theme both resolve to the same
21
+ theme-neutral, so using them as fill + text renders black-on-black (light) or
22
+ white-on-white (dark). Never consume them as a button fill.
23
+
24
+ ## Skin author rules
25
+
26
+ 1. Prefer declaring the full set per theme (light block and
27
+ `body[data-ds-dark-theme]` block), as matched colors.
28
+ 2. The legacy pair (declare both `brand-primary` and
29
+ `brand-primary-invert`) is still honored: the loader derives fill from
30
+ the brand, derives hover/dimmed toward the surface, and uses the invert
31
+ as the foreground — because the pair is matched by the author.
32
+ 3. Partial declarations warn (catalog warnings, `dsh-skin validate`,
33
+ `dsh-skin list`) but never fail: the loader completes what is missing,
34
+ and a skin with no brand and no button tokens at all deliberately keeps
35
+ the shell's own matched CTA.
36
+ 4. The loader never overrides a token the skin defines, and hygiene-wise a
37
+ new skin's button set should be self-contained (explicit
38
+ `button-primary-hover` etc.) rather than relying on derivation.
39
+
40
+ ## Completion matrix (loader, `css-safety/fallback.ts`)
41
+
42
+ | The skin declares | The loader adds |
43
+ | --- | --- |
44
+ | `brand-primary` | `button-primary-fill: var(brand-primary)` |
45
+ | fill available (explicit or derived) | `button-primary-hover` / `button-primary-dimmed`: `color-mix` toward `--dsw-alias-bg-layer-1` |
46
+ | `brand-primary` + `brand-primary-invert` | `label-primary-foreground: var(brand-primary-invert)` |
47
+ | nothing (no brand, no button tokens) | nothing — the official shell CTA applies |
48
+
49
+ ## Audit (`css-safety/token-audit.ts`)
50
+
51
+ Warning-only, never fatal — the completion rules keep every outcome legible.
52
+
53
+ - Completeness: fill satisfied by `button-primary-fill` or `brand-primary`;
54
+ hover by `button-primary-hover` or the fill anchor; foreground by
55
+ `label-primary-foreground` or the legacy invert pair.
56
+ - Contrast: per theme, WCAG 2.x ratio of the resolved foreground vs fill
57
+ (skin values, one `var()` hop, official static palette for
58
+ `--dsw-static-*` references, shell defaults as the fallback stand-ins).
59
+ Below **3:1** warns. Unresolvable chains skip the ratio rather than guess.
60
+ - Enforcement points: catalog build (surfaces in `dsh-skin list` and the
61
+ Skin Center diagnostics), `dsh-skin validate` / `install`, and the
62
+ built-in-skins CI gate (zero contract warnings for shipped skins).
63
+
64
+ ## Consumer rule (plugins)
65
+
66
+ A filled primary button uses the set — fill / hover / foreground — verbatim
67
+ (BG = `button-primary-fill`, label = `label-primary-foreground`, hover =
68
+ `button-primary-hover`). Do not use `brand-primary` as a button
69
+ background, do not introduce per-plugin hardcoded accents, and do not pair
70
+ the tokens across families (e.g. brand fill with shell foreground).
@@ -115,7 +115,7 @@ family / 插件区域:
115
115
  | `skill-explorer` | dsh-skill-explorer | `[data-dsh-skill-explorer-view]` / `[data-dsh-skill-explorer-entry]` |
116
116
  | `doctor` | dsh-doctor | web-ui.plugin.item 槽 entry id `doctor`(设置 → Web UI 插件 → Doctor 卡片);卡片内 `[data-dsh-plugin="doctor"]` |
117
117
  | `aionui-panel` | dsh-aionui-panel(停更) | dock entry id `aionui-*` |
118
- | `dsh-market` | dsh-market | 创意工坊商店一级页(settings.section id `dsh-market`),商店卡与目录条目容器 |
118
+ | `dsh-web-ui-market` | dsh-market | 创意工坊商店一级页(settings.section id `dsh-web-ui-market`),商店卡与目录条目容器 |
119
119
  | `skin-center` | skins/skin-center | 一级设置分区 settings.section id `skin-center`(列已安装皮肤,属内置源时显式标记) |
120
120
  | `session-id` | dsh-session-id | footer action slot entry id `session-id`;`[data-dsh-plugin="session-id"]`(面板 overlay 根 + 入口触发器) |
121
121
 
package/lib/client.js CHANGED
@@ -668,8 +668,8 @@ window.__ModuleLoader__.load({
668
668
  const manifestResponse = await fetchFn(payload.sceneUrl.replace("/scene-runtime/", "/scene-manifest/"));
669
669
  const manifestPayload = manifestResponse.ok ? await manifestResponse.json().catch(() => null) : null;
670
670
  const manifest = manifestPayload?.ok === true ? manifestPayload.manifest : void 0;
671
- if (manifest && typeof manifest.width === "number" && typeof manifest.height === "number") {
672
- const fullscreenIndex = manifest.layers?.findIndex((layer) => typeof layer.texUrl === "string" && Math.abs((layer.w ?? 0) - manifest.width) <= 1 && Math.abs((layer.h ?? 0) - manifest.height) <= 1 && Math.abs((layer.x ?? 0) - manifest.width / 2) <= 1 && Math.abs((layer.y ?? 0) - manifest.height / 2) <= 1) ?? -1;
671
+ if (manifest && manifest.timeSchedule === void 0 && typeof manifest.width === "number" && typeof manifest.height === "number") {
672
+ const fullscreenIndex = manifest.layers?.findIndex((layer) => typeof layer.texUrl === "string" && typeof layer.videoUrl !== "string" && Math.abs((layer.w ?? 0) - manifest.width) <= 1 && Math.abs((layer.h ?? 0) - manifest.height) <= 1 && Math.abs((layer.x ?? 0) - manifest.width / 2) <= 1 && Math.abs((layer.y ?? 0) - manifest.height / 2) <= 1) ?? -1;
673
673
  if (fullscreenIndex >= 0) {
674
674
  sceneBaseUrl = manifest.layers?.[fullscreenIndex]?.texUrl ?? null;
675
675
  preferSceneBase = manifest.layers?.slice(fullscreenIndex + 1).some((layer) => (layer.w ?? 0) > manifest.width * 1.25 || (layer.h ?? 0) > manifest.height * 1.25) === true;
@@ -2149,7 +2149,11 @@ window.__ModuleLoader__.load({
2149
2149
  setError(t("applyFailed"));
2150
2150
  return;
2151
2151
  }
2152
- run(target, () => preview.runSkin(() => switchAndDeactivateCustomTheme(target, entry)));
2152
+ run(target, () => preview.runSkin(async () => {
2153
+ const active = await switchAndDeactivateCustomTheme(target, entry);
2154
+ if (wallpaper.selection() !== "") wallpaper.clearSelection();
2155
+ return active;
2156
+ }));
2153
2157
  };
2154
2158
  const tryOnCustomTheme = () => {
2155
2159
  run("custom-theme", () => preview.runCustomTheme(async () => {
@@ -4399,10 +4403,16 @@ window.__ModuleLoader__.load({
4399
4403
  * @param ctx - client root context.
4400
4404
  */
4401
4405
  function apply(ctx) {
4402
- ctx.effect(() => ctx.locale.register(NS, {
4403
- zh,
4404
- en
4405
- }), "ui-skin-center: dictionaries");
4406
+ ctx.effect(() => {
4407
+ try {
4408
+ return ctx.locale.register(NS, {
4409
+ zh,
4410
+ en
4411
+ });
4412
+ } catch {
4413
+ return () => {};
4414
+ }
4415
+ }, "ui-skin-center: dictionaries");
4406
4416
  ctx.effect(() => {
4407
4417
  document.body.dataset.dshSkinCenter = "";
4408
4418
  return () => {
@@ -4530,14 +4540,20 @@ window.__ModuleLoader__.load({
4530
4540
  dispose: () => wallpaper.dispose()
4531
4541
  }
4532
4542
  });
4533
- ctx.slots.inject("settings.section", () => ctx.slots.register({
4534
- name: "settings.section",
4535
- id: "skin-center",
4536
- order: 120,
4537
- label: () => ctx.locale.bind("skinCenter")("title"),
4538
- locale: "skinCenter",
4539
- inject: injected
4540
- }, SkinCenterSection));
4543
+ ctx.slots.inject("settings.section", () => {
4544
+ try {
4545
+ return ctx.slots.register({
4546
+ name: "settings.section",
4547
+ id: "skin-center",
4548
+ order: 120,
4549
+ label: () => ctx.locale.bind("skinCenter")("title"),
4550
+ locale: "skinCenter",
4551
+ inject: injected
4552
+ }, SkinCenterSection);
4553
+ } catch {
4554
+ return () => {};
4555
+ }
4556
+ });
4541
4557
  }
4542
4558
  //#endregion
4543
4559
  exports.NS = NS;